@crouton-kit/crouter 0.1.1 → 0.1.2

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.
Files changed (46) hide show
  1. package/bin/crouter +2 -0
  2. package/bin/crtr +2 -0
  3. package/dist/cli.js +34 -4
  4. package/dist/commands/config.d.ts +2 -0
  5. package/dist/commands/config.js +126 -0
  6. package/dist/commands/doctor.d.ts +2 -0
  7. package/dist/commands/doctor.js +216 -0
  8. package/dist/commands/marketplace.d.ts +2 -0
  9. package/dist/commands/marketplace.js +365 -0
  10. package/dist/commands/plan.d.ts +2 -0
  11. package/dist/commands/plan.js +9 -0
  12. package/dist/commands/plugin.d.ts +2 -0
  13. package/dist/commands/plugin.js +364 -0
  14. package/dist/commands/skill.d.ts +2 -0
  15. package/dist/commands/skill.js +404 -0
  16. package/dist/commands/spec.d.ts +2 -0
  17. package/dist/commands/spec.js +9 -0
  18. package/dist/commands/update.d.ts +2 -0
  19. package/dist/commands/update.js +140 -0
  20. package/dist/core/artifact.d.ts +14 -0
  21. package/dist/core/artifact.js +187 -0
  22. package/dist/core/config.d.ts +10 -0
  23. package/dist/core/config.js +83 -0
  24. package/dist/core/errors.d.ts +12 -0
  25. package/dist/core/errors.js +28 -0
  26. package/dist/core/frontmatter.d.ts +8 -0
  27. package/dist/core/frontmatter.js +84 -0
  28. package/dist/core/fs-utils.d.ts +18 -0
  29. package/dist/core/fs-utils.js +115 -0
  30. package/dist/core/git.d.ts +18 -0
  31. package/dist/core/git.js +71 -0
  32. package/dist/core/manifest.d.ts +5 -0
  33. package/dist/core/manifest.js +15 -0
  34. package/dist/core/output.d.ts +35 -0
  35. package/dist/core/output.js +99 -0
  36. package/dist/core/resolver.d.ts +28 -0
  37. package/dist/core/resolver.js +228 -0
  38. package/dist/core/scope.d.ts +12 -0
  39. package/dist/core/scope.js +87 -0
  40. package/dist/prompts/plan.d.ts +1 -0
  41. package/dist/prompts/plan.js +99 -0
  42. package/dist/prompts/spec.d.ts +1 -0
  43. package/dist/prompts/spec.js +106 -0
  44. package/dist/types.d.ts +114 -0
  45. package/dist/types.js +33 -0
  46. package/package.json +8 -5
@@ -0,0 +1,404 @@
1
+ import { join } from 'node:path';
2
+ import { writeFileSync } from 'node:fs';
3
+ import { SKILL_ENTRY_FILE, SKILLS_DIR, } from '../types.js';
4
+ import { skillConfigKey } from '../types.js';
5
+ import { notFound, usage, general } from '../core/errors.js';
6
+ import { out, hint, info, jsonOut, handleError, } from '../core/output.js';
7
+ import { listScopes, requireScopeRoot, resolveScopeArg, projectScopeRoot, } from '../core/scope.js';
8
+ import { resolveSkill, listAllSkills, listInstalledPlugins, findPluginByName, parseSkillQualifier, } from '../core/resolver.js';
9
+ import { updateConfig, ensureScopeInitialized } from '../core/config.js';
10
+ import { parseFrontmatter, serializeFrontmatter } from '../core/frontmatter.js';
11
+ import { ensureDir, pathExists, readText, walkFiles } from '../core/fs-utils.js';
12
+ const KNOWN_VERBS = new Set([
13
+ 'list',
14
+ 'show',
15
+ 'path',
16
+ 'grep',
17
+ 'new',
18
+ 'where',
19
+ 'enable',
20
+ 'disable',
21
+ 'search',
22
+ ]);
23
+ const AUTHORING_GUIDE_SKILL = 'authoring-skills';
24
+ function buildShowFooter(skillPath) {
25
+ return (`crtr: edit this skill directly at ${skillPath} — ` +
26
+ `for SKILL.md authoring guidance run \`crtr skill ${AUTHORING_GUIDE_SKILL}\``);
27
+ }
28
+ function wrapSkill(name, path, content) {
29
+ return `<skill name="${name}" path="${path}">\n${content.endsWith('\n') ? content : content + '\n'}</skill>`;
30
+ }
31
+ export function registerSkillCommands(program) {
32
+ const skill = program
33
+ .command('skill [nameOrVerb] [rest...]')
34
+ .description('manage and inspect skills')
35
+ .option('--frontmatter', 'include YAML frontmatter in the printed body')
36
+ .action(async (nameOrVerb, _rest, opts) => {
37
+ if (nameOrVerb === undefined) {
38
+ skill.help();
39
+ return;
40
+ }
41
+ if (!KNOWN_VERBS.has(nameOrVerb)) {
42
+ try {
43
+ const skillObj = resolveSkill(nameOrVerb);
44
+ const content = readText(skillObj.path);
45
+ const body = opts.frontmatter ? content : parseFrontmatter(content).body;
46
+ out(wrapSkill(skillObj.name, skillObj.path, body));
47
+ hint(buildShowFooter(skillObj.path));
48
+ }
49
+ catch (e) {
50
+ handleError(e);
51
+ }
52
+ }
53
+ });
54
+ // list
55
+ skill
56
+ .command('list')
57
+ .description('list installed skills (disabled hidden unless -a)')
58
+ .option('--scope <scope>', 'user|project|all (default: all)')
59
+ .option('--plugin <name>', 'filter by plugin name')
60
+ .option('-a, --all', 'include disabled skills')
61
+ .option('--json', 'emit JSON')
62
+ .action(async (opts) => {
63
+ try {
64
+ const scopes = listScopes(opts.scope);
65
+ const skills = scopes
66
+ .flatMap((s) => listAllSkills(s))
67
+ .filter((sk) => {
68
+ if (opts.plugin !== undefined && sk.plugin !== opts.plugin)
69
+ return false;
70
+ if (!opts.all && !sk.enabled)
71
+ return false;
72
+ return true;
73
+ });
74
+ if (opts.json) {
75
+ jsonOut({
76
+ skills: skills.map((sk) => ({
77
+ name: sk.name,
78
+ plugin: sk.plugin,
79
+ scope: sk.scope,
80
+ path: sk.path,
81
+ description: sk.frontmatter.description,
82
+ enabled: sk.enabled,
83
+ disabled_in: sk.disabledIn,
84
+ })),
85
+ });
86
+ return;
87
+ }
88
+ for (const sk of skills) {
89
+ const desc = sk.frontmatter.description !== undefined ? sk.frontmatter.description : '';
90
+ const marker = sk.enabled ? '' : ` [disabled${sk.disabledIn ? `@${sk.disabledIn}` : ''}]`;
91
+ const line = `${sk.scope}:${sk.plugin}/${sk.name}${marker}${desc ? ` — ${desc}` : ''}`;
92
+ out(line);
93
+ }
94
+ }
95
+ catch (e) {
96
+ handleError(e, { json: opts.json });
97
+ }
98
+ });
99
+ // show
100
+ skill
101
+ .command('show <name>')
102
+ .description('print SKILL.md body to stdout (default verb)')
103
+ .option('--scope <scope>', 'user|project')
104
+ .option('--plugin <name>', 'filter by plugin name')
105
+ .option('--frontmatter', 'include YAML frontmatter in the printed body')
106
+ .option('--json', 'emit JSON')
107
+ .action(async (name, opts) => {
108
+ try {
109
+ const scopeArg = resolveScopeArg(opts.scope);
110
+ const resolveOpts = scopeArg !== 'all' ? { scope: scopeArg } : {};
111
+ if (opts.plugin !== undefined) {
112
+ Object.assign(resolveOpts, { pluginFilter: opts.plugin });
113
+ }
114
+ const skillObj = resolveSkill(name, resolveOpts);
115
+ const content = readText(skillObj.path);
116
+ const body = opts.frontmatter ? content : parseFrontmatter(content).body;
117
+ if (opts.json) {
118
+ jsonOut({
119
+ name: skillObj.name,
120
+ plugin: skillObj.plugin,
121
+ scope: skillObj.scope,
122
+ path: skillObj.path,
123
+ content,
124
+ authoring_guide_command: `crtr skill ${AUTHORING_GUIDE_SKILL}`,
125
+ });
126
+ return;
127
+ }
128
+ out(wrapSkill(skillObj.name, skillObj.path, body));
129
+ hint(buildShowFooter(skillObj.path));
130
+ }
131
+ catch (e) {
132
+ handleError(e, { json: opts.json });
133
+ }
134
+ });
135
+ // path
136
+ skill
137
+ .command('path <name>')
138
+ .description('print absolute path to SKILL.md')
139
+ .option('--scope <scope>', 'user|project')
140
+ .option('--plugin <name>', 'filter by plugin name')
141
+ .action(async (name, opts) => {
142
+ try {
143
+ const scopeArg = resolveScopeArg(opts.scope);
144
+ const resolveOpts = scopeArg !== 'all' ? { scope: scopeArg } : {};
145
+ if (opts.plugin !== undefined) {
146
+ Object.assign(resolveOpts, { pluginFilter: opts.plugin });
147
+ }
148
+ const skillObj = resolveSkill(name, resolveOpts);
149
+ out(skillObj.path);
150
+ }
151
+ catch (e) {
152
+ handleError(e);
153
+ }
154
+ });
155
+ // grep
156
+ skill
157
+ .command('grep <pattern>')
158
+ .description('search skill file contents for a regex pattern')
159
+ .option('--scope <scope>', 'user|project|all')
160
+ .option('--plugin <name>', 'filter by plugin name')
161
+ .option('--json', 'emit JSON')
162
+ .action(async (pattern, opts) => {
163
+ try {
164
+ let regex;
165
+ try {
166
+ regex = new RegExp(pattern);
167
+ }
168
+ catch {
169
+ throw usage(`invalid regex pattern: ${pattern}`);
170
+ }
171
+ const scopes = listScopes(opts.scope);
172
+ const pluginSkillsDirs = [];
173
+ for (const s of scopes) {
174
+ for (const plugin of listInstalledPlugins(s)) {
175
+ if (!plugin.enabled)
176
+ continue;
177
+ if (opts.plugin !== undefined && plugin.name !== opts.plugin)
178
+ continue;
179
+ pluginSkillsDirs.push(join(plugin.root, SKILLS_DIR));
180
+ }
181
+ }
182
+ const matchLines = [];
183
+ for (const skillsDir of pluginSkillsDirs) {
184
+ const files = walkFiles(skillsDir);
185
+ for (const file of files) {
186
+ const content = readText(file);
187
+ const lines = content.split('\n');
188
+ lines.forEach((lineText, idx) => {
189
+ if (regex.test(lineText)) {
190
+ matchLines.push({ path: file, line: idx + 1, text: lineText });
191
+ }
192
+ });
193
+ }
194
+ }
195
+ if (opts.json) {
196
+ jsonOut({ matches: matchLines });
197
+ return;
198
+ }
199
+ for (const m of matchLines) {
200
+ out(`${m.path}:${m.line}: ${m.text}`);
201
+ }
202
+ }
203
+ catch (e) {
204
+ handleError(e, { json: opts.json });
205
+ }
206
+ });
207
+ // new
208
+ skill
209
+ .command('new <qualifier>')
210
+ .description('scaffold a new skill as <plugin>:<name>')
211
+ .option('--scope <scope>', 'user|project (default: project then user)')
212
+ .option('--description <text>', 'skill description for frontmatter')
213
+ .action(async (qualifier, opts) => {
214
+ try {
215
+ if (!qualifier.includes(':')) {
216
+ throw usage('qualifier must be in the form <plugin>:<name> (e.g. authoring:my-skill)');
217
+ }
218
+ const { plugin: pluginName, name: skillName } = parseSkillQualifier(qualifier);
219
+ if (!pluginName) {
220
+ throw usage('qualifier must be in the form <plugin>:<name>');
221
+ }
222
+ const scopeArg = opts.scope !== undefined ? resolveScopeArg(opts.scope) : undefined;
223
+ let plugin;
224
+ if (scopeArg !== undefined && scopeArg !== 'all') {
225
+ plugin = findPluginByName(pluginName, scopeArg);
226
+ }
227
+ else {
228
+ plugin = findPluginByName(pluginName);
229
+ }
230
+ if (!plugin) {
231
+ throw notFound(`plugin not found: ${pluginName}`);
232
+ }
233
+ const skillDir = join(plugin.root, SKILLS_DIR, ...skillName.split('/'));
234
+ const skillFile = join(skillDir, SKILL_ENTRY_FILE);
235
+ if (pathExists(skillFile)) {
236
+ throw general(`skill already exists: ${skillFile}`);
237
+ }
238
+ ensureDir(skillDir);
239
+ const fm = serializeFrontmatter({
240
+ name: skillName,
241
+ description: opts.description,
242
+ });
243
+ writeFileSync(skillFile, fm, 'utf8');
244
+ out(skillFile);
245
+ hint(`crtr: scaffolded ${skillFile} — edit directly, then ` +
246
+ `\`crtr skill ${AUTHORING_GUIDE_SKILL}\` for SKILL.md authoring guidance`);
247
+ }
248
+ catch (e) {
249
+ handleError(e);
250
+ }
251
+ });
252
+ // where
253
+ skill
254
+ .command('where <name>')
255
+ .description('show resolution info as JSON')
256
+ .option('--scope <scope>', 'user|project')
257
+ .option('--plugin <name>', 'filter by plugin name')
258
+ .action(async (name, opts) => {
259
+ try {
260
+ const scopeArg = resolveScopeArg(opts.scope);
261
+ const resolveOpts = scopeArg !== 'all' ? { scope: scopeArg } : {};
262
+ if (opts.plugin !== undefined) {
263
+ Object.assign(resolveOpts, { pluginFilter: opts.plugin });
264
+ }
265
+ const skillObj = resolveSkill(name, resolveOpts);
266
+ jsonOut({
267
+ name: skillObj.name,
268
+ plugin: skillObj.plugin,
269
+ scope: skillObj.scope,
270
+ path: skillObj.path,
271
+ });
272
+ }
273
+ catch (e) {
274
+ handleError(e);
275
+ }
276
+ });
277
+ // enable
278
+ skill
279
+ .command('enable <name>')
280
+ .description('enable a skill (clears any disable in the chosen scope)')
281
+ .option('--scope <scope>', 'user|project (default: project if available, else user)')
282
+ .action(async (name, opts) => {
283
+ try {
284
+ await toggleSkill(name, true, opts.scope);
285
+ }
286
+ catch (e) {
287
+ handleError(e);
288
+ }
289
+ });
290
+ // disable
291
+ skill
292
+ .command('disable <name>')
293
+ .description('disable a skill (hides from list and agent discovery)')
294
+ .option('--scope <scope>', 'user|project (default: project if available, else user)')
295
+ .action(async (name, opts) => {
296
+ try {
297
+ await toggleSkill(name, false, opts.scope);
298
+ }
299
+ catch (e) {
300
+ handleError(e);
301
+ }
302
+ });
303
+ // search
304
+ skill
305
+ .command('search <query>')
306
+ .description('search skills by name, description, and keywords')
307
+ .option('--scope <scope>', 'user|project|all (default: all)')
308
+ .option('--plugin <name>', 'filter by plugin name')
309
+ .option('-a, --all', 'include disabled skills')
310
+ .option('--body', 'also search SKILL.md body')
311
+ .option('--json', 'emit JSON')
312
+ .action(async (query, opts) => {
313
+ try {
314
+ const needle = query.toLowerCase();
315
+ const scopes = listScopes(opts.scope);
316
+ const candidates = scopes
317
+ .flatMap((s) => listAllSkills(s))
318
+ .filter((sk) => {
319
+ if (opts.plugin !== undefined && sk.plugin !== opts.plugin)
320
+ return false;
321
+ if (!opts.all && !sk.enabled)
322
+ return false;
323
+ return true;
324
+ });
325
+ const hits = [];
326
+ for (const sk of candidates) {
327
+ const matched = [];
328
+ let score = 0;
329
+ if (sk.name.toLowerCase().includes(needle)) {
330
+ score += 10;
331
+ matched.push('name');
332
+ }
333
+ const desc = sk.frontmatter.description;
334
+ if (desc !== undefined && desc.toLowerCase().includes(needle)) {
335
+ score += 4;
336
+ matched.push('description');
337
+ }
338
+ const kws = sk.frontmatter.keywords;
339
+ if (kws && kws.some((k) => k.toLowerCase().includes(needle))) {
340
+ score += 6;
341
+ matched.push('keywords');
342
+ }
343
+ if (opts.body) {
344
+ const text = readText(sk.path).toLowerCase();
345
+ if (text.includes(needle)) {
346
+ score += 1;
347
+ matched.push('body');
348
+ }
349
+ }
350
+ if (score > 0)
351
+ hits.push({ skill: sk, score, matched });
352
+ }
353
+ hits.sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
354
+ if (opts.json) {
355
+ jsonOut({
356
+ query,
357
+ hits: hits.map((h) => ({
358
+ name: h.skill.name,
359
+ plugin: h.skill.plugin,
360
+ scope: h.skill.scope,
361
+ path: h.skill.path,
362
+ description: h.skill.frontmatter.description,
363
+ keywords: h.skill.frontmatter.keywords,
364
+ enabled: h.skill.enabled,
365
+ score: h.score,
366
+ matched: h.matched,
367
+ })),
368
+ });
369
+ return;
370
+ }
371
+ for (const h of hits) {
372
+ const desc = h.skill.frontmatter.description !== undefined
373
+ ? h.skill.frontmatter.description
374
+ : '';
375
+ const marker = h.skill.enabled ? '' : ' [disabled]';
376
+ const line = `${h.skill.scope}:${h.skill.plugin}/${h.skill.name}${marker}\t${h.matched.join(',')}\t${desc}`;
377
+ out(line);
378
+ }
379
+ }
380
+ catch (e) {
381
+ handleError(e, { json: opts.json });
382
+ }
383
+ });
384
+ }
385
+ async function toggleSkill(name, enabled, scopeArgRaw) {
386
+ let scope;
387
+ if (scopeArgRaw !== undefined) {
388
+ const resolved = resolveScopeArg(scopeArgRaw);
389
+ if (resolved === 'all')
390
+ throw usage('--scope must be user or project for enable/disable');
391
+ scope = resolved;
392
+ }
393
+ else {
394
+ scope = projectScopeRoot() !== null ? 'project' : 'user';
395
+ }
396
+ const skillObj = resolveSkill(name);
397
+ const key = skillConfigKey(skillObj.plugin, skillObj.name);
398
+ const scopeRootPath = requireScopeRoot(scope);
399
+ ensureScopeInitialized(scope, scopeRootPath);
400
+ updateConfig(scope, (cfg) => {
401
+ cfg.skills[key] = { enabled };
402
+ });
403
+ info(`${enabled ? 'enabled' : 'disabled'} ${skillObj.plugin}:${skillObj.name} in ${scope} scope`);
404
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerSpecCommand(program: Command): void;
@@ -0,0 +1,9 @@
1
+ import { registerArtifactCommand } from '../core/artifact.js';
2
+ import { specPrompt } from '../prompts/spec.js';
3
+ export function registerSpecCommand(program) {
4
+ registerArtifactCommand(program, {
5
+ command: 'spec',
6
+ kind: 'specs',
7
+ promptFn: specPrompt,
8
+ });
9
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerUpdateCommand(program: Command): void;
@@ -0,0 +1,140 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { join, dirname } from 'node:path';
4
+ import { readFileSync } from 'node:fs';
5
+ import { err, warn, handleError } from '../core/output.js';
6
+ import { projectScopeRoot } from '../core/scope.js';
7
+ import { updateState } from '../core/config.js';
8
+ import { listAllPlugins, listAllMarketplaces } from '../core/resolver.js';
9
+ import { pull, fetch, currentSha, remoteSha } from '../core/git.js';
10
+ import { nowIso } from '../core/fs-utils.js';
11
+ import { network, general } from '../core/errors.js';
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const PKG_ROOT = join(__dirname, '..', '..', '..');
15
+ const PACKAGE_JSON_PATH = join(PKG_ROOT, 'package.json');
16
+ function currentVersion() {
17
+ const raw = readFileSync(PACKAGE_JSON_PATH, 'utf8');
18
+ const parsed = JSON.parse(raw);
19
+ return parsed.version;
20
+ }
21
+ function selfUpdate() {
22
+ const res = spawnSync('npm', ['i', '-g', '@crouton-kit/crtr@latest'], { stdio: 'inherit' });
23
+ if (res.status !== 0) {
24
+ throw general('npm install failed');
25
+ }
26
+ }
27
+ function selfCheck() {
28
+ const res = spawnSync('npm', ['view', '@crouton-kit/crtr', 'version'], { encoding: 'utf8' });
29
+ if (res.status !== 0) {
30
+ warn('could not check for crtr updates (network unavailable)');
31
+ return;
32
+ }
33
+ const latest = res.stdout.trim();
34
+ const current = currentVersion();
35
+ if (latest !== current) {
36
+ err(`crtr: v${latest} available (current ${current}) — run \`crtr update --self\``);
37
+ }
38
+ }
39
+ function contentUpdate() {
40
+ const marketplaces = listAllMarketplaces();
41
+ for (const mkt of marketplaces) {
42
+ const res = pull(mkt.root);
43
+ if (res.status !== 0) {
44
+ throw network(`git pull failed for marketplace ${mkt.name}: ${res.stderr.trim()}`);
45
+ }
46
+ updateState(mkt.scope, (s) => {
47
+ if (!s.marketplaces[mkt.name])
48
+ s.marketplaces[mkt.name] = {};
49
+ s.marketplaces[mkt.name].last_updated = nowIso();
50
+ });
51
+ }
52
+ const plugins = listAllPlugins();
53
+ for (const plugin of plugins) {
54
+ if (!plugin.enabled)
55
+ continue;
56
+ if (plugin.sourceMarketplace)
57
+ continue;
58
+ const res = pull(plugin.root);
59
+ if (res.status !== 0) {
60
+ throw network(`git pull failed for plugin ${plugin.name}: ${res.stderr.trim()}`);
61
+ }
62
+ updateState(plugin.scope, (s) => {
63
+ if (!s.plugins[plugin.name])
64
+ s.plugins[plugin.name] = {};
65
+ s.plugins[plugin.name].last_updated = nowIso();
66
+ });
67
+ }
68
+ }
69
+ function contentCheck() {
70
+ const marketplaces = listAllMarketplaces();
71
+ for (const mkt of marketplaces) {
72
+ const fetchRes = fetch(mkt.root, mkt.ref);
73
+ if (fetchRes.status !== 0) {
74
+ warn(`could not fetch ${mkt.name} (network unavailable)`);
75
+ continue;
76
+ }
77
+ const head = currentSha(mkt.root);
78
+ const remote = remoteSha(mkt.root, mkt.ref);
79
+ if (head !== null && remote !== null && head !== remote) {
80
+ err(`crtr: marketplace ${mkt.name} has updates available — run \`crtr update --content\``);
81
+ }
82
+ }
83
+ const plugins = listAllPlugins();
84
+ for (const plugin of plugins) {
85
+ if (!plugin.enabled)
86
+ continue;
87
+ if (plugin.sourceMarketplace)
88
+ continue;
89
+ const ref = 'main';
90
+ const fetchRes = fetch(plugin.root, ref);
91
+ if (fetchRes.status !== 0) {
92
+ warn(`could not fetch ${plugin.name} (network unavailable)`);
93
+ continue;
94
+ }
95
+ const head = currentSha(plugin.root);
96
+ const remote = remoteSha(plugin.root, ref);
97
+ if (head !== null && remote !== null && head !== remote) {
98
+ err(`crtr: plugin ${plugin.name} has updates available — run \`crtr plugin update ${plugin.name}\``);
99
+ }
100
+ }
101
+ }
102
+ export function registerUpdateCommand(program) {
103
+ program
104
+ .command('update')
105
+ .description('update crtr itself and/or installed plugins and marketplaces')
106
+ .option('--self', 'update crtr binary via npm')
107
+ .option('--content', 'pull updates for all installed plugins and marketplaces')
108
+ .option('--check', 'check for updates without applying them')
109
+ .action(async (opts) => {
110
+ try {
111
+ const runSelf = opts.self === true;
112
+ const runContent = opts.content === true;
113
+ const runBoth = !runSelf && !runContent;
114
+ if (opts.check) {
115
+ if (runSelf || runBoth)
116
+ selfCheck();
117
+ if (runContent || runBoth)
118
+ contentCheck();
119
+ return;
120
+ }
121
+ if (runSelf || runBoth) {
122
+ selfUpdate();
123
+ const scopes = ['user'];
124
+ if (projectScopeRoot())
125
+ scopes.unshift('project');
126
+ for (const scope of scopes) {
127
+ updateState(scope, (s) => {
128
+ s.last_self_check = nowIso();
129
+ });
130
+ }
131
+ }
132
+ if (runContent || runBoth) {
133
+ contentUpdate();
134
+ }
135
+ }
136
+ catch (e) {
137
+ handleError(e);
138
+ }
139
+ });
140
+ }
@@ -0,0 +1,14 @@
1
+ import { Command } from 'commander';
2
+ export type ArtifactKind = 'plans' | 'specs';
3
+ export declare function mangleCwd(cwd?: string): string;
4
+ export declare function artifactsRoot(kind: ArtifactKind, cwd?: string): string;
5
+ export declare function sanitizeName(raw: string): string;
6
+ export declare function artifactPath(kind: ArtifactKind, name: string, cwd?: string): string;
7
+ export declare function inTmux(): boolean;
8
+ export declare function openInTmuxPane(path: string): void;
9
+ export interface RegisterArtifactOptions {
10
+ command: 'plan' | 'spec';
11
+ kind: ArtifactKind;
12
+ promptFn: (artifactsDir: string) => string;
13
+ }
14
+ export declare function registerArtifactCommand(program: Command, opts: RegisterArtifactOptions): void;