1use super::verbs;
16use super::{Command, Kind, Param, Writes, DB, FORMAT, LIMIT};
17
18const QUERY_PARAMS: &[Param] = &[
20 Param {
21 name: "sql",
22 kind: Kind::Text,
23 required: true,
24 positional: true,
25 description: "The SQL to run. One statement. Use ?1, ?2 ... for values you pass in \
26 'params' rather than pasting them into the text.",
27 },
28 Param {
29 name: "params",
30 kind: Kind::Values,
31 required: false,
32 positional: false,
33 description: "The values for ?1, ?2 ... in order, as a JSON array of strings, numbers, \
34 booleans or nulls. An array of numbers is a vector and \
35 {\"blob\":\"<hex>\"} is bytes. Binding is how you avoid quoting mistakes \
36 and SQL injection.",
37 },
38 Param {
39 name: "params-file",
40 kind: Kind::Text,
41 required: false,
42 positional: false,
43 description: "A file holding the JSON array for 'params', or - for standard input. What \
44 a wrapper that spawns this binary uses: a command line has a length \
45 ceiling, about 32 KB on Windows, and a parameter past it fails outright.",
46 },
47 LIMIT,
48 DB,
49 FORMAT,
50];
51
52const EXEC_PARAMS: &[Param] = &[
54 Param {
55 name: "sql",
56 kind: Kind::Text,
57 required: true,
58 positional: true,
59 description: "One statement to run for its effect: INSERT, UPDATE, DELETE, CREATE, DROP, \
60 ALTER or a PRAGMA that sets something.",
61 },
62 Param {
63 name: "params",
64 kind: Kind::Values,
65 required: false,
66 positional: false,
67 description: "The values for ?1, ?2 ... in order, as a JSON array. An array of numbers \
68 is a vector and {\"blob\":\"<hex>\"} is bytes.",
69 },
70 Param {
71 name: "params-file",
72 kind: Kind::Text,
73 required: false,
74 positional: false,
75 description: "A file holding the JSON array for 'params', or - for standard input. What \
76 a wrapper that spawns this binary uses: a command line has a length \
77 ceiling, about 32 KB on Windows, and a parameter past it fails outright.",
78 },
79 DB,
80 FORMAT,
81];
82
83const BATCH_PARAMS: &[Param] = &[
85 Param {
86 name: "sql",
87 kind: Kind::Text,
88 required: true,
89 positional: true,
90 description: "Several statements separated by semicolons. They run as one transaction: \
91 either all of them take effect or none of them do.",
92 },
93 DB,
94 FORMAT,
95];
96
97const RUN_PARAMS: &[Param] = &[
99 Param {
100 name: "input",
101 kind: Kind::Text,
102 required: true,
103 positional: true,
104 description: "Shell input, exactly as you would type it: SQL statements ending in a \
105 semicolon, and dot commands such as '.schema' or '.mode box' on their own \
106 lines. Everything the interactive shell can do is reachable here.",
107 },
108 DB,
109];
110
111const CREATE_PARAMS: &[Param] = &[
113 Param {
114 name: "path",
115 kind: Kind::Text,
116 required: true,
117 positional: true,
118 description: "Where to make the new database file. It refuses a path that already \
119 exists rather than overwriting it.",
120 },
121 FORMAT,
122];
123
124const PATTERN_PARAMS: &[Param] = &[
126 Param {
127 name: "pattern",
128 kind: Kind::Text,
129 required: false,
130 positional: true,
131 description: "A LIKE pattern to filter the names by, such as 'user%'. Omit it for all \
132 of them.",
133 },
134 DB,
135 FORMAT,
136];
137
138const SCHEMA_PARAMS: &[Param] = &[
140 Param {
141 name: "pattern",
142 kind: Kind::Text,
143 required: false,
144 positional: true,
145 description: "A LIKE pattern naming which objects to show. Omit it for the whole schema.",
146 },
147 Param {
148 name: "indent",
149 kind: Kind::Boolean,
150 required: false,
151 positional: false,
152 description: "Pretty-print each CREATE statement over several lines instead of one.",
153 },
154 DB,
155 FORMAT,
156];
157
158const DESCRIBE_PARAMS: &[Param] = &[
160 Param {
161 name: "table",
162 kind: Kind::Text,
163 required: true,
164 positional: true,
165 description: "The table or view to describe, by its exact name. Use 'tables' first if \
166 you are not sure what it is called.",
167 },
168 DB,
169 FORMAT,
170];
171
172const DB_ONLY: &[Param] = &[DB, FORMAT];
174
175const EXPLAIN_PARAMS: &[Param] = &[
177 Param {
178 name: "sql",
179 kind: Kind::Text,
180 required: true,
181 positional: true,
182 description: "The statement whose plan you want. It is not run - this only shows which \
183 indexes and scans the planner chose.",
184 },
185 DB,
186 FORMAT,
187];
188
189const IMPORT_PARAMS: &[Param] = &[
191 Param {
192 name: "file",
193 kind: Kind::Text,
194 required: true,
195 positional: true,
196 description: "The delimited file to read.",
197 },
198 Param {
199 name: "table",
200 kind: Kind::Text,
201 required: true,
202 positional: false,
203 description: "The table to load into. If it does not exist it is created, taking its \
204 column names from the file's first row.",
205 },
206 Param {
207 name: "format",
208 kind: Kind::Text,
209 required: false,
210 positional: false,
211 description: "'csv' (the default, RFC 4180 quoting), 'tabs', or 'ascii' for \\037 and \
212 \\036 separated input.",
213 },
214 Param {
215 name: "skip",
216 kind: Kind::Integer,
217 required: false,
218 positional: false,
219 description: "How many leading rows to ignore, for a file with a preamble above its \
220 header.",
221 },
222 DB,
223];
224
225const EXPORT_PARAMS: &[Param] = &[
227 Param {
228 name: "table",
229 kind: Kind::Text,
230 required: false,
231 positional: true,
232 description: "The table to write out whole. Give this or 'sql', not both.",
233 },
234 Param {
235 name: "sql",
236 kind: Kind::Text,
237 required: false,
238 positional: false,
239 description: "A query whose rows to write out, when you want less than a whole table.",
240 },
241 Param {
242 name: "format",
243 kind: Kind::Text,
244 required: false,
245 positional: false,
246 description: "csv, json, tabs, markdown, insert, quote, line or html. Defaults to csv.",
247 },
248 Param {
249 name: "out",
250 kind: Kind::Text,
251 required: false,
252 positional: false,
253 description: "A file to write to. Omitted, the rows come back in the result instead.",
254 },
255 DB,
256];
257
258const DUMP_PARAMS: &[Param] = &[
260 Param {
261 name: "objects",
262 kind: Kind::Text,
263 required: false,
264 positional: true,
265 description: "A LIKE pattern for the tables, indexes, triggers or views to dump. Omit \
266 it for the whole database.",
267 },
268 Param {
269 name: "data_only",
270 kind: Kind::Boolean,
271 required: false,
272 positional: false,
273 description: "Write only the INSERT statements, leaving out the CREATE statements.",
274 },
275 DB,
276];
277
278const BACKUP_PARAMS: &[Param] = &[
280 Param {
281 name: "file",
282 kind: Kind::Text,
283 required: true,
284 positional: true,
285 description: "Where to write the copy.",
286 },
287 DB,
288];
289
290const RESTORE_PARAMS: &[Param] = &[
292 Param {
293 name: "file",
294 kind: Kind::Text,
295 required: true,
296 positional: true,
297 description: "The copy to read back. It replaces what is in the open database.",
298 },
299 DB,
300];
301
302const ANALYZE_PARAMS: &[Param] = &[
304 Param {
305 name: "table",
306 kind: Kind::Text,
307 required: false,
308 positional: true,
309 description: "One table to gather statistics for. Omit it for every table.",
310 },
311 DB,
312 FORMAT,
313];
314
315const MIGRATE_PARAMS: &[Param] = &[
317 Param {
318 name: "source",
319 kind: Kind::Text,
320 required: false,
321 positional: true,
322 description: "The SQLite database file to read, or a postgres:// or mysql:// connection \
323 URL. The source is never written to. A connection URL holds a password, \
324 and an argument is visible in the process list for the whole run - so it \
325 may be left out and given in INILLUCENT_SOURCE_URL instead, or written as \
326 '-' to read one line from standard input.",
327 },
328 Param {
329 name: "destination",
330 kind: Kind::Text,
331 required: true,
332 positional: false,
333 description: "The .rdb file to build. It refuses to overwrite an existing file.",
334 },
335 Param {
336 name: "kind",
337 kind: Kind::Text,
338 required: false,
339 positional: false,
340 description: "'sqlite' (the default for a path), 'postgres' or 'mysql' (the default for \
341 a URL written with that scheme), or 'index' for a legacy retrieval index.",
342 },
343 Param {
344 name: "batch",
345 kind: Kind::Integer,
346 required: false,
347 positional: false,
348 description: "Rows per destination transaction while copying from a server. Default \
349 10000. It changes how long the migration takes and nothing about what it \
350 produces.",
351 },
352 Param {
353 name: "insecure-plaintext",
354 kind: Kind::Boolean,
355 required: false,
356 positional: false,
357 description: "Permit an unencrypted connection to a server. A migration to a host that \
358 is not a loopback address uses verified TLS, and refuses rather than \
359 falling back; this permits plaintext, and only together with \
360 sslmode=disable in the URL. Both are needed because either one alone is \
361 something people type without meaning it. The choice is recorded in the \
362 migration report.",
363 },
364];
365
366const SEARCH_PARAMS: &[Param] = &[
368 Param {
369 name: "query",
370 kind: Kind::Text,
371 required: true,
372 positional: true,
373 description: "What to search for, in FTS5 query syntax: bare words are ANDed, \
374 \"a phrase\" is quoted, and OR and NOT are available.",
375 },
376 Param {
377 name: "table",
378 kind: Kind::Text,
379 required: true,
380 positional: false,
381 description: "The full-text table to search - one created with USING fts5(...) or \
382 USING inillucent_search(...).",
383 },
384 Param {
385 name: "k",
386 kind: Kind::Integer,
387 required: false,
388 positional: false,
389 description: "How many results to return, best first. Defaults to 10.",
390 },
391 DB,
392 FORMAT,
393];
394
395const VECTOR_PARAMS: &[Param] = &[
397 Param {
398 name: "table",
399 kind: Kind::Text,
400 required: true,
401 positional: true,
402 description: "The table holding the vectors.",
403 },
404 Param {
405 name: "column",
406 kind: Kind::Text,
407 required: true,
408 positional: false,
409 description: "The VECTOR(N) column to measure against.",
410 },
411 Param {
412 name: "vector",
413 kind: Kind::Values,
414 required: true,
415 positional: false,
416 description: "The query vector, as a JSON array of numbers with exactly N elements.",
417 },
418 Param {
419 name: "k",
420 kind: Kind::Integer,
421 required: false,
422 positional: false,
423 description: "How many nearest rows to return. Defaults to 10.",
424 },
425 Param {
426 name: "measure",
427 kind: Kind::Text,
428 required: false,
429 positional: false,
430 description: "'cos' for cosine distance (the default), 'l2' for Euclidean, or 'dot' \
431 for the inner product.",
432 },
433 DB,
434 FORMAT,
435];
436
437const CAPABILITY_PARAMS: &[Param] = &[
439 Param {
440 name: "name",
441 kind: Kind::Text,
442 required: false,
443 positional: true,
444 description: "One capability to ask about, such as 'triggers'. Omit it for the whole \
445 table. A name that is not in the table answers no, not yes.",
446 },
447 FORMAT,
448];
449
450const HELP_PARAMS: &[Param] = &[
452 Param {
453 name: "topic",
454 kind: Kind::Text,
455 required: false,
456 positional: true,
457 description: "A command to explain in full. Omit it for the list of every command.",
458 },
459 FORMAT,
460];
461
462const SETUP_PARAMS: &[Param] = &[
464 Param {
465 name: "component",
466 kind: Kind::Text,
467 required: false,
468 positional: true,
469 description: "What to install: 'all' for both halves, 'runtime' for the ONNX Runtime \
470 shared library on its own, or 'model' for the weights on their own. Omit \
471 it to report what is installed and download nothing.",
472 },
473 Param {
474 name: "status",
475 kind: Kind::Boolean,
476 required: false,
477 positional: false,
478 description: "Say what is installed, where, and which residency profile is in force, and \
479 download nothing.",
480 },
481 Param {
482 name: "residency",
483 kind: Kind::Text,
484 required: false,
485 positional: false,
486 description: "When the model is in memory: 'resident' keeps it for the life of the process, \
487 'on-demand' loads it per call and drops it, 'idle' or 'idle:90s' loads it on \
488 use and drops it after a quiet period. Recorded for this machine; \
489 INILLUCENT_EMBED_RESIDENCY overrides it for one process.",
490 },
491 Param {
492 name: "gpu",
493 kind: Kind::Boolean,
494 required: false,
495 positional: false,
496 description: "Install the ONNX Runtime build carrying the CUDA execution provider, which \
497 exists for Windows and Linux on x86-64 only. It is a much larger download and \
498 it needs a CUDA install of its own to be usable.",
499 },
500 Param {
501 name: "force",
502 kind: Kind::Boolean,
503 required: false,
504 positional: false,
505 description: "Fetch and install again even when the files are already there and their \
506 digests match.",
507 },
508 Param {
509 name: "onnxruntime-version",
510 kind: Kind::Text,
511 required: false,
512 positional: false,
513 description: "The ONNX Runtime version to install. Defaults to the one this build pins a \
514 digest for; any other version is fetched and reported as unverified.",
515 },
516 Param {
517 name: "dir",
518 kind: Kind::Text,
519 required: false,
520 positional: false,
521 description: "Install somewhere other than the per-user directory. INILLUCENT_HOME does the \
522 same thing for every command at once.",
523 },
524 FORMAT,
525];
526
527const VERSION_PARAMS: &[Param] = &[FORMAT];
529
530pub static COMMANDS: &[Command] = &[
532 Command {
533 name: "query",
534 summary: "Run a SELECT and get its rows back.",
535 detail: "Use this for anything that reads. The rows come back as a table, or as typed \
536 JSON with output=json. 'total' is exact even when 'limit' cut the rows handed \
537 back, because the engine materialises the whole result - so a limit of 10 on a \
538 million-row query still costs what the million rows cost. Put a LIMIT in the SQL \
539 itself when you cannot afford that, where the planner can act on it.",
540 params: QUERY_PARAMS,
541 cli_only: None,
542 writes: Writes::No,
543 run: verbs::query,
544 },
545 Command {
546 name: "exec",
547 summary: "Run one statement that changes something, and get the row count back.",
548 detail: "INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, or a PRAGMA that sets a value. A \
549 statement with RETURNING gives its rows back as well as its count.",
550 params: EXEC_PARAMS,
551 cli_only: None,
552 writes: Writes::Yes,
553 run: verbs::exec,
554 },
555 Command {
556 name: "batch",
557 summary: "Run several statements as one transaction.",
558 detail: "Separate them with semicolons. Either all of them take effect or none of them \
559 do, which is what you want when creating a schema or loading related rows.",
560 params: BATCH_PARAMS,
561 cli_only: None,
562 writes: Writes::Yes,
563 run: verbs::batch,
564 },
565 Command {
566 name: "run",
567 summary: "Run shell input, dot commands and all, and get back what it printed.",
568 detail: "The escape hatch, and the reason every dot command is reachable from every front \
569 end: this drives the same shell 'inillucent shell' does. Use it for the things \
570 that have no verb of their own - '.eqp on', '.parameter set', '.testcase', \
571 '.archive'. Run 'inillucent run \".help\"' for the full list of dot commands.",
572 params: RUN_PARAMS,
573 cli_only: None,
574 writes: Writes::PerStatement,
580 run: verbs::run_input,
581 },
582 Command {
583 name: "create",
584 summary: "Make a new, empty database file.",
585 detail: "Refuses a path that already exists, so it can never destroy a database by being \
586 run twice. Every other command opens whatever it is pointed at.",
587 params: CREATE_PARAMS,
588 cli_only: None,
589 writes: Writes::Yes,
590 run: verbs::create,
591 },
592 Command {
593 name: "tables",
594 summary: "List the tables and views.",
595 detail: "The names, with what each one is. System tables whose names begin with sqlite_ \
596 are left out.",
597 params: PATTERN_PARAMS,
598 cli_only: None,
599 writes: Writes::No,
600 run: verbs::tables,
601 },
602 Command {
603 name: "describe",
604 summary: "Everything about one table: columns, types, keys, indexes, row count and DDL.",
605 detail: "Call this before writing SQL against a table you did not create. It answers in \
606 one call what four separate pragmas would, which matters because a caller that \
607 has to make four usually makes three and writes its query from an incomplete \
608 picture.",
609 params: DESCRIBE_PARAMS,
610 cli_only: None,
611 writes: Writes::No,
612 run: verbs::describe,
613 },
614 Command {
615 name: "schema",
616 summary: "Show the CREATE statements for the whole database or for what a pattern names.",
617 detail: "The schema as SQL, which is the form you can paste into another database.",
618 params: SCHEMA_PARAMS,
619 cli_only: None,
620 writes: Writes::No,
621 run: verbs::schema,
622 },
623 Command {
624 name: "indexes",
625 summary: "List the indexes and the table each one is on.",
626 detail: "Including the ones a UNIQUE constraint or a primary key created, which is why \
627 an index you did not write may appear here.",
628 params: PATTERN_PARAMS,
629 cli_only: None,
630 writes: Writes::No,
631 run: verbs::indexes,
632 },
633 Command {
634 name: "databases",
635 summary: "List the attached databases and the file behind each.",
636 detail: "'main' is the one that was opened; others come from ATTACH. 'temp' is the \
637 session's own scratch database and has no file.",
638 params: DB_ONLY,
639 cli_only: None,
640 writes: Writes::No,
641 run: verbs::databases,
642 },
643 Command {
644 name: "explain",
645 summary: "Show the query plan for a statement without running it.",
646 detail: "Which indexes are used, which scans are full, and in what order the tables are \
647 joined. This is how you find out why a query is slow before making it faster.",
648 params: EXPLAIN_PARAMS,
649 cli_only: None,
650 writes: Writes::No,
651 run: verbs::explain,
652 },
653 Command {
654 name: "import",
655 summary: "Load a CSV or tab-separated file into a table.",
656 detail: "The table is created from the file's first row if it does not exist. Quoting is \
657 RFC 4180 unless a different format is asked for.",
658 params: IMPORT_PARAMS,
659 cli_only: None,
660 writes: Writes::Yes,
661 run: verbs::import,
662 },
663 Command {
664 name: "export",
665 summary: "Write a table or a query's rows out as CSV, JSON or one of six other formats.",
666 detail: "With 'out' the rows go to a file and the result says so; without it they come \
667 back in the result, which is usually what an agent wants.",
668 params: EXPORT_PARAMS,
669 cli_only: None,
670 writes: Writes::No,
671 run: verbs::export,
672 },
673 Command {
674 name: "dump",
675 summary: "Render the database as the SQL that would rebuild it.",
676 detail: "Schema and data, in dependency order, inside a transaction. This is the \
677 portable form: it is text, and another SQLite-speaking database will read it.",
678 params: DUMP_PARAMS,
679 cli_only: None,
680 writes: Writes::No,
681 run: verbs::dump,
682 },
683 Command {
684 name: "backup",
685 summary: "Write a copy of the database to another file.",
686 detail: "A consistent copy taken while the database is open. The copy is a database, not \
687 a text dump.",
688 params: BACKUP_PARAMS,
689 cli_only: None,
690 writes: Writes::No,
691 run: verbs::backup,
692 },
693 Command {
694 name: "restore",
695 summary: "Point this session at a backup file, in place of the database it opened.",
696 detail: "The opposite of 'backup', and it does not overwrite anything: this engine's \
697 databases are whole files, so restoring is opening the other file rather than \
698 writing its pages over the one you are in. That means it lasts as long as the \
699 session does - useful from the shell and from 'run', where the statements after \
700 it read the restored file, and of no effect on its own, because a one-shot \
701 process ends immediately after. To replace a file, copy the backup over it. A \
702 backup file that is not there is refused rather than created empty.",
703 params: RESTORE_PARAMS,
704 cli_only: None,
705 writes: Writes::Yes,
706 run: verbs::restore,
707 },
708 Command {
709 name: "checkpoint",
710 summary: "Fold the write-ahead log back into the database file.",
711 detail: "Writes go to a log first and are folded in later. Doing it now shrinks the log \
712 and is what you want before copying the file by hand.",
713 params: DB_ONLY,
714 cli_only: None,
715 writes: Writes::Yes,
716 run: verbs::checkpoint,
717 },
718 Command {
719 name: "integrity-check",
720 summary: "Read every page and report whether the database holds together.",
721 detail: "Answers 'ok' on a healthy database. Anything else names what is wrong. It reads \
722 the whole file, so it costs what the file costs.",
723 params: DB_ONLY,
724 cli_only: None,
725 writes: Writes::No,
726 run: verbs::integrity_check,
727 },
728 Command {
729 name: "analyze",
730 summary: "Gather the statistics the query planner reads.",
731 detail: "Run it after loading a lot of data. Without statistics the planner guesses at \
732 how selective an index is, and a wrong guess is the usual reason a query that \
733 should use an index does not.",
734 params: ANALYZE_PARAMS,
735 cli_only: None,
736 writes: Writes::Yes,
737 run: verbs::analyze,
738 },
739 Command {
740 name: "stats",
741 summary: "Report the page cache, the pool size and the shape of the file.",
742 detail: "Cache hits and misses, how many pages the file holds and how many are free. \
743 This is where you look when a workload is slower than it should be.",
744 params: DB_ONLY,
745 cli_only: None,
746 writes: Writes::No,
747 run: verbs::stats,
748 },
749 Command {
750 name: "search",
751 summary: "Full-text search over an FTS5 or inillucent_search table.",
752 detail: "Writes the MATCH ... ORDER BY rank idiom for you, which is the part nobody \
753 remembers. The table has to be a full-text one; 'describe' will show you \
754 whether it is.",
755 params: SEARCH_PARAMS,
756 cli_only: None,
757 writes: Writes::No,
758 run: verbs::search,
759 },
760 Command {
761 name: "vector-search",
762 summary: "Find the rows whose vector is nearest to one you supply.",
763 detail: "Over a VECTOR(N) column, by cosine distance unless you ask for another measure. \
764 If there is an HNSW index on the column the planner uses it; if there is not, \
765 this is an exhaustive scan and is still correct.",
766 params: VECTOR_PARAMS,
767 cli_only: None,
768 writes: Writes::No,
769 run: verbs::vector_search,
770 },
771 Command {
772 name: "capabilities",
773 summary: "Ask what this engine can do before composing a statement.",
774 detail: "Every row is checked against the running engine by a test, in both directions - \
775 a claim of support that fails and a claim of absence that now works each turn \
776 the build red. So this is worth trusting in a way a hand-maintained feature \
777 list is not. A name that is not in the table answers no, because a capability \
778 that was never declared was never checked.",
779 params: CAPABILITY_PARAMS,
780 cli_only: None,
781 writes: Writes::No,
782 run: verbs::capabilities,
783 },
784 Command {
785 name: "functions",
786 summary: "List the SQL functions this engine answers.",
787 detail: "From the engine's own register, which is compared against the reference \
788 library's on every build - so this is what actually exists rather than what was \
789 documented once.",
790 params: PATTERN_PARAMS,
791 cli_only: None,
792 writes: Writes::No,
793 run: verbs::functions,
794 },
795 Command {
796 name: "migrate",
797 summary: "Build an inillucent database from a SQLite file, PostgreSQL or MySQL.",
798 detail: "Reads the source and writes a new .rdb with the same rows. A path is a SQLite \
799 database file; a postgres:// or mysql:// URL is a running server, read inside \
800 one repeatable-read snapshot so that every table is as of one instant. The \
801 source is never written to and the destination is never overwritten: the new \
802 file is staged under another name and published by a rename, so a half-written \
803 database never sits where an application would open it. A server migration is \
804 verified per table by row count and by an order-independent digest, and nothing \
805 that fails a check is published.",
806 params: MIGRATE_PARAMS,
807 cli_only: None,
808 writes: Writes::Yes,
809 run: verbs::migrate,
810 },
811 Command {
812 name: "setup-embeddings",
813 summary: "Download and install the embedding model and the runtime it needs.",
814 detail: "One command, on Windows, macOS and Linux. It fetches ONNX Runtime and the \
815 nomic-embed-text-v1.5 weights into a per-user directory, checks every byte \
816 against a digest pinned in this build, and leaves the engine able to answer \
817 embed(TEXT) with nothing exported by hand - so a mismatch is a refusal that \
818 names both digests rather than a shared library that loads and misbehaves. \
819 Name what you want: 'all' installs both halves, 'runtime' and 'model' one \
820 each. Run with no component at all and it reports what is installed and \
821 downloads nothing, which is what stops a 620 MB fetch being a surprise; \
822 '--status' does the same explicitly. About 620 MB the first time and \
823 nothing on a later run. '--residency' chooses when the model is in memory: \
824 'resident' keeps it, which is about 1.9 GB held and 12 to 36 ms a query; \
825 'on-demand' loads it per call, which holds nothing and costs about 0.8 s a \
826 query; 'idle' or 'idle:90s' loads it on use and drops it after a quiet \
827 period, which is the default and pays the load once for a burst of \
828 questions.",
829 params: SETUP_PARAMS,
830 cli_only: None,
831 writes: Writes::Yes,
832 run: crate::setup::setup_embeddings,
833 },
834 Command {
835 name: "version",
836 summary: "Report the engine, the dialect and the driver versions.",
837 detail: "The SQLite version named here is the dialect this engine implements, not a \
838 library it links. There is no SQLite in this binary.",
839 params: VERSION_PARAMS,
840 cli_only: None,
841 writes: Writes::No,
842 run: verbs::version,
843 },
844 Command {
845 name: "help",
846 summary: "List every command, or explain one in full.",
847 detail: "With no topic it prints the table. With one it prints that command's usage, \
848 what it is for, and every parameter it takes.",
849 params: HELP_PARAMS,
850 cli_only: None,
851 writes: Writes::No,
852 run: verbs::help,
853 },
854 Command {
855 name: "shell",
856 summary: "Start the interactive shell.",
857 detail: "The sqlite3-shaped REPL, with all 63 dot commands. Everything it can do is also \
858 reachable non-interactively through 'run'.",
859 params: &[],
860 cli_only: Some(
861 "it is a terminal REPL: it reads a keyboard and writes a screen, and neither exists \
862 at the other end of an MCP call. Use 'run' instead, which drives the same shell.",
863 ),
864 writes: Writes::Yes,
865 run: verbs::shell_placeholder,
866 },
867 Command {
868 name: "mcp",
869 summary: "Serve these commands to an agent over MCP on standard input and output.",
870 detail: "Every command in this table that is not marked cli-only becomes a tool named \
871 inillucent_<command>, with this same description and these same parameters.",
872 params: &[],
873 cli_only: Some(
874 "it is the server that would be exposing the tools, so offering it as one of them \
875 would let a client ask the server to serve itself.",
876 ),
877 writes: Writes::Yes,
878 run: verbs::mcp_placeholder,
879 },
880];