@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.
- package/LICENSE +21 -0
- package/README.md +90 -50
- package/THIRD_PARTY_NOTICES.md +22 -0
- package/assets/screenshots/webui-ask-answer.png +0 -0
- package/assets/screenshots/webui-collections.png +0 -0
- package/assets/screenshots/webui-editor.png +0 -0
- package/assets/screenshots/webui-home.png +0 -0
- package/assets/skill/SKILL.md +12 -12
- package/assets/skill/cli-reference.md +59 -57
- package/assets/skill/examples.md +8 -7
- package/assets/skill/mcp-reference.md +8 -4
- package/package.json +32 -25
- package/src/app/constants.ts +43 -42
- package/src/cli/colors.ts +1 -1
- package/src/cli/commands/ask.ts +44 -43
- package/src/cli/commands/cleanup.ts +9 -8
- package/src/cli/commands/collection/add.ts +12 -12
- package/src/cli/commands/collection/index.ts +4 -4
- package/src/cli/commands/collection/list.ts +26 -25
- package/src/cli/commands/collection/remove.ts +10 -10
- package/src/cli/commands/collection/rename.ts +10 -10
- package/src/cli/commands/context/add.ts +1 -1
- package/src/cli/commands/context/check.ts +17 -17
- package/src/cli/commands/context/index.ts +4 -4
- package/src/cli/commands/context/list.ts +11 -11
- package/src/cli/commands/context/rm.ts +1 -1
- package/src/cli/commands/doctor.ts +86 -84
- package/src/cli/commands/embed.ts +30 -28
- package/src/cli/commands/get.ts +27 -26
- package/src/cli/commands/index-cmd.ts +9 -9
- package/src/cli/commands/index.ts +16 -16
- package/src/cli/commands/init.ts +13 -12
- package/src/cli/commands/ls.ts +20 -19
- package/src/cli/commands/mcp/config.ts +30 -28
- package/src/cli/commands/mcp/index.ts +4 -4
- package/src/cli/commands/mcp/install.ts +17 -17
- package/src/cli/commands/mcp/paths.ts +133 -133
- package/src/cli/commands/mcp/status.ts +21 -21
- package/src/cli/commands/mcp/uninstall.ts +13 -13
- package/src/cli/commands/mcp.ts +2 -2
- package/src/cli/commands/models/clear.ts +12 -11
- package/src/cli/commands/models/index.ts +5 -5
- package/src/cli/commands/models/list.ts +31 -30
- package/src/cli/commands/models/path.ts +1 -1
- package/src/cli/commands/models/pull.ts +19 -18
- package/src/cli/commands/models/use.ts +4 -4
- package/src/cli/commands/multi-get.ts +38 -36
- package/src/cli/commands/query.ts +21 -20
- package/src/cli/commands/ref-parser.ts +10 -10
- package/src/cli/commands/reset.ts +40 -39
- package/src/cli/commands/search.ts +14 -13
- package/src/cli/commands/serve.ts +4 -4
- package/src/cli/commands/shared.ts +11 -10
- package/src/cli/commands/skill/index.ts +5 -5
- package/src/cli/commands/skill/install.ts +18 -17
- package/src/cli/commands/skill/paths-cmd.ts +11 -10
- package/src/cli/commands/skill/paths.ts +23 -23
- package/src/cli/commands/skill/show.ts +13 -12
- package/src/cli/commands/skill/uninstall.ts +16 -15
- package/src/cli/commands/status.ts +25 -24
- package/src/cli/commands/update.ts +3 -3
- package/src/cli/commands/vsearch.ts +17 -16
- package/src/cli/context.ts +5 -5
- package/src/cli/errors.ts +3 -3
- package/src/cli/format/search-results.ts +37 -37
- package/src/cli/options.ts +43 -43
- package/src/cli/program.ts +455 -459
- package/src/cli/progress.ts +1 -1
- package/src/cli/run.ts +24 -23
- package/src/collection/add.ts +9 -8
- package/src/collection/index.ts +3 -3
- package/src/collection/remove.ts +7 -6
- package/src/collection/types.ts +6 -6
- package/src/config/defaults.ts +1 -1
- package/src/config/index.ts +5 -5
- package/src/config/loader.ts +19 -18
- package/src/config/paths.ts +9 -8
- package/src/config/saver.ts +14 -13
- package/src/config/types.ts +53 -52
- package/src/converters/adapters/markitdownTs/adapter.ts +21 -19
- package/src/converters/adapters/officeparser/adapter.ts +18 -16
- package/src/converters/canonicalize.ts +12 -12
- package/src/converters/errors.ts +26 -22
- package/src/converters/index.ts +8 -8
- package/src/converters/mime.ts +25 -25
- package/src/converters/native/markdown.ts +10 -9
- package/src/converters/native/plaintext.ts +8 -7
- package/src/converters/path.ts +2 -2
- package/src/converters/pipeline.ts +11 -10
- package/src/converters/registry.ts +8 -8
- package/src/converters/types.ts +14 -14
- package/src/converters/versions.ts +4 -4
- package/src/index.ts +4 -4
- package/src/ingestion/chunker.ts +10 -9
- package/src/ingestion/index.ts +6 -6
- package/src/ingestion/language.ts +62 -62
- package/src/ingestion/sync.ts +50 -49
- package/src/ingestion/types.ts +10 -10
- package/src/ingestion/walker.ts +14 -13
- package/src/llm/cache.ts +51 -49
- package/src/llm/errors.ts +40 -36
- package/src/llm/index.ts +9 -9
- package/src/llm/lockfile.ts +6 -6
- package/src/llm/nodeLlamaCpp/adapter.ts +13 -12
- package/src/llm/nodeLlamaCpp/embedding.ts +9 -8
- package/src/llm/nodeLlamaCpp/generation.ts +7 -6
- package/src/llm/nodeLlamaCpp/lifecycle.ts +11 -10
- package/src/llm/nodeLlamaCpp/rerank.ts +6 -5
- package/src/llm/policy.ts +5 -5
- package/src/llm/registry.ts +6 -5
- package/src/llm/types.ts +2 -2
- package/src/mcp/resources/index.ts +15 -13
- package/src/mcp/server.ts +25 -23
- package/src/mcp/tools/get.ts +25 -23
- package/src/mcp/tools/index.ts +32 -29
- package/src/mcp/tools/multi-get.ts +34 -32
- package/src/mcp/tools/query.ts +29 -27
- package/src/mcp/tools/search.ts +14 -12
- package/src/mcp/tools/status.ts +12 -11
- package/src/mcp/tools/vsearch.ts +26 -24
- package/src/pipeline/answer.ts +9 -9
- package/src/pipeline/chunk-lookup.ts +1 -1
- package/src/pipeline/contextual.ts +4 -4
- package/src/pipeline/expansion.ts +23 -21
- package/src/pipeline/explain.ts +21 -21
- package/src/pipeline/fusion.ts +9 -9
- package/src/pipeline/hybrid.ts +41 -42
- package/src/pipeline/index.ts +10 -10
- package/src/pipeline/query-language.ts +39 -39
- package/src/pipeline/rerank.ts +8 -7
- package/src/pipeline/search.ts +22 -22
- package/src/pipeline/types.ts +8 -8
- package/src/pipeline/vsearch.ts +21 -24
- package/src/serve/CLAUDE.md +21 -15
- package/src/serve/config-sync.ts +9 -8
- package/src/serve/context.ts +19 -18
- package/src/serve/index.ts +1 -1
- package/src/serve/jobs.ts +7 -7
- package/src/serve/public/app.tsx +79 -25
- package/src/serve/public/components/AddCollectionDialog.tsx +382 -0
- package/src/serve/public/components/CaptureButton.tsx +60 -0
- package/src/serve/public/components/CaptureModal.tsx +365 -0
- package/src/serve/public/components/IndexingProgress.tsx +333 -0
- package/src/serve/public/components/ShortcutHelpModal.tsx +106 -0
- package/src/serve/public/components/ai-elements/code-block.tsx +42 -32
- package/src/serve/public/components/ai-elements/conversation.tsx +16 -14
- package/src/serve/public/components/ai-elements/inline-citation.tsx +33 -32
- package/src/serve/public/components/ai-elements/loader.tsx +5 -4
- package/src/serve/public/components/ai-elements/message.tsx +39 -37
- package/src/serve/public/components/ai-elements/prompt-input.tsx +97 -95
- package/src/serve/public/components/ai-elements/sources.tsx +12 -10
- package/src/serve/public/components/ai-elements/suggestion.tsx +10 -9
- package/src/serve/public/components/editor/CodeMirrorEditor.tsx +142 -0
- package/src/serve/public/components/editor/MarkdownPreview.tsx +311 -0
- package/src/serve/public/components/editor/index.ts +6 -0
- package/src/serve/public/components/preset-selector.tsx +29 -28
- package/src/serve/public/components/ui/badge.tsx +13 -12
- package/src/serve/public/components/ui/button-group.tsx +13 -12
- package/src/serve/public/components/ui/button.tsx +23 -22
- package/src/serve/public/components/ui/card.tsx +16 -16
- package/src/serve/public/components/ui/carousel.tsx +36 -35
- package/src/serve/public/components/ui/collapsible.tsx +1 -1
- package/src/serve/public/components/ui/command.tsx +17 -15
- package/src/serve/public/components/ui/dialog.tsx +13 -12
- package/src/serve/public/components/ui/dropdown-menu.tsx +13 -12
- package/src/serve/public/components/ui/hover-card.tsx +6 -5
- package/src/serve/public/components/ui/input-group.tsx +45 -43
- package/src/serve/public/components/ui/input.tsx +6 -6
- package/src/serve/public/components/ui/progress.tsx +5 -4
- package/src/serve/public/components/ui/scroll-area.tsx +11 -10
- package/src/serve/public/components/ui/select.tsx +19 -18
- package/src/serve/public/components/ui/separator.tsx +6 -5
- package/src/serve/public/components/ui/table.tsx +18 -18
- package/src/serve/public/components/ui/textarea.tsx +4 -4
- package/src/serve/public/components/ui/tooltip.tsx +5 -4
- package/src/serve/public/globals.css +27 -4
- package/src/serve/public/hooks/use-api.ts +8 -8
- package/src/serve/public/hooks/useCaptureModal.tsx +83 -0
- package/src/serve/public/hooks/useKeyboardShortcuts.ts +85 -0
- package/src/serve/public/index.html +4 -4
- package/src/serve/public/lib/utils.ts +6 -0
- package/src/serve/public/pages/Ask.tsx +27 -26
- package/src/serve/public/pages/Browse.tsx +28 -27
- package/src/serve/public/pages/Collections.tsx +439 -0
- package/src/serve/public/pages/Dashboard.tsx +166 -40
- package/src/serve/public/pages/DocView.tsx +258 -73
- package/src/serve/public/pages/DocumentEditor.tsx +510 -0
- package/src/serve/public/pages/Search.tsx +80 -58
- package/src/serve/routes/api.ts +272 -155
- package/src/serve/security.ts +4 -4
- package/src/serve/server.ts +66 -48
- package/src/store/index.ts +5 -5
- package/src/store/migrations/001-initial.ts +24 -23
- package/src/store/migrations/002-documents-fts.ts +7 -6
- package/src/store/migrations/index.ts +4 -4
- package/src/store/migrations/runner.ts +17 -15
- package/src/store/sqlite/adapter.ts +123 -121
- package/src/store/sqlite/fts5-snowball.ts +24 -23
- package/src/store/sqlite/index.ts +1 -1
- package/src/store/sqlite/setup.ts +12 -12
- package/src/store/sqlite/types.ts +4 -4
- package/src/store/types.ts +19 -19
- package/src/store/vector/index.ts +3 -3
- package/src/store/vector/sqlite-vec.ts +23 -20
- package/src/store/vector/stats.ts +10 -8
- package/src/store/vector/types.ts +2 -2
- package/vendor/fts5-snowball/README.md +6 -6
- package/assets/screenshots/webui-ask-answer.jpg +0 -0
- package/assets/screenshots/webui-home.jpg +0 -0
package/src/cli/program.ts
CHANGED
|
@@ -5,28 +5,29 @@
|
|
|
5
5
|
* @module src/cli/program
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Command } from
|
|
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
|
|
16
|
-
import { setColorsEnabled } from
|
|
16
|
+
} from "../app/constants";
|
|
17
|
+
import { setColorsEnabled } from "./colors";
|
|
17
18
|
import {
|
|
18
19
|
applyGlobalOptions,
|
|
19
20
|
type GlobalOptions,
|
|
20
21
|
parseGlobalOptions,
|
|
21
|
-
} from
|
|
22
|
-
import { CliError } from
|
|
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
|
|
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(
|
|
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
|
-
):
|
|
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
|
-
|
|
81
|
-
`Conflicting output formats: ${localFormats.map(([k]) => k).join(
|
|
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
|
|
88
|
+
return "files";
|
|
88
89
|
}
|
|
89
90
|
if (local.csv) {
|
|
90
|
-
return
|
|
91
|
+
return "csv";
|
|
91
92
|
}
|
|
92
93
|
if (local.md) {
|
|
93
|
-
return
|
|
94
|
+
return "md";
|
|
94
95
|
}
|
|
95
96
|
if (local.xml) {
|
|
96
|
-
return
|
|
97
|
+
return "xml";
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
// Local --json wins over global
|
|
100
101
|
if (local.json) {
|
|
101
|
-
return
|
|
102
|
+
return "json";
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
// Global --json as fallback
|
|
105
106
|
if (globals.json) {
|
|
106
|
-
return
|
|
107
|
+
return "json";
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
return
|
|
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,
|
|
123
|
+
.version(VERSION, "-V, --version", "show version")
|
|
123
124
|
.exitOverride() // Prevent Commander from calling process.exit()
|
|
124
125
|
.showSuggestionAfterError(true)
|
|
125
|
-
.showHelpAfterError(
|
|
126
|
+
.showHelpAfterError("(Use --help for available options)");
|
|
126
127
|
|
|
127
128
|
// Global flags - resolved via preAction hook
|
|
128
129
|
program
|
|
129
|
-
.option(
|
|
130
|
-
.option(
|
|
131
|
-
.option(
|
|
132
|
-
.option(
|
|
133
|
-
.option(
|
|
134
|
-
.option(
|
|
135
|
-
.option(
|
|
136
|
-
.option(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
174
|
-
.description(
|
|
175
|
-
.option(
|
|
176
|
-
.option(
|
|
177
|
-
.option(
|
|
178
|
-
.option(
|
|
179
|
-
.option(
|
|
180
|
-
.option(
|
|
181
|
-
.option(
|
|
182
|
-
.option(
|
|
183
|
-
.option(
|
|
184
|
-
.option(
|
|
185
|
-
.option(
|
|
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(
|
|
193
|
+
throw new CliError("VALIDATION", "Query cannot be empty");
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
// Validate minScore range
|
|
196
|
-
const minScore = parseOptionalFloat(
|
|
197
|
+
const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
|
|
197
198
|
if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
|
|
198
|
-
throw new CliError(
|
|
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(
|
|
203
|
+
? parsePositiveInt("limit", cmdOpts.limit)
|
|
203
204
|
: getDefaultLimit(format);
|
|
204
205
|
|
|
205
|
-
const { search, formatSearch } = await import(
|
|
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 ===
|
|
214
|
-
md: format ===
|
|
215
|
-
csv: format ===
|
|
216
|
-
xml: format ===
|
|
217
|
-
files: format ===
|
|
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 ?
|
|
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 ===
|
|
231
|
-
md: format ===
|
|
232
|
-
csv: format ===
|
|
233
|
-
xml: format ===
|
|
234
|
-
files: format ===
|
|
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(
|
|
244
|
-
.description(
|
|
245
|
-
.option(
|
|
246
|
-
.option(
|
|
247
|
-
.option(
|
|
248
|
-
.option(
|
|
249
|
-
.option(
|
|
250
|
-
.option(
|
|
251
|
-
.option(
|
|
252
|
-
.option(
|
|
253
|
-
.option(
|
|
254
|
-
.option(
|
|
255
|
-
.option(
|
|
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(
|
|
263
|
+
throw new CliError("VALIDATION", "Query cannot be empty");
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
// Validate minScore range
|
|
266
|
-
const minScore = parseOptionalFloat(
|
|
267
|
+
const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
|
|
267
268
|
if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
|
|
268
|
-
throw new CliError(
|
|
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(
|
|
273
|
+
? parsePositiveInt("limit", cmdOpts.limit)
|
|
273
274
|
: getDefaultLimit(format);
|
|
274
275
|
|
|
275
|
-
const { vsearch, formatVsearch } = await import(
|
|
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 ===
|
|
284
|
-
md: format ===
|
|
285
|
-
csv: format ===
|
|
286
|
-
xml: format ===
|
|
287
|
-
files: format ===
|
|
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(
|
|
292
|
+
throw new CliError("RUNTIME", result.error);
|
|
292
293
|
}
|
|
293
294
|
process.stdout.write(
|
|
294
295
|
`${formatVsearch(result, {
|
|
295
|
-
json: format ===
|
|
296
|
-
md: format ===
|
|
297
|
-
csv: format ===
|
|
298
|
-
xml: format ===
|
|
299
|
-
files: format ===
|
|
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(
|
|
309
|
-
.description(
|
|
310
|
-
.option(
|
|
311
|
-
.option(
|
|
312
|
-
.option(
|
|
313
|
-
.option(
|
|
314
|
-
.option(
|
|
315
|
-
.option(
|
|
316
|
-
.option(
|
|
317
|
-
.option(
|
|
318
|
-
.option(
|
|
319
|
-
.option(
|
|
320
|
-
.option(
|
|
321
|
-
.option(
|
|
322
|
-
.option(
|
|
323
|
-
.option(
|
|
324
|
-
.option(
|
|
325
|
-
.option(
|
|
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(
|
|
333
|
+
throw new CliError("VALIDATION", "Query cannot be empty");
|
|
333
334
|
}
|
|
334
335
|
|
|
335
336
|
// Validate minScore range
|
|
336
|
-
const minScore = parseOptionalFloat(
|
|
337
|
+
const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
|
|
337
338
|
if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
|
|
338
|
-
throw new CliError(
|
|
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(
|
|
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(
|
|
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 ===
|
|
381
|
-
md: format ===
|
|
382
|
-
csv: format ===
|
|
383
|
-
xml: format ===
|
|
384
|
-
files: format ===
|
|
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(
|
|
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(
|
|
402
|
-
.description(
|
|
403
|
-
.option(
|
|
404
|
-
.option(
|
|
405
|
-
.option(
|
|
406
|
-
.option(
|
|
407
|
-
.option(
|
|
408
|
-
.option(
|
|
409
|
-
.option(
|
|
410
|
-
.option(
|
|
411
|
-
.option(
|
|
412
|
-
.option(
|
|
413
|
-
.option(
|
|
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(
|
|
421
|
+
throw new CliError("VALIDATION", "Query cannot be empty");
|
|
421
422
|
}
|
|
422
423
|
|
|
423
424
|
const limit = cmdOpts.limit
|
|
424
|
-
? parsePositiveInt(
|
|
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(
|
|
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(
|
|
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 ===
|
|
460
|
-
md: format ===
|
|
460
|
+
json: format === "json",
|
|
461
|
+
md: format === "md",
|
|
461
462
|
});
|
|
462
463
|
|
|
463
464
|
if (!result.success) {
|
|
464
|
-
throw new CliError(
|
|
465
|
+
throw new CliError("RUNTIME", result.error);
|
|
465
466
|
}
|
|
466
467
|
process.stdout.write(
|
|
467
|
-
`${formatAsk(result, { json: format ===
|
|
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(
|
|
480
|
-
.description(
|
|
481
|
-
.option(
|
|
482
|
-
.option(
|
|
483
|
-
.option(
|
|
484
|
-
.option(
|
|
485
|
-
.option(
|
|
486
|
-
.option(
|
|
487
|
-
.option(
|
|
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(
|
|
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
|
-
|
|
|
501
|
-
|
|
|
502
|
-
|
|
|
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(
|
|
510
|
+
throw new CliError("RUNTIME", result.error ?? "Init failed");
|
|
510
511
|
}
|
|
511
512
|
|
|
512
513
|
if (result.alreadyInitialized) {
|
|
513
|
-
process.stdout.write(
|
|
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(
|
|
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(
|
|
535
|
-
.description(
|
|
536
|
-
.option(
|
|
537
|
-
.option(
|
|
538
|
-
.option(
|
|
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(
|
|
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(
|
|
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(
|
|
566
|
-
.description(
|
|
567
|
-
.option(
|
|
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(
|
|
573
|
-
const result = await status({ json: format ===
|
|
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(
|
|
577
|
+
throw new CliError("RUNTIME", result.error ?? "Status failed");
|
|
577
578
|
}
|
|
578
579
|
process.stdout.write(
|
|
579
|
-
`${formatStatus(result, { json: format ===
|
|
580
|
+
`${formatStatus(result, { json: format === "json" })}\n`
|
|
580
581
|
);
|
|
581
582
|
});
|
|
582
583
|
|
|
583
584
|
// doctor - Diagnose configuration issues
|
|
584
585
|
program
|
|
585
|
-
.command(
|
|
586
|
-
.description(
|
|
587
|
-
.option(
|
|
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(
|
|
591
|
-
const result = await doctor({ json: format ===
|
|
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 ===
|
|
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(
|
|
608
|
-
.description(
|
|
608
|
+
.command("get <ref>")
|
|
609
|
+
.description("Get document by URI or docid")
|
|
609
610
|
.option(
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
parsePositiveInt.bind(null,
|
|
611
|
+
"--from <line>",
|
|
612
|
+
"Start at line number",
|
|
613
|
+
parsePositiveInt.bind(null, "from")
|
|
613
614
|
)
|
|
614
615
|
.option(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
parsePositiveInt.bind(null,
|
|
616
|
+
"-l, --limit <lines>",
|
|
617
|
+
"Limit to N lines",
|
|
618
|
+
parsePositiveInt.bind(null, "limit")
|
|
618
619
|
)
|
|
619
|
-
.option(
|
|
620
|
-
.option(
|
|
621
|
-
.option(
|
|
622
|
-
.option(
|
|
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(
|
|
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 ===
|
|
636
|
-
md: format ===
|
|
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 ?
|
|
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 ===
|
|
650
|
-
md: format ===
|
|
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(
|
|
658
|
-
.description(
|
|
658
|
+
.command("multi-get <refs...>")
|
|
659
|
+
.description("Get multiple documents by URI or docid")
|
|
659
660
|
.option(
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
parsePositiveInt.bind(null,
|
|
661
|
+
"--max-bytes <n>",
|
|
662
|
+
"Max bytes per document",
|
|
663
|
+
parsePositiveInt.bind(null, "max-bytes")
|
|
663
664
|
)
|
|
664
|
-
.option(
|
|
665
|
-
.option(
|
|
666
|
-
.option(
|
|
667
|
-
.option(
|
|
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(
|
|
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 ===
|
|
679
|
-
files: format ===
|
|
680
|
-
md: format ===
|
|
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 ?
|
|
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 ===
|
|
694
|
-
files: format ===
|
|
695
|
-
md: format ===
|
|
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(
|
|
703
|
-
.description(
|
|
703
|
+
.command("ls [scope]")
|
|
704
|
+
.description("List indexed documents")
|
|
704
705
|
.option(
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
parsePositiveInt.bind(null,
|
|
706
|
+
"-n, --limit <num>",
|
|
707
|
+
"Max results",
|
|
708
|
+
parsePositiveInt.bind(null, "limit")
|
|
708
709
|
)
|
|
709
710
|
.option(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
parsePositiveInt.bind(null,
|
|
711
|
+
"--offset <num>",
|
|
712
|
+
"Skip first N results",
|
|
713
|
+
parsePositiveInt.bind(null, "offset")
|
|
713
714
|
)
|
|
714
|
-
.option(
|
|
715
|
-
.option(
|
|
716
|
-
.option(
|
|
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(
|
|
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 ===
|
|
729
|
-
files: format ===
|
|
730
|
-
md: format ===
|
|
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 ?
|
|
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 ===
|
|
743
|
-
files: format ===
|
|
744
|
-
md: format ===
|
|
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(
|
|
761
|
-
.description(
|
|
761
|
+
.command("mcp")
|
|
762
|
+
.description("MCP server and configuration");
|
|
762
763
|
|
|
763
764
|
// Default action: start MCP server
|
|
764
765
|
mcpCmd
|
|
765
|
-
.command(
|
|
766
|
-
.description(
|
|
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(
|
|
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(
|
|
778
|
-
.description(
|
|
778
|
+
.command("install")
|
|
779
|
+
.description("Install gno as MCP server in client configuration")
|
|
779
780
|
.option(
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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
|
-
|
|
786
|
-
|
|
787
|
-
|
|
786
|
+
"-s, --scope <scope>",
|
|
787
|
+
"scope (user, project) - project only for claude-code/codex/cursor/opencode",
|
|
788
|
+
"user"
|
|
788
789
|
)
|
|
789
|
-
.option(
|
|
790
|
-
.option(
|
|
791
|
-
.option(
|
|
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(
|
|
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
|
-
|
|
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 (![
|
|
808
|
+
if (!["user", "project"].includes(scope)) {
|
|
808
809
|
throw new CliError(
|
|
809
|
-
|
|
810
|
+
"VALIDATION",
|
|
810
811
|
`Invalid scope: ${scope}. Must be 'user' or 'project'.`
|
|
811
812
|
);
|
|
812
813
|
}
|
|
813
814
|
|
|
814
|
-
const { installMcp } = await import(
|
|
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
|
-
>[
|
|
819
|
-
scope: scope as
|
|
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(
|
|
830
|
-
.description(
|
|
830
|
+
.command("uninstall")
|
|
831
|
+
.description("Remove gno MCP server from client configuration")
|
|
831
832
|
.option(
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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(
|
|
837
|
-
.option(
|
|
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(
|
|
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
|
-
|
|
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 (![
|
|
854
|
+
if (!["user", "project"].includes(scope)) {
|
|
854
855
|
throw new CliError(
|
|
855
|
-
|
|
856
|
+
"VALIDATION",
|
|
856
857
|
`Invalid scope: ${scope}. Must be 'user' or 'project'.`
|
|
857
858
|
);
|
|
858
859
|
}
|
|
859
860
|
|
|
860
|
-
const { uninstallMcp } = await import(
|
|
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
|
-
>[
|
|
865
|
-
scope: scope as
|
|
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(
|
|
874
|
-
.description(
|
|
874
|
+
.command("status")
|
|
875
|
+
.description("Show MCP server installation status")
|
|
875
876
|
.option(
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
882
|
+
"-s, --scope <scope>",
|
|
883
|
+
"filter by scope (user, project, all)",
|
|
884
|
+
"all"
|
|
884
885
|
)
|
|
885
|
-
.option(
|
|
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 } =
|
|
892
|
-
|
|
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 !==
|
|
896
|
+
if (target !== "all" && !(MCP_TARGETS as string[]).includes(target)) {
|
|
897
897
|
throw new CliError(
|
|
898
|
-
|
|
899
|
-
`Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(
|
|
898
|
+
"VALIDATION",
|
|
899
|
+
`Invalid target: ${target}. Must be one of: ${MCP_TARGETS.join(", ")}, all.`
|
|
900
900
|
);
|
|
901
901
|
}
|
|
902
902
|
// Validate scope
|
|
903
|
-
if (![
|
|
903
|
+
if (!["user", "project", "all"].includes(scope)) {
|
|
904
904
|
throw new CliError(
|
|
905
|
-
|
|
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 !==
|
|
912
|
-
scope ===
|
|
911
|
+
target !== "all" &&
|
|
912
|
+
scope === "project" &&
|
|
913
913
|
!(TARGETS_WITH_PROJECT_SCOPE as string[]).includes(target)
|
|
914
914
|
) {
|
|
915
915
|
throw new CliError(
|
|
916
|
-
|
|
916
|
+
"VALIDATION",
|
|
917
917
|
`${target} does not support project scope.`
|
|
918
918
|
);
|
|
919
919
|
}
|
|
920
920
|
|
|
921
|
-
const { statusMcp } = await import(
|
|
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
|
-
>[
|
|
926
|
-
scope: scope as
|
|
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(
|
|
941
|
-
.description(
|
|
940
|
+
.command("collection")
|
|
941
|
+
.description("Manage collections");
|
|
942
942
|
|
|
943
943
|
collectionCmd
|
|
944
|
-
.command(
|
|
945
|
-
.description(
|
|
946
|
-
.requiredOption(
|
|
947
|
-
.option(
|
|
948
|
-
.option(
|
|
949
|
-
.option(
|
|
950
|
-
.option(
|
|
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(
|
|
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(
|
|
964
|
-
.description(
|
|
965
|
-
.option(
|
|
966
|
-
.option(
|
|
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(
|
|
971
|
+
const { collectionList } = await import("./commands/collection");
|
|
972
972
|
await collectionList({
|
|
973
|
-
json: format ===
|
|
974
|
-
md: format ===
|
|
973
|
+
json: format === "json",
|
|
974
|
+
md: format === "md",
|
|
975
975
|
});
|
|
976
976
|
});
|
|
977
977
|
|
|
978
978
|
collectionCmd
|
|
979
|
-
.command(
|
|
980
|
-
.description(
|
|
979
|
+
.command("remove <name>")
|
|
980
|
+
.description("Remove a collection")
|
|
981
981
|
.action(async (name: string) => {
|
|
982
|
-
const { collectionRemove } = await import(
|
|
982
|
+
const { collectionRemove } = await import("./commands/collection");
|
|
983
983
|
await collectionRemove(name);
|
|
984
984
|
});
|
|
985
985
|
|
|
986
986
|
collectionCmd
|
|
987
|
-
.command(
|
|
988
|
-
.description(
|
|
987
|
+
.command("rename <old> <new>")
|
|
988
|
+
.description("Rename a collection")
|
|
989
989
|
.action(async (oldName: string, newName: string) => {
|
|
990
|
-
const { collectionRename } = await import(
|
|
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(
|
|
997
|
-
.description(
|
|
996
|
+
.command("context")
|
|
997
|
+
.description("Manage context items");
|
|
998
998
|
|
|
999
999
|
contextCmd
|
|
1000
|
-
.command(
|
|
1001
|
-
.description(
|
|
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(
|
|
1003
|
+
const { contextAdd } = await import("./commands/context");
|
|
1004
1004
|
const exitCode = await contextAdd(scope, text);
|
|
1005
1005
|
if (exitCode !== 0) {
|
|
1006
|
-
throw new CliError(
|
|
1006
|
+
throw new CliError("RUNTIME", "Failed to add context");
|
|
1007
1007
|
}
|
|
1008
1008
|
});
|
|
1009
1009
|
|
|
1010
1010
|
contextCmd
|
|
1011
|
-
.command(
|
|
1012
|
-
.description(
|
|
1013
|
-
.option(
|
|
1014
|
-
.option(
|
|
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(
|
|
1020
|
-
await contextList(format as
|
|
1019
|
+
const { contextList } = await import("./commands/context");
|
|
1020
|
+
await contextList(format as "terminal" | "json" | "md");
|
|
1021
1021
|
});
|
|
1022
1022
|
|
|
1023
1023
|
contextCmd
|
|
1024
|
-
.command(
|
|
1025
|
-
.description(
|
|
1026
|
-
.option(
|
|
1027
|
-
.option(
|
|
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(
|
|
1033
|
-
await contextCheck(format as
|
|
1032
|
+
const { contextCheck } = await import("./commands/context");
|
|
1033
|
+
await contextCheck(format as "terminal" | "json" | "md");
|
|
1034
1034
|
});
|
|
1035
1035
|
|
|
1036
1036
|
contextCmd
|
|
1037
|
-
.command(
|
|
1038
|
-
.description(
|
|
1037
|
+
.command("rm <uri>")
|
|
1038
|
+
.description("Remove context item")
|
|
1039
1039
|
.action(async (uri: string) => {
|
|
1040
|
-
const { contextRm } = await import(
|
|
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(
|
|
1045
|
+
const modelsCmd = program.command("models").description("Manage LLM models");
|
|
1046
1046
|
|
|
1047
1047
|
modelsCmd
|
|
1048
|
-
.command(
|
|
1049
|
-
.description(
|
|
1050
|
-
.option(
|
|
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 } =
|
|
1056
|
-
|
|
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 ===
|
|
1059
|
+
`${formatModelsList(result, { json: format === "json" })}\n`
|
|
1061
1060
|
);
|
|
1062
1061
|
});
|
|
1063
1062
|
|
|
1064
1063
|
modelsCmd
|
|
1065
|
-
.command(
|
|
1066
|
-
.description(
|
|
1067
|
-
.argument(
|
|
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 } =
|
|
1071
|
-
|
|
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(
|
|
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(
|
|
1082
|
-
.description(
|
|
1083
|
-
.option(
|
|
1084
|
-
.option(
|
|
1085
|
-
.option(
|
|
1086
|
-
.option(
|
|
1087
|
-
.option(
|
|
1088
|
-
.option(
|
|
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(
|
|
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(
|
|
1112
|
+
throw new CliError("RUNTIME", `${result.failed} model(s) failed`);
|
|
1115
1113
|
}
|
|
1116
1114
|
});
|
|
1117
1115
|
|
|
1118
1116
|
modelsCmd
|
|
1119
|
-
.command(
|
|
1120
|
-
.description(
|
|
1117
|
+
.command("clear")
|
|
1118
|
+
.description("Clear model cache")
|
|
1121
1119
|
.action(async () => {
|
|
1122
1120
|
const globals = getGlobals();
|
|
1123
|
-
const { modelsClear, formatModelsClear } =
|
|
1124
|
-
|
|
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(
|
|
1132
|
-
.description(
|
|
1133
|
-
.option(
|
|
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 } =
|
|
1137
|
-
|
|
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 ===
|
|
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(
|
|
1148
|
-
.description(
|
|
1149
|
-
.option(
|
|
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(
|
|
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(
|
|
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(
|
|
1168
|
-
.description(
|
|
1169
|
-
.option(
|
|
1170
|
-
.option(
|
|
1171
|
-
.option(
|
|
1172
|
-
.option(
|
|
1173
|
-
.option(
|
|
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(
|
|
1174
|
+
const { embed, formatEmbed } = await import("./commands/embed");
|
|
1179
1175
|
const opts = {
|
|
1180
1176
|
model: cmdOpts.model as string | undefined,
|
|
1181
|
-
batchSize: parsePositiveInt(
|
|
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 ===
|
|
1181
|
+
json: format === "json",
|
|
1186
1182
|
};
|
|
1187
1183
|
const result = await embed(opts);
|
|
1188
1184
|
|
|
1189
1185
|
if (!result.success) {
|
|
1190
|
-
throw new CliError(
|
|
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(
|
|
1198
|
-
.description(
|
|
1193
|
+
.command("cleanup")
|
|
1194
|
+
.description("Clean orphaned data from index")
|
|
1199
1195
|
.action(async () => {
|
|
1200
|
-
const { cleanup, formatCleanup } = await import(
|
|
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(
|
|
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(
|
|
1212
|
-
.description(
|
|
1213
|
-
.option(
|
|
1214
|
-
.option(
|
|
1215
|
-
.option(
|
|
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(
|
|
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(
|
|
1236
|
-
.description(
|
|
1231
|
+
.command("skill")
|
|
1232
|
+
.description("Manage GNO agent skill");
|
|
1237
1233
|
|
|
1238
1234
|
skillCmd
|
|
1239
|
-
.command(
|
|
1240
|
-
.description(
|
|
1235
|
+
.command("install")
|
|
1236
|
+
.description("Install GNO skill to Claude Code or Codex")
|
|
1241
1237
|
.option(
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1238
|
+
"-s, --scope <scope>",
|
|
1239
|
+
"installation scope (project, user)",
|
|
1240
|
+
"project"
|
|
1245
1241
|
)
|
|
1246
1242
|
.option(
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1243
|
+
"-t, --target <target>",
|
|
1244
|
+
"target agent (claude, codex, all)",
|
|
1245
|
+
"claude"
|
|
1250
1246
|
)
|
|
1251
|
-
.option(
|
|
1252
|
-
.option(
|
|
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 (![
|
|
1254
|
+
if (!["project", "user"].includes(scope)) {
|
|
1259
1255
|
throw new CliError(
|
|
1260
|
-
|
|
1256
|
+
"VALIDATION",
|
|
1261
1257
|
`Invalid scope: ${scope}. Must be 'project' or 'user'.`
|
|
1262
1258
|
);
|
|
1263
1259
|
}
|
|
1264
1260
|
// Validate target
|
|
1265
|
-
if (![
|
|
1261
|
+
if (!["claude", "codex", "all"].includes(target)) {
|
|
1266
1262
|
throw new CliError(
|
|
1267
|
-
|
|
1263
|
+
"VALIDATION",
|
|
1268
1264
|
`Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
|
|
1269
1265
|
);
|
|
1270
1266
|
}
|
|
1271
1267
|
|
|
1272
|
-
const { installSkill } = await import(
|
|
1268
|
+
const { installSkill } = await import("./commands/skill/install.js");
|
|
1273
1269
|
await installSkill({
|
|
1274
|
-
scope: scope as
|
|
1275
|
-
target: target as
|
|
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(
|
|
1283
|
-
.description(
|
|
1278
|
+
.command("uninstall")
|
|
1279
|
+
.description("Uninstall GNO skill")
|
|
1284
1280
|
.option(
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1281
|
+
"-s, --scope <scope>",
|
|
1282
|
+
"installation scope (project, user)",
|
|
1283
|
+
"project"
|
|
1288
1284
|
)
|
|
1289
1285
|
.option(
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1286
|
+
"-t, --target <target>",
|
|
1287
|
+
"target agent (claude, codex, all)",
|
|
1288
|
+
"claude"
|
|
1293
1289
|
)
|
|
1294
|
-
.option(
|
|
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 (![
|
|
1296
|
+
if (!["project", "user"].includes(scope)) {
|
|
1301
1297
|
throw new CliError(
|
|
1302
|
-
|
|
1298
|
+
"VALIDATION",
|
|
1303
1299
|
`Invalid scope: ${scope}. Must be 'project' or 'user'.`
|
|
1304
1300
|
);
|
|
1305
1301
|
}
|
|
1306
1302
|
// Validate target
|
|
1307
|
-
if (![
|
|
1303
|
+
if (!["claude", "codex", "all"].includes(target)) {
|
|
1308
1304
|
throw new CliError(
|
|
1309
|
-
|
|
1305
|
+
"VALIDATION",
|
|
1310
1306
|
`Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
|
|
1311
1307
|
);
|
|
1312
1308
|
}
|
|
1313
1309
|
|
|
1314
|
-
const { uninstallSkill } = await import(
|
|
1310
|
+
const { uninstallSkill } = await import("./commands/skill/uninstall.js");
|
|
1315
1311
|
await uninstallSkill({
|
|
1316
|
-
scope: scope as
|
|
1317
|
-
target: target as
|
|
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(
|
|
1324
|
-
.description(
|
|
1325
|
-
.option(
|
|
1326
|
-
.option(
|
|
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(
|
|
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(
|
|
1337
|
-
.description(
|
|
1332
|
+
.command("paths")
|
|
1333
|
+
.description("Show resolved skill installation paths")
|
|
1338
1334
|
.option(
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1335
|
+
"-s, --scope <scope>",
|
|
1336
|
+
"filter by scope (project, user, all)",
|
|
1337
|
+
"all"
|
|
1342
1338
|
)
|
|
1343
1339
|
.option(
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1340
|
+
"-t, --target <target>",
|
|
1341
|
+
"filter by target (claude, codex, all)",
|
|
1342
|
+
"all"
|
|
1347
1343
|
)
|
|
1348
|
-
.option(
|
|
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 (![
|
|
1350
|
+
if (!["project", "user", "all"].includes(scope)) {
|
|
1355
1351
|
throw new CliError(
|
|
1356
|
-
|
|
1352
|
+
"VALIDATION",
|
|
1357
1353
|
`Invalid scope: ${scope}. Must be 'project', 'user', or 'all'.`
|
|
1358
1354
|
);
|
|
1359
1355
|
}
|
|
1360
1356
|
// Validate target
|
|
1361
|
-
if (![
|
|
1357
|
+
if (!["claude", "codex", "all"].includes(target)) {
|
|
1362
1358
|
throw new CliError(
|
|
1363
|
-
|
|
1359
|
+
"VALIDATION",
|
|
1364
1360
|
`Invalid target: ${target}. Must be 'claude', 'codex', or 'all'.`
|
|
1365
1361
|
);
|
|
1366
1362
|
}
|
|
1367
1363
|
|
|
1368
|
-
const { showPaths } = await import(
|
|
1364
|
+
const { showPaths } = await import("./commands/skill/paths-cmd.js");
|
|
1369
1365
|
await showPaths({
|
|
1370
|
-
scope: scope as
|
|
1371
|
-
target: target as
|
|
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(
|
|
1384
|
-
.description(
|
|
1385
|
-
.option(
|
|
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(
|
|
1384
|
+
const port = parsePositiveInt("port", cmdOpts.port);
|
|
1389
1385
|
|
|
1390
|
-
const { serve } = await import(
|
|
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(
|
|
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
|
});
|