happyskills 1.0.2 → 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.
- package/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/commands/access.js +41 -1
- package/src/commands/agents.js +30 -1
- package/src/commands/bump.js +33 -1
- package/src/commands/changelog.js +37 -1
- package/src/commands/check.js +30 -1
- package/src/commands/config.js +34 -1
- package/src/commands/convert.js +38 -1
- package/src/commands/delete.js +33 -1
- package/src/commands/diff.js +33 -1
- package/src/commands/disable.js +31 -1
- package/src/commands/enable.js +31 -1
- package/src/commands/feedback.js +41 -1
- package/src/commands/fork.js +35 -1
- package/src/commands/groups.js +40 -1
- package/src/commands/init.js +35 -1
- package/src/commands/install.js +35 -1
- package/src/commands/list.js +29 -1
- package/src/commands/login.js +34 -1
- package/src/commands/logout.js +20 -1
- package/src/commands/people.js +37 -1
- package/src/commands/postlex.js +38 -0
- package/src/commands/publish.js +40 -1
- package/src/commands/pull.js +47 -1
- package/src/commands/reconcile.js +36 -1
- package/src/commands/release.js +51 -1
- package/src/commands/schema.js +5 -0
- package/src/commands/search.js +46 -1
- package/src/commands/self-update.js +27 -1
- package/src/commands/setup.js +32 -1
- package/src/commands/snapshot.js +38 -1
- package/src/commands/status.js +26 -1
- package/src/commands/uninstall.js +30 -1
- package/src/commands/update.js +40 -1
- package/src/commands/validate.js +37 -1
- package/src/commands/versions.js +34 -1
- package/src/commands/visibility.js +33 -1
- package/src/commands/whoami.js +30 -1
- package/src/constants/error_codes.js +12 -1
- package/src/constants/next_step_actions.js +11 -1
- package/src/constants/next_step_by_error_code.js +27 -6
- package/src/constants/next_step_by_error_code.test.js +30 -0
- package/src/index.js +1 -1
- package/src/integration/cli.test.js +17 -4
- package/src/integration/schema.test.js +31 -0
- package/src/ui/output.js +7 -0
|
@@ -93,4 +93,33 @@ const run = (args) => catch_errors('Uninstall failed', async () => {
|
|
|
93
93
|
}
|
|
94
94
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
const schema = {
|
|
97
|
+
name: 'uninstall',
|
|
98
|
+
audience: 'consumer',
|
|
99
|
+
purpose: 'Remove one or more skills and prune their orphaned dependencies.',
|
|
100
|
+
mutation: true,
|
|
101
|
+
interactive_in_text_mode: false,
|
|
102
|
+
input: {
|
|
103
|
+
positional: [ { name: 'skills', required: true, type: 'string[]', pattern: '<owner>/<name>' } ],
|
|
104
|
+
flags: [
|
|
105
|
+
{ name: 'global', alias: 'g', type: 'boolean', default: false, description: 'Remove from global scope' },
|
|
106
|
+
{ name: 'agents', type: 'string', default: undefined, description: 'Target specific agents (comma-separated)' },
|
|
107
|
+
{ name: 'yes', alias: 'y', type: 'boolean', default: false, description: 'Skip confirmation prompts' },
|
|
108
|
+
{ name: 'json', type: 'boolean', default: false, description: 'Output as JSON' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
output: {
|
|
112
|
+
data_shape: {
|
|
113
|
+
results: 'array<{ skill: string, status: string, removed: string[], orphans_pruned: string[] } | { skill: string, status: "failed", error: { code: string, message: string } }>',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
errors: [
|
|
117
|
+
{ code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
|
|
118
|
+
],
|
|
119
|
+
examples: [
|
|
120
|
+
'happyskills uninstall acme/deploy-aws',
|
|
121
|
+
'happyskills uninstall acme/deploy-aws acme/monitor acme/logging',
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { run, schema }
|
package/src/commands/update.js
CHANGED
|
@@ -333,4 +333,43 @@ const run = (args) => catch_errors('Update failed', async () => {
|
|
|
333
333
|
}
|
|
334
334
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
335
335
|
|
|
336
|
-
|
|
336
|
+
const schema = {
|
|
337
|
+
name: 'update',
|
|
338
|
+
audience: 'consumer',
|
|
339
|
+
purpose: 'Bring installed skills up to date by batch-checking the registry and re-installing only outdated skills.',
|
|
340
|
+
mutation: true,
|
|
341
|
+
interactive_in_text_mode: true,
|
|
342
|
+
input: {
|
|
343
|
+
positional: [{ name: 'skill', required: false, type: 'string', pattern: '<owner>/<name>' }],
|
|
344
|
+
flags: [
|
|
345
|
+
{ name: 'all', type: 'boolean', default: false },
|
|
346
|
+
{ name: 'force', type: 'boolean', default: false },
|
|
347
|
+
{ name: 'global', type: 'boolean', default: false },
|
|
348
|
+
{ name: 'yes', type: 'boolean', default: false },
|
|
349
|
+
{ name: 'agents', type: 'string', default: null },
|
|
350
|
+
{ name: 'json', type: 'boolean', default: false }
|
|
351
|
+
]
|
|
352
|
+
},
|
|
353
|
+
output: {
|
|
354
|
+
data_shape: {
|
|
355
|
+
results: 'array<{ skill: string, installed: string, latest: string, status: string }>',
|
|
356
|
+
outdated_count: 'number',
|
|
357
|
+
up_to_date_count: 'number',
|
|
358
|
+
drift_count: 'number',
|
|
359
|
+
updated: 'array<{ skill: string, from: string, to: string }>',
|
|
360
|
+
skipped: 'array<{ skill: string, reason: string, suggestion: string }>',
|
|
361
|
+
already_up_to_date: 'array<{ skill: string, version: string }>',
|
|
362
|
+
symlink_repairs: 'array',
|
|
363
|
+
errors: 'array<{ skill: string, message: string }>'
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
errors: [
|
|
367
|
+
{ code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } }
|
|
368
|
+
],
|
|
369
|
+
examples: [
|
|
370
|
+
'happyskills update acme/deploy-aws',
|
|
371
|
+
'happyskills up --all -y --json'
|
|
372
|
+
]
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
module.exports = { run, schema }
|
package/src/commands/validate.js
CHANGED
|
@@ -232,4 +232,40 @@ const run = (args) => catch_errors('Validate failed', async () => {
|
|
|
232
232
|
process.exit(has_errors ? EXIT_CODES.ERROR : EXIT_CODES.SUCCESS)
|
|
233
233
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
const schema = {
|
|
236
|
+
name: 'validate',
|
|
237
|
+
audience: 'author',
|
|
238
|
+
purpose: 'Validate a local skill against all agentskills.io spec rules (structure, cross-file, file sizes, changelog, dependencies).',
|
|
239
|
+
mutation: true,
|
|
240
|
+
interactive_in_text_mode: false,
|
|
241
|
+
input: {
|
|
242
|
+
positional: [
|
|
243
|
+
{ name: 'skill-name', required: true, type: 'string', pattern: '<skill-name>' },
|
|
244
|
+
],
|
|
245
|
+
flags: [
|
|
246
|
+
{ name: 'global', short: 'g', type: 'boolean', default: false },
|
|
247
|
+
{ name: 'json', type: 'boolean', default: false },
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
output: {
|
|
251
|
+
data_shape: {
|
|
252
|
+
skill: 'string',
|
|
253
|
+
valid: 'boolean',
|
|
254
|
+
errors: 'array',
|
|
255
|
+
warnings: 'array',
|
|
256
|
+
checks_passed: 'number',
|
|
257
|
+
checks_failed: 'number',
|
|
258
|
+
checks_warned: 'number',
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
errors: [
|
|
262
|
+
{ code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
|
|
263
|
+
{ code: 'VALIDATION_FAILED', next_step: { kind: 'recovery', action: 'fix_validation_errors' } },
|
|
264
|
+
],
|
|
265
|
+
examples: [
|
|
266
|
+
'happyskills validate my-skill',
|
|
267
|
+
'happyskills v my-skill --json',
|
|
268
|
+
],
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = { run, schema }
|
package/src/commands/versions.js
CHANGED
|
@@ -89,4 +89,37 @@ const run = (args) => catch_errors('Versions failed', async () => {
|
|
|
89
89
|
print_table(['VERSION', 'PUBLISHED', 'COMMIT', 'MESSAGE'], rows)
|
|
90
90
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
const schema = {
|
|
93
|
+
name: 'versions',
|
|
94
|
+
audience: 'consumer',
|
|
95
|
+
purpose: 'List all published versions of a skill from the registry, newest first.',
|
|
96
|
+
mutation: false,
|
|
97
|
+
interactive_in_text_mode: false,
|
|
98
|
+
input: {
|
|
99
|
+
positional: [
|
|
100
|
+
{ name: 'skill', required: true, type: 'string', pattern: '<owner>/<name>' },
|
|
101
|
+
],
|
|
102
|
+
flags: [
|
|
103
|
+
{ name: 'limit', type: 'number', default: null },
|
|
104
|
+
{ name: 'json', type: 'boolean', default: false },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
output: {
|
|
108
|
+
data_shape: {
|
|
109
|
+
skill: 'string',
|
|
110
|
+
count: 'number',
|
|
111
|
+
versions: 'array<{ version: string, ref: string, commit: string, message: string, published_at: string|null }>',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
errors: [
|
|
115
|
+
{ code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
|
|
116
|
+
{ code: 'NOT_FOUND' },
|
|
117
|
+
{ code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } },
|
|
118
|
+
],
|
|
119
|
+
examples: [
|
|
120
|
+
'happyskills versions acme/deploy-aws',
|
|
121
|
+
'happyskills versions acme/deploy-aws --limit 20 --json',
|
|
122
|
+
],
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = { run, extract_version, sort_refs, schema }
|
|
@@ -76,4 +76,36 @@ const run = (args) => catch_errors('Visibility failed', async () => {
|
|
|
76
76
|
}
|
|
77
77
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
const schema = {
|
|
80
|
+
name: 'visibility',
|
|
81
|
+
audience: 'author',
|
|
82
|
+
purpose: 'Check or set a skill\'s visibility (public, private, workspace) in the registry.',
|
|
83
|
+
mutation: true,
|
|
84
|
+
interactive_in_text_mode: false,
|
|
85
|
+
input: {
|
|
86
|
+
positional: [
|
|
87
|
+
{ name: 'skill', required: true, type: 'string', pattern: '<owner>/<name>' },
|
|
88
|
+
{ name: 'visibility', required: false, type: 'string', pattern: 'public|private|workspace' },
|
|
89
|
+
],
|
|
90
|
+
flags: [
|
|
91
|
+
{ name: 'json', type: 'boolean', default: false },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
output: {
|
|
95
|
+
data_shape: {
|
|
96
|
+
skill: 'string',
|
|
97
|
+
visibility: 'string',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
errors: [
|
|
101
|
+
{ code: 'USAGE_ERROR', next_step: { kind: 'routing', action: 'discover_schema' } },
|
|
102
|
+
{ code: 'AUTH_REQUIRED', next_step: { kind: 'recovery', action: 'login' } },
|
|
103
|
+
{ code: 'NOT_FOUND' },
|
|
104
|
+
],
|
|
105
|
+
examples: [
|
|
106
|
+
'happyskills visibility acme/deploy-aws',
|
|
107
|
+
'happyskills visibility acme/deploy-aws public',
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { run, schema }
|
package/src/commands/whoami.js
CHANGED
|
@@ -67,4 +67,33 @@ const run = (args) => catch_errors('Whoami failed', async () => {
|
|
|
67
67
|
}
|
|
68
68
|
}).then(([errors]) => { if (errors) { exit_with_error(errors); return } })
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
const schema = {
|
|
71
|
+
name: 'whoami',
|
|
72
|
+
audience: 'account',
|
|
73
|
+
purpose: 'Show the currently authenticated user and their workspaces.',
|
|
74
|
+
mutation: false,
|
|
75
|
+
interactive_in_text_mode: false,
|
|
76
|
+
input: {
|
|
77
|
+
positional: [],
|
|
78
|
+
flags: [
|
|
79
|
+
{ name: 'json', type: 'boolean', default: false }
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
output: {
|
|
83
|
+
data_shape: {
|
|
84
|
+
username: 'string',
|
|
85
|
+
email: 'string',
|
|
86
|
+
workspaces: 'array'
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
errors: [
|
|
90
|
+
{ code: 'AUTH_REQUIRED', next_step: { kind: 'recovery', action: 'login' } },
|
|
91
|
+
{ code: 'NETWORK_ERROR', next_step: { kind: 'recovery', action: 'retry' } }
|
|
92
|
+
],
|
|
93
|
+
examples: [
|
|
94
|
+
'happyskills whoami',
|
|
95
|
+
'happyskills whoami --json'
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { run, schema }
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
// Auth & permission ────────────────────────────────────────────────────────
|
|
15
15
|
const AUTH_REQUIRED = 'AUTH_REQUIRED'
|
|
16
16
|
const INVALID_CREDENTIALS = 'INVALID_CREDENTIALS'
|
|
17
|
+
const AUTH_FAILED = 'AUTH_FAILED'
|
|
17
18
|
const EXPIRED_TOKEN = 'EXPIRED_TOKEN'
|
|
18
19
|
const INTERACTIVE_REQUIRED = 'INTERACTIVE_REQUIRED'
|
|
19
20
|
const FORBIDDEN = 'FORBIDDEN'
|
|
@@ -109,6 +110,13 @@ const WRITE_FAILED = 'WRITE_FAILED'
|
|
|
109
110
|
const PUBLISH_FAILED = 'PUBLISH_FAILED'
|
|
110
111
|
const RANKING_SCHEMA_MISMATCH = 'RANKING_SCHEMA_MISMATCH'
|
|
111
112
|
|
|
113
|
+
// pull --rebase pipeline (client-side, spec 260523-02) ──────────────────────
|
|
114
|
+
const COMPARE_FAILED = 'COMPARE_FAILED'
|
|
115
|
+
const CLONE_BASE_FAILED = 'CLONE_BASE_FAILED'
|
|
116
|
+
const CLONE_REMOTE_FAILED = 'CLONE_REMOTE_FAILED'
|
|
117
|
+
const READ_LOCAL_FAILED = 'READ_LOCAL_FAILED'
|
|
118
|
+
const SWAP_FAILED = 'SWAP_FAILED'
|
|
119
|
+
|
|
112
120
|
// Internal ─────────────────────────────────────────────────────────────────
|
|
113
121
|
const INTERNAL_ERROR = 'INTERNAL_ERROR'
|
|
114
122
|
const PERSIST_FAILED = 'PERSIST_FAILED'
|
|
@@ -147,7 +155,8 @@ const NOT_COMMUNITY_LISTED = 'NOT_COMMUNITY_LISTED'
|
|
|
147
155
|
const UNKNOWN_CODE = 'UNKNOWN_CODE'
|
|
148
156
|
|
|
149
157
|
const ERROR_CODES = Object.freeze({
|
|
150
|
-
AUTH_REQUIRED, INVALID_CREDENTIALS,
|
|
158
|
+
AUTH_REQUIRED, INVALID_CREDENTIALS, AUTH_FAILED, EXPIRED_TOKEN,
|
|
159
|
+
INTERACTIVE_REQUIRED,
|
|
151
160
|
FORBIDDEN, INSUFFICIENT_ACCESS, LAST_OWNER, ORG_RESTRICTED,
|
|
152
161
|
NETWORK_ERROR, RATE_LIMITED, DB_UNAVAILABLE, GITHUB_UNAVAILABLE,
|
|
153
162
|
EMBEDDING_UNAVAILABLE, REGISTRY_UNAVAILABLE,
|
|
@@ -172,6 +181,8 @@ const ERROR_CODES = Object.freeze({
|
|
|
172
181
|
AUTHORIZATION_PENDING,
|
|
173
182
|
COMMAND_NOT_FOUND, MIN_CLI_VERSION, SNAPSHOT_FAILED, WRITE_FAILED,
|
|
174
183
|
PUBLISH_FAILED, RANKING_SCHEMA_MISMATCH,
|
|
184
|
+
COMPARE_FAILED, CLONE_BASE_FAILED, CLONE_REMOTE_FAILED, READ_LOCAL_FAILED,
|
|
185
|
+
SWAP_FAILED,
|
|
175
186
|
INTERNAL_ERROR, PERSIST_FAILED, VERIFICATION_FAILED,
|
|
176
187
|
NO_COGNITO_USER, COGNITO_SYNC_FAILED, COGNITO_UNLINK_FAILED,
|
|
177
188
|
ALREADY_HAS_PASSWORD, EMAIL_ALIAS_CONFLICT, ALREADY_APPROVED,
|
|
@@ -31,6 +31,8 @@ const PROVIDE_CHANGELOG = 'provide_changelog'
|
|
|
31
31
|
const SELF_UPDATE = 'self_update'
|
|
32
32
|
const SHOW_FORMAT = 'show_format'
|
|
33
33
|
const RETRY_RANK = 'retry_rank'
|
|
34
|
+
const RETRY_OR_ABANDON = 'retry_or_abandon'
|
|
35
|
+
const REVIEW_PUBLISH_ERROR = 'review_publish_error'
|
|
34
36
|
|
|
35
37
|
// kind: clarification
|
|
36
38
|
const CLARIFY_QUERY = 'clarify_query'
|
|
@@ -45,6 +47,7 @@ const SPECIFY_WORKSPACE = 'specify_workspace'
|
|
|
45
47
|
const SPECIFY_BUMP_TYPE = 'specify_bump_type'
|
|
46
48
|
const RESOLVE_BUMP_DISAGREEMENT = 'resolve_bump_disagreement'
|
|
47
49
|
const PICK_VERSION = 'pick_version'
|
|
50
|
+
const RESOLVE_UNKNOWN_DRIFT = 'resolve_unknown_drift'
|
|
48
51
|
|
|
49
52
|
// kind: confirmation
|
|
50
53
|
const CONFIRM_DISCARD_OR_SNAPSHOT_FIRST = 'confirm_discard_or_snapshot_first'
|
|
@@ -59,6 +62,7 @@ const ATTACH_SCREENSHOT = 'attach_screenshot' // § 15.3.4
|
|
|
59
62
|
|
|
60
63
|
// kind: routing
|
|
61
64
|
const INSTALL_FIRST = 'install_first'
|
|
65
|
+
const DISCOVER_SCHEMA = 'discover_schema'
|
|
62
66
|
|
|
63
67
|
// kind lookup ──────────────────────────────────────────────────────────────
|
|
64
68
|
const ACTION_KIND = Object.freeze({
|
|
@@ -71,6 +75,8 @@ const ACTION_KIND = Object.freeze({
|
|
|
71
75
|
[SELF_UPDATE]: RECOVERY,
|
|
72
76
|
[SHOW_FORMAT]: RECOVERY,
|
|
73
77
|
[RETRY_RANK]: RECOVERY,
|
|
78
|
+
[RETRY_OR_ABANDON]: RECOVERY,
|
|
79
|
+
[REVIEW_PUBLISH_ERROR]: RECOVERY,
|
|
74
80
|
|
|
75
81
|
[CLARIFY_QUERY]: CLARIFICATION,
|
|
76
82
|
|
|
@@ -83,6 +89,7 @@ const ACTION_KIND = Object.freeze({
|
|
|
83
89
|
[SPECIFY_BUMP_TYPE]: DECISION,
|
|
84
90
|
[RESOLVE_BUMP_DISAGREEMENT]: DECISION,
|
|
85
91
|
[PICK_VERSION]: DECISION,
|
|
92
|
+
[RESOLVE_UNKNOWN_DRIFT]: DECISION,
|
|
86
93
|
|
|
87
94
|
[CONFIRM_DISCARD_OR_SNAPSHOT_FIRST]: CONFIRMATION,
|
|
88
95
|
[CONFIRM_CASCADE]: CONFIRMATION,
|
|
@@ -94,19 +101,22 @@ const ACTION_KIND = Object.freeze({
|
|
|
94
101
|
[ATTACH_SCREENSHOT]: CONTINUATION,
|
|
95
102
|
|
|
96
103
|
[INSTALL_FIRST]: ROUTING,
|
|
104
|
+
[DISCOVER_SCHEMA]: ROUTING,
|
|
97
105
|
})
|
|
98
106
|
|
|
99
107
|
const NEXT_STEP_ACTIONS = Object.freeze({
|
|
100
108
|
LOGIN, RETRY, RECONCILE_FIRST, PULL_REBASE_FIRST, FIX_VALIDATION_ERRORS,
|
|
101
109
|
PROVIDE_CHANGELOG, SELF_UPDATE, SHOW_FORMAT, RETRY_RANK,
|
|
110
|
+
RETRY_OR_ABANDON, REVIEW_PUBLISH_ERROR,
|
|
102
111
|
CLARIFY_QUERY,
|
|
103
112
|
RESOLVE_REGRESSION, RESOLVE_MISSING_SKILL_JSON, RESOLVE_MISSING_DIR,
|
|
104
113
|
RESOLVE_CONFLICTS, RESOLVE_PATCH_REJECTIONS, SPECIFY_WORKSPACE,
|
|
105
114
|
SPECIFY_BUMP_TYPE, RESOLVE_BUMP_DISAGREEMENT, PICK_VERSION,
|
|
115
|
+
RESOLVE_UNKNOWN_DRIFT,
|
|
106
116
|
CONFIRM_DISCARD_OR_SNAPSHOT_FIRST, CONFIRM_CASCADE, CONFIRM_DESTRUCTIVE,
|
|
107
117
|
PASS_YES_FLAG,
|
|
108
118
|
RANK_DIGESTS_INLINE, PRESENT_TO_USER, ATTACH_SCREENSHOT,
|
|
109
|
-
INSTALL_FIRST,
|
|
119
|
+
INSTALL_FIRST, DISCOVER_SCHEMA,
|
|
110
120
|
})
|
|
111
121
|
|
|
112
122
|
const NEXT_STEP_ACTION_LIST = Object.freeze(Object.values(NEXT_STEP_ACTIONS).slice().sort())
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
// applies — agent falls back to "explain to principal").
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
|
-
RECOVERY, DECISION, CONFIRMATION,
|
|
12
|
+
RECOVERY, DECISION, CONFIRMATION, ROUTING,
|
|
13
13
|
LOGIN, RETRY, RECONCILE_FIRST, PULL_REBASE_FIRST, FIX_VALIDATION_ERRORS,
|
|
14
|
-
PROVIDE_CHANGELOG, SELF_UPDATE, SHOW_FORMAT,
|
|
14
|
+
PROVIDE_CHANGELOG, SELF_UPDATE, SHOW_FORMAT, DISCOVER_SCHEMA,
|
|
15
15
|
RESOLVE_REGRESSION, RESOLVE_MISSING_SKILL_JSON, RESOLVE_MISSING_DIR,
|
|
16
16
|
RESOLVE_CONFLICTS, RESOLVE_PATCH_REJECTIONS, SPECIFY_WORKSPACE,
|
|
17
17
|
SPECIFY_BUMP_TYPE, RESOLVE_BUMP_DISAGREEMENT, PICK_VERSION,
|
|
@@ -45,12 +45,25 @@ const confirmation = (action, instructions, context = {}) => ({
|
|
|
45
45
|
principal_authorization_required: true,
|
|
46
46
|
})
|
|
47
47
|
|
|
48
|
+
const routing = (action, instructions, context = {}, opts = {}) => ({
|
|
49
|
+
kind: ROUTING,
|
|
50
|
+
action,
|
|
51
|
+
instructions,
|
|
52
|
+
context,
|
|
53
|
+
...(opts.route_to_skill ? { route_to_skill: opts.route_to_skill } : {}),
|
|
54
|
+
})
|
|
55
|
+
|
|
48
56
|
const NEXT_STEP_BY_ERROR_CODE = Object.freeze({
|
|
49
57
|
AUTH_REQUIRED: () => recovery(
|
|
50
58
|
LOGIN,
|
|
51
59
|
'Sign in to HappySkills, then re-run the command.',
|
|
52
60
|
{ commands: ['npx happyskills login --browser --json'] }
|
|
53
61
|
),
|
|
62
|
+
AUTH_FAILED: () => recovery(
|
|
63
|
+
LOGIN,
|
|
64
|
+
'The sign-in attempt did not complete. Start the login flow again, then re-run the command.',
|
|
65
|
+
{ commands: ['npx happyskills login --browser --json'] }
|
|
66
|
+
),
|
|
54
67
|
EXPIRED_TOKEN: () => recovery(
|
|
55
68
|
LOGIN,
|
|
56
69
|
'Your session has expired. Sign in again, then re-run the command.',
|
|
@@ -86,13 +99,21 @@ const NEXT_STEP_BY_ERROR_CODE = Object.freeze({
|
|
|
86
99
|
'The embedding service is temporarily unavailable. Retry shortly.',
|
|
87
100
|
{ retry_after_seconds: 10, max_attempts: 3 }
|
|
88
101
|
),
|
|
89
|
-
COMMAND_NOT_FOUND: (_msg, ctx = {}) =>
|
|
90
|
-
|
|
91
|
-
'The command does not exist.
|
|
102
|
+
COMMAND_NOT_FOUND: (_msg, ctx = {}) => routing(
|
|
103
|
+
DISCOVER_SCHEMA,
|
|
104
|
+
'The command does not exist. Run `happyskills schema --json` to discover every available command with its exact input, output, and error contract. If a suggestion is present and it matches the intent, you may re-run with that command instead.',
|
|
92
105
|
{
|
|
93
106
|
got: ctx.got,
|
|
94
107
|
...(ctx.suggestion ? { suggestion: ctx.suggestion } : {}),
|
|
95
|
-
commands: ['npx happyskills --
|
|
108
|
+
commands: ['npx happyskills schema --json'],
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
USAGE_ERROR: (_msg, ctx = {}) => routing(
|
|
112
|
+
DISCOVER_SCHEMA,
|
|
113
|
+
'The command was invoked with invalid arguments or flags. Run `happyskills schema --json` to see the exact input contract for every command, then re-run with corrected arguments.',
|
|
114
|
+
{
|
|
115
|
+
...(ctx.got ? { got: ctx.got } : {}),
|
|
116
|
+
commands: ['npx happyskills schema --json'],
|
|
96
117
|
}
|
|
97
118
|
),
|
|
98
119
|
INVALID_SLUG: () => recovery(
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// Unit coverage for the schema-discovery routing defaults. A generic agent
|
|
3
|
+
// that reaches for the conventional `help` fallback must be routed to the
|
|
4
|
+
// `schema --json` surface — the machine-readable contract is what gives it
|
|
5
|
+
// clarity and correctness on how to use the CLI, not just discoverability.
|
|
6
|
+
|
|
7
|
+
const { describe, it } = require('node:test')
|
|
8
|
+
const assert = require('node:assert/strict')
|
|
9
|
+
const { next_step_for_error } = require('./next_step_by_error_code')
|
|
10
|
+
const { DISCOVER_SCHEMA, ROUTING, kind_for_action } = require('./next_step_actions')
|
|
11
|
+
|
|
12
|
+
describe('next_step_by_error_code — schema discovery routing', () => {
|
|
13
|
+
it('discover_schema is a routing-kind action', () => {
|
|
14
|
+
assert.strictEqual(kind_for_action(DISCOVER_SCHEMA), ROUTING)
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
for (const code of ['COMMAND_NOT_FOUND', 'USAGE_ERROR']) {
|
|
18
|
+
it(`${code} routes the agent to \`schema --json\``, () => {
|
|
19
|
+
const ns = next_step_for_error(code, 'boom', { got: 'foo' })
|
|
20
|
+
assert.ok(ns, `${code} must produce a next_step`)
|
|
21
|
+
assert.strictEqual(ns.kind, ROUTING)
|
|
22
|
+
assert.strictEqual(ns.action, DISCOVER_SCHEMA)
|
|
23
|
+
assert.ok(Array.isArray(ns.context.commands))
|
|
24
|
+
assert.ok(
|
|
25
|
+
ns.context.commands.some(c => c.includes('schema --json')),
|
|
26
|
+
`${code} next_step must point at \`happyskills schema --json\``
|
|
27
|
+
)
|
|
28
|
+
})
|
|
29
|
+
}
|
|
30
|
+
})
|
package/src/index.js
CHANGED
|
@@ -206,7 +206,7 @@ const run = (argv) => {
|
|
|
206
206
|
if (suggestion) {
|
|
207
207
|
console.error(dim(` Did you mean: happyskills ${suggestion}?`))
|
|
208
208
|
} else {
|
|
209
|
-
console.error(dim(` Run happyskills --help for available commands.`))
|
|
209
|
+
console.error(dim(` Run happyskills --help for available commands, or happyskills schema --json for the full machine-readable surface.`))
|
|
210
210
|
}
|
|
211
211
|
return process.exit(EXIT_CODES.USAGE)
|
|
212
212
|
}
|
|
@@ -280,14 +280,27 @@ describe('CLI — --json: stdout is always valid JSON', () => {
|
|
|
280
280
|
assert.ok(typeof env.error.message === 'string' && env.error.message.length > 0)
|
|
281
281
|
})
|
|
282
282
|
|
|
283
|
-
it('unknown command with --json
|
|
283
|
+
it('unknown command with --json routes the agent to schema discovery', () => {
|
|
284
284
|
const { stdout, code } = run(['not-a-command', '--json'])
|
|
285
285
|
assert.strictEqual(code, 2)
|
|
286
286
|
const env = parse_json_output(stdout, 'unknown-command --json')
|
|
287
287
|
assert_error_envelope(env, 'COMMAND_NOT_FOUND', 'unknown command')
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
assert.
|
|
288
|
+
// Routing hint: discover_schema, pointing at `schema --json`. A confused
|
|
289
|
+
// agent that hit a bad command is handed the full machine-readable surface.
|
|
290
|
+
assert.strictEqual(env.next_step.kind, 'routing')
|
|
291
|
+
assert.strictEqual(env.next_step.action, 'discover_schema')
|
|
292
|
+
assert.ok(env.next_step.context.commands.some(c => c.includes('schema --json')))
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('usage error with --json routes the agent to schema discovery', () => {
|
|
296
|
+
// `--json --text` is mutually exclusive → USAGE_ERROR with no command-
|
|
297
|
+
// specific next_step, so the default discover_schema routing applies.
|
|
298
|
+
const { stdout, code } = run(['--json', '--text'])
|
|
299
|
+
assert.strictEqual(code, 2)
|
|
300
|
+
const env = parse_json_output(stdout, 'usage-error --json')
|
|
301
|
+
assert_error_envelope(env, 'USAGE_ERROR', 'mutually exclusive')
|
|
302
|
+
assert.strictEqual(env.next_step.action, 'discover_schema')
|
|
303
|
+
assert.ok(env.next_step.context.commands.some(c => c.includes('schema --json')))
|
|
291
304
|
})
|
|
292
305
|
|
|
293
306
|
it('network error produces an envelope with code NETWORK_ERROR + retry next_step', () => {
|
|
@@ -94,6 +94,37 @@ describe('happyskills schema --json', () => {
|
|
|
94
94
|
}
|
|
95
95
|
})
|
|
96
96
|
|
|
97
|
+
it('no command returns the stub purpose (anti-stub guard — spec 260602-01)', () => {
|
|
98
|
+
const { stdout } = run(['schema', '--json'])
|
|
99
|
+
const env = parse_envelope(stdout, 'schema --json anti-stub')
|
|
100
|
+
const stubbed = env.data.commands.filter(c => /no curated purpose/i.test(c.purpose))
|
|
101
|
+
assert.deepStrictEqual(
|
|
102
|
+
stubbed.map(c => c.name),
|
|
103
|
+
[],
|
|
104
|
+
`these commands still return the stub purpose and need a curated schema export: ${stubbed.map(c => c.name).join(', ')}`
|
|
105
|
+
)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('every command carries a non-empty examples array of strings', () => {
|
|
109
|
+
const { stdout } = run(['schema', '--json'])
|
|
110
|
+
const env = parse_envelope(stdout, 'schema --json examples')
|
|
111
|
+
for (const cmd of env.data.commands) {
|
|
112
|
+
assert.ok(Array.isArray(cmd.examples) && cmd.examples.length > 0, `command.examples must be a non-empty array for ${cmd.name}`)
|
|
113
|
+
for (const ex of cmd.examples) {
|
|
114
|
+
assert.ok(typeof ex === 'string' && ex.length > 0, `command.examples[] entries must be non-empty strings for ${cmd.name}`)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('every command.input exposes positional and flags arrays', () => {
|
|
120
|
+
const { stdout } = run(['schema', '--json'])
|
|
121
|
+
const env = parse_envelope(stdout, 'schema --json input shape')
|
|
122
|
+
for (const cmd of env.data.commands) {
|
|
123
|
+
assert.ok(Array.isArray(cmd.input.positional), `command.input.positional must be an array for ${cmd.name}`)
|
|
124
|
+
assert.ok(Array.isArray(cmd.input.flags), `command.input.flags must be an array for ${cmd.name}`)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
97
128
|
it('every command.errors[].code is in the closed enum, and the kind/action pair is valid', () => {
|
|
98
129
|
const { stdout } = run(['schema', '--json'])
|
|
99
130
|
const env = parse_envelope(stdout, 'schema --json error refs')
|
package/src/ui/output.js
CHANGED
|
@@ -44,8 +44,15 @@ const format_help = (text) => {
|
|
|
44
44
|
}).join('\n')
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
// Every --help (global and per-command) ends with a pointer to `schema
|
|
48
|
+
// --json`. An agent that reaches for the conventional `help` as a fallback
|
|
49
|
+
// then discovers the machine-readable surface, which is what it actually
|
|
50
|
+
// wants for clarity and correctness on how to use the CLI.
|
|
51
|
+
const SCHEMA_HINT = 'For the complete machine-readable CLI surface — every command\'s inputs, outputs, and error contracts in one call — run: happyskills schema --json'
|
|
52
|
+
|
|
47
53
|
const print_help = (text) => {
|
|
48
54
|
console.log(format_help(text))
|
|
55
|
+
if (!is_json_mode()) console.log(dim(`\n${SCHEMA_HINT}`))
|
|
49
56
|
}
|
|
50
57
|
|
|
51
58
|
// Visible length of a string, ignoring ANSI escape codes
|