@gmickel/gno 0.9.1 → 0.9.3
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/README.md +71 -59
- package/assets/screenshots/claudecodeskill.jpg +0 -0
- package/assets/screenshots/cli.jpg +0 -0
- package/assets/screenshots/raycast-mcp.jpg +0 -0
- package/assets/screenshots/webui-ask-answer.jpg +0 -0
- package/assets/screenshots/webui-collections.jpg +0 -0
- package/assets/screenshots/webui-editor.jpg +0 -0
- package/assets/screenshots/webui-home.jpg +0 -0
- package/assets/screenshots/webui-search.jpg +0 -0
- package/package.json +2 -1
- package/src/cli/commands/completion/completion.ts +201 -0
- package/src/cli/commands/completion/index.ts +8 -0
- package/src/cli/commands/completion/scripts.ts +365 -0
- package/src/cli/commands/skill/paths.ts +47 -28
- package/src/cli/context.ts +3 -0
- package/src/cli/pager.ts +200 -0
- package/src/cli/program.ts +125 -40
- package/src/serve/public/components/editor/MarkdownPreview.tsx +61 -22
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/pages/Ask.tsx +67 -3
- package/src/serve/public/pages/DocView.tsx +48 -4
- package/src/serve/public/pages/DocumentEditor.tsx +2 -2
- package/src/serve/routes/api.ts +4 -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
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell completion scripts for bash, zsh, and fish.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/completion/scripts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { CLI_NAME } from "../../../app/constants.js";
|
|
8
|
+
|
|
9
|
+
export type Shell = "bash" | "zsh" | "fish";
|
|
10
|
+
export const SUPPORTED_SHELLS: Shell[] = ["bash", "zsh", "fish"];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* All gno commands and subcommands for completion.
|
|
14
|
+
*/
|
|
15
|
+
const COMMANDS = [
|
|
16
|
+
"init",
|
|
17
|
+
"index",
|
|
18
|
+
"update",
|
|
19
|
+
"embed",
|
|
20
|
+
"status",
|
|
21
|
+
"doctor",
|
|
22
|
+
"cleanup",
|
|
23
|
+
"reset",
|
|
24
|
+
"search",
|
|
25
|
+
"vsearch",
|
|
26
|
+
"query",
|
|
27
|
+
"ask",
|
|
28
|
+
"get",
|
|
29
|
+
"multi-get",
|
|
30
|
+
"ls",
|
|
31
|
+
"serve",
|
|
32
|
+
"mcp",
|
|
33
|
+
"mcp serve",
|
|
34
|
+
"mcp install",
|
|
35
|
+
"mcp uninstall",
|
|
36
|
+
"mcp status",
|
|
37
|
+
"collection",
|
|
38
|
+
"collection add",
|
|
39
|
+
"collection list",
|
|
40
|
+
"collection remove",
|
|
41
|
+
"collection rename",
|
|
42
|
+
"context",
|
|
43
|
+
"context add",
|
|
44
|
+
"context list",
|
|
45
|
+
"context check",
|
|
46
|
+
"context rm",
|
|
47
|
+
"models",
|
|
48
|
+
"models list",
|
|
49
|
+
"models pull",
|
|
50
|
+
"models clear",
|
|
51
|
+
"models path",
|
|
52
|
+
"models use",
|
|
53
|
+
"skill",
|
|
54
|
+
"skill install",
|
|
55
|
+
"skill uninstall",
|
|
56
|
+
"skill show",
|
|
57
|
+
"skill paths",
|
|
58
|
+
"completion",
|
|
59
|
+
"completion output",
|
|
60
|
+
"completion install",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Global flags available on all commands.
|
|
65
|
+
*/
|
|
66
|
+
const GLOBAL_FLAGS = [
|
|
67
|
+
"--index",
|
|
68
|
+
"--config",
|
|
69
|
+
"--no-color",
|
|
70
|
+
"--no-pager",
|
|
71
|
+
"--verbose",
|
|
72
|
+
"--yes",
|
|
73
|
+
"--quiet",
|
|
74
|
+
"--json",
|
|
75
|
+
"--offline",
|
|
76
|
+
"--help",
|
|
77
|
+
"--version",
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generate bash completion script.
|
|
82
|
+
*/
|
|
83
|
+
export function generateBashCompletion(): string {
|
|
84
|
+
const commands = COMMANDS.filter((c) => !c.includes(" ")).join(" ");
|
|
85
|
+
const subcommands: Record<string, string> = {};
|
|
86
|
+
|
|
87
|
+
for (const cmd of COMMANDS) {
|
|
88
|
+
if (cmd.includes(" ")) {
|
|
89
|
+
const parts = cmd.split(" ");
|
|
90
|
+
const parent = parts[0];
|
|
91
|
+
const sub = parts[1];
|
|
92
|
+
if (parent && sub) {
|
|
93
|
+
subcommands[parent] = subcommands[parent]
|
|
94
|
+
? `${subcommands[parent]} ${sub}`
|
|
95
|
+
: sub;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const subCases = Object.entries(subcommands)
|
|
101
|
+
.map(
|
|
102
|
+
([parent, subs]) =>
|
|
103
|
+
` ${parent}) COMPREPLY=($(compgen -W "${subs}" -- "\${cur}")) ;;`
|
|
104
|
+
)
|
|
105
|
+
.join("\n");
|
|
106
|
+
|
|
107
|
+
return `# ${CLI_NAME} bash completion
|
|
108
|
+
# Add to ~/.bashrc or ~/.bash_completion
|
|
109
|
+
|
|
110
|
+
_${CLI_NAME}_completions() {
|
|
111
|
+
local cur prev cword
|
|
112
|
+
# Portable: don't rely on _init_completion (requires bash-completion package)
|
|
113
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
114
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
115
|
+
cword="\${COMP_CWORD}"
|
|
116
|
+
|
|
117
|
+
local commands="${commands}"
|
|
118
|
+
local global_flags="${GLOBAL_FLAGS.join(" ")}"
|
|
119
|
+
|
|
120
|
+
# Complete subcommands for parent commands
|
|
121
|
+
case "\${COMP_WORDS[1]}" in
|
|
122
|
+
${subCases}
|
|
123
|
+
esac
|
|
124
|
+
|
|
125
|
+
# Complete global flags
|
|
126
|
+
if [[ "\${cur}" == -* ]]; then
|
|
127
|
+
COMPREPLY=($(compgen -W "\${global_flags}" -- "\${cur}"))
|
|
128
|
+
return
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
# Complete top-level commands
|
|
132
|
+
if [[ \${cword} -eq 1 ]]; then
|
|
133
|
+
COMPREPLY=($(compgen -W "\${commands}" -- "\${cur}"))
|
|
134
|
+
return
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# Dynamic collection completion for -c/--collection flag
|
|
138
|
+
if [[ "\${prev}" == "-c" || "\${prev}" == "--collection" ]]; then
|
|
139
|
+
local collections
|
|
140
|
+
collections=$(${CLI_NAME} collection list --json 2>/dev/null | grep -o '"name":"[^"]*"' | cut -d'"' -f4)
|
|
141
|
+
COMPREPLY=($(compgen -W "\${collections}" -- "\${cur}"))
|
|
142
|
+
return
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
complete -F _${CLI_NAME}_completions ${CLI_NAME}
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generate zsh completion script.
|
|
152
|
+
*/
|
|
153
|
+
export function generateZshCompletion(): string {
|
|
154
|
+
const topLevel = COMMANDS.filter((c) => !c.includes(" "));
|
|
155
|
+
const subcommands: Record<string, string[]> = {};
|
|
156
|
+
|
|
157
|
+
for (const cmd of COMMANDS) {
|
|
158
|
+
if (cmd.includes(" ")) {
|
|
159
|
+
const parts = cmd.split(" ");
|
|
160
|
+
const parent = parts[0];
|
|
161
|
+
const sub = parts[1];
|
|
162
|
+
if (parent && sub) {
|
|
163
|
+
subcommands[parent] = subcommands[parent] || [];
|
|
164
|
+
subcommands[parent].push(sub);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const subCases = Object.entries(subcommands)
|
|
170
|
+
.map(
|
|
171
|
+
([parent, subs]) =>
|
|
172
|
+
` ${parent})
|
|
173
|
+
_arguments '2:subcommand:(${subs.join(" ")})'
|
|
174
|
+
;;`
|
|
175
|
+
)
|
|
176
|
+
.join("\n");
|
|
177
|
+
|
|
178
|
+
return `# ${CLI_NAME} zsh completion
|
|
179
|
+
# Add to ~/.zshrc or copy to a directory in $fpath
|
|
180
|
+
# If autoloading from fpath, add "#compdef ${CLI_NAME}" as first line
|
|
181
|
+
|
|
182
|
+
_${CLI_NAME}() {
|
|
183
|
+
local -a commands
|
|
184
|
+
commands=(
|
|
185
|
+
${topLevel.map((c) => ` '${c}:${getCommandDescription(c)}'`).join("\n")}
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
local -a global_flags
|
|
189
|
+
global_flags=(
|
|
190
|
+
'--index[index name]:name:'
|
|
191
|
+
'--config[config file path]:file:_files'
|
|
192
|
+
'-c[filter by collection]:collection:_${CLI_NAME}_collections'
|
|
193
|
+
'--collection[filter by collection]:collection:_${CLI_NAME}_collections'
|
|
194
|
+
'--no-color[disable colors]'
|
|
195
|
+
'--no-pager[disable paging]'
|
|
196
|
+
'--verbose[verbose logging]'
|
|
197
|
+
'--yes[non-interactive mode]'
|
|
198
|
+
'--quiet[suppress non-essential output]'
|
|
199
|
+
'--json[JSON output]'
|
|
200
|
+
'--offline[offline mode]'
|
|
201
|
+
'--help[show help]'
|
|
202
|
+
'--version[show version]'
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
_arguments -C \\
|
|
206
|
+
"\${global_flags[@]}" \\
|
|
207
|
+
'1:command:->command' \\
|
|
208
|
+
'*::arg:->args'
|
|
209
|
+
|
|
210
|
+
case "$state" in
|
|
211
|
+
command)
|
|
212
|
+
_describe -t commands 'gno commands' commands
|
|
213
|
+
;;
|
|
214
|
+
args)
|
|
215
|
+
# words[1] is the program name in zsh, words[2] is the command
|
|
216
|
+
case "\${words[2]}" in
|
|
217
|
+
${subCases}
|
|
218
|
+
esac
|
|
219
|
+
;;
|
|
220
|
+
esac
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# Dynamic collection completion
|
|
224
|
+
_${CLI_NAME}_collections() {
|
|
225
|
+
local -a collections
|
|
226
|
+
collections=(\${(f)"$($CLI_NAME collection list --json 2>/dev/null | grep -o '"name":"[^"]*"' | cut -d'"' -f4)"})
|
|
227
|
+
_describe -t collections 'collections' collections
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# Register completion (works when sourced or autoloaded)
|
|
231
|
+
(( $+functions[compdef] )) && compdef _${CLI_NAME} ${CLI_NAME}
|
|
232
|
+
`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generate fish completion script.
|
|
237
|
+
*/
|
|
238
|
+
export function generateFishCompletion(): string {
|
|
239
|
+
const topLevel = COMMANDS.filter((c) => !c.includes(" "));
|
|
240
|
+
const subcommands: Record<string, string[]> = {};
|
|
241
|
+
|
|
242
|
+
for (const cmd of COMMANDS) {
|
|
243
|
+
if (cmd.includes(" ")) {
|
|
244
|
+
const parts = cmd.split(" ");
|
|
245
|
+
const parent = parts[0];
|
|
246
|
+
const sub = parts[1];
|
|
247
|
+
if (parent && sub) {
|
|
248
|
+
subcommands[parent] = subcommands[parent] || [];
|
|
249
|
+
subcommands[parent].push(sub);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const topLevelCompletions = topLevel
|
|
255
|
+
.map(
|
|
256
|
+
(c) =>
|
|
257
|
+
`complete -c ${CLI_NAME} -f -n "__fish_use_subcommand" -a "${c}" -d "${getCommandDescription(c)}"`
|
|
258
|
+
)
|
|
259
|
+
.join("\n");
|
|
260
|
+
|
|
261
|
+
const subCompletions = Object.entries(subcommands)
|
|
262
|
+
.flatMap(([parent, subs]) =>
|
|
263
|
+
subs.map(
|
|
264
|
+
(sub) =>
|
|
265
|
+
`complete -c ${CLI_NAME} -f -n "__fish_seen_subcommand_from ${parent}" -a "${sub}" -d "${getCommandDescription(`${parent} ${sub}`)}"`
|
|
266
|
+
)
|
|
267
|
+
)
|
|
268
|
+
.join("\n");
|
|
269
|
+
|
|
270
|
+
return `# ${CLI_NAME} fish completion
|
|
271
|
+
# Copy to ~/.config/fish/completions/${CLI_NAME}.fish
|
|
272
|
+
|
|
273
|
+
# Disable file completion by default
|
|
274
|
+
complete -c ${CLI_NAME} -f
|
|
275
|
+
|
|
276
|
+
# Global flags
|
|
277
|
+
complete -c ${CLI_NAME} -l index -d "index name" -r
|
|
278
|
+
complete -c ${CLI_NAME} -l config -d "config file path" -r
|
|
279
|
+
complete -c ${CLI_NAME} -l no-color -d "disable colors"
|
|
280
|
+
complete -c ${CLI_NAME} -l verbose -d "verbose logging"
|
|
281
|
+
complete -c ${CLI_NAME} -l yes -d "non-interactive mode"
|
|
282
|
+
complete -c ${CLI_NAME} -l quiet -d "suppress non-essential output"
|
|
283
|
+
complete -c ${CLI_NAME} -l json -d "JSON output"
|
|
284
|
+
complete -c ${CLI_NAME} -l offline -d "offline mode"
|
|
285
|
+
complete -c ${CLI_NAME} -s h -l help -d "show help"
|
|
286
|
+
complete -c ${CLI_NAME} -s V -l version -d "show version"
|
|
287
|
+
|
|
288
|
+
# Top-level commands
|
|
289
|
+
${topLevelCompletions}
|
|
290
|
+
|
|
291
|
+
# Subcommands
|
|
292
|
+
${subCompletions}
|
|
293
|
+
|
|
294
|
+
# Dynamic collection completion for -c/--collection
|
|
295
|
+
complete -c ${CLI_NAME} -s c -l collection -d "filter by collection" -xa "(${CLI_NAME} collection list --json 2>/dev/null | string match -r '"name":"[^"]*"' | string replace -r '"name":"([^"]*)"' '$1')"
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get script for a specific shell.
|
|
301
|
+
*/
|
|
302
|
+
export function getCompletionScript(shell: Shell): string {
|
|
303
|
+
switch (shell) {
|
|
304
|
+
case "bash":
|
|
305
|
+
return generateBashCompletion();
|
|
306
|
+
case "zsh":
|
|
307
|
+
return generateZshCompletion();
|
|
308
|
+
case "fish":
|
|
309
|
+
return generateFishCompletion();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get brief description for a command.
|
|
315
|
+
*/
|
|
316
|
+
function getCommandDescription(cmd: string): string {
|
|
317
|
+
const descriptions: Record<string, string> = {
|
|
318
|
+
init: "Initialize GNO configuration",
|
|
319
|
+
index: "Index files from collections",
|
|
320
|
+
update: "Sync files from disk",
|
|
321
|
+
embed: "Generate embeddings",
|
|
322
|
+
status: "Show index status",
|
|
323
|
+
doctor: "Diagnose configuration issues",
|
|
324
|
+
cleanup: "Clean orphaned data",
|
|
325
|
+
reset: "Delete all GNO data",
|
|
326
|
+
search: "BM25 keyword search",
|
|
327
|
+
vsearch: "Vector similarity search",
|
|
328
|
+
query: "Hybrid search with reranking",
|
|
329
|
+
ask: "Query with grounded answer",
|
|
330
|
+
get: "Get document by URI",
|
|
331
|
+
"multi-get": "Get multiple documents",
|
|
332
|
+
ls: "List indexed documents",
|
|
333
|
+
serve: "Start web UI server",
|
|
334
|
+
mcp: "MCP server and configuration",
|
|
335
|
+
"mcp serve": "Start MCP server",
|
|
336
|
+
"mcp install": "Install MCP server to client",
|
|
337
|
+
"mcp uninstall": "Remove MCP server from client",
|
|
338
|
+
"mcp status": "Show MCP installation status",
|
|
339
|
+
collection: "Manage collections",
|
|
340
|
+
"collection add": "Add a collection",
|
|
341
|
+
"collection list": "List collections",
|
|
342
|
+
"collection remove": "Remove a collection",
|
|
343
|
+
"collection rename": "Rename a collection",
|
|
344
|
+
context: "Manage context items",
|
|
345
|
+
"context add": "Add context metadata",
|
|
346
|
+
"context list": "List context items",
|
|
347
|
+
"context check": "Check context configuration",
|
|
348
|
+
"context rm": "Remove context item",
|
|
349
|
+
models: "Manage LLM models",
|
|
350
|
+
"models list": "List available models",
|
|
351
|
+
"models pull": "Download models",
|
|
352
|
+
"models clear": "Clear model cache",
|
|
353
|
+
"models path": "Show model cache path",
|
|
354
|
+
"models use": "Switch active model preset",
|
|
355
|
+
skill: "Manage GNO agent skill",
|
|
356
|
+
"skill install": "Install GNO skill",
|
|
357
|
+
"skill uninstall": "Uninstall GNO skill",
|
|
358
|
+
"skill show": "Preview skill files",
|
|
359
|
+
"skill paths": "Show skill installation paths",
|
|
360
|
+
completion: "Shell completion scripts",
|
|
361
|
+
"completion output": "Output completion script",
|
|
362
|
+
"completion install": "Install shell completions",
|
|
363
|
+
};
|
|
364
|
+
return descriptions[cmd] || cmd;
|
|
365
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Path resolution for skill installation.
|
|
3
3
|
* Supports Claude Code and Codex targets with project/user scopes.
|
|
4
|
+
* Note: OpenCode and Amp use the same .claude path as Claude Code.
|
|
4
5
|
*
|
|
5
6
|
* @module src/cli/commands/skill/paths
|
|
6
7
|
*/
|
|
@@ -28,6 +29,8 @@ export const ENV_CODEX_SKILLS_DIR = "CODEX_SKILLS_DIR";
|
|
|
28
29
|
export type SkillScope = "project" | "user";
|
|
29
30
|
export type SkillTarget = "claude" | "codex";
|
|
30
31
|
|
|
32
|
+
export const SKILL_TARGETS: SkillTarget[] = ["claude", "codex"];
|
|
33
|
+
|
|
31
34
|
export interface SkillPathOptions {
|
|
32
35
|
scope: SkillScope;
|
|
33
36
|
target: SkillTarget;
|
|
@@ -53,13 +56,27 @@ export interface SkillPaths {
|
|
|
53
56
|
/** Skill name for the gno skill directory */
|
|
54
57
|
export const SKILL_NAME = "gno";
|
|
55
58
|
|
|
56
|
-
/**
|
|
57
|
-
|
|
59
|
+
/** Path configuration per target */
|
|
60
|
+
interface TargetPathConfig {
|
|
61
|
+
projectBase: string; // e.g., ".claude"
|
|
62
|
+
userBase: string; // e.g., ".claude" (joined with homedir) or ".config/opencode"
|
|
63
|
+
skillsSubdir: string; // e.g., "skills" or "skill"
|
|
64
|
+
envVar: string;
|
|
65
|
+
}
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
const TARGET_CONFIGS: Record<SkillTarget, TargetPathConfig> = {
|
|
68
|
+
claude: {
|
|
69
|
+
projectBase: ".claude",
|
|
70
|
+
userBase: ".claude",
|
|
71
|
+
skillsSubdir: "skills",
|
|
72
|
+
envVar: ENV_CLAUDE_SKILLS_DIR,
|
|
73
|
+
},
|
|
74
|
+
codex: {
|
|
75
|
+
projectBase: ".codex",
|
|
76
|
+
userBase: ".codex",
|
|
77
|
+
skillsSubdir: "skills",
|
|
78
|
+
envVar: ENV_CODEX_SKILLS_DIR,
|
|
79
|
+
},
|
|
63
80
|
};
|
|
64
81
|
|
|
65
82
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -71,19 +88,15 @@ const AGENT_DIRS: Record<SkillTarget, string> = {
|
|
|
71
88
|
*/
|
|
72
89
|
export function resolveSkillPaths(opts: SkillPathOptions): SkillPaths {
|
|
73
90
|
const { scope, target, cwd, homeDir } = opts;
|
|
91
|
+
const config = TARGET_CONFIGS[target];
|
|
74
92
|
|
|
75
93
|
// Check for env overrides first
|
|
76
|
-
const envOverride =
|
|
77
|
-
target === "claude"
|
|
78
|
-
? process.env[ENV_CLAUDE_SKILLS_DIR]
|
|
79
|
-
: process.env[ENV_CODEX_SKILLS_DIR];
|
|
94
|
+
const envOverride = process.env[config.envVar];
|
|
80
95
|
|
|
81
96
|
if (envOverride) {
|
|
82
97
|
// Require absolute path for security
|
|
83
98
|
if (!isAbsolute(envOverride)) {
|
|
84
|
-
throw new Error(
|
|
85
|
-
`${target === "claude" ? ENV_CLAUDE_SKILLS_DIR : ENV_CODEX_SKILLS_DIR} must be an absolute path`
|
|
86
|
-
);
|
|
99
|
+
throw new Error(`${config.envVar} must be an absolute path`);
|
|
87
100
|
}
|
|
88
101
|
const skillsDir = normalize(envOverride);
|
|
89
102
|
return {
|
|
@@ -94,18 +107,17 @@ export function resolveSkillPaths(opts: SkillPathOptions): SkillPaths {
|
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
// Resolve base directory
|
|
97
|
-
const agentDir = AGENT_DIRS[target];
|
|
98
110
|
let base: string;
|
|
99
111
|
|
|
100
112
|
if (scope === "user") {
|
|
101
113
|
const home = homeDir ?? process.env[ENV_SKILLS_HOME_OVERRIDE] ?? homedir();
|
|
102
|
-
base = join(home,
|
|
114
|
+
base = join(home, config.userBase);
|
|
103
115
|
} else {
|
|
104
116
|
const projectRoot = cwd ?? process.cwd();
|
|
105
|
-
base = join(projectRoot,
|
|
117
|
+
base = join(projectRoot, config.projectBase);
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
const skillsDir = join(base,
|
|
120
|
+
const skillsDir = join(base, config.skillsSubdir);
|
|
109
121
|
const gnoDir = join(skillsDir, SKILL_NAME);
|
|
110
122
|
|
|
111
123
|
return { base, skillsDir, gnoDir };
|
|
@@ -120,8 +132,7 @@ export function resolveAllPaths(
|
|
|
120
132
|
overrides?: { cwd?: string; homeDir?: string }
|
|
121
133
|
): Array<{ scope: SkillScope; target: SkillTarget; paths: SkillPaths }> {
|
|
122
134
|
const scopes: SkillScope[] = scope === "all" ? ["project", "user"] : [scope];
|
|
123
|
-
const targets: SkillTarget[] =
|
|
124
|
-
target === "all" ? ["claude", "codex"] : [target];
|
|
135
|
+
const targets: SkillTarget[] = target === "all" ? SKILL_TARGETS : [target];
|
|
125
136
|
|
|
126
137
|
const results: Array<{
|
|
127
138
|
scope: SkillScope;
|
|
@@ -147,11 +158,16 @@ export function resolveAllPaths(
|
|
|
147
158
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
148
159
|
|
|
149
160
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
161
|
+
* Get expected path suffixes for gno skill directory.
|
|
162
|
+
* Returns all valid suffixes since different targets use different subdir names.
|
|
152
163
|
*/
|
|
153
|
-
function
|
|
154
|
-
|
|
164
|
+
function getExpectedSuffixes(): string[] {
|
|
165
|
+
const subdirs = new Set(
|
|
166
|
+
Object.values(TARGET_CONFIGS).map((c) => c.skillsSubdir)
|
|
167
|
+
);
|
|
168
|
+
return Array.from(subdirs).map(
|
|
169
|
+
(subdir) => `${sep}${subdir}${sep}${SKILL_NAME}`
|
|
170
|
+
);
|
|
155
171
|
}
|
|
156
172
|
|
|
157
173
|
/**
|
|
@@ -164,11 +180,14 @@ export function validatePathForDeletion(
|
|
|
164
180
|
): string | null {
|
|
165
181
|
const normalized = normalize(destDir);
|
|
166
182
|
const normalizedBase = normalize(base);
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
// Must end with /skills/gno
|
|
170
|
-
|
|
171
|
-
|
|
183
|
+
const expectedSuffixes = getExpectedSuffixes();
|
|
184
|
+
|
|
185
|
+
// Must end with /skills/gno or /skill/gno (platform-aware)
|
|
186
|
+
const hasValidSuffix = expectedSuffixes.some((suffix) =>
|
|
187
|
+
normalized.endsWith(suffix)
|
|
188
|
+
);
|
|
189
|
+
if (!hasValidSuffix) {
|
|
190
|
+
return `Path does not end with expected suffix (${expectedSuffixes.join(" or ")})`;
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
// Minimum length sanity check
|
package/src/cli/context.ts
CHANGED
|
@@ -21,6 +21,7 @@ export interface GlobalOptions {
|
|
|
21
21
|
quiet: boolean;
|
|
22
22
|
json: boolean;
|
|
23
23
|
offline: boolean;
|
|
24
|
+
noPager: boolean;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -58,6 +59,8 @@ export function parseGlobalOptions(
|
|
|
58
59
|
quiet: Boolean(raw.quiet),
|
|
59
60
|
json: Boolean(raw.json),
|
|
60
61
|
offline: offlineEnabled,
|
|
62
|
+
// Commander: --no-pager => opts().pager === false
|
|
63
|
+
noPager: raw.pager === false,
|
|
61
64
|
};
|
|
62
65
|
}
|
|
63
66
|
|