@crouton-kit/crouter 0.1.2 → 0.1.4
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 +14 -1
- package/dist/cli.js +4 -0
- package/dist/commands/config.js +8 -0
- package/dist/commands/skill.js +2 -1
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.js +2 -2
- package/dist/core/auto-update.d.ts +1 -0
- package/dist/core/auto-update.js +86 -0
- package/dist/core/bootstrap.d.ts +4 -0
- package/dist/core/bootstrap.js +70 -0
- package/dist/core/config.js +17 -3
- package/dist/core/frontmatter.js +91 -19
- package/dist/prompts/plan.js +7 -0
- package/dist/prompts/skill.d.ts +1 -0
- package/dist/prompts/skill.js +49 -0
- package/dist/prompts/spec.js +7 -0
- package/dist/types.d.ts +4 -2
- package/dist/types.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,5 +9,18 @@ npm install -g @crouton-kit/crouter
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
crtr --help
|
|
13
13
|
```
|
|
14
|
+
|
|
15
|
+
## Official marketplace
|
|
16
|
+
|
|
17
|
+
`crtr` ships with the [crouter official marketplace](https://github.com/crouton-labs/crouter-official-marketplace) pre-installed. On first run it is cloned into your user scope and registered automatically — no plugins are enabled by default.
|
|
18
|
+
|
|
19
|
+
Browse and install plugins from it:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
crtr marketplace browse crouter-official-marketplace
|
|
23
|
+
crtr marketplace install crouter-official-marketplace:<plugin>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
To opt out of the bootstrap (e.g. in CI), set `CRTR_NO_BOOTSTRAP=1`.
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,8 @@ import { registerUpdateCommand } from './commands/update.js';
|
|
|
11
11
|
import { registerDoctorCommand } from './commands/doctor.js';
|
|
12
12
|
import { registerPlanCommand } from './commands/plan.js';
|
|
13
13
|
import { registerSpecCommand } from './commands/spec.js';
|
|
14
|
+
import { maybeAutoUpdate } from './core/auto-update.js';
|
|
15
|
+
import { ensureOfficialMarketplace } from './core/bootstrap.js';
|
|
14
16
|
function readPackageVersion() {
|
|
15
17
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
18
|
const pkgPath = join(here, '..', 'package.json');
|
|
@@ -32,6 +34,8 @@ registerUpdateCommand(program);
|
|
|
32
34
|
registerDoctorCommand(program);
|
|
33
35
|
registerPlanCommand(program);
|
|
34
36
|
registerSpecCommand(program);
|
|
37
|
+
ensureOfficialMarketplace(process.argv);
|
|
38
|
+
maybeAutoUpdate(process.argv);
|
|
35
39
|
program.parseAsync().catch((err) => {
|
|
36
40
|
process.stderr.write(`crtr: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
37
41
|
process.exit(1);
|
package/dist/commands/config.js
CHANGED
|
@@ -35,6 +35,14 @@ function setNestedValue(cfg, key, value) {
|
|
|
35
35
|
cfg.auto_update.content = value;
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
if (key === 'auto_update.crtr') {
|
|
39
|
+
const coerced = value === true ? 'notify' : value;
|
|
40
|
+
if (coerced !== 'notify' && coerced !== 'apply' && coerced !== false) {
|
|
41
|
+
throw usage(`auto_update.crtr must be 'notify', 'apply', or false`);
|
|
42
|
+
}
|
|
43
|
+
cfg.auto_update.crtr = coerced;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
38
46
|
if (parts.length === 1) {
|
|
39
47
|
cfg[topKey] = value;
|
|
40
48
|
return;
|
package/dist/commands/skill.js
CHANGED
|
@@ -9,6 +9,7 @@ import { resolveSkill, listAllSkills, listInstalledPlugins, findPluginByName, pa
|
|
|
9
9
|
import { updateConfig, ensureScopeInitialized } from '../core/config.js';
|
|
10
10
|
import { parseFrontmatter, serializeFrontmatter } from '../core/frontmatter.js';
|
|
11
11
|
import { ensureDir, pathExists, readText, walkFiles } from '../core/fs-utils.js';
|
|
12
|
+
import { skillPrompt } from '../prompts/skill.js';
|
|
12
13
|
const KNOWN_VERBS = new Set([
|
|
13
14
|
'list',
|
|
14
15
|
'show',
|
|
@@ -35,7 +36,7 @@ export function registerSkillCommands(program) {
|
|
|
35
36
|
.option('--frontmatter', 'include YAML frontmatter in the printed body')
|
|
36
37
|
.action(async (nameOrVerb, _rest, opts) => {
|
|
37
38
|
if (nameOrVerb === undefined) {
|
|
38
|
-
|
|
39
|
+
out(skillPrompt());
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
if (!KNOWN_VERBS.has(nameOrVerb)) {
|
package/dist/commands/update.js
CHANGED
|
@@ -24,7 +24,7 @@ function selfUpdate() {
|
|
|
24
24
|
throw general('npm install failed');
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
|
-
function selfCheck() {
|
|
27
|
+
export function selfCheck() {
|
|
28
28
|
const res = spawnSync('npm', ['view', '@crouton-kit/crtr', 'version'], { encoding: 'utf8' });
|
|
29
29
|
if (res.status !== 0) {
|
|
30
30
|
warn('could not check for crtr updates (network unavailable)');
|
|
@@ -66,7 +66,7 @@ function contentUpdate() {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
function contentCheck() {
|
|
69
|
+
export function contentCheck() {
|
|
70
70
|
const marketplaces = listAllMarketplaces();
|
|
71
71
|
for (const mkt of marketplaces) {
|
|
72
72
|
const fetchRes = fetch(mkt.root, mkt.ref);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function maybeAutoUpdate(argv: string[]): void;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readConfig, readState, updateState } from './config.js';
|
|
3
|
+
import { nowIso } from './fs-utils.js';
|
|
4
|
+
import { info } from './output.js';
|
|
5
|
+
import { selfCheck, contentCheck } from '../commands/update.js';
|
|
6
|
+
const HOUR_MS = 60 * 60 * 1000;
|
|
7
|
+
const SKIP_SUBCOMMANDS = new Set([
|
|
8
|
+
'update',
|
|
9
|
+
'help',
|
|
10
|
+
'--help',
|
|
11
|
+
'-h',
|
|
12
|
+
'--version',
|
|
13
|
+
'-v',
|
|
14
|
+
]);
|
|
15
|
+
function shouldSkipForArgv(argv) {
|
|
16
|
+
const sub = argv[2];
|
|
17
|
+
if (sub === undefined)
|
|
18
|
+
return true;
|
|
19
|
+
return SKIP_SUBCOMMANDS.has(sub);
|
|
20
|
+
}
|
|
21
|
+
function withinInterval(lastIso, intervalHours) {
|
|
22
|
+
if (!lastIso)
|
|
23
|
+
return false;
|
|
24
|
+
const last = Date.parse(lastIso);
|
|
25
|
+
if (!Number.isFinite(last))
|
|
26
|
+
return false;
|
|
27
|
+
return Date.now() - last < intervalHours * HOUR_MS;
|
|
28
|
+
}
|
|
29
|
+
function spawnDetachedSelfUpdate() {
|
|
30
|
+
const child = spawn('npm', ['i', '-g', '@crouton-kit/crtr@latest'], {
|
|
31
|
+
detached: true,
|
|
32
|
+
stdio: 'ignore',
|
|
33
|
+
});
|
|
34
|
+
child.unref();
|
|
35
|
+
}
|
|
36
|
+
function spawnDetachedContentUpdate() {
|
|
37
|
+
const child = spawn('crtr', ['update', '--content'], {
|
|
38
|
+
detached: true,
|
|
39
|
+
stdio: 'ignore',
|
|
40
|
+
});
|
|
41
|
+
child.unref();
|
|
42
|
+
}
|
|
43
|
+
export function maybeAutoUpdate(argv) {
|
|
44
|
+
try {
|
|
45
|
+
if (process.env.CRTR_NO_AUTO_UPDATE === '1')
|
|
46
|
+
return;
|
|
47
|
+
if (shouldSkipForArgv(argv))
|
|
48
|
+
return;
|
|
49
|
+
const cfg = readConfig('user');
|
|
50
|
+
const { crtr, content, interval_hours } = cfg.auto_update;
|
|
51
|
+
if (crtr === false && content === false)
|
|
52
|
+
return;
|
|
53
|
+
const state = readState('user');
|
|
54
|
+
if (state.last_self_check === undefined) {
|
|
55
|
+
updateState('user', (s) => {
|
|
56
|
+
s.last_self_check = nowIso();
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (withinInterval(state.last_self_check, interval_hours))
|
|
61
|
+
return;
|
|
62
|
+
updateState('user', (s) => {
|
|
63
|
+
s.last_self_check = nowIso();
|
|
64
|
+
});
|
|
65
|
+
if (crtr === 'notify') {
|
|
66
|
+
selfCheck();
|
|
67
|
+
}
|
|
68
|
+
else if (crtr === 'apply') {
|
|
69
|
+
info('applying self-update in background');
|
|
70
|
+
spawnDetachedSelfUpdate();
|
|
71
|
+
}
|
|
72
|
+
if (content === 'notify') {
|
|
73
|
+
contentCheck();
|
|
74
|
+
}
|
|
75
|
+
else if (content === 'apply') {
|
|
76
|
+
info('applying content updates in background');
|
|
77
|
+
spawnDetachedContentUpdate();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
if (process.env.CRTR_DEBUG === '1') {
|
|
82
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
83
|
+
process.stderr.write(`crtr: auto-update hook error: ${msg}\n`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const OFFICIAL_MARKETPLACE_NAME = "crouter-official-marketplace";
|
|
2
|
+
export declare const OFFICIAL_MARKETPLACE_URL = "https://github.com/crouton-labs/crouter-official-marketplace.git";
|
|
3
|
+
export declare const OFFICIAL_MARKETPLACE_REF = "main";
|
|
4
|
+
export declare function ensureOfficialMarketplace(argv: string[]): void;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { userScopeRoot } from './scope.js';
|
|
3
|
+
import { ensureDir, pathExists, removePath, nowIso } from './fs-utils.js';
|
|
4
|
+
import { readConfig, readState, updateConfig, updateState, ensureScopeInitialized } from './config.js';
|
|
5
|
+
import { clone } from './git.js';
|
|
6
|
+
import { readMarketplaceManifest } from './manifest.js';
|
|
7
|
+
export const OFFICIAL_MARKETPLACE_NAME = 'crouter-official-marketplace';
|
|
8
|
+
export const OFFICIAL_MARKETPLACE_URL = 'https://github.com/crouton-labs/crouter-official-marketplace.git';
|
|
9
|
+
export const OFFICIAL_MARKETPLACE_REF = 'main';
|
|
10
|
+
const SKIP_SUBCOMMANDS = new Set([
|
|
11
|
+
'help',
|
|
12
|
+
'--help',
|
|
13
|
+
'-h',
|
|
14
|
+
'--version',
|
|
15
|
+
'-v',
|
|
16
|
+
]);
|
|
17
|
+
function shouldSkipForArgv(argv) {
|
|
18
|
+
const sub = argv[2];
|
|
19
|
+
if (sub === undefined)
|
|
20
|
+
return true;
|
|
21
|
+
return SKIP_SUBCOMMANDS.has(sub);
|
|
22
|
+
}
|
|
23
|
+
export function ensureOfficialMarketplace(argv) {
|
|
24
|
+
try {
|
|
25
|
+
if (process.env.CRTR_NO_BOOTSTRAP === '1')
|
|
26
|
+
return;
|
|
27
|
+
if (shouldSkipForArgv(argv))
|
|
28
|
+
return;
|
|
29
|
+
const state = readState('user');
|
|
30
|
+
if (state.bootstrap_done === true)
|
|
31
|
+
return;
|
|
32
|
+
const cfg = readConfig('user');
|
|
33
|
+
if (cfg.marketplaces[OFFICIAL_MARKETPLACE_NAME] !== undefined) {
|
|
34
|
+
updateState('user', (s) => {
|
|
35
|
+
s.bootstrap_done = true;
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const root = userScopeRoot();
|
|
40
|
+
ensureScopeInitialized('user', root);
|
|
41
|
+
const mktsDir = join(root, 'marketplaces');
|
|
42
|
+
ensureDir(mktsDir);
|
|
43
|
+
const dest = join(mktsDir, OFFICIAL_MARKETPLACE_NAME);
|
|
44
|
+
if (pathExists(dest)) {
|
|
45
|
+
removePath(dest);
|
|
46
|
+
}
|
|
47
|
+
clone(OFFICIAL_MARKETPLACE_URL, dest, { depth: 1, ref: OFFICIAL_MARKETPLACE_REF });
|
|
48
|
+
const manifest = readMarketplaceManifest(dest);
|
|
49
|
+
if (manifest === null) {
|
|
50
|
+
removePath(dest);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
updateConfig('user', (c) => {
|
|
54
|
+
c.marketplaces[OFFICIAL_MARKETPLACE_NAME] = {
|
|
55
|
+
url: OFFICIAL_MARKETPLACE_URL,
|
|
56
|
+
ref: OFFICIAL_MARKETPLACE_REF,
|
|
57
|
+
installed_at: nowIso(),
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
updateState('user', (s) => {
|
|
61
|
+
s.bootstrap_done = true;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
if (process.env.CRTR_DEBUG === '1') {
|
|
66
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
67
|
+
process.stderr.write(`crtr: bootstrap error: ${msg}\n`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
package/dist/core/config.js
CHANGED
|
@@ -36,6 +36,7 @@ export function readState(scope) {
|
|
|
36
36
|
marketplaces: existing.marketplaces ?? {},
|
|
37
37
|
plugins: existing.plugins ?? {},
|
|
38
38
|
last_self_check: existing.last_self_check,
|
|
39
|
+
bootstrap_done: existing.bootstrap_done,
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
export function writeConfig(scope, config) {
|
|
@@ -55,6 +56,15 @@ export function ensureScopeInitialized(scope, root) {
|
|
|
55
56
|
writeJson(cfgPath, defaultScopeConfig());
|
|
56
57
|
}
|
|
57
58
|
}
|
|
59
|
+
function normalizeMode(value, fallback) {
|
|
60
|
+
if (value === true)
|
|
61
|
+
return 'notify';
|
|
62
|
+
if (value === false)
|
|
63
|
+
return false;
|
|
64
|
+
if (value === 'notify' || value === 'apply')
|
|
65
|
+
return value;
|
|
66
|
+
return fallback;
|
|
67
|
+
}
|
|
58
68
|
function mergeConfig(partial) {
|
|
59
69
|
const defaults = defaultScopeConfig();
|
|
60
70
|
const schema_version = partial.schema_version === undefined ? defaults.schema_version : partial.schema_version;
|
|
@@ -62,10 +72,14 @@ function mergeConfig(partial) {
|
|
|
62
72
|
const plugins = partial.plugins === undefined ? {} : partial.plugins;
|
|
63
73
|
const skills = partial.skills === undefined ? {} : partial.skills;
|
|
64
74
|
const au = partial.auto_update;
|
|
75
|
+
const rawInterval = au && typeof au.interval_hours === 'number' ? au.interval_hours : undefined;
|
|
76
|
+
const interval_hours = rawInterval !== undefined && Number.isFinite(rawInterval) && rawInterval >= 0
|
|
77
|
+
? rawInterval
|
|
78
|
+
: defaults.auto_update.interval_hours;
|
|
65
79
|
const auto_update = {
|
|
66
|
-
crtr: au
|
|
67
|
-
content: au
|
|
68
|
-
interval_hours
|
|
80
|
+
crtr: normalizeMode(au?.crtr, defaults.auto_update.crtr),
|
|
81
|
+
content: normalizeMode(au?.content, defaults.auto_update.content),
|
|
82
|
+
interval_hours,
|
|
69
83
|
};
|
|
70
84
|
return { schema_version, marketplaces, plugins, skills, auto_update };
|
|
71
85
|
}
|
package/dist/core/frontmatter.js
CHANGED
|
@@ -11,29 +11,103 @@ export function parseFrontmatter(source) {
|
|
|
11
11
|
function parseSimpleYaml(yaml) {
|
|
12
12
|
const lines = yaml.split(/\r?\n/);
|
|
13
13
|
const out = {};
|
|
14
|
-
let
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!raw.trim())
|
|
18
|
-
|
|
19
|
-
if (raw.startsWith(' - ') || raw.startsWith('- ')) {
|
|
20
|
-
const value = raw.replace(/^\s*-\s+/, '').trim();
|
|
21
|
-
if (currentKey && listBuffer)
|
|
22
|
-
listBuffer.push(stripQuotes(value));
|
|
14
|
+
let i = 0;
|
|
15
|
+
while (i < lines.length) {
|
|
16
|
+
const raw = lines[i];
|
|
17
|
+
if (!raw.trim()) {
|
|
18
|
+
i++;
|
|
23
19
|
continue;
|
|
24
20
|
}
|
|
25
21
|
const idx = raw.indexOf(':');
|
|
26
|
-
if (idx === -1)
|
|
22
|
+
if (idx === -1) {
|
|
23
|
+
i++;
|
|
27
24
|
continue;
|
|
28
|
-
if (currentKey && listBuffer) {
|
|
29
|
-
out[currentKey] = listBuffer;
|
|
30
|
-
listBuffer = null;
|
|
31
25
|
}
|
|
32
26
|
const key = raw.slice(0, idx).trim();
|
|
33
27
|
const rest = raw.slice(idx + 1).trim();
|
|
34
|
-
|
|
28
|
+
// Block scalar: `key: |` or `key: >` with optional chomp indicator (-/+)
|
|
29
|
+
const blockMatch = rest.match(/^([|>])([-+]?)\s*$/);
|
|
30
|
+
if (blockMatch) {
|
|
31
|
+
const style = blockMatch[1];
|
|
32
|
+
const chomp = blockMatch[2];
|
|
33
|
+
const collected = [];
|
|
34
|
+
let blockIndent = null;
|
|
35
|
+
let j = i + 1;
|
|
36
|
+
while (j < lines.length) {
|
|
37
|
+
const r = lines[j];
|
|
38
|
+
if (r.trim() === '') {
|
|
39
|
+
collected.push('');
|
|
40
|
+
j++;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const ind = r.match(/^(\s*)/)?.[1].length ?? 0;
|
|
44
|
+
if (blockIndent === null) {
|
|
45
|
+
if (ind === 0)
|
|
46
|
+
break;
|
|
47
|
+
blockIndent = ind;
|
|
48
|
+
}
|
|
49
|
+
if (ind < blockIndent)
|
|
50
|
+
break;
|
|
51
|
+
collected.push(r.slice(blockIndent));
|
|
52
|
+
j++;
|
|
53
|
+
}
|
|
54
|
+
while (collected.length > 0 && collected[collected.length - 1] === '')
|
|
55
|
+
collected.pop();
|
|
56
|
+
let value;
|
|
57
|
+
if (style === '|') {
|
|
58
|
+
value = collected.join('\n');
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const parts = [];
|
|
62
|
+
let para = [];
|
|
63
|
+
for (const ln of collected) {
|
|
64
|
+
if (ln === '') {
|
|
65
|
+
if (para.length > 0) {
|
|
66
|
+
parts.push(para.join(' '));
|
|
67
|
+
para = [];
|
|
68
|
+
}
|
|
69
|
+
parts.push('');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
para.push(ln);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (para.length > 0)
|
|
76
|
+
parts.push(para.join(' '));
|
|
77
|
+
const folded = [];
|
|
78
|
+
for (let k = 0; k < parts.length; k++) {
|
|
79
|
+
if (parts[k] === '' && (k === 0 || parts[k - 1] === ''))
|
|
80
|
+
continue;
|
|
81
|
+
folded.push(parts[k]);
|
|
82
|
+
}
|
|
83
|
+
value = folded.join('\n').replace(/\n+$/, '');
|
|
84
|
+
}
|
|
85
|
+
if (chomp !== '+')
|
|
86
|
+
value = value.replace(/\n+$/, '');
|
|
87
|
+
out[key] = value;
|
|
88
|
+
i = j;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// Empty value: could be a list on subsequent lines
|
|
35
92
|
if (rest === '') {
|
|
36
|
-
|
|
93
|
+
const buf = [];
|
|
94
|
+
let j = i + 1;
|
|
95
|
+
while (j < lines.length) {
|
|
96
|
+
const r = lines[j];
|
|
97
|
+
if (r.trim() === '') {
|
|
98
|
+
j++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (/^\s*-\s+/.test(r)) {
|
|
102
|
+
buf.push(stripQuotes(r.replace(/^\s*-\s+/, '').trim()));
|
|
103
|
+
j++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
if (buf.length > 0)
|
|
109
|
+
out[key] = buf;
|
|
110
|
+
i = j;
|
|
37
111
|
continue;
|
|
38
112
|
}
|
|
39
113
|
if (rest.startsWith('[') && rest.endsWith(']')) {
|
|
@@ -42,14 +116,12 @@ function parseSimpleYaml(yaml) {
|
|
|
42
116
|
.split(',')
|
|
43
117
|
.map((s) => stripQuotes(s.trim()))
|
|
44
118
|
.filter(Boolean);
|
|
45
|
-
|
|
119
|
+
i++;
|
|
46
120
|
continue;
|
|
47
121
|
}
|
|
48
122
|
out[key] = stripQuotes(rest);
|
|
49
|
-
|
|
123
|
+
i++;
|
|
50
124
|
}
|
|
51
|
-
if (currentKey && listBuffer)
|
|
52
|
-
out[currentKey] = listBuffer;
|
|
53
125
|
const fm = {
|
|
54
126
|
name: typeof out.name === 'string' ? out.name : '',
|
|
55
127
|
description: typeof out.description === 'string' ? out.description : undefined,
|
package/dist/prompts/plan.js
CHANGED
|
@@ -95,5 +95,12 @@ EOF
|
|
|
95
95
|
|
|
96
96
|
Your turn ends after the save command succeeds. No need to summarize the plan
|
|
97
97
|
in chat — the user can read the file.
|
|
98
|
+
|
|
99
|
+
## See also
|
|
100
|
+
|
|
101
|
+
- \`crtr plan list\` — list saved plans for the current directory
|
|
102
|
+
- \`crtr plan show <name>\` — print the body of a saved plan
|
|
103
|
+
- \`crtr plan edit <name>\` — open a saved plan in \$EDITOR
|
|
104
|
+
- \`crtr plan path [name]\` — absolute path of a plan or the plans directory
|
|
98
105
|
`;
|
|
99
106
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function skillPrompt(): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function skillPrompt() {
|
|
2
|
+
return `# Skill workflow
|
|
3
|
+
|
|
4
|
+
\`crtr\` ships skills — markdown reference with frontmatter that you pull on
|
|
5
|
+
demand. When the user's task matches a skill's description, run
|
|
6
|
+
\`crtr skill show <name>\` and apply the guidance. Ambiguous names exit \`4\` —
|
|
7
|
+
disambiguate with \`<plugin>:<name>\`.
|
|
8
|
+
|
|
9
|
+
## Discover
|
|
10
|
+
|
|
11
|
+
\`\`\`
|
|
12
|
+
crtr skill list # one per line: <scope>:<plugin>/<name> — <description>
|
|
13
|
+
crtr skill search <query> # rank by name, description, keywords
|
|
14
|
+
crtr skill grep <pattern> # regex search across SKILL.md bodies
|
|
15
|
+
\`\`\`
|
|
16
|
+
|
|
17
|
+
## Load
|
|
18
|
+
|
|
19
|
+
\`\`\`
|
|
20
|
+
crtr skill show <name> # print SKILL.md body to stdout
|
|
21
|
+
crtr skill show <plugin>:<name> # disambiguate when names collide
|
|
22
|
+
crtr skill path <name> # absolute path to SKILL.md
|
|
23
|
+
crtr skill where <name> # {scope, plugin, path} as JSON
|
|
24
|
+
\`\`\`
|
|
25
|
+
|
|
26
|
+
\`show\` is the default verb: \`crtr skill <name>\` (with no verb) also prints
|
|
27
|
+
the body.
|
|
28
|
+
|
|
29
|
+
## Author
|
|
30
|
+
|
|
31
|
+
\`\`\`
|
|
32
|
+
crtr skill new <plugin>:<name> --description "..." # scaffold a new skill
|
|
33
|
+
crtr skill show authoring-skills # the SKILL.md authoring guide
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
## Toggle
|
|
37
|
+
|
|
38
|
+
\`\`\`
|
|
39
|
+
crtr skill enable <name> # clear any disable in the chosen scope
|
|
40
|
+
crtr skill disable <name> # hide from list and agent discovery
|
|
41
|
+
\`\`\`
|
|
42
|
+
|
|
43
|
+
## Exit codes
|
|
44
|
+
|
|
45
|
+
- \`0\` — success
|
|
46
|
+
- \`3\` — skill not found
|
|
47
|
+
- \`4\` — ambiguous name; use \`<plugin>:<name>\`
|
|
48
|
+
`;
|
|
49
|
+
}
|
package/dist/prompts/spec.js
CHANGED
|
@@ -102,5 +102,12 @@ EOF
|
|
|
102
102
|
|
|
103
103
|
Your turn ends after the save command succeeds. No need to summarize the spec
|
|
104
104
|
in chat — the user can read the file.
|
|
105
|
+
|
|
106
|
+
## See also
|
|
107
|
+
|
|
108
|
+
- \`crtr spec list\` — list saved specs for the current directory
|
|
109
|
+
- \`crtr spec show <name>\` — print the body of a saved spec
|
|
110
|
+
- \`crtr spec edit <name>\` — open a saved spec in \$EDITOR
|
|
111
|
+
- \`crtr spec path [name]\` — absolute path of a spec or the specs directory
|
|
105
112
|
`;
|
|
106
113
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -47,9 +47,10 @@ export interface ConfigPluginEntry {
|
|
|
47
47
|
export interface ConfigSkillEntry {
|
|
48
48
|
enabled: boolean;
|
|
49
49
|
}
|
|
50
|
+
export type AutoUpdateMode = 'notify' | 'apply' | false;
|
|
50
51
|
export interface AutoUpdateConfig {
|
|
51
|
-
crtr:
|
|
52
|
-
content:
|
|
52
|
+
crtr: AutoUpdateMode;
|
|
53
|
+
content: AutoUpdateMode;
|
|
53
54
|
interval_hours: number;
|
|
54
55
|
}
|
|
55
56
|
export interface ScopeConfig {
|
|
@@ -67,6 +68,7 @@ export interface ScopeState {
|
|
|
67
68
|
last_updated?: string;
|
|
68
69
|
}>;
|
|
69
70
|
last_self_check?: string;
|
|
71
|
+
bootstrap_done?: boolean;
|
|
70
72
|
}
|
|
71
73
|
export interface SkillFrontmatter {
|
|
72
74
|
name: string;
|
package/dist/types.js
CHANGED
|
@@ -22,7 +22,7 @@ export function defaultScopeConfig() {
|
|
|
22
22
|
marketplaces: {},
|
|
23
23
|
plugins: {},
|
|
24
24
|
skills: {},
|
|
25
|
-
auto_update: { crtr:
|
|
25
|
+
auto_update: { crtr: 'notify', content: 'notify', interval_hours: 24 },
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
28
|
export function skillConfigKey(plugin, name) {
|