happyskills 1.1.0 → 1.2.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/package.json +1 -1
  3. package/src/commands/access.js +41 -1
  4. package/src/commands/agents.js +30 -1
  5. package/src/commands/bump.js +33 -1
  6. package/src/commands/changelog.js +37 -1
  7. package/src/commands/check.js +30 -1
  8. package/src/commands/config.js +34 -1
  9. package/src/commands/convert.js +38 -1
  10. package/src/commands/delete.js +33 -1
  11. package/src/commands/diff.js +33 -1
  12. package/src/commands/disable.js +31 -1
  13. package/src/commands/enable.js +31 -1
  14. package/src/commands/feedback.js +41 -1
  15. package/src/commands/fork.js +35 -1
  16. package/src/commands/groups.js +40 -1
  17. package/src/commands/init.js +35 -1
  18. package/src/commands/install.js +35 -1
  19. package/src/commands/list.js +29 -1
  20. package/src/commands/login.js +34 -1
  21. package/src/commands/logout.js +20 -1
  22. package/src/commands/people.js +37 -1
  23. package/src/commands/postlex.js +38 -0
  24. package/src/commands/publish.js +40 -1
  25. package/src/commands/pull.js +47 -1
  26. package/src/commands/reconcile.js +36 -1
  27. package/src/commands/release.js +51 -1
  28. package/src/commands/schema.js +5 -0
  29. package/src/commands/search.js +46 -1
  30. package/src/commands/self-update.js +27 -1
  31. package/src/commands/setup.js +32 -1
  32. package/src/commands/snapshot.js +38 -1
  33. package/src/commands/status.js +26 -1
  34. package/src/commands/uninstall.js +30 -1
  35. package/src/commands/update.js +40 -1
  36. package/src/commands/validate.js +37 -1
  37. package/src/commands/versions.js +34 -1
  38. package/src/commands/visibility.js +33 -1
  39. package/src/commands/whoami.js +30 -1
  40. package/src/constants/error_codes.js +12 -1
  41. package/src/constants/next_step_actions.js +8 -0
  42. package/src/constants/next_step_by_error_code.js +5 -0
  43. package/src/integration/schema.test.js +31 -0
@@ -260,4 +260,38 @@ const run = (args) => catch_errors('Install failed', async () => {
260
260
  print_hint(`See what's installed: ${code('happyskills list')}`)
261
261
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
262
262
 
263
- module.exports = { run }
263
+ const schema = {
264
+ name: 'install',
265
+ audience: 'consumer',
266
+ purpose: 'Install one or more skills and their dependencies; without arguments installs all dependencies from skill.json or skills-lock.json.',
267
+ mutation: true,
268
+ interactive_in_text_mode: false,
269
+ input: {
270
+ positional: [ { name: 'skills', required: false, type: 'string[]', pattern: '<owner>/<name>[@<version>]' } ],
271
+ flags: [
272
+ { name: 'global', alias: 'g', type: 'boolean', default: false, description: 'Install globally (~/.agents/skills/)' },
273
+ { name: 'version', type: 'string', default: undefined, description: 'Pin to specific version (single skill only)' },
274
+ { name: 'force', type: 'boolean', default: false, description: 'Force install on dependency conflicts' },
275
+ { name: 'fresh', type: 'boolean', default: false, description: 'Ignore lock file, re-resolve from scratch' },
276
+ { name: 'agents', type: 'string', default: undefined, description: 'Target specific agents (comma-separated)' },
277
+ { name: 'yes', alias: 'y', type: 'boolean', default: false, description: 'Skip confirmation prompts' },
278
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
279
+ ],
280
+ },
281
+ output: {
282
+ data_shape: {
283
+ results: 'array<{ skill: string, status: string, version: string, installed: string[], skipped: string[], skipped_deps: string[], warnings: string[], forced: string[], snapshot_id?: string } | { skill: string, status: "failed", error: { code: string, message: string } }>',
284
+ },
285
+ },
286
+ errors: [
287
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
288
+ { code: 'LOCAL_EDITS_PRESENT', next_step: { kind: 'confirmation', action: 'confirm_discard_or_snapshot_first' } },
289
+ { code: 'VERSION_NOT_FOUND', next_step: { kind: 'decision', action: 'pick_version' } },
290
+ ],
291
+ examples: [
292
+ 'happyskills install acme/deploy-aws',
293
+ 'happyskills install acme/deploy-aws@1.2.0 acme/monitor@latest',
294
+ ],
295
+ }
296
+
297
+ module.exports = { run, schema }
@@ -199,4 +199,32 @@ const run = (args) => catch_errors('List failed', async () => {
199
199
  }
200
200
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
201
201
 
202
- module.exports = { run }
202
+ const schema = {
203
+ name: 'list',
204
+ audience: 'consumer',
205
+ purpose: 'List installed skills, showing version, source, drift/ahead status, and agent-enabled state.',
206
+ mutation: false,
207
+ interactive_in_text_mode: false,
208
+ input: {
209
+ positional: [],
210
+ flags: [
211
+ { name: 'global', alias: 'g', type: 'boolean', default: false, description: 'List globally installed skills' },
212
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
213
+ ],
214
+ },
215
+ output: {
216
+ data_shape: {
217
+ skills: 'object<string, { version: string, type: string, source: string, status: string, enabled: boolean, drift?: object, ahead?: object }>',
218
+ drafts: 'array<{ name: string, description: string, version: string|null, type: string }>',
219
+ external: 'array<{ name: string, description: string }>',
220
+ agent_orphans: 'array<{ name: string, description: string, agents: string[] }>',
221
+ },
222
+ },
223
+ errors: [],
224
+ examples: [
225
+ 'happyskills list',
226
+ 'happyskills ls --json',
227
+ ],
228
+ }
229
+
230
+ module.exports = { run, schema }
@@ -218,4 +218,37 @@ const run = (args) => catch_errors('Login failed', async () => {
218
218
  print_hint(`Verify your identity: ${code('happyskills whoami')}`)
219
219
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
220
220
 
221
- module.exports = { run }
221
+ const schema = {
222
+ name: 'login',
223
+ audience: 'account',
224
+ purpose: 'Authenticate with the HappySkills registry via browser (device flow) or password; returns current auth status when already logged in.',
225
+ mutation: true,
226
+ interactive_in_text_mode: true,
227
+ input: {
228
+ positional: [],
229
+ flags: [
230
+ { name: 'browser', type: 'boolean', default: false },
231
+ { name: 'password', type: 'boolean', default: false },
232
+ { name: 'json', type: 'boolean', default: false },
233
+ ],
234
+ },
235
+ output: {
236
+ data_shape: {
237
+ status: 'string',
238
+ username: 'string',
239
+ email: 'string',
240
+ },
241
+ },
242
+ errors: [
243
+ { code: 'INTERACTIVE_REQUIRED' },
244
+ { code: 'INVALID_CREDENTIALS' },
245
+ { code: 'AUTH_FAILED', next_step: { kind: 'recovery', action: 'login' } },
246
+ { code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } },
247
+ ],
248
+ examples: [
249
+ 'happyskills login',
250
+ 'happyskills login --json --browser',
251
+ ],
252
+ }
253
+
254
+ module.exports = { run, schema }
@@ -30,4 +30,23 @@ const run = (args) => catch_errors('Logout failed', async () => {
30
30
  print_hint(`Log back in: ${code('happyskills login')}`)
31
31
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
32
32
 
33
- module.exports = { run }
33
+ const schema = {
34
+ name: 'logout',
35
+ audience: 'account',
36
+ purpose: 'Clear stored authentication credentials from disk.',
37
+ mutation: true,
38
+ interactive_in_text_mode: false,
39
+ input: {
40
+ positional: [],
41
+ flags: [
42
+ { name: 'json', type: 'boolean', default: false }
43
+ ]
44
+ },
45
+ output: { data_shape: { status: 'string' } },
46
+ errors: [],
47
+ examples: [
48
+ 'happyskills logout'
49
+ ]
50
+ }
51
+
52
+ module.exports = { run, schema }
@@ -159,4 +159,40 @@ const run = (args) => catch_errors('People command failed', async () => {
159
159
  throw new UsageError(`Unknown subcommand: ${sub}. Run happyskills people --help`)
160
160
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
161
161
 
162
- module.exports = { run }
162
+ const schema = {
163
+ name: 'people',
164
+ audience: 'account',
165
+ purpose: 'Manage workspace membership: list, add, remove, change roles, and search users.',
166
+ mutation: true,
167
+ interactive_in_text_mode: true,
168
+ input: {
169
+ positional: [
170
+ { name: 'subcommand', required: true, type: 'string', enum: ['list', 'add', 'remove', 'role', 'search'] }
171
+ ],
172
+ flags: [
173
+ { name: 'workspace', alias: 'w', type: 'string' },
174
+ { name: 'role', type: 'string' },
175
+ { name: 'limit', type: 'number' },
176
+ { name: 'yes', alias: 'y', type: 'boolean' },
177
+ { name: 'json', type: 'boolean' }
178
+ ]
179
+ },
180
+ output: {
181
+ data_shape: {
182
+ results: 'array<{ username: string, email: string, role: string, created_at: string }>'
183
+ }
184
+ },
185
+ errors: [
186
+ { code: 'AUTH_REQUIRED', next_step: { kind: 'recovery', action: 'login' } },
187
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
188
+ { code: 'NOT_FOUND' },
189
+ { code: 'FORBIDDEN' },
190
+ { code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } }
191
+ ],
192
+ examples: [
193
+ 'happyskills people list -w acme',
194
+ 'happyskills people add -w acme alice --role admin'
195
+ ]
196
+ }
197
+
198
+ module.exports = { run, schema }
@@ -518,8 +518,46 @@ const run = (args) => catch_errors('Postlex failed', async () => {
518
518
  console.log(`\n${gray(`Showing ${final_ordering.length} result${final_ordering.length === 1 ? '' : 's'}.`)}\n`)
519
519
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
520
520
 
521
+ const schema = {
522
+ name: 'postlex',
523
+ audience: 'consumer',
524
+ purpose: 'Apply deterministic slug-overlap promotion to an LLM-emitted ranking and emit a next_step envelope; the finalization step of the rerank discovery protocol.',
525
+ mutation: false,
526
+ interactive_in_text_mode: false,
527
+ input: {
528
+ positional: [],
529
+ flags: [
530
+ { name: 'query', type: 'string', required: true, description: 'The original search query (used for slug-overlap scoring)' },
531
+ { name: 'ranking', type: 'string', required: true, description: 'Path to ranking JSON, or `-` for stdin' },
532
+ { name: 'search-output', type: 'string', default: undefined, description: 'Path to full search --with-rerank --json response envelope (recommended)' },
533
+ { name: 'data', type: 'string', default: undefined, description: 'Legacy: separate data file when --ranking does not embed it' },
534
+ { name: 'clarification-turns-used', type: 'number', default: 0, description: 'Clarification budget already spent (0-2)' },
535
+ { name: 'original-query', type: 'string', default: undefined, description: 'Original user query (opaque context from prior step)' },
536
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
537
+ ],
538
+ },
539
+ output: {
540
+ data_shape: {
541
+ final_ordering: 'array<{ rank: number, candidate_id: number, slug: string, name: string, workspace_slug: string|null, description: string, version: string, match_quality: string|null, quality_score: number|null, star_count: number, rationale: string }>',
542
+ postlex_promoted: 'boolean',
543
+ promoted_from_rank: 'number|null',
544
+ exact_match_count_in_window: 'number',
545
+ formulated_query: 'string',
546
+ },
547
+ },
548
+ errors: [
549
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
550
+ { code: 'RANKING_SCHEMA_MISMATCH', next_step: { kind: 'recovery', action: 'retry_rank' } },
551
+ ],
552
+ examples: [
553
+ 'happyskills postlex --query "deploy aws" --ranking - --search-output /tmp/search-out.json',
554
+ 'happyskills postlex --query "deploy aws" --ranking r.json --data d.json --json',
555
+ ],
556
+ }
557
+
521
558
  module.exports = {
522
559
  run,
560
+ schema,
523
561
  // Exported for unit testing — pure functions only.
524
562
  validate_ranking,
525
563
  apply_postlex,
@@ -339,4 +339,43 @@ const run = (args) => catch_errors('Publish failed', async () => {
339
339
  print_hint(`Check versions: ${code('happyskills check')}`)
340
340
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
341
341
 
342
- module.exports = { run }
342
+ const schema = {
343
+ name: 'publish',
344
+ audience: 'author',
345
+ purpose: 'Push a skill to the registry, resolving the owner workspace from skills-lock.json; optionally bumps the version before publishing.',
346
+ mutation: true,
347
+ interactive_in_text_mode: false,
348
+ input: {
349
+ positional: [ { name: 'skill', required: true, type: 'string', description: 'Name of the installed skill' } ],
350
+ flags: [
351
+ { name: 'bump', type: 'string', default: undefined, description: 'Auto-bump version before publishing (patch, minor, major)' },
352
+ { name: 'workspace', type: 'string', default: undefined, description: 'Target workspace (overrides lock file owner)' },
353
+ { name: 'public', type: 'boolean', default: false, description: 'Publish as public (default is private)' },
354
+ { name: 'force', type: 'boolean', default: false, description: 'Bypass divergence check (may overwrite remote changes)' },
355
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
356
+ ],
357
+ },
358
+ output: {
359
+ data_shape: {
360
+ skill: 'string',
361
+ version: 'string',
362
+ ref: 'string',
363
+ commit: 'string | null',
364
+ bumped_from: 'string | null',
365
+ warnings: 'string[] | undefined',
366
+ },
367
+ },
368
+ errors: [
369
+ { code: 'AUTH_REQUIRED', next_step: { kind: 'recovery', action: 'login' } },
370
+ { code: 'VALIDATION_FAILED', next_step: { kind: 'recovery', action: 'fix_validation_errors' } },
371
+ { code: 'DEPENDENCY_VALIDATION_FAILED', next_step: { kind: 'recovery', action: 'fix_validation_errors' } },
372
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
373
+ { code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } },
374
+ ],
375
+ examples: [
376
+ 'happyskills publish my-skill',
377
+ 'happyskills publish deploy-aws --bump patch',
378
+ ],
379
+ }
380
+
381
+ module.exports = { run, schema }
@@ -611,4 +611,50 @@ const reconcile_dependencies = (skill_dir, skill_name, lock_data, is_global, pro
611
611
  }
612
612
  })
613
613
 
614
- module.exports = { run }
614
+ const schema = {
615
+ name: 'pull',
616
+ audience: 'consumer',
617
+ purpose: 'Pull remote changes for an installed skill and merge with local files, with optional rebase or conflict-resolution strategies.',
618
+ mutation: true,
619
+ interactive_in_text_mode: false,
620
+ input: {
621
+ positional: [{ name: 'skill', required: true, type: 'string', pattern: '<owner>/<name>' }],
622
+ flags: [
623
+ { name: 'theirs', type: 'string|boolean', default: false },
624
+ { name: 'ours', type: 'string|boolean', default: false },
625
+ { name: 'force', type: 'boolean', default: false },
626
+ { name: 'rebase', type: 'boolean', default: false },
627
+ { name: 'global', type: 'boolean', default: false },
628
+ { name: 'strict', type: 'boolean', default: false },
629
+ { name: 'json', type: 'boolean', default: false },
630
+ { name: 'full-report', type: 'boolean', default: false }
631
+ ]
632
+ },
633
+ output: {
634
+ data_shape: {
635
+ status: 'string',
636
+ skill: 'string',
637
+ version: 'string|null',
638
+ report: 'object',
639
+ conflict_files: 'array<string>',
640
+ json_conflicts: 'array<object>',
641
+ drift: 'object|null',
642
+ resolution_steps: 'array|null'
643
+ }
644
+ },
645
+ errors: [
646
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
647
+ // --rebase pipeline failures — recover by retrying or abandoning to the snapshot
648
+ { code: 'COMPARE_FAILED', next_step: { kind: 'recovery', action: 'retry_or_abandon' } },
649
+ { code: 'CLONE_BASE_FAILED', next_step: { kind: 'recovery', action: 'retry_or_abandon' } },
650
+ { code: 'CLONE_REMOTE_FAILED', next_step: { kind: 'recovery', action: 'retry_or_abandon' } },
651
+ { code: 'READ_LOCAL_FAILED', next_step: { kind: 'recovery', action: 'retry_or_abandon' } },
652
+ { code: 'SWAP_FAILED', next_step: { kind: 'recovery', action: 'retry_or_abandon' } }
653
+ ],
654
+ examples: [
655
+ 'happyskills pull acme/deploy-aws',
656
+ 'happyskills pull acme/deploy-aws --rebase --json'
657
+ ]
658
+ }
659
+
660
+ module.exports = { run, schema }
@@ -274,4 +274,39 @@ const run = (args) => catch_errors('Reconcile failed', async () => {
274
274
  }
275
275
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
276
276
 
277
- module.exports = { run, reconcile_one, SUBTYPE_OPTIONS, ACTION_NEXT_STEPS }
277
+ const schema = {
278
+ name: 'reconcile',
279
+ audience: 'consumer',
280
+ purpose: 'Diagnose and repair genuine drift between the lock file and disk (regression, missing files, or unparseable skill.json); the ahead state is not drift and is reported as a non-event.',
281
+ mutation: true,
282
+ interactive_in_text_mode: false,
283
+ input: {
284
+ positional: [ { name: 'skill', required: true, type: 'string', pattern: '<owner>/<name>' } ],
285
+ flags: [
286
+ { name: 'apply', type: 'string', default: undefined, description: 'Execute one of the recommended options directly (skips the next_step round-trip)' },
287
+ { name: 'global', alias: 'g', type: 'boolean', default: false, description: 'Operate on globally-installed skills' },
288
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
289
+ ],
290
+ },
291
+ output: {
292
+ data_shape: {
293
+ skill: 'string',
294
+ no_drift: 'boolean',
295
+ status: 'string',
296
+ drift_state: 'string',
297
+ applied: '{ applied: string, new_disk_version: string } | undefined',
298
+ lock_version: 'string | undefined',
299
+ disk_version: 'string | undefined',
300
+ ahead: '{ lock_version: string, disk_version: string, has_changelog_entry: boolean, changelog_version: string | null } | undefined',
301
+ },
302
+ },
303
+ errors: [
304
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
305
+ ],
306
+ examples: [
307
+ 'happyskills reconcile acme/deploy-aws --json',
308
+ 'happyskills reconcile acme/deploy-aws --apply restore_from_lock_version --json',
309
+ ],
310
+ }
311
+
312
+ module.exports = { run, reconcile_one, SUBTYPE_OPTIONS, ACTION_NEXT_STEPS, schema }
@@ -481,4 +481,54 @@ const run = (args) => catch_errors('Release wrapper failed', async () => {
481
481
  }
482
482
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
483
483
 
484
- module.exports = { run, orchestrate, determine_target_version, compute_bump }
484
+ const schema = {
485
+ name: 'release',
486
+ audience: 'author',
487
+ purpose: 'Atomic release pipeline: snapshot, validate, bump version, verify CHANGELOG, and publish in one deterministic command; restores the snapshot on any failure.',
488
+ mutation: true,
489
+ interactive_in_text_mode: false,
490
+ input: {
491
+ positional: [ { name: 'skill', required: true, type: 'string', description: 'Name of the skill to release' } ],
492
+ flags: [
493
+ { name: 'bump', type: 'string', default: undefined, description: 'patch | minor | major | explicit semver' },
494
+ { name: 'no-bump', type: 'boolean', default: false, description: 'Refuse to bump; require disk to already be ahead' },
495
+ { name: 'changelog-from', type: 'string', default: undefined, description: 'Source for the new CHANGELOG entry (auto or file path)' },
496
+ { name: 'workspace', type: 'string', default: undefined, description: 'Target workspace slug' },
497
+ { name: 'public', type: 'boolean', default: false, description: 'Publish as public on first publish' },
498
+ { name: 'dry-run', type: 'boolean', default: false, description: 'Validate and check status without mutating' },
499
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
500
+ ],
501
+ },
502
+ output: {
503
+ data_shape: {
504
+ published: 'boolean',
505
+ skill: 'string',
506
+ version: 'string',
507
+ workspace: 'string',
508
+ commit: 'string | null',
509
+ ref: 'string',
510
+ ahead_recognized: 'boolean',
511
+ bump_applied: 'boolean',
512
+ warnings: 'string[]',
513
+ snapshot_id_preserved: 'boolean',
514
+ },
515
+ },
516
+ errors: [
517
+ { code: 'VALIDATION_FAILED', next_step: { kind: 'recovery', action: 'fix_validation_errors' } },
518
+ { code: 'DRIFT_DETECTED', next_step: { kind: 'recovery', action: 'reconcile_first' } },
519
+ { code: 'MISSING_VERSION', next_step: { kind: 'decision', action: 'specify_bump_type' } },
520
+ { code: 'INVALID_BUMP', next_step: { kind: 'decision', action: 'specify_bump_type' } },
521
+ { code: 'BUMP_DISAGREEMENT', next_step: { kind: 'decision', action: 'resolve_bump_disagreement' } },
522
+ { code: 'CHANGELOG_SOURCE_UNREADABLE', next_step: { kind: 'recovery', action: 'provide_changelog' } },
523
+ { code: 'MISSING_CHANGELOG_ENTRY', next_step: { kind: 'recovery', action: 'provide_changelog' } },
524
+ { code: 'WORKSPACE_UNRESOLVED', next_step: { kind: 'decision', action: 'specify_workspace' } },
525
+ { code: 'WRITE_FAILED', next_step: { kind: 'recovery', action: 'retry' } },
526
+ { code: 'PUBLISH_FAILED', next_step: { kind: 'recovery', action: 'review_publish_error' } },
527
+ ],
528
+ examples: [
529
+ 'happyskills release my-skill --workspace acme --json',
530
+ 'happyskills release my-skill --bump patch --workspace acme --json',
531
+ ],
532
+ }
533
+
534
+ module.exports = { run, orchestrate, determine_target_version, compute_bump, schema }
@@ -84,6 +84,7 @@ const default_schema = (name) => ({
84
84
  input: { positional: [], flags: [] },
85
85
  output: { data_shape: {} },
86
86
  errors: [],
87
+ examples: [],
87
88
  })
88
89
 
89
90
  // Load the per-command schema export. Misses fall back to default_schema.
@@ -174,6 +175,10 @@ const schema = {
174
175
  next_step_kinds: 'array<string>',
175
176
  } },
176
177
  errors: [],
178
+ examples: [
179
+ 'happyskills schema --json',
180
+ 'happyskills schema --text',
181
+ ],
177
182
  }
178
183
 
179
184
  module.exports = { run, schema, build_schema_payload }
@@ -435,4 +435,49 @@ const run = (args) => catch_errors('Search failed', async () => {
435
435
  }
436
436
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
437
437
 
438
- module.exports = { run, build_search_next_step }
438
+ const schema = {
439
+ name: 'search',
440
+ audience: 'consumer',
441
+ purpose: 'Search the skill registry using smart semantic dispatch (natural-language, fuzzy-slug, or fuzzy-scoped); use --with-rerank to get the rerank-protocol envelope for agentic discovery.',
442
+ mutation: false,
443
+ interactive_in_text_mode: false,
444
+ input: {
445
+ positional: [ { name: 'query', required: false, type: 'string', description: 'Search term (optional with --mine/--personal/--workspace)' } ],
446
+ flags: [
447
+ { name: 'workspace', alias: 'w', type: 'string', default: undefined, description: 'Search within specific workspace(s) (comma-separated)' },
448
+ { name: 'mine', type: 'boolean', default: false, description: 'Search across all your workspaces' },
449
+ { name: 'personal', type: 'boolean', default: false, description: 'Search only your personal workspace' },
450
+ { name: 'tags', type: 'string', default: undefined, description: 'Filter by tags (comma-separated)' },
451
+ { name: 'type', type: 'string', default: undefined, description: 'Filter by type: skill, kit' },
452
+ { name: 'exact', type: 'boolean', default: false, description: 'Force keyword-only FTS matching' },
453
+ { name: 'with-rerank', type: 'boolean', default: false, description: 'Include rerank digests + next_step envelope (agentic discovery protocol)' },
454
+ { name: 'clarification-turns-used', type: 'number', default: 0, description: 'Clarification budget already spent (0-2)' },
455
+ { name: 'limit', type: 'number', required: true, description: 'Max results (1-50)' },
456
+ { name: 'min-quality', type: 'number', default: undefined, description: 'Minimum quality score 0-100' },
457
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
458
+ ],
459
+ },
460
+ output: {
461
+ data_shape: {
462
+ query: 'string',
463
+ formulated_query: 'string',
464
+ mode: 'string',
465
+ results: 'array<{ skill: string, name: string, type: string, description: string, version: string, visibility: string, workspace_slug: string, stars: number, quality_score: number|null, quality_tier: string|null, relevance_score: number|null, match_quality: string|null, tags: string[], download_count: number, created_at: string, updated_at: string, similar_count: number, similar_repos: array }>',
466
+ count: 'number',
467
+ rerank_digests: 'array (present when --with-rerank)',
468
+ rerank_system_prompt: 'string|null (present when --with-rerank)',
469
+ rerank_response_schema: 'object|null (present when --with-rerank)',
470
+ },
471
+ },
472
+ errors: [
473
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
474
+ { code: 'AUTH_REQUIRED', next_step: { kind: 'recovery', action: 'login' } },
475
+ { code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } },
476
+ ],
477
+ examples: [
478
+ 'happyskills search "deploy infra to AWS" --limit 10',
479
+ 'happyskills search "deploy infra to AWS" --with-rerank --json --limit 50',
480
+ ],
481
+ }
482
+
483
+ module.exports = { run, build_search_next_step, schema }
@@ -80,4 +80,30 @@ const run = (args) => catch_errors('Self-update failed', async () => {
80
80
  print_hint(`Run ${code('happyskills --version')} to confirm.`)
81
81
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
82
82
 
83
- module.exports = { run }
83
+ const schema = {
84
+ name: 'self-update',
85
+ audience: 'account',
86
+ purpose: 'Upgrade the happyskills CLI to the latest version published on npm.',
87
+ mutation: true,
88
+ interactive_in_text_mode: false,
89
+ input: {
90
+ positional: [],
91
+ flags: [
92
+ { name: 'json', type: 'boolean', default: false }
93
+ ]
94
+ },
95
+ output: {
96
+ data_shape: {
97
+ status: 'string',
98
+ version: 'string'
99
+ }
100
+ },
101
+ errors: [
102
+ { code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } }
103
+ ],
104
+ examples: [
105
+ 'happyskills self-update'
106
+ ]
107
+ }
108
+
109
+ module.exports = { run, schema }
@@ -55,4 +55,35 @@ const run = (args) => catch_errors('Setup failed', async () => {
55
55
  }
56
56
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
57
57
 
58
- module.exports = { run }
58
+ const schema = {
59
+ name: 'setup',
60
+ audience: 'account',
61
+ purpose: 'Install (or update) the official HappySkills CLI skill into the local or global agent skills directory.',
62
+ mutation: true,
63
+ interactive_in_text_mode: false,
64
+ input: {
65
+ positional: [],
66
+ flags: [
67
+ { name: 'global', type: 'boolean', default: false },
68
+ { name: 'agents', type: 'string', default: null },
69
+ { name: 'json', type: 'boolean', default: false }
70
+ ]
71
+ },
72
+ output: {
73
+ data_shape: {
74
+ skill: 'string',
75
+ version: 'string',
76
+ status: 'string'
77
+ }
78
+ },
79
+ errors: [
80
+ { code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } },
81
+ { code: 'REGISTRY_UNAVAILABLE', next_step: { kind: 'recovery', action: 'retry' } }
82
+ ],
83
+ examples: [
84
+ 'happyskills setup',
85
+ 'happyskills setup -g'
86
+ ]
87
+ }
88
+
89
+ module.exports = { run, schema }
@@ -249,4 +249,41 @@ const run = (args) => catch_errors('Snapshot failed', async () => {
249
249
  if (err) throw err
250
250
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
251
251
 
252
- module.exports = { run }
252
+ const schema = {
253
+ name: 'snapshot',
254
+ audience: 'consumer',
255
+ purpose: 'Capture and restore skill state — create, list, restore, delete, or prune snapshots as a safety net for every mutation.',
256
+ mutation: true,
257
+ interactive_in_text_mode: false,
258
+ input: {
259
+ positional: [
260
+ { name: 'subcommand', required: true, type: 'string', enum: ['create', 'list', 'restore', 'delete', 'prune'] },
261
+ { name: 'target', required: true, type: 'string', description: '<owner/skill> for create/list/prune; <snapshot-id> for restore/delete' },
262
+ ],
263
+ flags: [
264
+ { name: 'note', type: 'string', default: undefined, description: 'Attach a note to a new snapshot (create only)' },
265
+ { name: 'keep', type: 'number', default: 10, description: 'Retention count for prune' },
266
+ { name: 'global', alias: 'g', type: 'boolean', default: false, description: 'Operate on the global snapshot store' },
267
+ { name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
268
+ ],
269
+ },
270
+ output: {
271
+ data_shape: {
272
+ 'create': '{ snapshot_id: string, path: string, note?: string, pruned?: string[] }',
273
+ 'list': '{ workspace: string, skill: string, snapshots: array<{ snapshot_id: string, timestamp: string, note: string }> }',
274
+ 'restore': '{ workspace: string, skill: string, snapshot_id: string }',
275
+ 'delete': '{ deleted: boolean, snapshot_id: string, reason?: string }',
276
+ 'prune': '{ kept: number, deleted: number }',
277
+ },
278
+ },
279
+ errors: [
280
+ { code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
281
+ { code: 'SNAPSHOT_FAILED' },
282
+ ],
283
+ examples: [
284
+ 'happyskills snapshot create acme/deploy-aws --note "pre-release" --json',
285
+ 'happyskills snapshot restore snap_20260523T103045Z_abc123 --json',
286
+ ],
287
+ }
288
+
289
+ module.exports = { run, schema }
@@ -224,4 +224,29 @@ const run = (args) => catch_errors('Status failed', async () => {
224
224
  }
225
225
  }).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
226
226
 
227
- module.exports = { run }
227
+ const schema = {
228
+ name: 'status',
229
+ audience: 'consumer',
230
+ purpose: 'Show divergence status for installed skills — drift, ahead, modified, outdated, conflicts, or clean.',
231
+ mutation: false,
232
+ interactive_in_text_mode: false,
233
+ input: {
234
+ positional: [{ name: 'skill', required: false, type: 'string', pattern: '<owner>/<name>' }],
235
+ flags: [
236
+ { name: 'global', type: 'boolean', default: false },
237
+ { name: 'json', type: 'boolean', default: false }
238
+ ]
239
+ },
240
+ output: {
241
+ data_shape: {
242
+ results: 'array<{ skill: string, base_version: string|null, base_commit: string|null, local_modified: boolean, modified_files: array|null, remote_updated: boolean, remote_version: string|null, remote_commit: string|null, conflict_files: array, drift: object|null, ahead: object|null, status: string }>'
243
+ }
244
+ },
245
+ errors: [],
246
+ examples: [
247
+ 'happyskills status',
248
+ 'happyskills status acme/deploy-aws'
249
+ ]
250
+ }
251
+
252
+ module.exports = { run, schema }