@skhema/cli 0.5.0 → 0.6.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 -1
- package/dist/cli.js +14 -2
- package/dist/commands/element.d.ts +3 -2
- package/dist/commands/element.d.ts.map +1 -1
- package/dist/commands/element.js +52 -2
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +98 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +5 -4
- package/dist/lib/skills/installer.d.ts +14 -1
- package/dist/lib/skills/installer.d.ts.map +1 -1
- package/dist/lib/skills/installer.js +40 -0
- package/dist/lib/skills/types.d.ts +6 -0
- package/dist/lib/skills/types.d.ts.map +1 -1
- package/dist/program.d.ts +13 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +34 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ SKHEMA_API_KEY=sk_live_… npx @skhema/cli init --yes --json
|
|
|
31
31
|
future commands (never overwriting a different stored key without `--force`).
|
|
32
32
|
- **Installs skills** into every detected agent platform (idempotent — reruns
|
|
33
33
|
report `unchanged`).
|
|
34
|
-
- **Registers the MCP server** (`https://mcp
|
|
34
|
+
- **Registers the MCP server** (`https://mcp.skhema.com/mcp`, Streamable
|
|
35
35
|
HTTP) in detected clients by safely merging a dedicated config file and writing
|
|
36
36
|
a `.bak` backup — **or printing copy-paste instructions** when a client's config
|
|
37
37
|
isn't cleanly writable. `--yes` registers non-interactively; without it, `init`
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { CommanderError } from 'commander';
|
|
1
2
|
import { createRequire } from 'module';
|
|
2
3
|
import { isApprovedContributor } from './lib/auth/entitlements.js';
|
|
3
4
|
import { getStoredCredentials } from './lib/auth/token-store.js';
|
|
4
5
|
import { outputError } from './lib/output.js';
|
|
5
|
-
import { buildProgram } from './program.js';
|
|
6
|
+
import { buildProgram, resolveCommanderExitCode } from './program.js';
|
|
6
7
|
const require = createRequire(import.meta.url);
|
|
7
8
|
const pkg = require('../package.json');
|
|
8
9
|
const program = buildProgram(pkg.version);
|
|
@@ -35,6 +36,17 @@ async function main() {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
+
try {
|
|
40
|
+
await program.parseAsync();
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
// With exitOverride set, commander throws instead of exiting for every
|
|
44
|
+
// parser-level outcome (usage error, --help, --version). Map those onto the
|
|
45
|
+
// ExitCode contract; anything else is a real failure and should propagate.
|
|
46
|
+
if (err instanceof CommanderError) {
|
|
47
|
+
process.exit(resolveCommanderExitCode(err));
|
|
48
|
+
}
|
|
49
|
+
throw err;
|
|
50
|
+
}
|
|
39
51
|
}
|
|
40
52
|
main();
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
/**
|
|
3
|
-
* Element commands — the strategic building blocks in a workspace.
|
|
4
|
-
* workspace-scoped via `--workspace` (falling back to the default workspace)
|
|
3
|
+
* Element commands — the strategic building blocks in a workspace. Most are
|
|
4
|
+
* workspace-scoped via `--workspace` (falling back to the default workspace);
|
|
5
|
+
* `types` is a local, network-free lookup of the method vocabulary.
|
|
5
6
|
*/
|
|
6
7
|
export declare function registerElementCommands(program: Command): void;
|
|
7
8
|
//# sourceMappingURL=element.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../../src/commands/element.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"element.d.ts","sourceRoot":"","sources":["../../src/commands/element.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AA2EnC;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2T9D"}
|
package/dist/commands/element.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getElementDefinition, getElementsForComponent, SKHEMA_MAPPING, } from '@skhema/method';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
3
|
import { resolveClient } from '../lib/api/client.js';
|
|
3
4
|
import { asRecord, buildBody, coerceList, readPayloadFile, resolveWorkspaceId, } from '../lib/api/command-helpers.js';
|
|
@@ -8,9 +9,47 @@ function summarize(content) {
|
|
|
8
9
|
const text = typeof content === 'string' ? content : '';
|
|
9
10
|
return text.length > 48 ? `${text.slice(0, 47)}…` : text || '—';
|
|
10
11
|
}
|
|
12
|
+
/** Human-friendly rendering of an expected mood (e.g. `noun_phrase` → `noun phrase`). */
|
|
13
|
+
function formatMood(mood) {
|
|
14
|
+
return mood ? mood.replace(/_/g, ' ') : '—';
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The component → allowed-element-type mapping, read straight from
|
|
18
|
+
* `@skhema/method` (SKHEMA_MAPPING) in canonical component-flow order. This is
|
|
19
|
+
* the same vocabulary the backend enforces, so agents can discover the
|
|
20
|
+
* allow-list locally instead of probing it via `bad_request` errors.
|
|
21
|
+
*/
|
|
22
|
+
function buildElementTypeMapping() {
|
|
23
|
+
return SKHEMA_MAPPING.componentFlow.map((component) => ({
|
|
24
|
+
component: component.value,
|
|
25
|
+
label: component.label,
|
|
26
|
+
acronym: component.acronym,
|
|
27
|
+
elementTypes: getElementsForComponent(component.value).map((element) => ({
|
|
28
|
+
type: element.value,
|
|
29
|
+
label: element.label,
|
|
30
|
+
acronym: element.acronym,
|
|
31
|
+
mood: getElementDefinition(element.value)?.expectedMood ?? null,
|
|
32
|
+
})),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
/** Render the mapping as a per-component section with an element-type table. */
|
|
36
|
+
function renderElementTypeMapping(mapping) {
|
|
37
|
+
logHeader('Element types by component');
|
|
38
|
+
for (const component of mapping) {
|
|
39
|
+
log('');
|
|
40
|
+
log(chalk.bold(`${component.label} ${chalk.dim(`(${component.acronym})`)}`));
|
|
41
|
+
logTable(['acronym', 'type', 'label', 'mood'], component.elementTypes.map((element) => [
|
|
42
|
+
element.acronym,
|
|
43
|
+
element.type,
|
|
44
|
+
element.label,
|
|
45
|
+
formatMood(element.mood),
|
|
46
|
+
]));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
11
49
|
/**
|
|
12
|
-
* Element commands — the strategic building blocks in a workspace.
|
|
13
|
-
* workspace-scoped via `--workspace` (falling back to the default workspace)
|
|
50
|
+
* Element commands — the strategic building blocks in a workspace. Most are
|
|
51
|
+
* workspace-scoped via `--workspace` (falling back to the default workspace);
|
|
52
|
+
* `types` is a local, network-free lookup of the method vocabulary.
|
|
14
53
|
*/
|
|
15
54
|
export function registerElementCommands(program) {
|
|
16
55
|
const element = program.command('element').description('Element commands');
|
|
@@ -200,4 +239,15 @@ export function registerElementCommands(program) {
|
|
|
200
239
|
return data;
|
|
201
240
|
});
|
|
202
241
|
});
|
|
242
|
+
element
|
|
243
|
+
.command('types')
|
|
244
|
+
.description('List element types allowed in each component (method vocabulary, no network)')
|
|
245
|
+
.action(async () => {
|
|
246
|
+
await runCommand('element types', async () => {
|
|
247
|
+
const mapping = buildElementTypeMapping();
|
|
248
|
+
if (!getGlobalOptions().json)
|
|
249
|
+
renderElementTypeMapping(mapping);
|
|
250
|
+
return { components: mapping };
|
|
251
|
+
});
|
|
252
|
+
});
|
|
203
253
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../../src/commands/skills.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAwCnC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6U7D"}
|
package/dist/commands/skills.js
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
3
|
import ora from 'ora';
|
|
3
4
|
import { getGlobalOptions, log, logHeader, logTable, outputError, outputSuccess, } from '../lib/output.js';
|
|
4
5
|
import { listBundledSkills, readBundledSkill } from '../lib/skills/bundled.js';
|
|
5
|
-
import { installSkills, updateSkills } from '../lib/skills/installer.js';
|
|
6
|
+
import { installSkills, pruneSkills, updateSkills, } from '../lib/skills/installer.js';
|
|
6
7
|
import { readLock } from '../lib/skills/lock.js';
|
|
7
8
|
import { detectPlatforms } from '../lib/skills/platform-detect.js';
|
|
9
|
+
function interactiveTerminal() {
|
|
10
|
+
return (Boolean(process.stdin.isTTY && process.stdout.isTTY) &&
|
|
11
|
+
!getGlobalOptions().json);
|
|
12
|
+
}
|
|
13
|
+
async function confirm(question) {
|
|
14
|
+
const rl = readline.createInterface({
|
|
15
|
+
input: process.stdin,
|
|
16
|
+
output: process.stdout,
|
|
17
|
+
});
|
|
18
|
+
const answer = await new Promise((resolve) => {
|
|
19
|
+
rl.question(`${question} [y/N]: `, (value) => {
|
|
20
|
+
rl.close();
|
|
21
|
+
resolve(value.trim().toLowerCase());
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
return answer === 'y' || answer === 'yes';
|
|
25
|
+
}
|
|
8
26
|
export function registerSkillsCommands(program) {
|
|
9
27
|
const skills = program.command('skills').description('AI skills management');
|
|
10
28
|
skills
|
|
@@ -171,4 +189,83 @@ export function registerSkillsCommands(program) {
|
|
|
171
189
|
process.exit(1);
|
|
172
190
|
}
|
|
173
191
|
});
|
|
192
|
+
skills
|
|
193
|
+
.command('prune')
|
|
194
|
+
.description('Remove installed skills that are no longer bundled with this CLI (retired or renamed)')
|
|
195
|
+
.option('--platform <id>', 'Prune a specific platform only')
|
|
196
|
+
.option('--yes', 'Actually remove; without it, only preview what would go')
|
|
197
|
+
.action(async (options) => {
|
|
198
|
+
const opts = getGlobalOptions();
|
|
199
|
+
const spinner = opts.json || opts.quiet
|
|
200
|
+
? null
|
|
201
|
+
: ora('Detecting agent platforms...').start();
|
|
202
|
+
try {
|
|
203
|
+
const platforms = detectPlatforms().filter((p) => p.detected);
|
|
204
|
+
const targets = options.platform
|
|
205
|
+
? platforms.filter((p) => p.id === options.platform)
|
|
206
|
+
: platforms;
|
|
207
|
+
if (targets.length === 0) {
|
|
208
|
+
spinner?.warn(options.platform
|
|
209
|
+
? `Platform "${options.platform}" not detected.`
|
|
210
|
+
: 'No agent platforms detected.');
|
|
211
|
+
if (opts.json)
|
|
212
|
+
outputSuccess([], 'skills prune');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
// Preview first — this never writes.
|
|
216
|
+
const preview = await pruneSkills(targets, { apply: false });
|
|
217
|
+
if (preview.length === 0) {
|
|
218
|
+
spinner?.succeed('No retired skills to prune. Everything is current.');
|
|
219
|
+
if (opts.json)
|
|
220
|
+
outputSuccess([], 'skills prune');
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
spinner?.info(`Found ${preview.length} retired skill install(s) across ${new Set(preview.map((r) => r.platform.id)).size} platform(s).`);
|
|
224
|
+
// Mutate only with --yes, or after an interactive confirmation.
|
|
225
|
+
let apply = Boolean(options.yes);
|
|
226
|
+
if (!apply) {
|
|
227
|
+
if (interactiveTerminal()) {
|
|
228
|
+
logHeader('Would remove');
|
|
229
|
+
logTable(['Platform', 'Skill', 'Path'], preview.map((r) => [
|
|
230
|
+
r.platform.name,
|
|
231
|
+
r.skill,
|
|
232
|
+
chalk.dim(r.targetPath),
|
|
233
|
+
]));
|
|
234
|
+
apply = await confirm('Remove these skills?');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!apply) {
|
|
238
|
+
const results = preview;
|
|
239
|
+
if (opts.json) {
|
|
240
|
+
outputSuccess(results, 'skills prune');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
logHeader('Prune Preview (dry run — pass --yes to remove)');
|
|
244
|
+
logTable(['Platform', 'Skill', 'Path'], results.map((r) => [
|
|
245
|
+
r.platform.name,
|
|
246
|
+
r.skill,
|
|
247
|
+
chalk.dim(r.targetPath),
|
|
248
|
+
]));
|
|
249
|
+
log('');
|
|
250
|
+
log(chalk.dim('Re-run with --yes to remove these skills.'));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const results = await pruneSkills(targets, { apply: true });
|
|
254
|
+
if (opts.json) {
|
|
255
|
+
outputSuccess(results, 'skills prune');
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
logHeader('Prune Results');
|
|
259
|
+
logTable(['Platform', 'Skill', 'Status'], results.map((r) => [
|
|
260
|
+
r.platform.name,
|
|
261
|
+
r.skill,
|
|
262
|
+
chalk.red(r.action),
|
|
263
|
+
]));
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
spinner?.fail('Prune failed');
|
|
267
|
+
outputError(error instanceof Error ? error.message : 'Unknown error', 'skills prune');
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
174
271
|
}
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAChD,eAAO,MAAM,aAAa,QACgC,CAAA;AAC1D,eAAO,MAAM,YAAY,yBAAyB,CAAA;AAClD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AACrC,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,eAAO,MAAM,aAAa,SAAU,CAAA;AACpC,eAAO,MAAM,gBAAgB,mBAAmB,CAAA;AAChD,eAAO,MAAM,gBAAgB,YAAY,CAAA;AAGzC,eAAO,MAAM,wBAAwB,YAAY,CAAA;AACjD,eAAO,MAAM,eAAe,YAAY,CAAA;AACxC,eAAO,MAAM,gBAAgB,qBAAqB,CAAA;AAClD,eAAO,MAAM,YAAY,iBAAiB,CAAA;AAC1C,eAAO,MAAM,WAAW,gBAAgB,CAAA;AACxC,eAAO,MAAM,eAAe,4BAA4B,CAAA;AAGxD,eAAO,MAAM,YAAY,QAC+B,CAAA;AAGxD,eAAO,MAAM,eAAe,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,aAAa,sBAAsB,CAAA;AAChD,eAAO,MAAM,aAAa,QACgC,CAAA;AAC1D,eAAO,MAAM,YAAY,yBAAyB,CAAA;AAClD,eAAO,MAAM,oBAAoB,IAAI,CAAA;AACrC,eAAO,MAAM,kBAAkB,MAAM,CAAA;AACrC,eAAO,MAAM,aAAa,SAAU,CAAA;AACpC,eAAO,MAAM,gBAAgB,mBAAmB,CAAA;AAChD,eAAO,MAAM,gBAAgB,YAAY,CAAA;AAGzC,eAAO,MAAM,wBAAwB,YAAY,CAAA;AACjD,eAAO,MAAM,eAAe,YAAY,CAAA;AACxC,eAAO,MAAM,gBAAgB,qBAAqB,CAAA;AAClD,eAAO,MAAM,YAAY,iBAAiB,CAAA;AAC1C,eAAO,MAAM,WAAW,gBAAgB,CAAA;AACxC,eAAO,MAAM,eAAe,4BAA4B,CAAA;AAGxD,eAAO,MAAM,YAAY,QAC+B,CAAA;AAGxD,eAAO,MAAM,eAAe,mBAAmB,CAAA;AAQ/C,eAAO,MAAM,cAAc,QACiC,CAAA;AAM5D,eAAO,MAAM,YAAY,QACsD,CAAA;AAC/E,eAAO,MAAM,wBAAwB,QAEa,CAAA"}
|
package/dist/lib/config.js
CHANGED
|
@@ -19,11 +19,12 @@ export const API_BASE_URL = process.env.SKHEMA_API_URL ?? 'https://api.skhema.co
|
|
|
19
19
|
// Environment variable an unattended CI/agent run sets to supply an API key.
|
|
20
20
|
export const API_KEY_ENV_VAR = 'SKHEMA_API_KEY';
|
|
21
21
|
// Public Skhema MCP server endpoint (Streamable HTTP transport). This is the
|
|
22
|
-
// protocol endpoint AI clients connect to —
|
|
23
|
-
//
|
|
24
|
-
// CLI only registers the URL; it
|
|
22
|
+
// protocol endpoint AI clients connect to — mcp.skhema.com IS the MCP server.
|
|
23
|
+
// (The human-facing management UI now lives at app.skhema.com/mcp.) Auth is
|
|
24
|
+
// per-client browser OAuth on first use, so the CLI only registers the URL; it
|
|
25
|
+
// never injects a credential here.
|
|
25
26
|
// Override via env for staging/preview environments.
|
|
26
|
-
export const MCP_SERVER_URL = process.env.SKHEMA_MCP_URL ?? 'https://mcp
|
|
27
|
+
export const MCP_SERVER_URL = process.env.SKHEMA_MCP_URL ?? 'https://mcp.skhema.com/mcp';
|
|
27
28
|
// Supabase project configuration.
|
|
28
29
|
// Publishable keys are safe to embed in client-side code (they are the new
|
|
29
30
|
// replacement for the legacy anon key and rely on RLS + server-side auth
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
import type { DetectedPlatform, InstallResult } from './types.js';
|
|
1
|
+
import type { DetectedPlatform, InstallResult, PruneResult } from './types.js';
|
|
2
2
|
export declare function installSkills(platforms: DetectedPlatform[]): Promise<InstallResult[]>;
|
|
3
3
|
export declare function updateSkills(platforms: DetectedPlatform[]): Promise<InstallResult[]>;
|
|
4
|
+
/**
|
|
5
|
+
* Find skills we previously installed that are no longer bundled with this CLI
|
|
6
|
+
* (retired or renamed) and remove them from each platform.
|
|
7
|
+
*
|
|
8
|
+
* Attribution is anchored on the per-platform lock file: only skills recorded
|
|
9
|
+
* there by our own installer are candidates, so a skill authored by the user or
|
|
10
|
+
* another tool that happens to share the skills directory is never touched. With
|
|
11
|
+
* `apply: false` (the default), nothing is written — the returned list is a
|
|
12
|
+
* preview of what removal would do.
|
|
13
|
+
*/
|
|
14
|
+
export declare function pruneSkills(platforms: DetectedPlatform[], options?: {
|
|
15
|
+
apply: boolean;
|
|
16
|
+
}): Promise<PruneResult[]>;
|
|
4
17
|
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../../src/lib/skills/installer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AA+
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../../../src/lib/skills/installer.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AA+D9E,wBAAsB,aAAa,CACjC,SAAS,EAAE,gBAAgB,EAAE,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC,CA0C1B;AAED,wBAAsB,YAAY,CAChC,SAAS,EAAE,gBAAgB,EAAE,GAC5B,OAAO,CAAC,aAAa,EAAE,CAAC,CAG1B;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,OAAO,GAAE;IAAE,KAAK,EAAE,OAAO,CAAA;CAAqB,GAC7C,OAAO,CAAC,WAAW,EAAE,CAAC,CAmCxB"}
|
|
@@ -83,3 +83,43 @@ export async function updateSkills(platforms) {
|
|
|
83
83
|
// Update uses the same logic as install — content comparison handles skip/update
|
|
84
84
|
return installSkills(platforms);
|
|
85
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Find skills we previously installed that are no longer bundled with this CLI
|
|
88
|
+
* (retired or renamed) and remove them from each platform.
|
|
89
|
+
*
|
|
90
|
+
* Attribution is anchored on the per-platform lock file: only skills recorded
|
|
91
|
+
* there by our own installer are candidates, so a skill authored by the user or
|
|
92
|
+
* another tool that happens to share the skills directory is never touched. With
|
|
93
|
+
* `apply: false` (the default), nothing is written — the returned list is a
|
|
94
|
+
* preview of what removal would do.
|
|
95
|
+
*/
|
|
96
|
+
export async function pruneSkills(platforms, options = { apply: false }) {
|
|
97
|
+
const bundledNames = new Set(getBundledSkills().map((skill) => skill.name));
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const platform of platforms) {
|
|
100
|
+
const lock = readLock(platform);
|
|
101
|
+
const retired = Object.keys(lock.skills)
|
|
102
|
+
.filter((name) => !bundledNames.has(name))
|
|
103
|
+
.sort((a, b) => a.localeCompare(b));
|
|
104
|
+
for (const name of retired) {
|
|
105
|
+
const targetPath = path.join(platform.skillsDir, name);
|
|
106
|
+
if (options.apply) {
|
|
107
|
+
// Remove only the directory our lock attributes to this skill.
|
|
108
|
+
if (fs.existsSync(targetPath)) {
|
|
109
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
110
|
+
}
|
|
111
|
+
delete lock.skills[name];
|
|
112
|
+
}
|
|
113
|
+
results.push({
|
|
114
|
+
platform,
|
|
115
|
+
skill: name,
|
|
116
|
+
action: options.apply ? 'removed' : 'would-remove',
|
|
117
|
+
targetPath,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (options.apply && retired.length > 0) {
|
|
121
|
+
writeLock(platform, lock);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
@@ -17,6 +17,12 @@ export interface InstallResult {
|
|
|
17
17
|
action: 'installed' | 'updated' | 'skipped';
|
|
18
18
|
targetPath: string;
|
|
19
19
|
}
|
|
20
|
+
export interface PruneResult {
|
|
21
|
+
platform: DetectedPlatform;
|
|
22
|
+
skill: string;
|
|
23
|
+
action: 'removed' | 'would-remove';
|
|
24
|
+
targetPath: string;
|
|
25
|
+
}
|
|
20
26
|
export interface SkillLock {
|
|
21
27
|
version: string;
|
|
22
28
|
installedAt: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/skills/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAA;IAC3C,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CACZ,MAAM,EACN;QACE,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,CACF,CAAA;CACF"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/skills/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,EAAE,CAAA;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAAA;IAC3C,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,gBAAgB,CAAA;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,SAAS,GAAG,cAAc,CAAA;IAClC,UAAU,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,MAAM,CACZ,MAAM,EACN;QACE,OAAO,EAAE,MAAM,CAAA;QACf,QAAQ,EAAE,MAAM,CAAA;QAChB,WAAW,EAAE,MAAM,CAAA;KACpB,CACF,CAAA;CACF"}
|
package/dist/program.d.ts
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, CommanderError } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the process exit code for a parser-level {@link CommanderError}.
|
|
4
|
+
*
|
|
5
|
+
* Commander's own defaults are inconsistent (unknown option → 1, unknown
|
|
6
|
+
* command silently swallowed → 0, missing subcommand → 1). We collapse them
|
|
7
|
+
* onto the CLI's stable {@link ExitCode} contract: printing help or the version
|
|
8
|
+
* is an informational success (`0`), and every other parser failure — unknown
|
|
9
|
+
* option, unknown/missing command, missing or excess argument — is a usage
|
|
10
|
+
* error ({@link ExitCode.Usage}). Commander tags the informational cases with
|
|
11
|
+
* `exitCode === 0`, which is the only discriminator we need.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveCommanderExitCode(err: CommanderError): number;
|
|
2
14
|
/**
|
|
3
15
|
* Assemble the full skhema CLI program. The runtime entry (cli.ts) and the
|
|
4
16
|
* docs reference generator both build from here, so every registered command
|
package/dist/program.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/program.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAsBnD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,cAAc,GAAG,MAAM,CAEpE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAqErD"}
|
package/dist/program.js
CHANGED
|
@@ -16,8 +16,23 @@ import { registerStrategyCommands } from './commands/strategy.js';
|
|
|
16
16
|
import { registerWebhookCommands } from './commands/webhook.js';
|
|
17
17
|
import { registerWorkspaceCommands } from './commands/workspace.js';
|
|
18
18
|
import { setApiKeyFlag } from './lib/api/credential-context.js';
|
|
19
|
+
import { ExitCode } from './lib/api/run-command.js';
|
|
19
20
|
import { showBanner } from './lib/banner.js';
|
|
20
21
|
import { setGlobalOptions } from './lib/output.js';
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the process exit code for a parser-level {@link CommanderError}.
|
|
24
|
+
*
|
|
25
|
+
* Commander's own defaults are inconsistent (unknown option → 1, unknown
|
|
26
|
+
* command silently swallowed → 0, missing subcommand → 1). We collapse them
|
|
27
|
+
* onto the CLI's stable {@link ExitCode} contract: printing help or the version
|
|
28
|
+
* is an informational success (`0`), and every other parser failure — unknown
|
|
29
|
+
* option, unknown/missing command, missing or excess argument — is a usage
|
|
30
|
+
* error ({@link ExitCode.Usage}). Commander tags the informational cases with
|
|
31
|
+
* `exitCode === 0`, which is the only discriminator we need.
|
|
32
|
+
*/
|
|
33
|
+
export function resolveCommanderExitCode(err) {
|
|
34
|
+
return err.exitCode === 0 ? ExitCode.Ok : ExitCode.Usage;
|
|
35
|
+
}
|
|
21
36
|
/**
|
|
22
37
|
* Assemble the full skhema CLI program. The runtime entry (cli.ts) and the
|
|
23
38
|
* docs reference generator both build from here, so every registered command
|
|
@@ -25,6 +40,15 @@ import { setGlobalOptions } from './lib/output.js';
|
|
|
25
40
|
*/
|
|
26
41
|
export function buildProgram(version) {
|
|
27
42
|
const program = new Command();
|
|
43
|
+
// Throw a CommanderError instead of calling process.exit directly, so the
|
|
44
|
+
// entry point can map every parser-level failure onto the ExitCode taxonomy.
|
|
45
|
+
// Set before registering subcommands: commander copies the exit callback into
|
|
46
|
+
// each child at `.command()` time, so this one call covers the whole tree.
|
|
47
|
+
program.exitOverride();
|
|
48
|
+
// The default `.action()` below (banner on bare invocation) otherwise
|
|
49
|
+
// suppresses commander's implicit `help` command, so `skhema help [command]`
|
|
50
|
+
// would fall through to the unknown-command guard. Re-enable it explicitly.
|
|
51
|
+
program.helpCommand(true);
|
|
28
52
|
program
|
|
29
53
|
.name('skhema')
|
|
30
54
|
.description('Skhema CLI — drive the Skhema Public API from your terminal, CI, or agent')
|
|
@@ -43,7 +67,16 @@ export function buildProgram(version) {
|
|
|
43
67
|
setApiKeyFlag(opts.apiKey);
|
|
44
68
|
})
|
|
45
69
|
.action(() => {
|
|
46
|
-
//
|
|
70
|
+
// A bare `skhema` invocation shows the banner. Any leftover operand means
|
|
71
|
+
// an unknown command was given: commander routes it here (the default
|
|
72
|
+
// action) instead of erroring, so we must reject it ourselves — otherwise
|
|
73
|
+
// the banner prints and the process exits 0, hiding the mistake.
|
|
74
|
+
if (program.args.length > 0) {
|
|
75
|
+
program.error(`unknown command '${program.args[0]}'`, {
|
|
76
|
+
code: 'commander.unknownCommand',
|
|
77
|
+
exitCode: ExitCode.Usage,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
47
80
|
showBanner(version);
|
|
48
81
|
program.help();
|
|
49
82
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skhema/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Skhema CLI - Authentication and AI skills management for agent platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@skhema/agent-sdk": "0.1.4",
|
|
45
|
-
"@skhema/method": "0.
|
|
45
|
+
"@skhema/method": "0.4.0",
|
|
46
46
|
"@skhema/sdk": "0.2.4",
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"commander": "^12.0.0",
|