@trawlme/cli 1.18.0 → 1.18.1
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/dist/index.d.ts +7 -0
- package/dist/index.js +11 -1
- package/dist/lib/prompt.js +39 -21
- package/dist/lib/skills.d.ts +13 -3
- package/dist/lib/skills.js +23 -4
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -17,4 +17,11 @@ export declare function collectCommandNames(root: Command): string[];
|
|
|
17
17
|
export declare function createProgram(): Command;
|
|
18
18
|
/** True when this module is the process entrypoint (not merely imported by a test). */
|
|
19
19
|
export declare function isEntryPoint(argv1: string | undefined, moduleUrl: string): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* True when the invocation is a pure `--help`/`--version` query. These must not
|
|
22
|
+
* trigger the skills auto-sync (a filesystem-mutating startup side effect) — a
|
|
23
|
+
* user running `trawl --version` never expects it to rewrite their skills dirs.
|
|
24
|
+
* (#73)
|
|
25
|
+
*/
|
|
26
|
+
export declare function isHelpOrVersion(argv: string[]): boolean;
|
|
20
27
|
export declare function runCli(argv?: string[]): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -62,8 +62,18 @@ export function createProgram() {
|
|
|
62
62
|
export function isEntryPoint(argv1, moduleUrl) {
|
|
63
63
|
return argv1 !== undefined && moduleUrl === pathToFileURL(argv1).href;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* True when the invocation is a pure `--help`/`--version` query. These must not
|
|
67
|
+
* trigger the skills auto-sync (a filesystem-mutating startup side effect) — a
|
|
68
|
+
* user running `trawl --version` never expects it to rewrite their skills dirs.
|
|
69
|
+
* (#73)
|
|
70
|
+
*/
|
|
71
|
+
export function isHelpOrVersion(argv) {
|
|
72
|
+
return argv.some((a) => a === '-h' || a === '--help' || a === '-V' || a === '--version');
|
|
73
|
+
}
|
|
65
74
|
export async function runCli(argv = process.argv) {
|
|
66
|
-
|
|
75
|
+
if (!isHelpOrVersion(argv))
|
|
76
|
+
autoUpdateInstalledSkills();
|
|
67
77
|
initPostHog();
|
|
68
78
|
const program = createProgram();
|
|
69
79
|
registerAllowedCommands(collectCommandNames(program));
|
package/dist/lib/prompt.js
CHANGED
|
@@ -6,26 +6,38 @@ export async function promptPassword(prompt) {
|
|
|
6
6
|
process.stdin.setRawMode(true);
|
|
7
7
|
process.stdin.resume();
|
|
8
8
|
process.stdin.setEncoding('utf8');
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
9
|
+
const restore = () => {
|
|
10
|
+
process.stdin.setRawMode(false);
|
|
11
|
+
process.stdin.pause();
|
|
12
|
+
process.stdin.removeListener('data', onData);
|
|
13
|
+
};
|
|
14
|
+
const onData = (chunk) => {
|
|
15
|
+
// A stdin chunk is NOT a single keystroke. Terminals deliver a paste as
|
|
16
|
+
// one chunk (Windows Terminal always chunk-pastes), and a paste that
|
|
17
|
+
// ends in a newline embeds a literal \r mid-chunk. Iterate per code
|
|
18
|
+
// point so an embedded Enter finalizes the password instead of being
|
|
19
|
+
// appended to it and hanging for a second Enter. (#73)
|
|
20
|
+
for (const char of chunk) {
|
|
21
|
+
if (char === '\r' || char === '\n') {
|
|
22
|
+
restore();
|
|
23
|
+
process.stderr.write('\n');
|
|
24
|
+
resolve(password);
|
|
25
|
+
return; // ignore anything after the first terminator
|
|
26
|
+
}
|
|
27
|
+
if (char === '') {
|
|
28
|
+
// Ctrl+C: restore the terminal and terminate the line before
|
|
29
|
+
// exiting so the shell prompt isn't left mid-line. 130 = 128+SIGINT.
|
|
30
|
+
restore();
|
|
31
|
+
process.stderr.write('\n');
|
|
32
|
+
process.exit(130);
|
|
33
|
+
}
|
|
34
|
+
if (char === '' || char === '\b') {
|
|
35
|
+
// Backspace
|
|
36
|
+
password = password.slice(0, -1);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
password += char;
|
|
40
|
+
}
|
|
29
41
|
}
|
|
30
42
|
};
|
|
31
43
|
process.stdin.on('data', onData);
|
|
@@ -36,8 +48,14 @@ export async function promptPassword(prompt) {
|
|
|
36
48
|
// `question` callback never fires; without a `close` handler the
|
|
37
49
|
// promise hangs forever and the process exits 0 without logging in.
|
|
38
50
|
// Reject loudly instead. (#68)
|
|
51
|
+
//
|
|
52
|
+
// No `output` is wired to the interface: readline echoes typed input
|
|
53
|
+
// whenever its output is a TTY, so `trawl login < creds` run from a real
|
|
54
|
+
// terminal would print the password. Omitting output (and terminal:false)
|
|
55
|
+
// guarantees the secret is never echoed. The prompt itself was already
|
|
56
|
+
// written to stderr above. (#73)
|
|
39
57
|
import('readline').then(({ createInterface }) => {
|
|
40
|
-
const rl = createInterface({ input: process.stdin,
|
|
58
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
41
59
|
let answered = false;
|
|
42
60
|
rl.question('', (answer) => {
|
|
43
61
|
answered = true;
|
package/dist/lib/skills.d.ts
CHANGED
|
@@ -5,8 +5,18 @@ export declare function uninstallSkill(name: string, scope: 'user' | 'local'): s
|
|
|
5
5
|
export declare function getInstalledVersion(name: string, scope: 'user' | 'local'): string | null;
|
|
6
6
|
export declare function isSkillInstalled(name: string, scope: 'user' | 'local'): boolean;
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* Called on CLI startup to keep skills in sync with the CLI
|
|
10
|
-
* Never throws — failures are silent so they don't break unrelated
|
|
8
|
+
* Re-installs any CLI-owned skill whose installed version doesn't match the
|
|
9
|
+
* bundled one. Called on CLI startup to keep skills in sync with the CLI
|
|
10
|
+
* version. Never throws — failures are silent so they don't break unrelated
|
|
11
|
+
* commands.
|
|
12
|
+
*
|
|
13
|
+
* Ownership guard (#73): `installSkill` does `rmSync(recursive)` on the target
|
|
14
|
+
* dir, so this MUST only ever touch dirs the CLI itself installed. Proof of
|
|
15
|
+
* ownership is a `.version` marker. A user-created `.claude/skills/<name>` dir
|
|
16
|
+
* that happens to collide with a bundled skill name carries no marker, so it is
|
|
17
|
+
* left untouched instead of being silently deleted + overwritten.
|
|
18
|
+
*
|
|
19
|
+
* Opt-out: `TRAWL_SKILLS_SYNC=0` disables auto-sync entirely (mirrors
|
|
20
|
+
* `TRAWL_TELEMETRY=0`), for users who manage their skills by hand.
|
|
11
21
|
*/
|
|
12
22
|
export declare function autoUpdateInstalledSkills(): void;
|
package/dist/lib/skills.js
CHANGED
|
@@ -54,20 +54,39 @@ export function isSkillInstalled(name, scope) {
|
|
|
54
54
|
return existsSync(join(getSkillsBase(scope), name));
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
58
|
-
* Called on CLI startup to keep skills in sync with the CLI
|
|
59
|
-
* Never throws — failures are silent so they don't break unrelated
|
|
57
|
+
* Re-installs any CLI-owned skill whose installed version doesn't match the
|
|
58
|
+
* bundled one. Called on CLI startup to keep skills in sync with the CLI
|
|
59
|
+
* version. Never throws — failures are silent so they don't break unrelated
|
|
60
|
+
* commands.
|
|
61
|
+
*
|
|
62
|
+
* Ownership guard (#73): `installSkill` does `rmSync(recursive)` on the target
|
|
63
|
+
* dir, so this MUST only ever touch dirs the CLI itself installed. Proof of
|
|
64
|
+
* ownership is a `.version` marker. A user-created `.claude/skills/<name>` dir
|
|
65
|
+
* that happens to collide with a bundled skill name carries no marker, so it is
|
|
66
|
+
* left untouched instead of being silently deleted + overwritten.
|
|
67
|
+
*
|
|
68
|
+
* Opt-out: `TRAWL_SKILLS_SYNC=0` disables auto-sync entirely (mirrors
|
|
69
|
+
* `TRAWL_TELEMETRY=0`), for users who manage their skills by hand.
|
|
60
70
|
*/
|
|
61
71
|
export function autoUpdateInstalledSkills() {
|
|
72
|
+
if (process.env['TRAWL_SKILLS_SYNC'] === '0')
|
|
73
|
+
return;
|
|
62
74
|
try {
|
|
63
75
|
const bundledVersion = getBundledSkillsVersion();
|
|
64
76
|
for (const name of listBundledSkills()) {
|
|
65
77
|
for (const scope of ['user', 'local']) {
|
|
66
78
|
if (!isSkillInstalled(name, scope))
|
|
67
79
|
continue;
|
|
68
|
-
|
|
80
|
+
const installed = getInstalledVersion(name, scope);
|
|
81
|
+
// No `.version` marker → not ours → never delete it.
|
|
82
|
+
if (installed === null)
|
|
83
|
+
continue;
|
|
84
|
+
if (installed === bundledVersion)
|
|
69
85
|
continue;
|
|
70
86
|
installSkill(name, scope);
|
|
87
|
+
// One honest line so a destructive-looking re-sync is never silent.
|
|
88
|
+
// stderr keeps stdout clean for --json consumers.
|
|
89
|
+
process.stderr.write(`trawl: re-synced skill "${name}" (${scope}) ${installed} → ${bundledVersion}\n`);
|
|
71
90
|
}
|
|
72
91
|
}
|
|
73
92
|
}
|