@gmickel/gno 0.7.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -50
  3. package/THIRD_PARTY_NOTICES.md +22 -0
  4. package/assets/screenshots/webui-ask-answer.png +0 -0
  5. package/assets/screenshots/webui-collections.png +0 -0
  6. package/assets/screenshots/webui-editor.png +0 -0
  7. package/assets/screenshots/webui-home.png +0 -0
  8. package/assets/skill/SKILL.md +12 -12
  9. package/assets/skill/cli-reference.md +59 -57
  10. package/assets/skill/examples.md +8 -7
  11. package/assets/skill/mcp-reference.md +8 -4
  12. package/package.json +32 -25
  13. package/src/app/constants.ts +43 -42
  14. package/src/cli/colors.ts +1 -1
  15. package/src/cli/commands/ask.ts +44 -43
  16. package/src/cli/commands/cleanup.ts +9 -8
  17. package/src/cli/commands/collection/add.ts +12 -12
  18. package/src/cli/commands/collection/index.ts +4 -4
  19. package/src/cli/commands/collection/list.ts +26 -25
  20. package/src/cli/commands/collection/remove.ts +10 -10
  21. package/src/cli/commands/collection/rename.ts +10 -10
  22. package/src/cli/commands/context/add.ts +1 -1
  23. package/src/cli/commands/context/check.ts +17 -17
  24. package/src/cli/commands/context/index.ts +4 -4
  25. package/src/cli/commands/context/list.ts +11 -11
  26. package/src/cli/commands/context/rm.ts +1 -1
  27. package/src/cli/commands/doctor.ts +86 -84
  28. package/src/cli/commands/embed.ts +30 -28
  29. package/src/cli/commands/get.ts +27 -26
  30. package/src/cli/commands/index-cmd.ts +9 -9
  31. package/src/cli/commands/index.ts +16 -16
  32. package/src/cli/commands/init.ts +13 -12
  33. package/src/cli/commands/ls.ts +20 -19
  34. package/src/cli/commands/mcp/config.ts +30 -28
  35. package/src/cli/commands/mcp/index.ts +4 -4
  36. package/src/cli/commands/mcp/install.ts +17 -17
  37. package/src/cli/commands/mcp/paths.ts +133 -133
  38. package/src/cli/commands/mcp/status.ts +21 -21
  39. package/src/cli/commands/mcp/uninstall.ts +13 -13
  40. package/src/cli/commands/mcp.ts +2 -2
  41. package/src/cli/commands/models/clear.ts +12 -11
  42. package/src/cli/commands/models/index.ts +5 -5
  43. package/src/cli/commands/models/list.ts +31 -30
  44. package/src/cli/commands/models/path.ts +1 -1
  45. package/src/cli/commands/models/pull.ts +19 -18
  46. package/src/cli/commands/models/use.ts +4 -4
  47. package/src/cli/commands/multi-get.ts +38 -36
  48. package/src/cli/commands/query.ts +21 -20
  49. package/src/cli/commands/ref-parser.ts +10 -10
  50. package/src/cli/commands/reset.ts +40 -39
  51. package/src/cli/commands/search.ts +14 -13
  52. package/src/cli/commands/serve.ts +4 -4
  53. package/src/cli/commands/shared.ts +11 -10
  54. package/src/cli/commands/skill/index.ts +5 -5
  55. package/src/cli/commands/skill/install.ts +18 -17
  56. package/src/cli/commands/skill/paths-cmd.ts +11 -10
  57. package/src/cli/commands/skill/paths.ts +23 -23
  58. package/src/cli/commands/skill/show.ts +13 -12
  59. package/src/cli/commands/skill/uninstall.ts +16 -15
  60. package/src/cli/commands/status.ts +25 -24
  61. package/src/cli/commands/update.ts +3 -3
  62. package/src/cli/commands/vsearch.ts +17 -16
  63. package/src/cli/context.ts +5 -5
  64. package/src/cli/errors.ts +3 -3
  65. package/src/cli/format/search-results.ts +37 -37
  66. package/src/cli/options.ts +43 -43
  67. package/src/cli/program.ts +455 -459
  68. package/src/cli/progress.ts +1 -1
  69. package/src/cli/run.ts +24 -23
  70. package/src/collection/add.ts +9 -8
  71. package/src/collection/index.ts +3 -3
  72. package/src/collection/remove.ts +7 -6
  73. package/src/collection/types.ts +6 -6
  74. package/src/config/defaults.ts +1 -1
  75. package/src/config/index.ts +5 -5
  76. package/src/config/loader.ts +19 -18
  77. package/src/config/paths.ts +9 -8
  78. package/src/config/saver.ts +14 -13
  79. package/src/config/types.ts +53 -52
  80. package/src/converters/adapters/markitdownTs/adapter.ts +21 -19
  81. package/src/converters/adapters/officeparser/adapter.ts +18 -16
  82. package/src/converters/canonicalize.ts +12 -12
  83. package/src/converters/errors.ts +26 -22
  84. package/src/converters/index.ts +8 -8
  85. package/src/converters/mime.ts +25 -25
  86. package/src/converters/native/markdown.ts +10 -9
  87. package/src/converters/native/plaintext.ts +8 -7
  88. package/src/converters/path.ts +2 -2
  89. package/src/converters/pipeline.ts +11 -10
  90. package/src/converters/registry.ts +8 -8
  91. package/src/converters/types.ts +14 -14
  92. package/src/converters/versions.ts +4 -4
  93. package/src/index.ts +4 -4
  94. package/src/ingestion/chunker.ts +10 -9
  95. package/src/ingestion/index.ts +6 -6
  96. package/src/ingestion/language.ts +62 -62
  97. package/src/ingestion/sync.ts +50 -49
  98. package/src/ingestion/types.ts +10 -10
  99. package/src/ingestion/walker.ts +14 -13
  100. package/src/llm/cache.ts +51 -49
  101. package/src/llm/errors.ts +40 -36
  102. package/src/llm/index.ts +9 -9
  103. package/src/llm/lockfile.ts +6 -6
  104. package/src/llm/nodeLlamaCpp/adapter.ts +13 -12
  105. package/src/llm/nodeLlamaCpp/embedding.ts +9 -8
  106. package/src/llm/nodeLlamaCpp/generation.ts +7 -6
  107. package/src/llm/nodeLlamaCpp/lifecycle.ts +11 -10
  108. package/src/llm/nodeLlamaCpp/rerank.ts +6 -5
  109. package/src/llm/policy.ts +5 -5
  110. package/src/llm/registry.ts +6 -5
  111. package/src/llm/types.ts +2 -2
  112. package/src/mcp/resources/index.ts +15 -13
  113. package/src/mcp/server.ts +25 -23
  114. package/src/mcp/tools/get.ts +25 -23
  115. package/src/mcp/tools/index.ts +32 -29
  116. package/src/mcp/tools/multi-get.ts +34 -32
  117. package/src/mcp/tools/query.ts +29 -27
  118. package/src/mcp/tools/search.ts +14 -12
  119. package/src/mcp/tools/status.ts +12 -11
  120. package/src/mcp/tools/vsearch.ts +26 -24
  121. package/src/pipeline/answer.ts +9 -9
  122. package/src/pipeline/chunk-lookup.ts +1 -1
  123. package/src/pipeline/contextual.ts +4 -4
  124. package/src/pipeline/expansion.ts +23 -21
  125. package/src/pipeline/explain.ts +21 -21
  126. package/src/pipeline/fusion.ts +9 -9
  127. package/src/pipeline/hybrid.ts +41 -42
  128. package/src/pipeline/index.ts +10 -10
  129. package/src/pipeline/query-language.ts +39 -39
  130. package/src/pipeline/rerank.ts +8 -7
  131. package/src/pipeline/search.ts +22 -22
  132. package/src/pipeline/types.ts +8 -8
  133. package/src/pipeline/vsearch.ts +21 -24
  134. package/src/serve/CLAUDE.md +21 -15
  135. package/src/serve/config-sync.ts +9 -8
  136. package/src/serve/context.ts +19 -18
  137. package/src/serve/index.ts +1 -1
  138. package/src/serve/jobs.ts +7 -7
  139. package/src/serve/public/app.tsx +79 -25
  140. package/src/serve/public/components/AddCollectionDialog.tsx +382 -0
  141. package/src/serve/public/components/CaptureButton.tsx +60 -0
  142. package/src/serve/public/components/CaptureModal.tsx +365 -0
  143. package/src/serve/public/components/IndexingProgress.tsx +333 -0
  144. package/src/serve/public/components/ShortcutHelpModal.tsx +106 -0
  145. package/src/serve/public/components/ai-elements/code-block.tsx +42 -32
  146. package/src/serve/public/components/ai-elements/conversation.tsx +16 -14
  147. package/src/serve/public/components/ai-elements/inline-citation.tsx +33 -32
  148. package/src/serve/public/components/ai-elements/loader.tsx +5 -4
  149. package/src/serve/public/components/ai-elements/message.tsx +39 -37
  150. package/src/serve/public/components/ai-elements/prompt-input.tsx +97 -95
  151. package/src/serve/public/components/ai-elements/sources.tsx +12 -10
  152. package/src/serve/public/components/ai-elements/suggestion.tsx +10 -9
  153. package/src/serve/public/components/editor/CodeMirrorEditor.tsx +142 -0
  154. package/src/serve/public/components/editor/MarkdownPreview.tsx +311 -0
  155. package/src/serve/public/components/editor/index.ts +6 -0
  156. package/src/serve/public/components/preset-selector.tsx +29 -28
  157. package/src/serve/public/components/ui/badge.tsx +13 -12
  158. package/src/serve/public/components/ui/button-group.tsx +13 -12
  159. package/src/serve/public/components/ui/button.tsx +23 -22
  160. package/src/serve/public/components/ui/card.tsx +16 -16
  161. package/src/serve/public/components/ui/carousel.tsx +36 -35
  162. package/src/serve/public/components/ui/collapsible.tsx +1 -1
  163. package/src/serve/public/components/ui/command.tsx +17 -15
  164. package/src/serve/public/components/ui/dialog.tsx +13 -12
  165. package/src/serve/public/components/ui/dropdown-menu.tsx +13 -12
  166. package/src/serve/public/components/ui/hover-card.tsx +6 -5
  167. package/src/serve/public/components/ui/input-group.tsx +45 -43
  168. package/src/serve/public/components/ui/input.tsx +6 -6
  169. package/src/serve/public/components/ui/progress.tsx +5 -4
  170. package/src/serve/public/components/ui/scroll-area.tsx +11 -10
  171. package/src/serve/public/components/ui/select.tsx +19 -18
  172. package/src/serve/public/components/ui/separator.tsx +6 -5
  173. package/src/serve/public/components/ui/table.tsx +18 -18
  174. package/src/serve/public/components/ui/textarea.tsx +4 -4
  175. package/src/serve/public/components/ui/tooltip.tsx +5 -4
  176. package/src/serve/public/globals.css +27 -4
  177. package/src/serve/public/hooks/use-api.ts +8 -8
  178. package/src/serve/public/hooks/useCaptureModal.tsx +83 -0
  179. package/src/serve/public/hooks/useKeyboardShortcuts.ts +85 -0
  180. package/src/serve/public/index.html +4 -4
  181. package/src/serve/public/lib/utils.ts +6 -0
  182. package/src/serve/public/pages/Ask.tsx +27 -26
  183. package/src/serve/public/pages/Browse.tsx +28 -27
  184. package/src/serve/public/pages/Collections.tsx +439 -0
  185. package/src/serve/public/pages/Dashboard.tsx +166 -40
  186. package/src/serve/public/pages/DocView.tsx +258 -73
  187. package/src/serve/public/pages/DocumentEditor.tsx +510 -0
  188. package/src/serve/public/pages/Search.tsx +80 -58
  189. package/src/serve/routes/api.ts +272 -155
  190. package/src/serve/security.ts +4 -4
  191. package/src/serve/server.ts +66 -48
  192. package/src/store/index.ts +5 -5
  193. package/src/store/migrations/001-initial.ts +24 -23
  194. package/src/store/migrations/002-documents-fts.ts +7 -6
  195. package/src/store/migrations/index.ts +4 -4
  196. package/src/store/migrations/runner.ts +17 -15
  197. package/src/store/sqlite/adapter.ts +123 -121
  198. package/src/store/sqlite/fts5-snowball.ts +24 -23
  199. package/src/store/sqlite/index.ts +1 -1
  200. package/src/store/sqlite/setup.ts +12 -12
  201. package/src/store/sqlite/types.ts +4 -4
  202. package/src/store/types.ts +19 -19
  203. package/src/store/vector/index.ts +3 -3
  204. package/src/store/vector/sqlite-vec.ts +23 -20
  205. package/src/store/vector/stats.ts +10 -8
  206. package/src/store/vector/types.ts +2 -2
  207. package/vendor/fts5-snowball/README.md +6 -6
  208. package/assets/screenshots/webui-ask-answer.jpg +0 -0
  209. package/assets/screenshots/webui-home.jpg +0 -0
@@ -5,28 +5,29 @@
5
5
  * @module src/cli/program
6
6
  */
7
7
 
8
- import { Command } from 'commander';
8
+ import { Command } from "commander";
9
+
9
10
  import {
10
11
  CLI_NAME,
11
12
  DOCS_URL,
12
13
  ISSUES_URL,
13
14
  PRODUCT_NAME,
14
15
  VERSION,
15
- } from '../app/constants';
16
- import { setColorsEnabled } from './colors';
16
+ } from "../app/constants";
17
+ import { setColorsEnabled } from "./colors";
17
18
  import {
18
19
  applyGlobalOptions,
19
20
  type GlobalOptions,
20
21
  parseGlobalOptions,
21
- } from './context';
22
- import { CliError } from './errors';
22
+ } from "./context";
23
+ import { CliError } from "./errors";
23
24
  import {
24
25
  assertFormatSupported,
25
26
  CMD,
26
27
  getDefaultLimit,
27
28
  parseOptionalFloat,
28
29
  parsePositiveInt,
29
- } from './options';
30
+ } from "./options";
30
31
 
31
32
  // ─────────────────────────────────────────────────────────────────────────────
32
33
  // Global State (set by preAction hook)
@@ -41,7 +42,7 @@ const globalState: { current: GlobalOptions | null } = { current: null };
41
42
  */
42
43
  export function getGlobals(): GlobalOptions {
43
44
  if (!globalState.current) {
44
- throw new Error('Global options not resolved - called before preAction?');
45
+ throw new Error("Global options not resolved - called before preAction?");
45
46
  }
46
47
  return globalState.current;
47
48
  }
@@ -62,7 +63,7 @@ export function resetGlobals(): void {
62
63
  */
63
64
  function getFormat(
64
65
  cmdOpts: Record<string, unknown>
65
- ): 'terminal' | 'json' | 'files' | 'csv' | 'md' | 'xml' {
66
+ ): "terminal" | "json" | "files" | "csv" | "md" | "xml" {
66
67
  const globals = getGlobals();
67
68
 
68
69
  const local = {
@@ -77,36 +78,36 @@ function getFormat(
77
78
  const localFormats = Object.entries(local).filter(([_, v]) => v);
78
79
  if (localFormats.length > 1) {
79
80
  throw new CliError(
80
- 'VALIDATION',
81
- `Conflicting output formats: ${localFormats.map(([k]) => k).join(', ')}. Choose one.`
81
+ "VALIDATION",
82
+ `Conflicting output formats: ${localFormats.map(([k]) => k).join(", ")}. Choose one.`
82
83
  );
83
84
  }
84
85
 
85
86
  // Local non-json format wins (--md, --csv, --files, --xml)
86
87
  if (local.files) {
87
- return 'files';
88
+ return "files";
88
89
  }
89
90
  if (local.csv) {
90
- return 'csv';
91
+ return "csv";
91
92
  }
92
93
  if (local.md) {
93
- return 'md';
94
+ return "md";
94
95
  }
95
96
  if (local.xml) {
96
- return 'xml';
97
+ return "xml";
97
98
  }
98
99
 
99
100
  // Local --json wins over global
100
101
  if (local.json) {
101
- return 'json';
102
+ return "json";
102
103
  }
103
104
 
104
105
  // Global --json as fallback
105
106
  if (globals.json) {
106
- return 'json';
107
+ return "json";
107
108
  }
108
109
 
109
- return 'terminal';
110
+ return "terminal";
110
111
  }
111
112
 
112
113
  // ─────────────────────────────────────────────────────────────────────────────
@@ -119,24 +120,24 @@ export function createProgram(): Command {
119
120
  program
120
121
  .name(CLI_NAME)
121
122
  .description(`${PRODUCT_NAME} - Local Knowledge Index and Retrieval`)
122
- .version(VERSION, '-V, --version', 'show version')
123
+ .version(VERSION, "-V, --version", "show version")
123
124
  .exitOverride() // Prevent Commander from calling process.exit()
124
125
  .showSuggestionAfterError(true)
125
- .showHelpAfterError('(Use --help for available options)');
126
+ .showHelpAfterError("(Use --help for available options)");
126
127
 
127
128
  // Global flags - resolved via preAction hook
128
129
  program
129
- .option('--index <name>', 'index name', 'default')
130
- .option('--config <path>', 'config file path')
131
- .option('--no-color', 'disable colors')
132
- .option('--verbose', 'verbose logging')
133
- .option('--yes', 'non-interactive mode')
134
- .option('-q, --quiet', 'suppress non-essential output')
135
- .option('--json', 'JSON output (for errors and supported commands)')
136
- .option('--offline', 'offline mode (use cached models only)');
130
+ .option("--index <name>", "index name", "default")
131
+ .option("--config <path>", "config file path")
132
+ .option("--no-color", "disable colors")
133
+ .option("--verbose", "verbose logging")
134
+ .option("--yes", "non-interactive mode")
135
+ .option("-q, --quiet", "suppress non-essential output")
136
+ .option("--json", "JSON output (for errors and supported commands)")
137
+ .option("--offline", "offline mode (use cached models only)");
137
138
 
138
139
  // Resolve globals ONCE before any command runs (ensures consistency)
139
- program.hook('preAction', (thisCommand) => {
140
+ program.hook("preAction", (thisCommand) => {
140
141
  const rootOpts = thisCommand.optsWithGlobals();
141
142
  const globals = parseGlobalOptions(rootOpts);
142
143
  applyGlobalOptions(globals);
@@ -154,7 +155,7 @@ export function createProgram(): Command {
154
155
 
155
156
  // Add docs/support links to help footer
156
157
  program.addHelpText(
157
- 'after',
158
+ "after",
158
159
  `
159
160
  Documentation: ${DOCS_URL}
160
161
  Report issues: ${ISSUES_URL}`
@@ -170,39 +171,39 @@ Report issues: ${ISSUES_URL}`
170
171
  function wireSearchCommands(program: Command): void {
171
172
  // search - BM25 keyword search
172
173
  program
173
- .command('search <query>')
174
- .description('BM25 keyword search')
175
- .option('-n, --limit <num>', 'max results')
176
- .option('--min-score <num>', 'minimum score threshold')
177
- .option('-c, --collection <name>', 'filter by collection')
178
- .option('--lang <code>', 'language filter/hint (BCP-47)')
179
- .option('--full', 'include full content')
180
- .option('--line-numbers', 'include line numbers in output')
181
- .option('--json', 'JSON output')
182
- .option('--md', 'Markdown output')
183
- .option('--csv', 'CSV output')
184
- .option('--xml', 'XML output')
185
- .option('--files', 'file paths only')
174
+ .command("search <query>")
175
+ .description("BM25 keyword search")
176
+ .option("-n, --limit <num>", "max results")
177
+ .option("--min-score <num>", "minimum score threshold")
178
+ .option("-c, --collection <name>", "filter by collection")
179
+ .option("--lang <code>", "language filter/hint (BCP-47)")
180
+ .option("--full", "include full content")
181
+ .option("--line-numbers", "include line numbers in output")
182
+ .option("--json", "JSON output")
183
+ .option("--md", "Markdown output")
184
+ .option("--csv", "CSV output")
185
+ .option("--xml", "XML output")
186
+ .option("--files", "file paths only")
186
187
  .action(async (queryText: string, cmdOpts: Record<string, unknown>) => {
187
188
  const format = getFormat(cmdOpts);
188
189
  assertFormatSupported(CMD.search, format);
189
190
 
190
191
  // Validate empty query
191
192
  if (!queryText.trim()) {
192
- throw new CliError('VALIDATION', 'Query cannot be empty');
193
+ throw new CliError("VALIDATION", "Query cannot be empty");
193
194
  }
194
195
 
195
196
  // Validate minScore range
196
- const minScore = parseOptionalFloat('min-score', cmdOpts.minScore);
197
+ const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
197
198
  if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
198
- throw new CliError('VALIDATION', '--min-score must be between 0 and 1');
199
+ throw new CliError("VALIDATION", "--min-score must be between 0 and 1");
199
200
  }
200
201
 
201
202
  const limit = cmdOpts.limit
202
- ? parsePositiveInt('limit', cmdOpts.limit)
203
+ ? parsePositiveInt("limit", cmdOpts.limit)
203
204
  : getDefaultLimit(format);
204
205
 
205
- const { search, formatSearch } = await import('./commands/search');
206
+ const { search, formatSearch } = await import("./commands/search");
206
207
  const result = await search(queryText, {
207
208
  limit,
208
209
  minScore,
@@ -210,28 +211,28 @@ function wireSearchCommands(program: Command): void {
210
211
  lang: cmdOpts.lang as string | undefined,
211
212
  full: Boolean(cmdOpts.full),
212
213
  lineNumbers: Boolean(cmdOpts.lineNumbers),
213
- json: format === 'json',
214
- md: format === 'md',
215
- csv: format === 'csv',
216
- xml: format === 'xml',
217
- files: format === 'files',
214
+ json: format === "json",
215
+ md: format === "md",
216
+ csv: format === "csv",
217
+ xml: format === "xml",
218
+ files: format === "files",
218
219
  });
219
220
 
220
221
  // Check success before printing - stdout is for successful outputs only
221
222
  if (!result.success) {
222
223
  // Map validation errors to exit code 1
223
224
  throw new CliError(
224
- result.isValidation ? 'VALIDATION' : 'RUNTIME',
225
+ result.isValidation ? "VALIDATION" : "RUNTIME",
225
226
  result.error
226
227
  );
227
228
  }
228
229
  process.stdout.write(
229
230
  `${formatSearch(result, {
230
- json: format === 'json',
231
- md: format === 'md',
232
- csv: format === 'csv',
233
- xml: format === 'xml',
234
- files: format === 'files',
231
+ json: format === "json",
232
+ md: format === "md",
233
+ csv: format === "csv",
234
+ xml: format === "xml",
235
+ files: format === "files",
235
236
  full: Boolean(cmdOpts.full),
236
237
  lineNumbers: Boolean(cmdOpts.lineNumbers),
237
238
  })}\n`
@@ -240,39 +241,39 @@ function wireSearchCommands(program: Command): void {
240
241
 
241
242
  // vsearch - Vector similarity search
242
243
  program
243
- .command('vsearch <query>')
244
- .description('Vector similarity search')
245
- .option('-n, --limit <num>', 'max results')
246
- .option('--min-score <num>', 'minimum score threshold')
247
- .option('-c, --collection <name>', 'filter by collection')
248
- .option('--lang <code>', 'language filter/hint (BCP-47)')
249
- .option('--full', 'include full content')
250
- .option('--line-numbers', 'include line numbers in output')
251
- .option('--json', 'JSON output')
252
- .option('--md', 'Markdown output')
253
- .option('--csv', 'CSV output')
254
- .option('--xml', 'XML output')
255
- .option('--files', 'file paths only')
244
+ .command("vsearch <query>")
245
+ .description("Vector similarity search")
246
+ .option("-n, --limit <num>", "max results")
247
+ .option("--min-score <num>", "minimum score threshold")
248
+ .option("-c, --collection <name>", "filter by collection")
249
+ .option("--lang <code>", "language filter/hint (BCP-47)")
250
+ .option("--full", "include full content")
251
+ .option("--line-numbers", "include line numbers in output")
252
+ .option("--json", "JSON output")
253
+ .option("--md", "Markdown output")
254
+ .option("--csv", "CSV output")
255
+ .option("--xml", "XML output")
256
+ .option("--files", "file paths only")
256
257
  .action(async (queryText: string, cmdOpts: Record<string, unknown>) => {
257
258
  const format = getFormat(cmdOpts);
258
259
  assertFormatSupported(CMD.vsearch, format);
259
260
 
260
261
  // Validate empty query
261
262
  if (!queryText.trim()) {
262
- throw new CliError('VALIDATION', 'Query cannot be empty');
263
+ throw new CliError("VALIDATION", "Query cannot be empty");
263
264
  }
264
265
 
265
266
  // Validate minScore range
266
- const minScore = parseOptionalFloat('min-score', cmdOpts.minScore);
267
+ const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
267
268
  if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
268
- throw new CliError('VALIDATION', '--min-score must be between 0 and 1');
269
+ throw new CliError("VALIDATION", "--min-score must be between 0 and 1");
269
270
  }
270
271
 
271
272
  const limit = cmdOpts.limit
272
- ? parsePositiveInt('limit', cmdOpts.limit)
273
+ ? parsePositiveInt("limit", cmdOpts.limit)
273
274
  : getDefaultLimit(format);
274
275
 
275
- const { vsearch, formatVsearch } = await import('./commands/vsearch');
276
+ const { vsearch, formatVsearch } = await import("./commands/vsearch");
276
277
  const result = await vsearch(queryText, {
277
278
  limit,
278
279
  minScore,
@@ -280,23 +281,23 @@ function wireSearchCommands(program: Command): void {
280
281
  lang: cmdOpts.lang as string | undefined,
281
282
  full: Boolean(cmdOpts.full),
282
283
  lineNumbers: Boolean(cmdOpts.lineNumbers),
283
- json: format === 'json',
284
- md: format === 'md',
285
- csv: format === 'csv',
286
- xml: format === 'xml',
287
- files: format === 'files',
284
+ json: format === "json",
285
+ md: format === "md",
286
+ csv: format === "csv",
287
+ xml: format === "xml",
288
+ files: format === "files",
288
289
  });
289
290
 
290
291
  if (!result.success) {
291
- throw new CliError('RUNTIME', result.error);
292
+ throw new CliError("RUNTIME", result.error);
292
293
  }
293
294
  process.stdout.write(
294
295
  `${formatVsearch(result, {
295
- json: format === 'json',
296
- md: format === 'md',
297
- csv: format === 'csv',
298
- xml: format === 'xml',
299
- files: format === 'files',
296
+ json: format === "json",
297
+ md: format === "md",
298
+ csv: format === "csv",
299
+ xml: format === "xml",
300
+ files: format === "files",
300
301
  full: Boolean(cmdOpts.full),
301
302
  lineNumbers: Boolean(cmdOpts.lineNumbers),
302
303
  })}\n`
@@ -305,41 +306,41 @@ function wireSearchCommands(program: Command): void {
305
306
 
306
307
  // query - Hybrid search with expansion and reranking
307
308
  program
308
- .command('query <query>')
309
- .description('Hybrid search with expansion and reranking')
310
- .option('-n, --limit <num>', 'max results')
311
- .option('--min-score <num>', 'minimum score threshold')
312
- .option('-c, --collection <name>', 'filter by collection')
313
- .option('--lang <code>', 'language hint (BCP-47)')
314
- .option('--full', 'include full content')
315
- .option('--line-numbers', 'include line numbers in output')
316
- .option('--fast', 'skip expansion and reranking (fastest, ~0.7s)')
317
- .option('--thorough', 'enable query expansion (slower, ~5-8s)')
318
- .option('--no-expand', 'disable query expansion')
319
- .option('--no-rerank', 'disable reranking')
320
- .option('--explain', 'include scoring explanation')
321
- .option('--json', 'JSON output')
322
- .option('--md', 'Markdown output')
323
- .option('--csv', 'CSV output')
324
- .option('--xml', 'XML output')
325
- .option('--files', 'file paths only')
309
+ .command("query <query>")
310
+ .description("Hybrid search with expansion and reranking")
311
+ .option("-n, --limit <num>", "max results")
312
+ .option("--min-score <num>", "minimum score threshold")
313
+ .option("-c, --collection <name>", "filter by collection")
314
+ .option("--lang <code>", "language hint (BCP-47)")
315
+ .option("--full", "include full content")
316
+ .option("--line-numbers", "include line numbers in output")
317
+ .option("--fast", "skip expansion and reranking (fastest, ~0.7s)")
318
+ .option("--thorough", "enable query expansion (slower, ~5-8s)")
319
+ .option("--no-expand", "disable query expansion")
320
+ .option("--no-rerank", "disable reranking")
321
+ .option("--explain", "include scoring explanation")
322
+ .option("--json", "JSON output")
323
+ .option("--md", "Markdown output")
324
+ .option("--csv", "CSV output")
325
+ .option("--xml", "XML output")
326
+ .option("--files", "file paths only")
326
327
  .action(async (queryText: string, cmdOpts: Record<string, unknown>) => {
327
328
  const format = getFormat(cmdOpts);
328
329
  assertFormatSupported(CMD.query, format);
329
330
 
330
331
  // Validate empty query
331
332
  if (!queryText.trim()) {
332
- throw new CliError('VALIDATION', 'Query cannot be empty');
333
+ throw new CliError("VALIDATION", "Query cannot be empty");
333
334
  }
334
335
 
335
336
  // Validate minScore range
336
- const minScore = parseOptionalFloat('min-score', cmdOpts.minScore);
337
+ const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
337
338
  if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
338
- throw new CliError('VALIDATION', '--min-score must be between 0 and 1');
339
+ throw new CliError("VALIDATION", "--min-score must be between 0 and 1");
339
340
  }
340
341
 
341
342
  const limit = cmdOpts.limit
342
- ? parsePositiveInt('limit', cmdOpts.limit)
343
+ ? parsePositiveInt("limit", cmdOpts.limit)
343
344
  : getDefaultLimit(format);
344
345
 
345
346
  // Determine expansion/rerank settings based on flags
@@ -366,7 +367,7 @@ function wireSearchCommands(program: Command): void {
366
367
  }
367
368
  }
368
369
 
369
- const { query, formatQuery } = await import('./commands/query');
370
+ const { query, formatQuery } = await import("./commands/query");
370
371
  const result = await query(queryText, {
371
372
  limit,
372
373
  minScore,
@@ -377,15 +378,15 @@ function wireSearchCommands(program: Command): void {
377
378
  noExpand,
378
379
  noRerank,
379
380
  explain: Boolean(cmdOpts.explain),
380
- json: format === 'json',
381
- md: format === 'md',
382
- csv: format === 'csv',
383
- xml: format === 'xml',
384
- files: format === 'files',
381
+ json: format === "json",
382
+ md: format === "md",
383
+ csv: format === "csv",
384
+ xml: format === "xml",
385
+ files: format === "files",
385
386
  });
386
387
 
387
388
  if (!result.success) {
388
- throw new CliError('RUNTIME', result.error);
389
+ throw new CliError("RUNTIME", result.error);
389
390
  }
390
391
  process.stdout.write(
391
392
  `${formatQuery(result, {
@@ -398,35 +399,35 @@ function wireSearchCommands(program: Command): void {
398
399
 
399
400
  // ask - Human-friendly query with grounded answer
400
401
  program
401
- .command('ask <query>')
402
- .description('Human-friendly query with grounded answer')
403
- .option('-n, --limit <num>', 'max source results')
404
- .option('-c, --collection <name>', 'filter by collection')
405
- .option('--lang <code>', 'language hint (BCP-47)')
406
- .option('--fast', 'skip expansion and reranking (fastest)')
407
- .option('--thorough', 'enable query expansion (slower)')
408
- .option('--answer', 'generate short grounded answer')
409
- .option('--no-answer', 'force retrieval-only output')
410
- .option('--max-answer-tokens <num>', 'max answer tokens')
411
- .option('--show-sources', 'show all retrieved sources (not just cited)')
412
- .option('--json', 'JSON output')
413
- .option('--md', 'Markdown output')
402
+ .command("ask <query>")
403
+ .description("Human-friendly query with grounded answer")
404
+ .option("-n, --limit <num>", "max source results")
405
+ .option("-c, --collection <name>", "filter by collection")
406
+ .option("--lang <code>", "language hint (BCP-47)")
407
+ .option("--fast", "skip expansion and reranking (fastest)")
408
+ .option("--thorough", "enable query expansion (slower)")
409
+ .option("--answer", "generate short grounded answer")
410
+ .option("--no-answer", "force retrieval-only output")
411
+ .option("--max-answer-tokens <num>", "max answer tokens")
412
+ .option("--show-sources", "show all retrieved sources (not just cited)")
413
+ .option("--json", "JSON output")
414
+ .option("--md", "Markdown output")
414
415
  .action(async (queryText: string, cmdOpts: Record<string, unknown>) => {
415
416
  const format = getFormat(cmdOpts);
416
417
  assertFormatSupported(CMD.ask, format);
417
418
 
418
419
  // Validate empty query
419
420
  if (!queryText.trim()) {
420
- throw new CliError('VALIDATION', 'Query cannot be empty');
421
+ throw new CliError("VALIDATION", "Query cannot be empty");
421
422
  }
422
423
 
423
424
  const limit = cmdOpts.limit
424
- ? parsePositiveInt('limit', cmdOpts.limit)
425
+ ? parsePositiveInt("limit", cmdOpts.limit)
425
426
  : getDefaultLimit(format);
426
427
 
427
428
  // Parse max-answer-tokens (optional, defaults to 512 in command impl)
428
429
  const maxAnswerTokens = cmdOpts.maxAnswerTokens
429
- ? parsePositiveInt('max-answer-tokens', cmdOpts.maxAnswerTokens)
430
+ ? parsePositiveInt("max-answer-tokens", cmdOpts.maxAnswerTokens)
430
431
  : undefined;
431
432
 
432
433
  // Determine expansion/rerank settings based on flags
@@ -442,7 +443,7 @@ function wireSearchCommands(program: Command): void {
442
443
  noRerank = false;
443
444
  }
444
445
 
445
- const { ask, formatAsk } = await import('./commands/ask');
446
+ const { ask, formatAsk } = await import("./commands/ask");
446
447
  const showSources = Boolean(cmdOpts.showSources);
447
448
  const result = await ask(queryText, {
448
449
  limit,
@@ -456,15 +457,15 @@ function wireSearchCommands(program: Command): void {
456
457
  noAnswer: Boolean(cmdOpts.noAnswer),
457
458
  maxAnswerTokens,
458
459
  showSources,
459
- json: format === 'json',
460
- md: format === 'md',
460
+ json: format === "json",
461
+ md: format === "md",
461
462
  });
462
463
 
463
464
  if (!result.success) {
464
- throw new CliError('RUNTIME', result.error);
465
+ throw new CliError("RUNTIME", result.error);
465
466
  }
466
467
  process.stdout.write(
467
- `${formatAsk(result, { json: format === 'json', md: format === 'md', showSources })}\n`
468
+ `${formatAsk(result, { json: format === "json", md: format === "md", showSources })}\n`
468
469
  );
469
470
  });
470
471
  }
@@ -476,19 +477,19 @@ function wireSearchCommands(program: Command): void {
476
477
  function wireOnboardingCommands(program: Command): void {
477
478
  // init - Initialize GNO
478
479
  program
479
- .command('init [path]')
480
- .description('Initialize GNO configuration')
481
- .option('-n, --name <name>', 'collection name')
482
- .option('--pattern <glob>', 'file matching pattern')
483
- .option('--include <exts>', 'extension allowlist (CSV)')
484
- .option('--exclude <patterns>', 'exclude patterns (CSV)')
485
- .option('--update <cmd>', 'shell command to run before indexing')
486
- .option('--tokenizer <type>', 'FTS tokenizer (unicode61, porter, trigram)')
487
- .option('--language <code>', 'language hint (BCP-47)')
480
+ .command("init [path]")
481
+ .description("Initialize GNO configuration")
482
+ .option("-n, --name <name>", "collection name")
483
+ .option("--pattern <glob>", "file matching pattern")
484
+ .option("--include <exts>", "extension allowlist (CSV)")
485
+ .option("--exclude <patterns>", "exclude patterns (CSV)")
486
+ .option("--update <cmd>", "shell command to run before indexing")
487
+ .option("--tokenizer <type>", "FTS tokenizer (unicode61, porter, trigram)")
488
+ .option("--language <code>", "language hint (BCP-47)")
488
489
  .action(
489
490
  async (path: string | undefined, cmdOpts: Record<string, unknown>) => {
490
491
  const globals = getGlobals();
491
- const { init } = await import('./commands/init');
492
+ const { init } = await import("./commands/init");
492
493
  const result = await init({
493
494
  path,
494
495
  name: cmdOpts.name as string | undefined,
@@ -497,27 +498,27 @@ function wireOnboardingCommands(program: Command): void {
497
498
  exclude: cmdOpts.exclude as string | undefined,
498
499
  update: cmdOpts.update as string | undefined,
499
500
  tokenizer: cmdOpts.tokenizer as
500
- | 'unicode61'
501
- | 'porter'
502
- | 'trigram'
501
+ | "unicode61"
502
+ | "porter"
503
+ | "trigram"
503
504
  | undefined,
504
505
  language: cmdOpts.language as string | undefined,
505
506
  yes: globals.yes,
506
507
  });
507
508
 
508
509
  if (!result.success) {
509
- throw new CliError('RUNTIME', result.error ?? 'Init failed');
510
+ throw new CliError("RUNTIME", result.error ?? "Init failed");
510
511
  }
511
512
 
512
513
  if (result.alreadyInitialized) {
513
- process.stdout.write('GNO already initialized.\n');
514
+ process.stdout.write("GNO already initialized.\n");
514
515
  if (result.collectionAdded) {
515
516
  process.stdout.write(
516
517
  `Collection "${result.collectionAdded}" added.\n`
517
518
  );
518
519
  }
519
520
  } else {
520
- process.stdout.write('GNO initialized successfully.\n');
521
+ process.stdout.write("GNO initialized successfully.\n");
521
522
  process.stdout.write(`Config: ${result.configPath}\n`);
522
523
  process.stdout.write(`Database: ${result.dbPath}\n`);
523
524
  if (result.collectionAdded) {
@@ -531,18 +532,18 @@ function wireOnboardingCommands(program: Command): void {
531
532
 
532
533
  // index - Index collections
533
534
  program
534
- .command('index [collection]')
535
- .description('Index files from collections')
536
- .option('--no-embed', 'skip embedding after sync')
537
- .option('--git-pull', 'run git pull in git repositories')
538
- .option('--models-pull', 'download models if missing')
535
+ .command("index [collection]")
536
+ .description("Index files from collections")
537
+ .option("--no-embed", "skip embedding after sync")
538
+ .option("--git-pull", "run git pull in git repositories")
539
+ .option("--models-pull", "download models if missing")
539
540
  .action(
540
541
  async (
541
542
  collection: string | undefined,
542
543
  cmdOpts: Record<string, unknown>
543
544
  ) => {
544
545
  const globals = getGlobals();
545
- const { index, formatIndex } = await import('./commands/index-cmd');
546
+ const { index, formatIndex } = await import("./commands/index-cmd");
546
547
  const opts = {
547
548
  collection,
548
549
  noEmbed: cmdOpts.embed === false,
@@ -554,7 +555,7 @@ function wireOnboardingCommands(program: Command): void {
554
555
  const result = await index(opts);
555
556
 
556
557
  if (!result.success) {
557
- throw new CliError('RUNTIME', result.error ?? 'Index failed');
558
+ throw new CliError("RUNTIME", result.error ?? "Index failed");
558
559
  }
559
560
  process.stdout.write(`${formatIndex(result, opts)}\n`);
560
561
  }
@@ -562,37 +563,37 @@ function wireOnboardingCommands(program: Command): void {
562
563
 
563
564
  // status - Show index status
564
565
  program
565
- .command('status')
566
- .description('Show index status')
567
- .option('--json', 'JSON output')
566
+ .command("status")
567
+ .description("Show index status")
568
+ .option("--json", "JSON output")
568
569
  .action(async (cmdOpts: Record<string, unknown>) => {
569
570
  const format = getFormat(cmdOpts);
570
571
  assertFormatSupported(CMD.status, format);
571
572
 
572
- const { status, formatStatus } = await import('./commands/status');
573
- const result = await status({ json: format === 'json' });
573
+ const { status, formatStatus } = await import("./commands/status");
574
+ const result = await status({ json: format === "json" });
574
575
 
575
576
  if (!result.success) {
576
- throw new CliError('RUNTIME', result.error ?? 'Status failed');
577
+ throw new CliError("RUNTIME", result.error ?? "Status failed");
577
578
  }
578
579
  process.stdout.write(
579
- `${formatStatus(result, { json: format === 'json' })}\n`
580
+ `${formatStatus(result, { json: format === "json" })}\n`
580
581
  );
581
582
  });
582
583
 
583
584
  // doctor - Diagnose configuration issues
584
585
  program
585
- .command('doctor')
586
- .description('Diagnose configuration issues')
587
- .option('--json', 'JSON output')
586
+ .command("doctor")
587
+ .description("Diagnose configuration issues")
588
+ .option("--json", "JSON output")
588
589
  .action(async (cmdOpts: Record<string, unknown>) => {
589
590
  const format = getFormat(cmdOpts);
590
- const { doctor, formatDoctor } = await import('./commands/doctor');
591
- const result = await doctor({ json: format === 'json' });
591
+ const { doctor, formatDoctor } = await import("./commands/doctor");
592
+ const result = await doctor({ json: format === "json" });
592
593
 
593
594
  // Doctor always succeeds but may report issues
594
595
  process.stdout.write(
595
- `${formatDoctor(result, { json: format === 'json' })}\n`
596
+ `${formatDoctor(result, { json: format === "json" })}\n`
596
597
  );
597
598
  });
598
599
  }
@@ -604,41 +605,41 @@ function wireOnboardingCommands(program: Command): void {
604
605
  function wireRetrievalCommands(program: Command): void {
605
606
  // get - Retrieve document by URI or docid
606
607
  program
607
- .command('get <ref>')
608
- .description('Get document by URI or docid')
608
+ .command("get <ref>")
609
+ .description("Get document by URI or docid")
609
610
  .option(
610
- '--from <line>',
611
- 'Start at line number',
612
- parsePositiveInt.bind(null, 'from')
611
+ "--from <line>",
612
+ "Start at line number",
613
+ parsePositiveInt.bind(null, "from")
613
614
  )
614
615
  .option(
615
- '-l, --limit <lines>',
616
- 'Limit to N lines',
617
- parsePositiveInt.bind(null, 'limit')
616
+ "-l, --limit <lines>",
617
+ "Limit to N lines",
618
+ parsePositiveInt.bind(null, "limit")
618
619
  )
619
- .option('--line-numbers', 'Prefix lines with numbers')
620
- .option('--source', 'Include source metadata')
621
- .option('--json', 'JSON output')
622
- .option('--md', 'Markdown output')
620
+ .option("--line-numbers", "Prefix lines with numbers")
621
+ .option("--source", "Include source metadata")
622
+ .option("--json", "JSON output")
623
+ .option("--md", "Markdown output")
623
624
  .action(async (ref: string, cmdOpts: Record<string, unknown>) => {
624
625
  const format = getFormat(cmdOpts);
625
626
  assertFormatSupported(CMD.get, format);
626
627
  const globals = getGlobals();
627
628
 
628
- const { get, formatGet } = await import('./commands/get');
629
+ const { get, formatGet } = await import("./commands/get");
629
630
  const result = await get(ref, {
630
631
  configPath: globals.config,
631
632
  from: cmdOpts.from as number | undefined,
632
633
  limit: cmdOpts.limit as number | undefined,
633
634
  lineNumbers: Boolean(cmdOpts.lineNumbers),
634
635
  source: Boolean(cmdOpts.source),
635
- json: format === 'json',
636
- md: format === 'md',
636
+ json: format === "json",
637
+ md: format === "md",
637
638
  });
638
639
 
639
640
  if (!result.success) {
640
641
  throw new CliError(
641
- result.isValidation ? 'VALIDATION' : 'RUNTIME',
642
+ result.isValidation ? "VALIDATION" : "RUNTIME",
642
643
  result.error
643
644
  );
644
645
  }
@@ -646,43 +647,43 @@ function wireRetrievalCommands(program: Command): void {
646
647
  process.stdout.write(
647
648
  `${formatGet(result, {
648
649
  lineNumbers: Boolean(cmdOpts.lineNumbers),
649
- json: format === 'json',
650
- md: format === 'md',
650
+ json: format === "json",
651
+ md: format === "md",
651
652
  })}\n`
652
653
  );
653
654
  });
654
655
 
655
656
  // multi-get - Retrieve multiple documents
656
657
  program
657
- .command('multi-get <refs...>')
658
- .description('Get multiple documents by URI or docid')
658
+ .command("multi-get <refs...>")
659
+ .description("Get multiple documents by URI or docid")
659
660
  .option(
660
- '--max-bytes <n>',
661
- 'Max bytes per document',
662
- parsePositiveInt.bind(null, 'max-bytes')
661
+ "--max-bytes <n>",
662
+ "Max bytes per document",
663
+ parsePositiveInt.bind(null, "max-bytes")
663
664
  )
664
- .option('--line-numbers', 'Include line numbers')
665
- .option('--json', 'JSON output')
666
- .option('--files', 'File protocol output')
667
- .option('--md', 'Markdown output')
665
+ .option("--line-numbers", "Include line numbers")
666
+ .option("--json", "JSON output")
667
+ .option("--files", "File protocol output")
668
+ .option("--md", "Markdown output")
668
669
  .action(async (refs: string[], cmdOpts: Record<string, unknown>) => {
669
670
  const format = getFormat(cmdOpts);
670
671
  assertFormatSupported(CMD.multiGet, format);
671
672
  const globals = getGlobals();
672
673
 
673
- const { multiGet, formatMultiGet } = await import('./commands/multi-get');
674
+ const { multiGet, formatMultiGet } = await import("./commands/multi-get");
674
675
  const result = await multiGet(refs, {
675
676
  configPath: globals.config,
676
677
  maxBytes: cmdOpts.maxBytes as number | undefined,
677
678
  lineNumbers: Boolean(cmdOpts.lineNumbers),
678
- json: format === 'json',
679
- files: format === 'files',
680
- md: format === 'md',
679
+ json: format === "json",
680
+ files: format === "files",
681
+ md: format === "md",
681
682
  });
682
683
 
683
684
  if (!result.success) {
684
685
  throw new CliError(
685
- result.isValidation ? 'VALIDATION' : 'RUNTIME',
686
+ result.isValidation ? "VALIDATION" : "RUNTIME",
686
687
  result.error
687
688
  );
688
689
  }
@@ -690,58 +691,58 @@ function wireRetrievalCommands(program: Command): void {
690
691
  process.stdout.write(
691
692
  `${formatMultiGet(result, {
692
693
  lineNumbers: Boolean(cmdOpts.lineNumbers),
693
- json: format === 'json',
694
- files: format === 'files',
695
- md: format === 'md',
694
+ json: format === "json",
695
+ files: format === "files",
696
+ md: format === "md",
696
697
  })}\n`
697
698
  );
698
699
  });
699
700
 
700
701
  // ls - List indexed documents
701
702
  program
702
- .command('ls [scope]')
703
- .description('List indexed documents')
703
+ .command("ls [scope]")
704
+ .description("List indexed documents")
704
705
  .option(
705
- '-n, --limit <num>',
706
- 'Max results',
707
- parsePositiveInt.bind(null, 'limit')
706
+ "-n, --limit <num>",
707
+ "Max results",
708
+ parsePositiveInt.bind(null, "limit")
708
709
  )
709
710
  .option(
710
- '--offset <num>',
711
- 'Skip first N results',
712
- parsePositiveInt.bind(null, 'offset')
711
+ "--offset <num>",
712
+ "Skip first N results",
713
+ parsePositiveInt.bind(null, "offset")
713
714
  )
714
- .option('--json', 'JSON output')
715
- .option('--files', 'File protocol output')
716
- .option('--md', 'Markdown output')
715
+ .option("--json", "JSON output")
716
+ .option("--files", "File protocol output")
717
+ .option("--md", "Markdown output")
717
718
  .action(
718
719
  async (scope: string | undefined, cmdOpts: Record<string, unknown>) => {
719
720
  const format = getFormat(cmdOpts);
720
721
  assertFormatSupported(CMD.ls, format);
721
722
  const globals = getGlobals();
722
723
 
723
- const { ls, formatLs } = await import('./commands/ls');
724
+ const { ls, formatLs } = await import("./commands/ls");
724
725
  const result = await ls(scope, {
725
726
  configPath: globals.config,
726
727
  limit: cmdOpts.limit as number | undefined,
727
728
  offset: cmdOpts.offset as number | undefined,
728
- json: format === 'json',
729
- files: format === 'files',
730
- md: format === 'md',
729
+ json: format === "json",
730
+ files: format === "files",
731
+ md: format === "md",
731
732
  });
732
733
 
733
734
  if (!result.success) {
734
735
  throw new CliError(
735
- result.isValidation ? 'VALIDATION' : 'RUNTIME',
736
+ result.isValidation ? "VALIDATION" : "RUNTIME",
736
737
  result.error
737
738
  );
738
739
  }
739
740
 
740
741
  process.stdout.write(
741
742
  `${formatLs(result, {
742
- json: format === 'json',
743
- files: format === 'files',
744
- md: format === 'md',
743
+ json: format === "json",
744
+ files: format === "files",
745
+ md: format === "md",
745
746
  })}\n`
746
747
  );
747
748
  }
@@ -757,16 +758,16 @@ function wireMcpCommand(program: Command): void {
757
758
  // CRITICAL: helpOption(false) on server command prevents --help from writing
758
759
  // to stdout which would corrupt the JSON-RPC stream
759
760
  const mcpCmd = program
760
- .command('mcp')
761
- .description('MCP server and configuration');
761
+ .command("mcp")
762
+ .description("MCP server and configuration");
762
763
 
763
764
  // Default action: start MCP server
764
765
  mcpCmd
765
- .command('serve', { isDefault: true })
766
- .description('Start MCP server (stdio transport)')
766
+ .command("serve", { isDefault: true })
767
+ .description("Start MCP server (stdio transport)")
767
768
  .helpOption(false)
768
769
  .action(async () => {
769
- const { mcpCommand } = await import('./commands/mcp.js');
770
+ const { mcpCommand } = await import("./commands/mcp.js");
770
771
  const globalOpts = program.opts();
771
772
  const globals = parseGlobalOptions(globalOpts);
772
773
  await mcpCommand(globals);
@@ -774,49 +775,49 @@ function wireMcpCommand(program: Command): void {
774
775
 
775
776
  // install - Install gno MCP server to client configs
776
777
  mcpCmd
777
- .command('install')
778
- .description('Install gno as MCP server in client configuration')
778
+ .command("install")
779
+ .description("Install gno as MCP server in client configuration")
779
780
  .option(
780
- '-t, --target <target>',
781
- 'target client (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex)',
782
- 'claude-desktop'
781
+ "-t, --target <target>",
782
+ "target client (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex)",
783
+ "claude-desktop"
783
784
  )
784
785
  .option(
785
- '-s, --scope <scope>',
786
- 'scope (user, project) - project only for claude-code/codex/cursor/opencode',
787
- 'user'
786
+ "-s, --scope <scope>",
787
+ "scope (user, project) - project only for claude-code/codex/cursor/opencode",
788
+ "user"
788
789
  )
789
- .option('-f, --force', 'overwrite existing configuration')
790
- .option('--dry-run', 'show what would be done without making changes')
791
- .option('--json', 'JSON output')
790
+ .option("-f, --force", "overwrite existing configuration")
791
+ .option("--dry-run", "show what would be done without making changes")
792
+ .option("--json", "JSON output")
792
793
  .action(async (cmdOpts: Record<string, unknown>) => {
793
794
  const target = cmdOpts.target as string;
794
795
  const scope = cmdOpts.scope as string;
795
796
 
796
797
  // Import MCP_TARGETS for validation
797
- const { MCP_TARGETS } = await import('./commands/mcp/paths.js');
798
+ const { MCP_TARGETS } = await import("./commands/mcp/paths.js");
798
799
 
799
800
  // Validate target
800
801
  if (!(MCP_TARGETS as string[]).includes(target)) {
801
802
  throw new CliError(
802
- 'VALIDATION',
803
- `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(', ')}.`
803
+ "VALIDATION",
804
+ `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}.`
804
805
  );
805
806
  }
806
807
  // Validate scope
807
- if (!['user', 'project'].includes(scope)) {
808
+ if (!["user", "project"].includes(scope)) {
808
809
  throw new CliError(
809
- 'VALIDATION',
810
+ "VALIDATION",
810
811
  `Invalid scope: ${scope}. Must be 'user' or 'project'.`
811
812
  );
812
813
  }
813
814
 
814
- const { installMcp } = await import('./commands/mcp/install.js');
815
+ const { installMcp } = await import("./commands/mcp/install.js");
815
816
  await installMcp({
816
817
  target: target as NonNullable<
817
818
  Parameters<typeof installMcp>[0]
818
- >['target'],
819
- scope: scope as 'user' | 'project',
819
+ >["target"],
820
+ scope: scope as "user" | "project",
820
821
  force: Boolean(cmdOpts.force),
821
822
  dryRun: Boolean(cmdOpts.dryRun),
822
823
  // Pass undefined if not set, so global --json can take effect
@@ -826,43 +827,43 @@ function wireMcpCommand(program: Command): void {
826
827
 
827
828
  // uninstall - Remove gno MCP server from client configs
828
829
  mcpCmd
829
- .command('uninstall')
830
- .description('Remove gno MCP server from client configuration')
830
+ .command("uninstall")
831
+ .description("Remove gno MCP server from client configuration")
831
832
  .option(
832
- '-t, --target <target>',
833
- 'target client (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex)',
834
- 'claude-desktop'
833
+ "-t, --target <target>",
834
+ "target client (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex)",
835
+ "claude-desktop"
835
836
  )
836
- .option('-s, --scope <scope>', 'scope (user, project)', 'user')
837
- .option('--json', 'JSON output')
837
+ .option("-s, --scope <scope>", "scope (user, project)", "user")
838
+ .option("--json", "JSON output")
838
839
  .action(async (cmdOpts: Record<string, unknown>) => {
839
840
  const target = cmdOpts.target as string;
840
841
  const scope = cmdOpts.scope as string;
841
842
 
842
843
  // Import MCP_TARGETS for validation
843
- const { MCP_TARGETS } = await import('./commands/mcp/paths.js');
844
+ const { MCP_TARGETS } = await import("./commands/mcp/paths.js");
844
845
 
845
846
  // Validate target
846
847
  if (!(MCP_TARGETS as string[]).includes(target)) {
847
848
  throw new CliError(
848
- 'VALIDATION',
849
- `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(', ')}.`
849
+ "VALIDATION",
850
+ `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}.`
850
851
  );
851
852
  }
852
853
  // Validate scope
853
- if (!['user', 'project'].includes(scope)) {
854
+ if (!["user", "project"].includes(scope)) {
854
855
  throw new CliError(
855
- 'VALIDATION',
856
+ "VALIDATION",
856
857
  `Invalid scope: ${scope}. Must be 'user' or 'project'.`
857
858
  );
858
859
  }
859
860
 
860
- const { uninstallMcp } = await import('./commands/mcp/uninstall.js');
861
+ const { uninstallMcp } = await import("./commands/mcp/uninstall.js");
861
862
  await uninstallMcp({
862
863
  target: target as NonNullable<
863
864
  Parameters<typeof uninstallMcp>[0]
864
- >['target'],
865
- scope: scope as 'user' | 'project',
865
+ >["target"],
866
+ scope: scope as "user" | "project",
866
867
  // Pass undefined if not set, so global --json can take effect
867
868
  json: cmdOpts.json === true ? true : undefined,
868
869
  });
@@ -870,60 +871,59 @@ function wireMcpCommand(program: Command): void {
870
871
 
871
872
  // status - Show MCP installation status
872
873
  mcpCmd
873
- .command('status')
874
- .description('Show MCP server installation status')
874
+ .command("status")
875
+ .description("Show MCP server installation status")
875
876
  .option(
876
- '-t, --target <target>',
877
- 'filter by target (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex, all)',
878
- 'all'
877
+ "-t, --target <target>",
878
+ "filter by target (claude-desktop, cursor, zed, windsurf, opencode, amp, lmstudio, librechat, claude-code, codex, all)",
879
+ "all"
879
880
  )
880
881
  .option(
881
- '-s, --scope <scope>',
882
- 'filter by scope (user, project, all)',
883
- 'all'
882
+ "-s, --scope <scope>",
883
+ "filter by scope (user, project, all)",
884
+ "all"
884
885
  )
885
- .option('--json', 'JSON output')
886
+ .option("--json", "JSON output")
886
887
  .action(async (cmdOpts: Record<string, unknown>) => {
887
888
  const target = cmdOpts.target as string;
888
889
  const scope = cmdOpts.scope as string;
889
890
 
890
891
  // Import MCP_TARGETS for validation
891
- const { MCP_TARGETS, TARGETS_WITH_PROJECT_SCOPE } = await import(
892
- './commands/mcp/paths.js'
893
- );
892
+ const { MCP_TARGETS, TARGETS_WITH_PROJECT_SCOPE } =
893
+ await import("./commands/mcp/paths.js");
894
894
 
895
895
  // Validate target
896
- if (target !== 'all' && !(MCP_TARGETS as string[]).includes(target)) {
896
+ if (target !== "all" && !(MCP_TARGETS as string[]).includes(target)) {
897
897
  throw new CliError(
898
- 'VALIDATION',
899
- `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(', ')}, all.`
898
+ "VALIDATION",
899
+ `Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}, all.`
900
900
  );
901
901
  }
902
902
  // Validate scope
903
- if (!['user', 'project', 'all'].includes(scope)) {
903
+ if (!["user", "project", "all"].includes(scope)) {
904
904
  throw new CliError(
905
- 'VALIDATION',
905
+ "VALIDATION",
906
906
  `Invalid scope: ${scope}. Must be 'user', 'project', or 'all'.`
907
907
  );
908
908
  }
909
909
  // Validate target/scope combination
910
910
  if (
911
- target !== 'all' &&
912
- scope === 'project' &&
911
+ target !== "all" &&
912
+ scope === "project" &&
913
913
  !(TARGETS_WITH_PROJECT_SCOPE as string[]).includes(target)
914
914
  ) {
915
915
  throw new CliError(
916
- 'VALIDATION',
916
+ "VALIDATION",
917
917
  `${target} does not support project scope.`
918
918
  );
919
919
  }
920
920
 
921
- const { statusMcp } = await import('./commands/mcp/status.js');
921
+ const { statusMcp } = await import("./commands/mcp/status.js");
922
922
  await statusMcp({
923
923
  target: target as NonNullable<
924
924
  Parameters<typeof statusMcp>[0]
925
- >['target'],
926
- scope: scope as 'user' | 'project' | 'all',
925
+ >["target"],
926
+ scope: scope as "user" | "project" | "all",
927
927
  // Pass undefined if not set, so global --json can take effect
928
928
  json: cmdOpts.json === true ? true : undefined,
929
929
  });
@@ -937,19 +937,19 @@ function wireMcpCommand(program: Command): void {
937
937
  function wireManagementCommands(program: Command): void {
938
938
  // collection subcommands
939
939
  const collectionCmd = program
940
- .command('collection')
941
- .description('Manage collections');
940
+ .command("collection")
941
+ .description("Manage collections");
942
942
 
943
943
  collectionCmd
944
- .command('add <path>')
945
- .description('Add a collection')
946
- .requiredOption('-n, --name <name>', 'collection name')
947
- .option('--pattern <glob>', 'file matching pattern')
948
- .option('--include <exts>', 'extension allowlist (CSV)')
949
- .option('--exclude <patterns>', 'exclude patterns (CSV)')
950
- .option('--update <cmd>', 'shell command to run before indexing')
944
+ .command("add <path>")
945
+ .description("Add a collection")
946
+ .requiredOption("-n, --name <name>", "collection name")
947
+ .option("--pattern <glob>", "file matching pattern")
948
+ .option("--include <exts>", "extension allowlist (CSV)")
949
+ .option("--exclude <patterns>", "exclude patterns (CSV)")
950
+ .option("--update <cmd>", "shell command to run before indexing")
951
951
  .action(async (path: string, cmdOpts: Record<string, unknown>) => {
952
- const { collectionAdd } = await import('./commands/collection');
952
+ const { collectionAdd } = await import("./commands/collection");
953
953
  await collectionAdd(path, {
954
954
  name: cmdOpts.name as string,
955
955
  pattern: cmdOpts.pattern as string | undefined,
@@ -960,136 +960,134 @@ function wireManagementCommands(program: Command): void {
960
960
  });
961
961
 
962
962
  collectionCmd
963
- .command('list')
964
- .description('List collections')
965
- .option('--json', 'JSON output')
966
- .option('--md', 'Markdown output')
963
+ .command("list")
964
+ .description("List collections")
965
+ .option("--json", "JSON output")
966
+ .option("--md", "Markdown output")
967
967
  .action(async (cmdOpts: Record<string, unknown>) => {
968
968
  const format = getFormat(cmdOpts);
969
969
  assertFormatSupported(CMD.collectionList, format);
970
970
 
971
- const { collectionList } = await import('./commands/collection');
971
+ const { collectionList } = await import("./commands/collection");
972
972
  await collectionList({
973
- json: format === 'json',
974
- md: format === 'md',
973
+ json: format === "json",
974
+ md: format === "md",
975
975
  });
976
976
  });
977
977
 
978
978
  collectionCmd
979
- .command('remove <name>')
980
- .description('Remove a collection')
979
+ .command("remove <name>")
980
+ .description("Remove a collection")
981
981
  .action(async (name: string) => {
982
- const { collectionRemove } = await import('./commands/collection');
982
+ const { collectionRemove } = await import("./commands/collection");
983
983
  await collectionRemove(name);
984
984
  });
985
985
 
986
986
  collectionCmd
987
- .command('rename <old> <new>')
988
- .description('Rename a collection')
987
+ .command("rename <old> <new>")
988
+ .description("Rename a collection")
989
989
  .action(async (oldName: string, newName: string) => {
990
- const { collectionRename } = await import('./commands/collection');
990
+ const { collectionRename } = await import("./commands/collection");
991
991
  await collectionRename(oldName, newName);
992
992
  });
993
993
 
994
994
  // context subcommands
995
995
  const contextCmd = program
996
- .command('context')
997
- .description('Manage context items');
996
+ .command("context")
997
+ .description("Manage context items");
998
998
 
999
999
  contextCmd
1000
- .command('add <scope> <text>')
1001
- .description('Add context metadata for a scope')
1000
+ .command("add <scope> <text>")
1001
+ .description("Add context metadata for a scope")
1002
1002
  .action(async (scope: string, text: string) => {
1003
- const { contextAdd } = await import('./commands/context');
1003
+ const { contextAdd } = await import("./commands/context");
1004
1004
  const exitCode = await contextAdd(scope, text);
1005
1005
  if (exitCode !== 0) {
1006
- throw new CliError('RUNTIME', 'Failed to add context');
1006
+ throw new CliError("RUNTIME", "Failed to add context");
1007
1007
  }
1008
1008
  });
1009
1009
 
1010
1010
  contextCmd
1011
- .command('list')
1012
- .description('List context items')
1013
- .option('--json', 'JSON output')
1014
- .option('--md', 'Markdown output')
1011
+ .command("list")
1012
+ .description("List context items")
1013
+ .option("--json", "JSON output")
1014
+ .option("--md", "Markdown output")
1015
1015
  .action(async (cmdOpts: Record<string, unknown>) => {
1016
1016
  const format = getFormat(cmdOpts);
1017
1017
  assertFormatSupported(CMD.contextList, format);
1018
1018
 
1019
- const { contextList } = await import('./commands/context');
1020
- await contextList(format as 'terminal' | 'json' | 'md');
1019
+ const { contextList } = await import("./commands/context");
1020
+ await contextList(format as "terminal" | "json" | "md");
1021
1021
  });
1022
1022
 
1023
1023
  contextCmd
1024
- .command('check')
1025
- .description('Check context configuration')
1026
- .option('--json', 'JSON output')
1027
- .option('--md', 'Markdown output')
1024
+ .command("check")
1025
+ .description("Check context configuration")
1026
+ .option("--json", "JSON output")
1027
+ .option("--md", "Markdown output")
1028
1028
  .action(async (cmdOpts: Record<string, unknown>) => {
1029
1029
  const format = getFormat(cmdOpts);
1030
1030
  assertFormatSupported(CMD.contextCheck, format);
1031
1031
 
1032
- const { contextCheck } = await import('./commands/context');
1033
- await contextCheck(format as 'terminal' | 'json' | 'md');
1032
+ const { contextCheck } = await import("./commands/context");
1033
+ await contextCheck(format as "terminal" | "json" | "md");
1034
1034
  });
1035
1035
 
1036
1036
  contextCmd
1037
- .command('rm <uri>')
1038
- .description('Remove context item')
1037
+ .command("rm <uri>")
1038
+ .description("Remove context item")
1039
1039
  .action(async (uri: string) => {
1040
- const { contextRm } = await import('./commands/context');
1040
+ const { contextRm } = await import("./commands/context");
1041
1041
  await contextRm(uri);
1042
1042
  });
1043
1043
 
1044
1044
  // models subcommands
1045
- const modelsCmd = program.command('models').description('Manage LLM models');
1045
+ const modelsCmd = program.command("models").description("Manage LLM models");
1046
1046
 
1047
1047
  modelsCmd
1048
- .command('list')
1049
- .description('List available models')
1050
- .option('--json', 'JSON output')
1048
+ .command("list")
1049
+ .description("List available models")
1050
+ .option("--json", "JSON output")
1051
1051
  .action(async (cmdOpts: Record<string, unknown>) => {
1052
1052
  const format = getFormat(cmdOpts);
1053
1053
  assertFormatSupported(CMD.modelsList, format);
1054
1054
 
1055
- const { modelsList, formatModelsList } = await import(
1056
- './commands/models'
1057
- );
1058
- const result = await modelsList({ json: format === 'json' });
1055
+ const { modelsList, formatModelsList } =
1056
+ await import("./commands/models");
1057
+ const result = await modelsList({ json: format === "json" });
1059
1058
  process.stdout.write(
1060
- `${formatModelsList(result, { json: format === 'json' })}\n`
1059
+ `${formatModelsList(result, { json: format === "json" })}\n`
1061
1060
  );
1062
1061
  });
1063
1062
 
1064
1063
  modelsCmd
1065
- .command('use')
1066
- .description('Switch active model preset')
1067
- .argument('<preset>', 'preset ID (slim, balanced, quality)')
1064
+ .command("use")
1065
+ .description("Switch active model preset")
1066
+ .argument("<preset>", "preset ID (slim, balanced, quality)")
1068
1067
  .action(async (preset: string) => {
1069
1068
  const globals = getGlobals();
1070
- const { modelsUse, formatModelsUse } = await import(
1071
- './commands/models/use'
1072
- );
1069
+ const { modelsUse, formatModelsUse } =
1070
+ await import("./commands/models/use");
1073
1071
  const result = await modelsUse(preset, { configPath: globals.config });
1074
1072
  if (!result.success) {
1075
- throw new CliError('VALIDATION', result.error);
1073
+ throw new CliError("VALIDATION", result.error);
1076
1074
  }
1077
1075
  process.stdout.write(`${formatModelsUse(result)}\n`);
1078
1076
  });
1079
1077
 
1080
1078
  modelsCmd
1081
- .command('pull')
1082
- .description('Download models')
1083
- .option('--all', 'download all configured models')
1084
- .option('--embed', 'download embedding model')
1085
- .option('--rerank', 'download reranker model')
1086
- .option('--gen', 'download generation model')
1087
- .option('--force', 'force re-download')
1088
- .option('--no-progress', 'disable download progress')
1079
+ .command("pull")
1080
+ .description("Download models")
1081
+ .option("--all", "download all configured models")
1082
+ .option("--embed", "download embedding model")
1083
+ .option("--rerank", "download reranker model")
1084
+ .option("--gen", "download generation model")
1085
+ .option("--force", "force re-download")
1086
+ .option("--no-progress", "disable download progress")
1089
1087
  .action(async (cmdOpts: Record<string, unknown>) => {
1090
1088
  const globals = getGlobals();
1091
1089
  const { modelsPull, formatModelsPull, createProgressRenderer } =
1092
- await import('./commands/models');
1090
+ await import("./commands/models");
1093
1091
 
1094
1092
  // Merge global quiet/json with local --no-progress
1095
1093
  const showProgress =
@@ -1111,45 +1109,43 @@ function wireManagementCommands(program: Command): void {
1111
1109
  // This allows partial success output before throwing
1112
1110
  process.stdout.write(`${formatModelsPull(result)}\n`);
1113
1111
  if (result.failed > 0) {
1114
- throw new CliError('RUNTIME', `${result.failed} model(s) failed`);
1112
+ throw new CliError("RUNTIME", `${result.failed} model(s) failed`);
1115
1113
  }
1116
1114
  });
1117
1115
 
1118
1116
  modelsCmd
1119
- .command('clear')
1120
- .description('Clear model cache')
1117
+ .command("clear")
1118
+ .description("Clear model cache")
1121
1119
  .action(async () => {
1122
1120
  const globals = getGlobals();
1123
- const { modelsClear, formatModelsClear } = await import(
1124
- './commands/models'
1125
- );
1121
+ const { modelsClear, formatModelsClear } =
1122
+ await import("./commands/models");
1126
1123
  const result = await modelsClear({ yes: globals.yes });
1127
1124
  process.stdout.write(`${formatModelsClear(result)}\n`);
1128
1125
  });
1129
1126
 
1130
1127
  modelsCmd
1131
- .command('path')
1132
- .description('Show model cache path')
1133
- .option('--json', 'JSON output')
1128
+ .command("path")
1129
+ .description("Show model cache path")
1130
+ .option("--json", "JSON output")
1134
1131
  .action(async (cmdOpts: Record<string, unknown>) => {
1135
1132
  const format = getFormat(cmdOpts);
1136
- const { modelsPath, formatModelsPath } = await import(
1137
- './commands/models'
1138
- );
1133
+ const { modelsPath, formatModelsPath } =
1134
+ await import("./commands/models");
1139
1135
  const result = modelsPath();
1140
1136
  process.stdout.write(
1141
- `${formatModelsPath(result, { json: format === 'json' })}\n`
1137
+ `${formatModelsPath(result, { json: format === "json" })}\n`
1142
1138
  );
1143
1139
  });
1144
1140
 
1145
1141
  // update - Sync files from disk
1146
1142
  program
1147
- .command('update')
1148
- .description('Sync files from disk into the index')
1149
- .option('--git-pull', 'run git pull in git repositories')
1143
+ .command("update")
1144
+ .description("Sync files from disk into the index")
1145
+ .option("--git-pull", "run git pull in git repositories")
1150
1146
  .action(async (cmdOpts: Record<string, unknown>) => {
1151
1147
  const globals = getGlobals();
1152
- const { update, formatUpdate } = await import('./commands/update');
1148
+ const { update, formatUpdate } = await import("./commands/update");
1153
1149
  const opts = {
1154
1150
  gitPull: Boolean(cmdOpts.gitPull),
1155
1151
  verbose: globals.verbose,
@@ -1157,64 +1153,64 @@ function wireManagementCommands(program: Command): void {
1157
1153
  const result = await update(opts);
1158
1154
 
1159
1155
  if (!result.success) {
1160
- throw new CliError('RUNTIME', result.error ?? 'Update failed');
1156
+ throw new CliError("RUNTIME", result.error ?? "Update failed");
1161
1157
  }
1162
1158
  process.stdout.write(`${formatUpdate(result, opts)}\n`);
1163
1159
  });
1164
1160
 
1165
1161
  // embed - Generate embeddings
1166
1162
  program
1167
- .command('embed')
1168
- .description('Generate embeddings for indexed documents')
1169
- .option('--model <uri>', 'embedding model URI')
1170
- .option('--batch-size <num>', 'batch size', '32')
1171
- .option('--force', 'regenerate all embeddings')
1172
- .option('--dry-run', 'show what would be done')
1173
- .option('--json', 'JSON output')
1163
+ .command("embed")
1164
+ .description("Generate embeddings for indexed documents")
1165
+ .option("--model <uri>", "embedding model URI")
1166
+ .option("--batch-size <num>", "batch size", "32")
1167
+ .option("--force", "regenerate all embeddings")
1168
+ .option("--dry-run", "show what would be done")
1169
+ .option("--json", "JSON output")
1174
1170
  .action(async (cmdOpts: Record<string, unknown>) => {
1175
1171
  const globals = getGlobals();
1176
1172
  const format = getFormat(cmdOpts);
1177
1173
 
1178
- const { embed, formatEmbed } = await import('./commands/embed');
1174
+ const { embed, formatEmbed } = await import("./commands/embed");
1179
1175
  const opts = {
1180
1176
  model: cmdOpts.model as string | undefined,
1181
- batchSize: parsePositiveInt('batch-size', cmdOpts.batchSize),
1177
+ batchSize: parsePositiveInt("batch-size", cmdOpts.batchSize),
1182
1178
  force: Boolean(cmdOpts.force),
1183
1179
  dryRun: Boolean(cmdOpts.dryRun),
1184
1180
  yes: globals.yes,
1185
- json: format === 'json',
1181
+ json: format === "json",
1186
1182
  };
1187
1183
  const result = await embed(opts);
1188
1184
 
1189
1185
  if (!result.success) {
1190
- throw new CliError('RUNTIME', result.error ?? 'Embed failed');
1186
+ throw new CliError("RUNTIME", result.error ?? "Embed failed");
1191
1187
  }
1192
1188
  process.stdout.write(`${formatEmbed(result, opts)}\n`);
1193
1189
  });
1194
1190
 
1195
1191
  // cleanup - Clean stale data
1196
1192
  program
1197
- .command('cleanup')
1198
- .description('Clean orphaned data from index')
1193
+ .command("cleanup")
1194
+ .description("Clean orphaned data from index")
1199
1195
  .action(async () => {
1200
- const { cleanup, formatCleanup } = await import('./commands/cleanup');
1196
+ const { cleanup, formatCleanup } = await import("./commands/cleanup");
1201
1197
  const result = await cleanup();
1202
1198
 
1203
1199
  if (!result.success) {
1204
- throw new CliError('RUNTIME', result.error ?? 'Cleanup failed');
1200
+ throw new CliError("RUNTIME", result.error ?? "Cleanup failed");
1205
1201
  }
1206
1202
  process.stdout.write(`${formatCleanup(result)}\n`);
1207
1203
  });
1208
1204
 
1209
1205
  // reset - Reset GNO to fresh state
1210
1206
  program
1211
- .command('reset')
1212
- .description('Delete all GNO data and start fresh')
1213
- .option('--confirm', 'confirm destructive operation')
1214
- .option('--keep-config', 'preserve config file')
1215
- .option('--keep-cache', 'preserve model cache')
1207
+ .command("reset")
1208
+ .description("Delete all GNO data and start fresh")
1209
+ .option("--confirm", "confirm destructive operation")
1210
+ .option("--keep-config", "preserve config file")
1211
+ .option("--keep-cache", "preserve model cache")
1216
1212
  .action(async (cmdOpts: Record<string, unknown>) => {
1217
- const { reset, formatReset } = await import('./commands/reset');
1213
+ const { reset, formatReset } = await import("./commands/reset");
1218
1214
  const globals = getGlobals();
1219
1215
  const result = await reset({
1220
1216
  // Accept either --confirm or global --yes
@@ -1232,100 +1228,100 @@ function wireManagementCommands(program: Command): void {
1232
1228
 
1233
1229
  function wireSkillCommands(program: Command): void {
1234
1230
  const skillCmd = program
1235
- .command('skill')
1236
- .description('Manage GNO agent skill');
1231
+ .command("skill")
1232
+ .description("Manage GNO agent skill");
1237
1233
 
1238
1234
  skillCmd
1239
- .command('install')
1240
- .description('Install GNO skill to Claude Code or Codex')
1235
+ .command("install")
1236
+ .description("Install GNO skill to Claude Code or Codex")
1241
1237
  .option(
1242
- '-s, --scope <scope>',
1243
- 'installation scope (project, user)',
1244
- 'project'
1238
+ "-s, --scope <scope>",
1239
+ "installation scope (project, user)",
1240
+ "project"
1245
1241
  )
1246
1242
  .option(
1247
- '-t, --target <target>',
1248
- 'target agent (claude, codex, all)',
1249
- 'claude'
1243
+ "-t, --target <target>",
1244
+ "target agent (claude, codex, all)",
1245
+ "claude"
1250
1246
  )
1251
- .option('-f, --force', 'overwrite existing installation')
1252
- .option('--json', 'JSON output')
1247
+ .option("-f, --force", "overwrite existing installation")
1248
+ .option("--json", "JSON output")
1253
1249
  .action(async (cmdOpts: Record<string, unknown>) => {
1254
1250
  const scope = cmdOpts.scope as string;
1255
1251
  const target = cmdOpts.target as string;
1256
1252
 
1257
1253
  // Validate scope
1258
- if (!['project', 'user'].includes(scope)) {
1254
+ if (!["project", "user"].includes(scope)) {
1259
1255
  throw new CliError(
1260
- 'VALIDATION',
1256
+ "VALIDATION",
1261
1257
  `Invalid scope: ${scope}. Must be 'project' or 'user'.`
1262
1258
  );
1263
1259
  }
1264
1260
  // Validate target
1265
- if (!['claude', 'codex', 'all'].includes(target)) {
1261
+ if (!["claude", "codex", "all"].includes(target)) {
1266
1262
  throw new CliError(
1267
- 'VALIDATION',
1263
+ "VALIDATION",
1268
1264
  `Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
1269
1265
  );
1270
1266
  }
1271
1267
 
1272
- const { installSkill } = await import('./commands/skill/install.js');
1268
+ const { installSkill } = await import("./commands/skill/install.js");
1273
1269
  await installSkill({
1274
- scope: scope as 'project' | 'user',
1275
- target: target as 'claude' | 'codex' | 'all',
1270
+ scope: scope as "project" | "user",
1271
+ target: target as "claude" | "codex" | "all",
1276
1272
  force: Boolean(cmdOpts.force),
1277
1273
  json: Boolean(cmdOpts.json),
1278
1274
  });
1279
1275
  });
1280
1276
 
1281
1277
  skillCmd
1282
- .command('uninstall')
1283
- .description('Uninstall GNO skill')
1278
+ .command("uninstall")
1279
+ .description("Uninstall GNO skill")
1284
1280
  .option(
1285
- '-s, --scope <scope>',
1286
- 'installation scope (project, user)',
1287
- 'project'
1281
+ "-s, --scope <scope>",
1282
+ "installation scope (project, user)",
1283
+ "project"
1288
1284
  )
1289
1285
  .option(
1290
- '-t, --target <target>',
1291
- 'target agent (claude, codex, all)',
1292
- 'claude'
1286
+ "-t, --target <target>",
1287
+ "target agent (claude, codex, all)",
1288
+ "claude"
1293
1289
  )
1294
- .option('--json', 'JSON output')
1290
+ .option("--json", "JSON output")
1295
1291
  .action(async (cmdOpts: Record<string, unknown>) => {
1296
1292
  const scope = cmdOpts.scope as string;
1297
1293
  const target = cmdOpts.target as string;
1298
1294
 
1299
1295
  // Validate scope
1300
- if (!['project', 'user'].includes(scope)) {
1296
+ if (!["project", "user"].includes(scope)) {
1301
1297
  throw new CliError(
1302
- 'VALIDATION',
1298
+ "VALIDATION",
1303
1299
  `Invalid scope: ${scope}. Must be 'project' or 'user'.`
1304
1300
  );
1305
1301
  }
1306
1302
  // Validate target
1307
- if (!['claude', 'codex', 'all'].includes(target)) {
1303
+ if (!["claude", "codex", "all"].includes(target)) {
1308
1304
  throw new CliError(
1309
- 'VALIDATION',
1305
+ "VALIDATION",
1310
1306
  `Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
1311
1307
  );
1312
1308
  }
1313
1309
 
1314
- const { uninstallSkill } = await import('./commands/skill/uninstall.js');
1310
+ const { uninstallSkill } = await import("./commands/skill/uninstall.js");
1315
1311
  await uninstallSkill({
1316
- scope: scope as 'project' | 'user',
1317
- target: target as 'claude' | 'codex' | 'all',
1312
+ scope: scope as "project" | "user",
1313
+ target: target as "claude" | "codex" | "all",
1318
1314
  json: Boolean(cmdOpts.json),
1319
1315
  });
1320
1316
  });
1321
1317
 
1322
1318
  skillCmd
1323
- .command('show')
1324
- .description('Preview skill files without installing')
1325
- .option('--file <name>', 'specific file to show')
1326
- .option('--all', 'show all skill files')
1319
+ .command("show")
1320
+ .description("Preview skill files without installing")
1321
+ .option("--file <name>", "specific file to show")
1322
+ .option("--all", "show all skill files")
1327
1323
  .action(async (cmdOpts: Record<string, unknown>) => {
1328
- const { showSkill } = await import('./commands/skill/show.js');
1324
+ const { showSkill } = await import("./commands/skill/show.js");
1329
1325
  await showSkill({
1330
1326
  file: cmdOpts.file as string | undefined,
1331
1327
  all: Boolean(cmdOpts.all),
@@ -1333,42 +1329,42 @@ function wireSkillCommands(program: Command): void {
1333
1329
  });
1334
1330
 
1335
1331
  skillCmd
1336
- .command('paths')
1337
- .description('Show resolved skill installation paths')
1332
+ .command("paths")
1333
+ .description("Show resolved skill installation paths")
1338
1334
  .option(
1339
- '-s, --scope <scope>',
1340
- 'filter by scope (project, user, all)',
1341
- 'all'
1335
+ "-s, --scope <scope>",
1336
+ "filter by scope (project, user, all)",
1337
+ "all"
1342
1338
  )
1343
1339
  .option(
1344
- '-t, --target <target>',
1345
- 'filter by target (claude, codex, all)',
1346
- 'all'
1340
+ "-t, --target <target>",
1341
+ "filter by target (claude, codex, all)",
1342
+ "all"
1347
1343
  )
1348
- .option('--json', 'JSON output')
1344
+ .option("--json", "JSON output")
1349
1345
  .action(async (cmdOpts: Record<string, unknown>) => {
1350
1346
  const scope = cmdOpts.scope as string;
1351
1347
  const target = cmdOpts.target as string;
1352
1348
 
1353
1349
  // Validate scope
1354
- if (!['project', 'user', 'all'].includes(scope)) {
1350
+ if (!["project", "user", "all"].includes(scope)) {
1355
1351
  throw new CliError(
1356
- 'VALIDATION',
1352
+ "VALIDATION",
1357
1353
  `Invalid scope: ${scope}. Must be 'project', 'user', or 'all'.`
1358
1354
  );
1359
1355
  }
1360
1356
  // Validate target
1361
- if (!['claude', 'codex', 'all'].includes(target)) {
1357
+ if (!["claude", "codex", "all"].includes(target)) {
1362
1358
  throw new CliError(
1363
- 'VALIDATION',
1359
+ "VALIDATION",
1364
1360
  `Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
1365
1361
  );
1366
1362
  }
1367
1363
 
1368
- const { showPaths } = await import('./commands/skill/paths-cmd.js');
1364
+ const { showPaths } = await import("./commands/skill/paths-cmd.js");
1369
1365
  await showPaths({
1370
- scope: scope as 'project' | 'user' | 'all',
1371
- target: target as 'claude' | 'codex' | 'all',
1366
+ scope: scope as "project" | "user" | "all",
1367
+ target: target as "claude" | "codex" | "all",
1372
1368
  json: Boolean(cmdOpts.json),
1373
1369
  });
1374
1370
  });
@@ -1380,14 +1376,14 @@ function wireSkillCommands(program: Command): void {
1380
1376
 
1381
1377
  function wireServeCommand(program: Command): void {
1382
1378
  program
1383
- .command('serve')
1384
- .description('Start web UI server')
1385
- .option('-p, --port <num>', 'port to listen on', '3000')
1379
+ .command("serve")
1380
+ .description("Start web UI server")
1381
+ .option("-p, --port <num>", "port to listen on", "3000")
1386
1382
  .action(async (cmdOpts: Record<string, unknown>) => {
1387
1383
  const globals = getGlobals();
1388
- const port = parsePositiveInt('port', cmdOpts.port);
1384
+ const port = parsePositiveInt("port", cmdOpts.port);
1389
1385
 
1390
- const { serve } = await import('./commands/serve.js');
1386
+ const { serve } = await import("./commands/serve.js");
1391
1387
  const result = await serve({
1392
1388
  port,
1393
1389
  configPath: globals.config,
@@ -1395,7 +1391,7 @@ function wireServeCommand(program: Command): void {
1395
1391
  });
1396
1392
 
1397
1393
  if (!result.success) {
1398
- throw new CliError('RUNTIME', result.error ?? 'Server failed to start');
1394
+ throw new CliError("RUNTIME", result.error ?? "Server failed to start");
1399
1395
  }
1400
1396
  // Server runs until SIGINT/SIGTERM - no output needed here
1401
1397
  });