@claude-transcripts/cli 0.3.3 → 0.4.0
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 +1 -0
- package/dist/{app-vcj5svg7.js → app-a93wk4rv.js} +2 -2
- package/dist/{cli-d7a6svha.js → cli-fptk6bgx.js} +472 -103
- package/dist/cli.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,7 @@ upgrades in place: it's idempotent and keeps your history.
|
|
|
109
109
|
| `doctor [options]` | Smoke-test the write/read/search path end-to-end |
|
|
110
110
|
| `hook [action] [options]` | The Claude Code hook, and its registration |
|
|
111
111
|
| `statusline [action] [options]` | The Claude Code statusline indicator (recording / off), and its registration |
|
|
112
|
+
| `completions <shell>` | Print a shell completion script to eval or source from your shell's rc |
|
|
112
113
|
|
|
113
114
|
**Global options** (every command)
|
|
114
115
|
|
|
@@ -1,4 +1,385 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// packages/shared/src/model/cli-project.ts
|
|
3
|
+
var isFlag = (a) => a.name.startsWith("--");
|
|
4
|
+
function cliArgType(a) {
|
|
5
|
+
if (a.type)
|
|
6
|
+
return a.type;
|
|
7
|
+
if (a.choices)
|
|
8
|
+
return "string";
|
|
9
|
+
if (typeof a.default === "number")
|
|
10
|
+
return "number";
|
|
11
|
+
if (typeof a.default === "string")
|
|
12
|
+
return "string";
|
|
13
|
+
return isFlag(a) ? "boolean" : "string";
|
|
14
|
+
}
|
|
15
|
+
function cliCommandsByGroup(spec) {
|
|
16
|
+
return spec.groups.map((g) => ({ ...g, commands: spec.commands.filter((c) => c.group === g.key) })).filter((g) => g.commands.length > 0);
|
|
17
|
+
}
|
|
18
|
+
function cliUsage(cmd) {
|
|
19
|
+
const parts = (cmd.args ?? []).filter((a) => !isFlag(a)).map((a) => a.required ? `<${a.name}>` : `[${a.name}]`);
|
|
20
|
+
const hasFlags = (cmd.args ?? []).some(isFlag);
|
|
21
|
+
return [cmd.name, ...parts, ...hasFlags ? ["[options]"] : []].join(" ");
|
|
22
|
+
}
|
|
23
|
+
function cliArgHint(a) {
|
|
24
|
+
const bits = [];
|
|
25
|
+
if (a.choices)
|
|
26
|
+
bits.push(a.choices.join(" | "));
|
|
27
|
+
if (a.default !== undefined && a.default !== false)
|
|
28
|
+
bits.push(`default ${a.default}`);
|
|
29
|
+
return bits.length ? `(${bits.join("; ")})` : "";
|
|
30
|
+
}
|
|
31
|
+
function cliArgLabel(a) {
|
|
32
|
+
if (!isFlag(a))
|
|
33
|
+
return a.name;
|
|
34
|
+
switch (cliArgType(a)) {
|
|
35
|
+
case "number":
|
|
36
|
+
return `${a.name} <n>`;
|
|
37
|
+
case "string":
|
|
38
|
+
return `${a.name} <value>`;
|
|
39
|
+
default:
|
|
40
|
+
return a.name;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function validateCliArgs(spec, cmd, parsed) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
const args = cmd.args ?? [];
|
|
46
|
+
const known = new Map;
|
|
47
|
+
for (const a of [...spec.globalArgs, ...args])
|
|
48
|
+
if (isFlag(a))
|
|
49
|
+
known.set(a.name.slice(2), a);
|
|
50
|
+
for (const [key, value] of Object.entries(parsed.options)) {
|
|
51
|
+
const def = known.get(key);
|
|
52
|
+
if (!def) {
|
|
53
|
+
errors.push(`unknown option --${key}`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
errors.push(...checkValue(def, value));
|
|
57
|
+
}
|
|
58
|
+
const positionals = args.filter((a) => !isFlag(a));
|
|
59
|
+
positionals.forEach((def, i) => {
|
|
60
|
+
const value = parsed.positionals[i];
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
if (def.required)
|
|
63
|
+
errors.push(`missing required argument <${def.name}>`);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
errors.push(...checkValue(def, value));
|
|
67
|
+
});
|
|
68
|
+
return errors;
|
|
69
|
+
}
|
|
70
|
+
function checkValue(def, value) {
|
|
71
|
+
const type = cliArgType(def);
|
|
72
|
+
const label = isFlag(def) ? def.name : `<${def.name}>`;
|
|
73
|
+
if (typeof value === "boolean") {
|
|
74
|
+
return type === "boolean" ? [] : [`${label} needs a value`];
|
|
75
|
+
}
|
|
76
|
+
if (type === "boolean")
|
|
77
|
+
return [`${label} takes no value (got "${value}")`];
|
|
78
|
+
if (def.choices && !def.choices.includes(value)) {
|
|
79
|
+
return [`${label} must be one of ${def.choices.join(" | ")} (got "${value}")`];
|
|
80
|
+
}
|
|
81
|
+
if (type === "number" && !/^-?\d+(\.\d+)?$/.test(value)) {
|
|
82
|
+
return [`${label} must be a number (got "${value}")`];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
var COMPLETION_SHELLS = ["bash", "zsh", "fish"];
|
|
87
|
+
function toCompletions(spec, shell, bin) {
|
|
88
|
+
const model = completionModel(spec);
|
|
89
|
+
const fn = `_${bin.replace(/[^A-Za-z0-9]/g, "_")}`;
|
|
90
|
+
switch (shell) {
|
|
91
|
+
case "bash":
|
|
92
|
+
return bashCompletions(model, bin, fn);
|
|
93
|
+
case "zsh":
|
|
94
|
+
return zshCompletions(model, bin, fn);
|
|
95
|
+
case "fish":
|
|
96
|
+
return fishCompletions(model, bin, fn);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
var takesValue = (a) => isFlag(a) && cliArgType(a) !== "boolean";
|
|
100
|
+
function completionModel(spec) {
|
|
101
|
+
const flags = new Map;
|
|
102
|
+
const valued = [];
|
|
103
|
+
const choices = [];
|
|
104
|
+
for (const c of spec.commands) {
|
|
105
|
+
const all = [...(c.args ?? []).filter(isFlag), ...spec.globalArgs.filter(isFlag)];
|
|
106
|
+
flags.set(c.name, all);
|
|
107
|
+
for (const a of all) {
|
|
108
|
+
if (!takesValue(a))
|
|
109
|
+
continue;
|
|
110
|
+
valued.push(`${c.name}:${a.name}`);
|
|
111
|
+
if (a.choices)
|
|
112
|
+
choices.push([`${c.name}:${a.name}`, a.choices]);
|
|
113
|
+
}
|
|
114
|
+
(c.args ?? []).filter((a) => !isFlag(a)).forEach((a, i) => {
|
|
115
|
+
if (a.choices)
|
|
116
|
+
choices.push([`${c.name}:${i + 1}`, a.choices]);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const leading = spec.globalArgs.filter((a) => isFlag(a) && !takesValue(a));
|
|
120
|
+
return { commands: spec.commands, leading, flags, valued, choices };
|
|
121
|
+
}
|
|
122
|
+
var header = (shell, bin) => `# ${shell} completion for ${bin} \u2014 generated from CLI_SPEC; do not edit.`;
|
|
123
|
+
var names = (args) => args.map((a) => a.name).join(" ");
|
|
124
|
+
function bashCompletions(m, bin, fn) {
|
|
125
|
+
const flagCases = m.commands.map((c) => ` ${c.name}) printf '%s' "${names(m.flags.get(c.name) ?? [])}" ;;`).join(`
|
|
126
|
+
`);
|
|
127
|
+
const choiceCases = m.choices.map(([k, v]) => ` ${k}) printf '%s' "${v.join(" ")}" ;;`).join(`
|
|
128
|
+
`);
|
|
129
|
+
return `${header("bash", bin)}
|
|
130
|
+
# Load it with: eval "$(${bin} completions bash)" (e.g. in ~/.bashrc)
|
|
131
|
+
# Works with bash 3.2+ and needs no bash-completion package.
|
|
132
|
+
|
|
133
|
+
# Does flag \`cmd:--flag\` ($1) take a value?
|
|
134
|
+
${fn}_valued() {
|
|
135
|
+
case "$1" in
|
|
136
|
+
${m.valued.length ? `${m.valued.join("|")}) return 0 ;;` : ""}
|
|
137
|
+
esac
|
|
138
|
+
return 1
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# The values to offer for $1 \u2014 \`cmd:--flag\` or \`cmd:<positional index>\`.
|
|
142
|
+
${fn}_choices() {
|
|
143
|
+
case "$1" in
|
|
144
|
+
${choiceCases}
|
|
145
|
+
esac
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
# The flags command $1 accepts, its own and the global ones.
|
|
149
|
+
${fn}_flags() {
|
|
150
|
+
case "$1" in
|
|
151
|
+
${flagCases}
|
|
152
|
+
esac
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
${fn}() {
|
|
156
|
+
# Split the line ourselves: COMP_WORDS also breaks at ":" and "=", which would turn
|
|
157
|
+
# \`--webapi http://x\` into four words and miscount everything after it.
|
|
158
|
+
local line="\${COMP_LINE:0:COMP_POINT}" cur cmd npos=0 flag="" i n w
|
|
159
|
+
local -a words
|
|
160
|
+
read -ra words <<<"$line"
|
|
161
|
+
[[ $line == *[[:space:]] || \${#words[@]} -eq 0 ]] && words+=("")
|
|
162
|
+
n=\${#words[@]}
|
|
163
|
+
cur="\${words[n - 1]}"
|
|
164
|
+
COMPREPLY=()
|
|
165
|
+
# \`--flag=value\`: bash replaces only the part after "=", which compgen can't target.
|
|
166
|
+
[[ $cur == *=* ]] && return 0
|
|
167
|
+
|
|
168
|
+
if ((n == 2)); then
|
|
169
|
+
if [[ $cur == -* ]]; then
|
|
170
|
+
COMPREPLY=($(compgen -W "${names(m.leading)}" -- "$cur"))
|
|
171
|
+
else
|
|
172
|
+
COMPREPLY=($(compgen -W "${m.commands.map((c) => c.name).join(" ")}" -- "$cur"))
|
|
173
|
+
fi
|
|
174
|
+
return 0
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# Only the first word is the command; after a leading flag nothing runs a command.
|
|
178
|
+
cmd="\${words[1]}"
|
|
179
|
+
[[ $cmd == -* ]] && return 0
|
|
180
|
+
|
|
181
|
+
# Count positionals the way parseFlags reads them: a bare --flag takes the next word
|
|
182
|
+
# as its value unless that word is another --flag.
|
|
183
|
+
for ((i = 2; i < n - 1; i++)); do
|
|
184
|
+
w="\${words[i]}"
|
|
185
|
+
if [[ -n $flag && $w != --* ]]; then
|
|
186
|
+
flag=""
|
|
187
|
+
continue
|
|
188
|
+
fi
|
|
189
|
+
flag=""
|
|
190
|
+
case "$w" in
|
|
191
|
+
--*=*) ;;
|
|
192
|
+
--*) flag="$w" ;;
|
|
193
|
+
*) npos=$((npos + 1)) ;;
|
|
194
|
+
esac
|
|
195
|
+
done
|
|
196
|
+
|
|
197
|
+
if [[ -n $flag ]] && ${fn}_valued "$cmd:$flag"; then
|
|
198
|
+
COMPREPLY=($(compgen -W "$(${fn}_choices "$cmd:$flag")" -- "$cur"))
|
|
199
|
+
elif [[ $cur == -* || -n $flag ]]; then
|
|
200
|
+
COMPREPLY=($(compgen -W "$(${fn}_flags "$cmd")" -- "$cur"))
|
|
201
|
+
else
|
|
202
|
+
COMPREPLY=($(compgen -W "$(${fn}_choices "$cmd:$((npos + 1))")" -- "$cur"))
|
|
203
|
+
fi
|
|
204
|
+
return 0
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
complete -F ${fn} ${bin}
|
|
208
|
+
`;
|
|
209
|
+
}
|
|
210
|
+
var zshQ = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
211
|
+
var zshEntry = (name, desc = "") => zshQ(`${name.replace(/:/g, "\\:")}:${desc}`);
|
|
212
|
+
function zshCompletions(m, bin, fn) {
|
|
213
|
+
const entries = (args) => args.map((a) => zshEntry(a.name, a.description)).join(" ");
|
|
214
|
+
const flagCases = m.commands.map((c) => ` (${c.name}) reply=(${entries(m.flags.get(c.name) ?? [])}) ;;`).join(`
|
|
215
|
+
`);
|
|
216
|
+
const choiceCases = m.choices.map(([k, v]) => ` (${k}) reply=(${v.join(" ")}) ;;`).join(`
|
|
217
|
+
`);
|
|
218
|
+
const commands = m.commands.map((c) => ` ${zshEntry(c.name, c.summary)}`).join(`
|
|
219
|
+
`);
|
|
220
|
+
return `#compdef ${bin}
|
|
221
|
+
${header("zsh", bin)}
|
|
222
|
+
# Load it with: eval "$(${bin} completions zsh)" (after compinit, e.g. in ~/.zshrc)
|
|
223
|
+
# or save it as \`${fn}\` in a directory on $fpath.
|
|
224
|
+
|
|
225
|
+
${fn}_valued() {
|
|
226
|
+
case $1 in
|
|
227
|
+
(${m.valued.join("|")}) return 0 ;;
|
|
228
|
+
esac
|
|
229
|
+
return 1
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
${fn}_choices() {
|
|
233
|
+
reply=()
|
|
234
|
+
case $1 in
|
|
235
|
+
${choiceCases}
|
|
236
|
+
esac
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
${fn}_flags() {
|
|
240
|
+
reply=()
|
|
241
|
+
case $1 in
|
|
242
|
+
${flagCases}
|
|
243
|
+
esac
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
${fn}() {
|
|
247
|
+
local cur=$words[CURRENT] cmd=$words[2] npos=0 flag="" i w
|
|
248
|
+
local -a reply commands
|
|
249
|
+
commands=(
|
|
250
|
+
${commands}
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if (( CURRENT == 2 )); then
|
|
254
|
+
if [[ $cur == -* ]]; then
|
|
255
|
+
reply=(${entries(m.leading)})
|
|
256
|
+
_describe -t options option reply
|
|
257
|
+
else
|
|
258
|
+
_describe -t commands '${bin} command' commands
|
|
259
|
+
fi
|
|
260
|
+
return
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Only the first word is the command; after a leading flag nothing runs a command.
|
|
264
|
+
[[ $cmd == -* ]] && return 1
|
|
265
|
+
|
|
266
|
+
# Count positionals the way parseFlags reads them: a bare --flag takes the next word
|
|
267
|
+
# as its value unless that word is another --flag.
|
|
268
|
+
for (( i = 3; i < CURRENT; i++ )); do
|
|
269
|
+
w=$words[i]
|
|
270
|
+
if [[ -n $flag && $w != --* ]]; then
|
|
271
|
+
flag=""
|
|
272
|
+
continue
|
|
273
|
+
fi
|
|
274
|
+
flag=""
|
|
275
|
+
case $w in
|
|
276
|
+
(--*=*) ;;
|
|
277
|
+
(--*) flag=$w ;;
|
|
278
|
+
(*) npos=$((npos + 1)) ;;
|
|
279
|
+
esac
|
|
280
|
+
done
|
|
281
|
+
|
|
282
|
+
if [[ $cur == --*=* ]]; then
|
|
283
|
+
flag=\${cur%%=*}
|
|
284
|
+
${fn}_valued "$cmd:$flag" || return 1
|
|
285
|
+
compset -P '*='
|
|
286
|
+
fi
|
|
287
|
+
|
|
288
|
+
if [[ -n $flag ]] && ${fn}_valued "$cmd:$flag"; then
|
|
289
|
+
${fn}_choices "$cmd:$flag"
|
|
290
|
+
if (( $#reply )); then compadd -a reply; else _message value; fi
|
|
291
|
+
elif [[ $cur == -* || -n $flag ]]; then
|
|
292
|
+
${fn}_flags "$cmd"
|
|
293
|
+
_describe -t options option reply
|
|
294
|
+
else
|
|
295
|
+
${fn}_choices "$cmd:$((npos + 1))"
|
|
296
|
+
(( $#reply )) && compadd -a reply
|
|
297
|
+
fi
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if [[ $zsh_eval_context[-1] == loadautofunc ]]; then
|
|
301
|
+
${fn} "$@"
|
|
302
|
+
else
|
|
303
|
+
compdef ${fn} ${bin}
|
|
304
|
+
fi
|
|
305
|
+
`;
|
|
306
|
+
}
|
|
307
|
+
var fishQ = (s) => `'${s.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
308
|
+
function fishCompletions(m, bin, fn) {
|
|
309
|
+
const c = `complete -c ${bin}`;
|
|
310
|
+
const flagLine = (cond, a, cmd) => {
|
|
311
|
+
const parts = [c, "-n", fishQ(cond), "-l", a.name.slice(2)];
|
|
312
|
+
if (takesValue(a)) {
|
|
313
|
+
const ch = m.choices.find(([k]) => k === `${cmd}:${a.name}`)?.[1];
|
|
314
|
+
parts.push(ch ? `-xa ${fishQ(ch.join(" "))}` : "-x");
|
|
315
|
+
}
|
|
316
|
+
if (a.description)
|
|
317
|
+
parts.push("-d", fishQ(a.description));
|
|
318
|
+
return parts.join(" ");
|
|
319
|
+
};
|
|
320
|
+
const lines = [];
|
|
321
|
+
for (const cmd of m.commands) {
|
|
322
|
+
lines.push(`${c} -n ${fn}_at_command -a ${cmd.name} -d ${fishQ(cmd.summary)}`);
|
|
323
|
+
}
|
|
324
|
+
for (const a of m.leading)
|
|
325
|
+
lines.push(flagLine(`${fn}_at_command`, a));
|
|
326
|
+
for (const cmd of m.commands) {
|
|
327
|
+
lines.push("");
|
|
328
|
+
for (const [key, values] of m.choices) {
|
|
329
|
+
const [name, pos] = key.split(":");
|
|
330
|
+
if (name !== cmd.name || pos?.startsWith("--"))
|
|
331
|
+
continue;
|
|
332
|
+
lines.push(`${c} -n '${fn}_at_positional ${cmd.name} ${pos}' -a ${fishQ(values.join(" "))}`);
|
|
333
|
+
}
|
|
334
|
+
for (const a of m.flags.get(cmd.name) ?? [])
|
|
335
|
+
lines.push(flagLine(`${fn}_in ${cmd.name}`, a, cmd.name));
|
|
336
|
+
}
|
|
337
|
+
return `${header("fish", bin)}
|
|
338
|
+
# Load it with: ${bin} completions fish | source (e.g. in ~/.config/fish/config.fish)
|
|
339
|
+
# or save it as ~/.config/fish/completions/${bin}.fish
|
|
340
|
+
|
|
341
|
+
# Nothing typed after the binary yet: the next word is the command.
|
|
342
|
+
function ${fn}_at_command
|
|
343
|
+
test (count (commandline -opc)) -eq 1
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# The command (the first word, and only the first) is $argv[1].
|
|
347
|
+
function ${fn}_in
|
|
348
|
+
set -l t (commandline -opc)
|
|
349
|
+
test "$t[2]" = "$argv[1]"
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# The word being completed is positional $argv[2] of command $argv[1]. Positionals
|
|
353
|
+
# are counted the way parseFlags reads them: a bare --flag takes the next word as its
|
|
354
|
+
# value unless that word is another --flag.
|
|
355
|
+
function ${fn}_at_positional
|
|
356
|
+
set -l t (commandline -opc)
|
|
357
|
+
test "$t[2]" = "$argv[1]"; or return 1
|
|
358
|
+
set -l npos 0
|
|
359
|
+
set -l flag 0
|
|
360
|
+
for w in $t[3..-1]
|
|
361
|
+
if test $flag = 1; and not string match -q -- '--*' $w
|
|
362
|
+
set flag 0
|
|
363
|
+
continue
|
|
364
|
+
end
|
|
365
|
+
set flag 0
|
|
366
|
+
switch $w
|
|
367
|
+
case '--*=*'
|
|
368
|
+
case '--*'
|
|
369
|
+
set flag 1
|
|
370
|
+
case '*'
|
|
371
|
+
set npos (math $npos + 1)
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
test $flag = 0; and test $npos -eq (math $argv[2] - 1)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
${c} -f
|
|
378
|
+
${lines.join(`
|
|
379
|
+
`)}
|
|
380
|
+
`;
|
|
381
|
+
}
|
|
382
|
+
|
|
2
383
|
// packages/shared/src/model/cli.ts
|
|
3
384
|
var WEBAPI = {
|
|
4
385
|
name: "--webapi",
|
|
@@ -318,95 +699,24 @@ var CLI_SPEC = {
|
|
|
318
699
|
{ name: "--dry-run", description: "with `install`: show the change without writing" }
|
|
319
700
|
],
|
|
320
701
|
examples: ["statusline install", "statusline status"]
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
name: "completions",
|
|
705
|
+
group: "admin",
|
|
706
|
+
summary: "Print a shell completion script to eval or source from your shell's rc",
|
|
707
|
+
args: [
|
|
708
|
+
{
|
|
709
|
+
name: "shell",
|
|
710
|
+
required: true,
|
|
711
|
+
choices: COMPLETION_SHELLS,
|
|
712
|
+
description: "the shell to complete for"
|
|
713
|
+
}
|
|
714
|
+
],
|
|
715
|
+
examples: ["completions bash", "completions fish | source"]
|
|
321
716
|
}
|
|
322
717
|
]
|
|
323
718
|
};
|
|
324
719
|
|
|
325
|
-
// packages/shared/src/model/cli-project.ts
|
|
326
|
-
var isFlag = (a) => a.name.startsWith("--");
|
|
327
|
-
function cliArgType(a) {
|
|
328
|
-
if (a.type)
|
|
329
|
-
return a.type;
|
|
330
|
-
if (a.choices)
|
|
331
|
-
return "string";
|
|
332
|
-
if (typeof a.default === "number")
|
|
333
|
-
return "number";
|
|
334
|
-
if (typeof a.default === "string")
|
|
335
|
-
return "string";
|
|
336
|
-
return isFlag(a) ? "boolean" : "string";
|
|
337
|
-
}
|
|
338
|
-
function cliCommandsByGroup(spec) {
|
|
339
|
-
return spec.groups.map((g) => ({ ...g, commands: spec.commands.filter((c) => c.group === g.key) })).filter((g) => g.commands.length > 0);
|
|
340
|
-
}
|
|
341
|
-
function cliUsage(cmd) {
|
|
342
|
-
const parts = (cmd.args ?? []).filter((a) => !isFlag(a)).map((a) => a.required ? `<${a.name}>` : `[${a.name}]`);
|
|
343
|
-
const hasFlags = (cmd.args ?? []).some(isFlag);
|
|
344
|
-
return [cmd.name, ...parts, ...hasFlags ? ["[options]"] : []].join(" ");
|
|
345
|
-
}
|
|
346
|
-
function cliArgHint(a) {
|
|
347
|
-
const bits = [];
|
|
348
|
-
if (a.choices)
|
|
349
|
-
bits.push(a.choices.join(" | "));
|
|
350
|
-
if (a.default !== undefined && a.default !== false)
|
|
351
|
-
bits.push(`default ${a.default}`);
|
|
352
|
-
return bits.length ? `(${bits.join("; ")})` : "";
|
|
353
|
-
}
|
|
354
|
-
function cliArgLabel(a) {
|
|
355
|
-
if (!isFlag(a))
|
|
356
|
-
return a.name;
|
|
357
|
-
switch (cliArgType(a)) {
|
|
358
|
-
case "number":
|
|
359
|
-
return `${a.name} <n>`;
|
|
360
|
-
case "string":
|
|
361
|
-
return `${a.name} <value>`;
|
|
362
|
-
default:
|
|
363
|
-
return a.name;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function validateCliArgs(spec, cmd, parsed) {
|
|
367
|
-
const errors = [];
|
|
368
|
-
const args = cmd.args ?? [];
|
|
369
|
-
const known = new Map;
|
|
370
|
-
for (const a of [...spec.globalArgs, ...args])
|
|
371
|
-
if (isFlag(a))
|
|
372
|
-
known.set(a.name.slice(2), a);
|
|
373
|
-
for (const [key, value] of Object.entries(parsed.options)) {
|
|
374
|
-
const def = known.get(key);
|
|
375
|
-
if (!def) {
|
|
376
|
-
errors.push(`unknown option --${key}`);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
errors.push(...checkValue(def, value));
|
|
380
|
-
}
|
|
381
|
-
const positionals = args.filter((a) => !isFlag(a));
|
|
382
|
-
positionals.forEach((def, i) => {
|
|
383
|
-
const value = parsed.positionals[i];
|
|
384
|
-
if (value === undefined) {
|
|
385
|
-
if (def.required)
|
|
386
|
-
errors.push(`missing required argument <${def.name}>`);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
errors.push(...checkValue(def, value));
|
|
390
|
-
});
|
|
391
|
-
return errors;
|
|
392
|
-
}
|
|
393
|
-
function checkValue(def, value) {
|
|
394
|
-
const type = cliArgType(def);
|
|
395
|
-
const label = isFlag(def) ? def.name : `<${def.name}>`;
|
|
396
|
-
if (typeof value === "boolean") {
|
|
397
|
-
return type === "boolean" ? [] : [`${label} needs a value`];
|
|
398
|
-
}
|
|
399
|
-
if (type === "boolean")
|
|
400
|
-
return [`${label} takes no value (got "${value}")`];
|
|
401
|
-
if (def.choices && !def.choices.includes(value)) {
|
|
402
|
-
return [`${label} must be one of ${def.choices.join(" | ")} (got "${value}")`];
|
|
403
|
-
}
|
|
404
|
-
if (type === "number" && !/^-?\d+(\.\d+)?$/.test(value)) {
|
|
405
|
-
return [`${label} must be a number (got "${value}")`];
|
|
406
|
-
}
|
|
407
|
-
return [];
|
|
408
|
-
}
|
|
409
|
-
|
|
410
720
|
// packages/shared/src/couch-url.ts
|
|
411
721
|
var DEFAULT_COUCHDB_HOST = "127.0.0.1";
|
|
412
722
|
var DEFAULT_COUCHDB_PORT = "7652";
|
|
@@ -2854,6 +3164,14 @@ async function runBackfill(argv) {
|
|
|
2854
3164
|
return failed > 0 ? 1 : 0;
|
|
2855
3165
|
}
|
|
2856
3166
|
|
|
3167
|
+
// packages/cli/src/commands/completions.ts
|
|
3168
|
+
var BIN = "claude-transcripts";
|
|
3169
|
+
async function runCompletions(argv) {
|
|
3170
|
+
const shell = parseFlags(argv).positionals[0];
|
|
3171
|
+
process.stdout.write(toCompletions(CLI_SPEC, shell, BIN));
|
|
3172
|
+
return 0;
|
|
3173
|
+
}
|
|
3174
|
+
|
|
2857
3175
|
// packages/cli/src/commands/doctor.ts
|
|
2858
3176
|
import { hostname as hostname2 } from "os";
|
|
2859
3177
|
function buildTranscript(sessionId, cwd, ts) {
|
|
@@ -4912,6 +5230,7 @@ async function runImport(argv) {
|
|
|
4912
5230
|
|
|
4913
5231
|
// packages/cli/src/commands/install.ts
|
|
4914
5232
|
import { existsSync as existsSync8, readFileSync as readFileSync12 } from "fs";
|
|
5233
|
+
import { join as join9 } from "path";
|
|
4915
5234
|
|
|
4916
5235
|
// packages/cli/src/lib/assets.ts
|
|
4917
5236
|
import {
|
|
@@ -5515,8 +5834,27 @@ async function provisionGarage(env, bucket, instanceEnvPath) {
|
|
|
5515
5834
|
S3_SECRET_KEY: secretKey
|
|
5516
5835
|
});
|
|
5517
5836
|
steps.push({ name: "garage:key", ok: true, detail: `created ${accessKey}` });
|
|
5837
|
+
const stale = await removeStaleKeys(api, accessKey);
|
|
5838
|
+
if (stale)
|
|
5839
|
+
steps.push(stale);
|
|
5518
5840
|
return { steps, env: next };
|
|
5519
5841
|
}
|
|
5842
|
+
async function removeStaleKeys(api, keep) {
|
|
5843
|
+
const list = await api("GET", "/v2/ListKeys");
|
|
5844
|
+
if (!list.ok || !Array.isArray(list.json)) {
|
|
5845
|
+
return { name: "garage:stale-keys", ok: false, detail: `list failed (HTTP ${list.status})` };
|
|
5846
|
+
}
|
|
5847
|
+
const stale = list.json.filter((k) => k?.name === KEY_NAME && typeof k.id === "string" && k.id !== keep).map((k) => k.id);
|
|
5848
|
+
if (stale.length === 0)
|
|
5849
|
+
return null;
|
|
5850
|
+
const failed = [];
|
|
5851
|
+
for (const id of stale) {
|
|
5852
|
+
const del = await api("POST", `/v2/DeleteKey?id=${encodeURIComponent(id)}`);
|
|
5853
|
+
if (!del.ok)
|
|
5854
|
+
failed.push(`${id} (HTTP ${del.status})`);
|
|
5855
|
+
}
|
|
5856
|
+
return failed.length ? { name: "garage:stale-keys", ok: false, detail: `could not delete ${failed.join(", ")}` } : { name: "garage:stale-keys", ok: true, detail: `deleted ${stale.join(", ")}` };
|
|
5857
|
+
}
|
|
5520
5858
|
|
|
5521
5859
|
// packages/cli/src/commands/statusline.ts
|
|
5522
5860
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
|
|
@@ -5922,7 +6260,7 @@ async function runInstall(argv) {
|
|
|
5922
6260
|
install: done.`);
|
|
5923
6261
|
console.log(` UI http://127.0.0.1:${env.WEBAPI_PORT}/app`);
|
|
5924
6262
|
console.log(` API http://127.0.0.1:${env.WEBAPI_PORT}/api/docs`);
|
|
5925
|
-
console.log(` data
|
|
6263
|
+
console.log(` data ${join9(paths.deployDir, "data")} (survives uninstall unless --purge)`);
|
|
5926
6264
|
console.log(" remove claude-transcripts uninstall");
|
|
5927
6265
|
return 0;
|
|
5928
6266
|
}
|
|
@@ -6336,15 +6674,15 @@ async function runSessions(argv) {
|
|
|
6336
6674
|
// packages/cli/src/commands/setup.ts
|
|
6337
6675
|
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
6338
6676
|
import { homedir as homedir3 } from "os";
|
|
6339
|
-
import { dirname as dirname7, join as
|
|
6340
|
-
var REPO_ROOT =
|
|
6341
|
-
var HOOK_DIR =
|
|
6342
|
-
var HOOK_CONFIG_PATH = process.env.CT_HOOK_CONFIG ??
|
|
6677
|
+
import { dirname as dirname7, join as join10 } from "path";
|
|
6678
|
+
var REPO_ROOT = join10(import.meta.dir, "..", "..", "..", "..");
|
|
6679
|
+
var HOOK_DIR = join10(REPO_ROOT, "hooks");
|
|
6680
|
+
var HOOK_CONFIG_PATH = process.env.CT_HOOK_CONFIG ?? join10(homedir3(), ".config", "claude-transcripts", "config.json");
|
|
6343
6681
|
var GLOBAL_SETTINGS = installPaths().claudeSettings;
|
|
6344
|
-
var PROJECT_SETTINGS =
|
|
6682
|
+
var PROJECT_SETTINGS = join10(process.cwd(), ".claude", "settings.json");
|
|
6345
6683
|
function loadRepoConfig() {
|
|
6346
|
-
const live =
|
|
6347
|
-
const template =
|
|
6684
|
+
const live = join10(REPO_ROOT, "config", "config.json");
|
|
6685
|
+
const template = join10(REPO_ROOT, "config", "config.template.json");
|
|
6348
6686
|
return JSON.parse(readFileSync13(existsSync10(live) ? live : template, "utf8"));
|
|
6349
6687
|
}
|
|
6350
6688
|
function couchHeaders(auth) {
|
|
@@ -6404,7 +6742,7 @@ function readSettings3(path) {
|
|
|
6404
6742
|
function globalRegistrationBlockedBy(settingsPath = GLOBAL_SETTINGS) {
|
|
6405
6743
|
return pluginRegistration(settingsPath, readSettings3(settingsPath));
|
|
6406
6744
|
}
|
|
6407
|
-
var DISPATCH_PATH =
|
|
6745
|
+
var DISPATCH_PATH = join10(HOOK_DIR, "scripts", "dispatch.ts");
|
|
6408
6746
|
function dispatchCommand() {
|
|
6409
6747
|
return hookLaunch(`bun run "${DISPATCH_PATH}"`);
|
|
6410
6748
|
}
|
|
@@ -6415,7 +6753,7 @@ function isRegistered(path) {
|
|
|
6415
6753
|
}
|
|
6416
6754
|
function registerGlobal(dryRun) {
|
|
6417
6755
|
const cmd = dispatchCommand();
|
|
6418
|
-
const generated = JSON.parse(readFileSync13(
|
|
6756
|
+
const generated = JSON.parse(readFileSync13(join10(HOOK_DIR, "hooks", "hooks.json"), "utf8"));
|
|
6419
6757
|
const settings = readSettings3(GLOBAL_SETTINGS);
|
|
6420
6758
|
const hooks = settings.hooks ?? {};
|
|
6421
6759
|
for (const [event, groups] of Object.entries(generated.hooks)) {
|
|
@@ -6655,12 +6993,30 @@ async function runTurns(argv) {
|
|
|
6655
6993
|
}
|
|
6656
6994
|
|
|
6657
6995
|
// packages/cli/src/commands/uninstall.ts
|
|
6658
|
-
import { existsSync as existsSync12, rmSync } from "fs";
|
|
6996
|
+
import { existsSync as existsSync12, readdirSync, rmSync } from "fs";
|
|
6997
|
+
import { join as join11 } from "path";
|
|
6998
|
+
var DATA_SUBDIR = "data";
|
|
6999
|
+
function removeDeployDir(deployDir, purge) {
|
|
7000
|
+
if (purge) {
|
|
7001
|
+
rmSync(deployDir, { recursive: true, force: true });
|
|
7002
|
+
return;
|
|
7003
|
+
}
|
|
7004
|
+
let entries;
|
|
7005
|
+
try {
|
|
7006
|
+
entries = readdirSync(deployDir);
|
|
7007
|
+
} catch {
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
for (const entry of entries) {
|
|
7011
|
+
if (entry !== DATA_SUBDIR)
|
|
7012
|
+
rmSync(join11(deployDir, entry), { recursive: true, force: true });
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
6659
7015
|
async function confirmPurge(auto) {
|
|
6660
7016
|
if (auto)
|
|
6661
7017
|
return true;
|
|
6662
7018
|
process.stdout.write(`
|
|
6663
|
-
uninstall: --purge DELETES ALL RECORDED HISTORY (CouchDB + S3
|
|
7019
|
+
uninstall: --purge DELETES ALL RECORDED HISTORY (CouchDB + S3 data).
|
|
6664
7020
|
` + "Type 'delete my history' to confirm: ");
|
|
6665
7021
|
const line = (await new Response(Bun.stdin.stream()).text()).trim();
|
|
6666
7022
|
return line === "delete my history";
|
|
@@ -6673,10 +7029,11 @@ async function runUninstall(argv) {
|
|
|
6673
7029
|
console.error("uninstall: not confirmed \u2014 nothing was removed.");
|
|
6674
7030
|
return 1;
|
|
6675
7031
|
}
|
|
6676
|
-
console.log("uninstall: deregistering the hook");
|
|
7032
|
+
console.log("uninstall: deregistering the hook and the statusline");
|
|
6677
7033
|
await runHook(["uninstall"]);
|
|
7034
|
+
await runStatusline(["uninstall"]);
|
|
6678
7035
|
if (existsSync12(paths.instanceEnv)) {
|
|
6679
|
-
console.log(
|
|
7036
|
+
console.log("uninstall: stopping the stack");
|
|
6680
7037
|
try {
|
|
6681
7038
|
const env = loadOrCreateInstanceEnv(paths.instanceEnv);
|
|
6682
7039
|
await composeDown({ paths, env, app: true }, purge);
|
|
@@ -6684,17 +7041,28 @@ async function runUninstall(argv) {
|
|
|
6684
7041
|
console.warn(`uninstall: could not stop the stack (continuing): ${err.message}`);
|
|
6685
7042
|
}
|
|
6686
7043
|
}
|
|
6687
|
-
|
|
7044
|
+
const targets = [paths.instanceEnv, paths.versionFile, ...purge ? [paths.hookConfig] : []];
|
|
7045
|
+
for (const target of targets) {
|
|
6688
7046
|
try {
|
|
6689
7047
|
rmSync(target, { recursive: true, force: true });
|
|
6690
7048
|
} catch {}
|
|
6691
7049
|
}
|
|
7050
|
+
const dataDir = join11(paths.deployDir, DATA_SUBDIR);
|
|
7051
|
+
try {
|
|
7052
|
+
removeDeployDir(paths.deployDir, purge);
|
|
7053
|
+
} catch (err) {
|
|
7054
|
+
console.error(`uninstall: could not remove ${paths.deployDir}: ${err.message}`);
|
|
7055
|
+
if (purge)
|
|
7056
|
+
console.error(`uninstall: delete ${dataDir} by hand to finish the purge`);
|
|
7057
|
+
return 1;
|
|
7058
|
+
}
|
|
6692
7059
|
console.log("uninstall: removed generated config and deployment assets");
|
|
6693
7060
|
if (purge) {
|
|
6694
7061
|
rmSync(paths.appConfig, { force: true });
|
|
6695
7062
|
console.log("uninstall: purged history and app config");
|
|
6696
7063
|
} else {
|
|
6697
|
-
console.log(`uninstall: history kept in
|
|
7064
|
+
console.log(`uninstall: history kept in ${dataDir}`);
|
|
7065
|
+
console.log(`uninstall: ${paths.hookConfig} and ${paths.appConfig} left in place`);
|
|
6698
7066
|
console.log("uninstall: re-run with --purge to delete the data too");
|
|
6699
7067
|
}
|
|
6700
7068
|
console.log(`uninstall: the binary itself is still at ${paths.binDir}/claude-transcripts`);
|
|
@@ -6718,7 +7086,8 @@ var COMMANDS = {
|
|
|
6718
7086
|
doctor: runDoctor,
|
|
6719
7087
|
sessions: runSessions,
|
|
6720
7088
|
search: runSearch,
|
|
6721
|
-
turns: runTurns
|
|
7089
|
+
turns: runTurns,
|
|
7090
|
+
completions: runCompletions
|
|
6722
7091
|
};
|
|
6723
7092
|
|
|
6724
|
-
export {
|
|
7093
|
+
export { isFlag, cliCommandsByGroup, cliUsage, cliArgHint, cliArgLabel, validateCliArgs, CLI_SPEC, parseFlags, VERSION, COMMANDS };
|
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
3
|
import {
|
|
4
|
-
CLI_SPEC,
|
|
5
4
|
cliUsage,
|
|
6
5
|
validateCliArgs,
|
|
6
|
+
CLI_SPEC,
|
|
7
7
|
parseFlags,
|
|
8
8
|
VERSION,
|
|
9
9
|
COMMANDS
|
|
10
|
-
} from "./cli-
|
|
10
|
+
} from "./cli-fptk6bgx.js";
|
|
11
11
|
|
|
12
12
|
// packages/cli/src/cli.tsx
|
|
13
13
|
var [, , first, ...rest] = process.argv;
|
|
14
14
|
var USAGE_ERROR = 2;
|
|
15
15
|
async function showHelp(props, stream = process.stdout) {
|
|
16
|
-
const { renderHelp } = await import("./app-
|
|
16
|
+
const { renderHelp } = await import("./app-a93wk4rv.js");
|
|
17
17
|
renderHelp(props, stream);
|
|
18
18
|
}
|
|
19
19
|
var argv = first === undefined ? [] : [first, ...rest];
|
package/package.json
CHANGED