@trawlme/cli 1.7.0 → 1.8.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 +15 -0
- package/dist/commands/skills.d.ts +2 -0
- package/dist/commands/skills.js +83 -0
- package/dist/index.js +4 -0
- package/dist/lib/skills.d.ts +12 -0
- package/dist/lib/skills.js +77 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -56,6 +56,21 @@ trawl scraps account clear-session <id>
|
|
|
56
56
|
trawl scraps account status <id> [--json]
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
### Claude Code skills
|
|
60
|
+
|
|
61
|
+
The CLI bundles a Claude Code skill that teaches Claude how to use `trawl`. Once installed, Claude can manage scraps for you via prompts.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
trawl skills list List bundled skills and install status
|
|
65
|
+
trawl skills install [<skill>] [--local] Install all (or one). Default: ~/.claude/skills/
|
|
66
|
+
trawl skills uninstall [<skill>] [--local] Remove
|
|
67
|
+
trawl skills update [<skill>] [--local] Reinstall (force sync with CLI version)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Skills auto-update silently when you upgrade the CLI — no need to re-install manually.
|
|
71
|
+
|
|
72
|
+
You can also install skills standalone (without the CLI): `npx @trawlme/skills install`.
|
|
73
|
+
|
|
59
74
|
## Environment variables
|
|
60
75
|
|
|
61
76
|
| Variable | Description |
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { listBundledSkills, installSkill, uninstallSkill, getBundledSkillsVersion, getInstalledVersion, isSkillInstalled, } from '../lib/skills.js';
|
|
4
|
+
function pickScope(opts) {
|
|
5
|
+
return opts.local ? 'local' : 'user';
|
|
6
|
+
}
|
|
7
|
+
function pickSkills(arg) {
|
|
8
|
+
const all = listBundledSkills();
|
|
9
|
+
if (!arg || arg === 'all')
|
|
10
|
+
return all;
|
|
11
|
+
if (!all.includes(arg)) {
|
|
12
|
+
throw new Error(`Unknown skill "${arg}". Available: ${all.join(', ') || '(none)'}`);
|
|
13
|
+
}
|
|
14
|
+
return [arg];
|
|
15
|
+
}
|
|
16
|
+
export const skills = new Command('skills').description('Manage Claude Code skills bundled with the Trawl CLI');
|
|
17
|
+
skills
|
|
18
|
+
.command('list')
|
|
19
|
+
.description('List bundled skills and their installed status')
|
|
20
|
+
.action(() => {
|
|
21
|
+
const bundled = listBundledSkills();
|
|
22
|
+
if (!bundled.length) {
|
|
23
|
+
console.log(chalk.dim('No bundled skills.'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const version = getBundledSkillsVersion();
|
|
27
|
+
console.log(chalk.dim(`@trawlme/skills@${version}\n`));
|
|
28
|
+
for (const name of bundled) {
|
|
29
|
+
const userInstalled = isSkillInstalled(name, 'user');
|
|
30
|
+
const localInstalled = isSkillInstalled(name, 'local');
|
|
31
|
+
const installedVersion = getInstalledVersion(name, 'user') ?? getInstalledVersion(name, 'local');
|
|
32
|
+
const stale = installedVersion && installedVersion !== version;
|
|
33
|
+
const tag = userInstalled
|
|
34
|
+
? localInstalled
|
|
35
|
+
? '(user + local)'
|
|
36
|
+
: '(user)'
|
|
37
|
+
: localInstalled
|
|
38
|
+
? '(local)'
|
|
39
|
+
: '(not installed)';
|
|
40
|
+
const staleNote = stale ? chalk.yellow(` outdated: ${installedVersion} → ${version}`) : '';
|
|
41
|
+
console.log(` ${name} ${chalk.dim(tag)}${staleNote}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
skills
|
|
45
|
+
.command('install [skill]')
|
|
46
|
+
.description('Install one or all bundled skills')
|
|
47
|
+
.option('--local', 'Install at project level (./.claude/skills) instead of user level (~/.claude/skills)')
|
|
48
|
+
.action((skill, opts) => {
|
|
49
|
+
const scope = pickScope(opts);
|
|
50
|
+
const targets = pickSkills(skill);
|
|
51
|
+
for (const name of targets) {
|
|
52
|
+
const dest = installSkill(name, scope);
|
|
53
|
+
console.log(chalk.green(`✓ Installed "${name}"`) + chalk.dim(` at ${dest}`));
|
|
54
|
+
}
|
|
55
|
+
console.log(chalk.dim(' Restart Claude Code if it was already running.'));
|
|
56
|
+
});
|
|
57
|
+
skills
|
|
58
|
+
.command('uninstall [skill]')
|
|
59
|
+
.description('Remove one or all bundled skills')
|
|
60
|
+
.option('--local', 'Remove from project level (./.claude/skills)')
|
|
61
|
+
.action((skill, opts) => {
|
|
62
|
+
const scope = pickScope(opts);
|
|
63
|
+
const targets = pickSkills(skill);
|
|
64
|
+
for (const name of targets) {
|
|
65
|
+
const dest = uninstallSkill(name, scope);
|
|
66
|
+
if (dest)
|
|
67
|
+
console.log(chalk.green(`✓ Removed "${name}"`) + chalk.dim(` from ${dest}`));
|
|
68
|
+
else
|
|
69
|
+
console.log(chalk.dim(` No "${name}" found`));
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
skills
|
|
73
|
+
.command('update [skill]')
|
|
74
|
+
.description('Reinstall over the existing skill (force sync with CLI version)')
|
|
75
|
+
.option('--local', 'Update at project level')
|
|
76
|
+
.action((skill, opts) => {
|
|
77
|
+
const scope = pickScope(opts);
|
|
78
|
+
const targets = pickSkills(skill);
|
|
79
|
+
for (const name of targets) {
|
|
80
|
+
const dest = installSkill(name, scope);
|
|
81
|
+
console.log(chalk.green(`✓ Updated "${name}"`) + chalk.dim(` at ${dest}`));
|
|
82
|
+
}
|
|
83
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,9 @@ import { Command } from 'commander';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { login, logout } from './commands/login.js';
|
|
5
5
|
import { scraps } from './commands/scraps.js';
|
|
6
|
+
import { skills } from './commands/skills.js';
|
|
7
|
+
import { autoUpdateInstalledSkills } from './lib/skills.js';
|
|
8
|
+
autoUpdateInstalledSkills();
|
|
6
9
|
const program = new Command()
|
|
7
10
|
.name('trawl')
|
|
8
11
|
.description('Trawl CLI — manage scraps from the terminal')
|
|
@@ -11,6 +14,7 @@ const program = new Command()
|
|
|
11
14
|
program.addCommand(login);
|
|
12
15
|
program.addCommand(logout);
|
|
13
16
|
program.addCommand(scraps);
|
|
17
|
+
program.addCommand(skills);
|
|
14
18
|
program.parseAsync().catch((err) => {
|
|
15
19
|
const { debug } = program.opts();
|
|
16
20
|
if (debug || process.env['DEBUG']) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function getBundledSkillsVersion(): string;
|
|
2
|
+
export declare function listBundledSkills(): string[];
|
|
3
|
+
export declare function installSkill(name: string, scope: 'user' | 'local'): string;
|
|
4
|
+
export declare function uninstallSkill(name: string, scope: 'user' | 'local'): string | null;
|
|
5
|
+
export declare function getInstalledVersion(name: string, scope: 'user' | 'local'): string | null;
|
|
6
|
+
export declare function isSkillInstalled(name: string, scope: 'user' | 'local'): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Silently re-installs any skill whose installed version doesn't match the bundled one.
|
|
9
|
+
* Called on CLI startup to keep skills in sync with the CLI version.
|
|
10
|
+
* Never throws — failures are silent so they don't break unrelated commands.
|
|
11
|
+
*/
|
|
12
|
+
export declare function autoUpdateInstalledSkills(): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { mkdirSync, existsSync, rmSync, readFileSync, writeFileSync, readdirSync, statSync, cpSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
function getSkillsPackageRoot() {
|
|
7
|
+
const pkgJson = require.resolve('@trawlme/skills/package.json');
|
|
8
|
+
return dirname(pkgJson);
|
|
9
|
+
}
|
|
10
|
+
export function getBundledSkillsVersion() {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(join(getSkillsPackageRoot(), 'package.json'), 'utf8'));
|
|
12
|
+
return pkg.version;
|
|
13
|
+
}
|
|
14
|
+
export function listBundledSkills() {
|
|
15
|
+
const src = join(getSkillsPackageRoot(), 'skills');
|
|
16
|
+
if (!existsSync(src))
|
|
17
|
+
return [];
|
|
18
|
+
return readdirSync(src).filter((name) => {
|
|
19
|
+
const dir = join(src, name);
|
|
20
|
+
return statSync(dir).isDirectory() && existsSync(join(dir, 'SKILL.md'));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function getSkillsBase(scope) {
|
|
24
|
+
const base = scope === 'local' ? join(process.cwd(), '.claude') : join(homedir(), '.claude');
|
|
25
|
+
return join(base, 'skills');
|
|
26
|
+
}
|
|
27
|
+
export function installSkill(name, scope) {
|
|
28
|
+
const src = join(getSkillsPackageRoot(), 'skills', name);
|
|
29
|
+
if (!existsSync(src)) {
|
|
30
|
+
throw new Error(`Skill "${name}" not found in @trawlme/skills`);
|
|
31
|
+
}
|
|
32
|
+
const dest = join(getSkillsBase(scope), name);
|
|
33
|
+
if (existsSync(dest))
|
|
34
|
+
rmSync(dest, { recursive: true, force: true });
|
|
35
|
+
mkdirSync(dest, { recursive: true });
|
|
36
|
+
cpSync(src, dest, { recursive: true });
|
|
37
|
+
writeFileSync(join(dest, '.version'), getBundledSkillsVersion(), 'utf8');
|
|
38
|
+
return dest;
|
|
39
|
+
}
|
|
40
|
+
export function uninstallSkill(name, scope) {
|
|
41
|
+
const dest = join(getSkillsBase(scope), name);
|
|
42
|
+
if (!existsSync(dest))
|
|
43
|
+
return null;
|
|
44
|
+
rmSync(dest, { recursive: true, force: true });
|
|
45
|
+
return dest;
|
|
46
|
+
}
|
|
47
|
+
export function getInstalledVersion(name, scope) {
|
|
48
|
+
const versionFile = join(getSkillsBase(scope), name, '.version');
|
|
49
|
+
if (!existsSync(versionFile))
|
|
50
|
+
return null;
|
|
51
|
+
return readFileSync(versionFile, 'utf8').trim();
|
|
52
|
+
}
|
|
53
|
+
export function isSkillInstalled(name, scope) {
|
|
54
|
+
return existsSync(join(getSkillsBase(scope), name));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Silently re-installs any skill whose installed version doesn't match the bundled one.
|
|
58
|
+
* Called on CLI startup to keep skills in sync with the CLI version.
|
|
59
|
+
* Never throws — failures are silent so they don't break unrelated commands.
|
|
60
|
+
*/
|
|
61
|
+
export function autoUpdateInstalledSkills() {
|
|
62
|
+
try {
|
|
63
|
+
const bundledVersion = getBundledSkillsVersion();
|
|
64
|
+
for (const name of listBundledSkills()) {
|
|
65
|
+
for (const scope of ['user', 'local']) {
|
|
66
|
+
if (!isSkillInstalled(name, scope))
|
|
67
|
+
continue;
|
|
68
|
+
if (getInstalledVersion(name, scope) === bundledVersion)
|
|
69
|
+
continue;
|
|
70
|
+
installSkill(name, scope);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Silent: skill auto-update should never block the CLI
|
|
76
|
+
}
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trawlme/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "Trawl CLI — manage scraps from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"url": "https://github.com/comes-io/trawl_cli/issues"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
+
"@trawlme/skills": "^1.0.0",
|
|
43
44
|
"chalk": "^5.6.2",
|
|
44
45
|
"commander": "^14.0.3",
|
|
45
46
|
"conf": "^15.1.0",
|