@crouton-kit/crouter 0.3.1 → 0.3.3

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.
@@ -33,6 +33,7 @@ describe('skill find list params', () => {
33
33
  includeDisabledFlag,
34
34
  { kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
35
35
  { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
36
+ { kind: 'flag', name: 'full', type: 'bool', required: false, constraint: '' },
36
37
  ];
37
38
  test('no args: defaults applied', async () => {
38
39
  const r = await parseArgv(params, []);
@@ -71,6 +72,12 @@ describe('skill find list params', () => {
71
72
  const r = await parseArgv(params, ['--plugin', 'my-plugin']);
72
73
  assert.equal(r['plugin'], 'my-plugin');
73
74
  });
75
+ test('--full presence = true, absence = false', async () => {
76
+ const present = await parseArgv(params, ['--full']);
77
+ assert.equal(present['full'], true);
78
+ const absent = await parseArgv(params, []);
79
+ assert.equal(absent['full'], false);
80
+ });
74
81
  });
75
82
  // ---------------------------------------------------------------------------
76
83
  // skill find search
@@ -21,7 +21,7 @@ import { readConfig } from '../core/config.js';
21
21
  import { mkdirSync, existsSync } from 'node:fs';
22
22
  import { join, resolve } from 'node:path';
23
23
  import { randomBytes } from 'node:crypto';
24
- import { ask, launchReview, display, inbox, scanInbox, validateDeck, parseDeck, deckPath, atomicWriteJson, readJson, } from '@crouton-kit/humanloop';
24
+ import { ask, launchReview, display, inbox, scanInbox, validateDeck, approveDeck, notifyDeck, parseDeck, deckPath, atomicWriteJson, readJson, } from '@crouton-kit/humanloop';
25
25
  const DECK_SCHEMA_HINT = 'Deck must match the humanloop deck schema: {title?, ' +
26
26
  'source?:{sessionName?,askedBy?,blockedSince?}, ' +
27
27
  'interactions:[{id,title,subtitle?,(body?|bodyPath?),options:[{id,label,' +
@@ -146,20 +146,10 @@ const humanApprove = defineLeaf({
146
146
  const title = input['title'];
147
147
  const subtitle = input['subtitle'];
148
148
  const body = input['body'];
149
- const interaction = {
150
- id: 'approve',
151
- title,
152
- kind: 'validation',
153
- options: [
154
- { id: 'yes', label: 'Yes' },
155
- { id: 'no', label: 'No' },
156
- ],
157
- };
158
- if (subtitle !== undefined)
159
- interaction['subtitle'] = subtitle;
160
- if (body !== undefined)
161
- interaction['body'] = body;
162
- const deck = validateDeck({ interactions: [interaction] });
149
+ const deck = approveDeck(title, {
150
+ ...(subtitle !== undefined ? { subtitle } : {}),
151
+ ...(body !== undefined ? { body } : {}),
152
+ });
163
153
  const cwd = process.cwd();
164
154
  const { jobId } = createJob('human', { cwd });
165
155
  const idir = interactionDir(jobId, cwd);
@@ -250,15 +240,7 @@ const humanNotify = defineLeaf({
250
240
  run: async (input) => {
251
241
  const title = input['title'];
252
242
  const body = input['body'];
253
- const interaction = {
254
- id: 'notify',
255
- title,
256
- kind: 'notify',
257
- options: [{ id: 'ack', label: 'OK' }],
258
- };
259
- if (body !== undefined)
260
- interaction['body'] = body;
261
- const deck = validateDeck({ interactions: [interaction] });
243
+ const deck = notifyDeck(title, body !== undefined ? { body } : {});
262
244
  const cwd = process.cwd();
263
245
  const id = `nfy-${randomBytes(4).toString('hex')}`;
264
246
  const idir = interactionDir(id, cwd);
@@ -86,11 +86,13 @@ const findList = defineLeaf({
86
86
  { kind: 'flag', name: 'include-disabled', type: 'bool', required: false, constraint: 'When present, includes disabled skills.' },
87
87
  { kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: 'Max 200.' },
88
88
  { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: 'Opaque token from next_cursor. Omit on first call.' },
89
+ { kind: 'flag', name: 'full', type: 'bool', required: false, constraint: 'When present, includes each skill\'s description in items. Off by default to keep enumerations cheap; pair with --plugin or --limit to bound cost.' },
89
90
  ],
90
91
  output: [
91
- { name: 'items', type: 'object[]', required: true, constraint: 'Each: {name, plugin, scope, path, description?, enabled, disabled_in?}. Sorted by scope then plugin then name ascending.' },
92
+ { name: 'items', type: 'object[]', required: true, constraint: 'Each: {name, plugin, scope, enabled, disabled_in?}. With --full, each item also includes description. Sorted by scope then plugin then name ascending.' },
92
93
  { name: 'next_cursor', type: 'string | null', required: true, constraint: 'null means no more items.' },
93
94
  { name: 'total', type: 'integer | null', required: true, constraint: 'Exact when cheap; null otherwise.' },
95
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands for drilling into an item or refining the list.' },
94
96
  ],
95
97
  outputKind: 'object',
96
98
  effects: ['None. Read-only.'],
@@ -102,6 +104,7 @@ const findList = defineLeaf({
102
104
  const limitRaw = input['limit'];
103
105
  const limit = Math.min(Math.max(1, limitRaw), 200);
104
106
  const cursor = input['cursor'];
107
+ const full = input['full'];
105
108
  const scopes = listScopes(scopeStr);
106
109
  const skills = scopes
107
110
  .flatMap((s) => listAllSkills(s))
@@ -137,17 +140,22 @@ const findList = defineLeaf({
137
140
  total: 'count',
138
141
  });
139
142
  return {
140
- items: result.items.map((sk) => ({
141
- name: sk.name,
142
- plugin: sk.plugin,
143
- scope: sk.scope,
144
- path: sk.path,
145
- description: sk.frontmatter.description !== undefined ? sk.frontmatter.description : null,
146
- enabled: sk.enabled,
147
- disabled_in: sk.disabledIn !== undefined ? sk.disabledIn : null,
148
- })),
143
+ items: result.items.map((sk) => {
144
+ const base = {
145
+ name: sk.name,
146
+ plugin: sk.plugin,
147
+ scope: sk.scope,
148
+ enabled: sk.enabled,
149
+ disabled_in: sk.disabledIn !== undefined ? sk.disabledIn : null,
150
+ };
151
+ if (full) {
152
+ base['description'] = sk.frontmatter.description !== undefined ? sk.frontmatter.description : null;
153
+ }
154
+ return base;
155
+ }),
149
156
  next_cursor: result.next_cursor,
150
157
  total: result.total,
158
+ follow_up: 'Use `crtr skill read show <name>` for the full SKILL.md body. Run `crtr skill find list -h` for filters and verbosity.',
151
159
  };
152
160
  },
153
161
  });
@@ -165,7 +173,8 @@ const findSearch = defineLeaf({
165
173
  ],
166
174
  output: [
167
175
  { name: 'query', type: 'string', required: true, constraint: 'Echo of the input query.' },
168
- { name: 'hits', type: 'object[]', required: true, constraint: 'Each: {name, plugin, scope, path, description?, keywords?, enabled, score, matched}. Sorted by score descending.' },
176
+ { name: 'hits', type: 'object[]', required: true, constraint: 'Each: {name, plugin, scope, score, description}. Sorted by score descending. description is the frontmatter line — the discriminator for picking which hit to read in full.' },
177
+ { name: 'follow_up', type: 'string', required: true, constraint: 'Concrete next commands for drilling into a hit or refining the search.' },
169
178
  ],
170
179
  outputKind: 'object',
171
180
  effects: ['None. Read-only.'],
@@ -228,13 +237,10 @@ const findSearch = defineLeaf({
228
237
  name: h.skill.name,
229
238
  plugin: h.skill.plugin,
230
239
  scope: h.skill.scope,
231
- path: h.skill.path,
232
- description: h.skill.frontmatter.description !== undefined ? h.skill.frontmatter.description : null,
233
- keywords: h.skill.frontmatter.keywords !== undefined ? h.skill.frontmatter.keywords : null,
234
- enabled: h.skill.enabled,
235
240
  score: h.score,
236
- matched: h.matched,
241
+ description: h.skill.frontmatter.description !== undefined ? h.skill.frontmatter.description : null,
237
242
  })),
243
+ follow_up: 'Use `crtr skill read show <name>` for the full SKILL.md body. Run `crtr skill find search -h` for filters.',
238
244
  };
239
245
  },
240
246
  });
@@ -675,7 +681,7 @@ export function registerSkill() {
675
681
  help: {
676
682
  name: 'skill',
677
683
  summary: 'discover, read, author, and manage skill state',
678
- model: 'To consume: `find search <topic>` discovers candidates; `read show <name>` loads each relevant SKILL.md body (multiple may apply load them all). To create: `author guide` picks a template, then re-run with `--type <t>` for the skeleton, then `author scaffold <plugin>:<name>` materializes the file. `state enable|disable` toggles visibility without deleting.',
684
+ model: '`find` when you do not yet know which skill applies — it locates candidates by topic, keyword, or body text. `read` when you have a name and need the SKILL.md content or its on-disk location. `author` when you are writing a new skill it carries the template workflow and the scaffolder. `state` when a skill should be hidden from discovery without being removed. Append `-h` at any branch or leaf for its full schema.',
679
685
  dynamicState: buildSkillCatalog,
680
686
  children: [
681
687
  { name: 'find', desc: 'list, search, or grep skills', useWhen: 'discovering what skills are available' },
@@ -605,7 +605,7 @@ const sysUpdateLeaf = defineLeaf({
605
605
  const r = selfCheck();
606
606
  if (r !== null) {
607
607
  updates.push({
608
- name: '@crouton-kit/crtr',
608
+ name: '@crouton-kit/crouter',
609
609
  kind: 'self',
610
610
  current: r.current,
611
611
  latest: r.latest,
@@ -615,7 +615,7 @@ const sysUpdateLeaf = defineLeaf({
615
615
  }
616
616
  else {
617
617
  updates.push({
618
- name: '@crouton-kit/crtr',
618
+ name: '@crouton-kit/crouter',
619
619
  kind: 'self',
620
620
  current: null,
621
621
  latest: null,
@@ -647,7 +647,7 @@ const sysUpdateLeaf = defineLeaf({
647
647
  void (async () => {
648
648
  try {
649
649
  if (resolvedTarget === 'self' || resolvedTarget === 'all') {
650
- appendEvent(jobId, { level: 'info', event: 'self-update:start', message: 'running npm install -g @crouton-kit/crtr@latest' });
650
+ appendEvent(jobId, { level: 'info', event: 'self-update:start', message: 'running npm install -g @crouton-kit/crouter@latest' });
651
651
  selfUpdate();
652
652
  const scopes = ['user'];
653
653
  if (projectScopeRoot())
@@ -27,7 +27,7 @@ function withinInterval(lastIso, intervalHours) {
27
27
  return Date.now() - last < intervalHours * HOUR_MS;
28
28
  }
29
29
  function spawnDetachedSelfUpdate() {
30
- const child = spawn('npm', ['i', '-g', '@crouton-kit/crtr@latest'], {
30
+ const child = spawn('npm', ['i', '-g', '@crouton-kit/crouter@latest'], {
31
31
  detached: true,
32
32
  stdio: 'ignore',
33
33
  });
@@ -73,11 +73,10 @@ export function maybeAutoUpdate(argv) {
73
73
  spawnDetachedSelfUpdate();
74
74
  }
75
75
  if (content === 'notify') {
76
- const entries = contentCheck();
77
- for (const e of entries) {
78
- if (!e.up_to_date && !e.unreachable) {
79
- process.stderr.write(`crtr: ${e.kind} ${e.name} has updates available — run \`crtr sys update\`\n`);
80
- }
76
+ const stale = contentCheck().filter((e) => !e.up_to_date && !e.unreachable);
77
+ if (stale.length > 0) {
78
+ const list = stale.map((e) => `${e.kind} ${e.name}`).join(', ');
79
+ process.stderr.write(`crtr: updates available for ${list} — run \`crtr sys update\`\n`);
81
80
  }
82
81
  }
83
82
  else if (content === 'apply') {
@@ -21,7 +21,7 @@ export function currentVersion() {
21
21
  return parsed.version;
22
22
  }
23
23
  export function selfUpdate() {
24
- const res = spawnSync('npm', ['i', '-g', '@crouton-kit/crtr@latest'], { stdio: 'inherit' });
24
+ const res = spawnSync('npm', ['i', '-g', '@crouton-kit/crouter@latest'], { stdio: 'inherit' });
25
25
  if (res.status !== 0) {
26
26
  throw general('npm install failed');
27
27
  }
@@ -29,7 +29,7 @@ export function selfUpdate() {
29
29
  /** Check whether a newer crtr version is available on npm.
30
30
  * Warns to stderr if network unavailable; returns {current, latest} or null if unreachable. */
31
31
  export function selfCheck() {
32
- const res = spawnSync('npm', ['view', '@crouton-kit/crtr', 'version'], { encoding: 'utf8' });
32
+ const res = spawnSync('npm', ['view', '@crouton-kit/crouter', 'version'], { encoding: 'utf8' });
33
33
  if (res.status !== 0) {
34
34
  return null;
35
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crouton-kit/crouter",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "description": "crtr — fast access to skills, plugins, and marketplaces",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "license": "MIT",
37
37
  "dependencies": {
38
- "@crouton-kit/humanloop": "^0.3.6",
38
+ "@crouton-kit/humanloop": "^0.3.8",
39
39
  "commander": "^13.0.0"
40
40
  },
41
41
  "devDependencies": {