@crouton-kit/crouter 0.2.6 → 0.3.1

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 (79) hide show
  1. package/dist/builtin-skills/skills/crouter-development/marketplaces/SKILL.md +9 -9
  2. package/dist/builtin-skills/skills/crouter-development/plugins/SKILL.md +19 -19
  3. package/dist/cli.js +42 -37
  4. package/dist/commands/__tests__/human.test.d.ts +1 -0
  5. package/dist/commands/__tests__/human.test.js +214 -0
  6. package/dist/commands/__tests__/skill.test.d.ts +1 -0
  7. package/dist/commands/__tests__/skill.test.js +287 -0
  8. package/dist/commands/debug.d.ts +3 -0
  9. package/dist/commands/debug.js +179 -0
  10. package/dist/commands/flow.d.ts +2 -0
  11. package/dist/commands/flow.js +24 -0
  12. package/dist/commands/human.d.ts +2 -0
  13. package/dist/commands/human.js +480 -0
  14. package/dist/commands/job.d.ts +2 -0
  15. package/dist/commands/job.js +669 -0
  16. package/dist/commands/pkg.d.ts +2 -0
  17. package/dist/commands/pkg.js +1021 -0
  18. package/dist/commands/plan.d.ts +4 -2
  19. package/dist/commands/plan.js +306 -22
  20. package/dist/commands/skill.d.ts +2 -2
  21. package/dist/commands/skill.js +607 -456
  22. package/dist/commands/spec.d.ts +3 -2
  23. package/dist/commands/spec.js +283 -10
  24. package/dist/commands/sys.d.ts +2 -0
  25. package/dist/commands/sys.js +712 -0
  26. package/dist/core/__tests__/argv-parser.test.d.ts +1 -0
  27. package/dist/core/__tests__/argv-parser.test.js +199 -0
  28. package/dist/core/__tests__/flow-leaves.test.d.ts +1 -0
  29. package/dist/core/__tests__/flow-leaves.test.js +248 -0
  30. package/dist/core/__tests__/job.test.d.ts +1 -0
  31. package/dist/core/__tests__/job.test.js +346 -0
  32. package/dist/core/__tests__/pkg.test.d.ts +1 -0
  33. package/dist/core/__tests__/pkg.test.js +218 -0
  34. package/dist/core/__tests__/sys.test.d.ts +1 -0
  35. package/dist/core/__tests__/sys.test.js +208 -0
  36. package/dist/core/artifact.d.ts +29 -18
  37. package/dist/core/artifact.js +78 -221
  38. package/dist/core/auto-update.js +11 -3
  39. package/dist/core/command.d.ts +36 -0
  40. package/dist/core/command.js +287 -0
  41. package/dist/core/errors.d.ts +3 -0
  42. package/dist/core/errors.js +5 -0
  43. package/dist/core/fs-utils.d.ts +1 -0
  44. package/dist/core/fs-utils.js +4 -0
  45. package/dist/core/help.d.ts +98 -0
  46. package/dist/core/help.js +163 -0
  47. package/dist/core/io.d.ts +29 -0
  48. package/dist/core/io.js +83 -0
  49. package/dist/core/jobs.d.ts +87 -0
  50. package/dist/core/jobs.js +353 -0
  51. package/dist/core/pagination.d.ts +33 -0
  52. package/dist/core/pagination.js +89 -0
  53. package/dist/core/self-update.d.ts +21 -0
  54. package/dist/{commands/update.js → core/self-update.js} +28 -63
  55. package/dist/core/spawn.d.ts +47 -65
  56. package/dist/core/spawn.js +78 -228
  57. package/dist/prompts/agent.d.ts +10 -5
  58. package/dist/prompts/agent.js +51 -74
  59. package/dist/prompts/debug.d.ts +8 -0
  60. package/dist/prompts/debug.js +37 -0
  61. package/dist/prompts/review.js +4 -11
  62. package/dist/prompts/skill.d.ts +0 -1
  63. package/dist/prompts/skill.js +95 -149
  64. package/package.json +4 -2
  65. package/dist/commands/agent.d.ts +0 -2
  66. package/dist/commands/agent.js +0 -265
  67. package/dist/commands/config.d.ts +0 -2
  68. package/dist/commands/config.js +0 -146
  69. package/dist/commands/doctor.d.ts +0 -2
  70. package/dist/commands/doctor.js +0 -268
  71. package/dist/commands/marketplace.d.ts +0 -2
  72. package/dist/commands/marketplace.js +0 -365
  73. package/dist/commands/plugin.d.ts +0 -2
  74. package/dist/commands/plugin.js +0 -367
  75. package/dist/commands/update.d.ts +0 -4
  76. package/dist/prompts/plan.d.ts +0 -1
  77. package/dist/prompts/plan.js +0 -175
  78. package/dist/prompts/spec.d.ts +0 -1
  79. package/dist/prompts/spec.js +0 -153
@@ -0,0 +1,346 @@
1
+ // Tests for the job subtree argv migration.
2
+ // Exercises parseArgv against each leaf's param schema directly — no subprocess,
3
+ // no tmux, no filesystem side-effects from the handler.
4
+ //
5
+ // Run with: node --import tsx/esm --test src/core/__tests__/job.test.ts
6
+ import { test, describe } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
9
+ import { tmpdir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ import { parseArgv } from '../command.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Param schemas extracted from each leaf (mirrors job.ts exactly)
14
+ // ---------------------------------------------------------------------------
15
+ const startPromptParams = [
16
+ { kind: 'stdin', name: 'prompt', required: true, constraint: '' },
17
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
18
+ ];
19
+ const startForkParams = [
20
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
21
+ ];
22
+ const startPlannerParams = [
23
+ { kind: 'positional', name: 'spec_path', type: 'path', required: true, constraint: '' },
24
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
25
+ ];
26
+ const startImplementerParams = [
27
+ { kind: 'positional', name: 'plan_path', type: 'path', required: true, constraint: '' },
28
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
29
+ ];
30
+ const startReviewerParams = [
31
+ { kind: 'positional', name: 'artifact_path', type: 'path', required: true, constraint: '' },
32
+ { kind: 'flag', name: 'kind', type: 'enum', choices: ['plan', 'spec'], required: true, constraint: '' },
33
+ { kind: 'flag', name: 'spec-path', type: 'path', required: false, constraint: '' },
34
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
35
+ ];
36
+ const readListParams = [
37
+ { kind: 'flag', name: 'limit', type: 'int', required: false, default: 20, constraint: '' },
38
+ { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
39
+ ];
40
+ const readStatusParams = [
41
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
42
+ ];
43
+ const readResultParams = [
44
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
45
+ { kind: 'flag', name: 'wait', type: 'bool', required: false, constraint: '' },
46
+ ];
47
+ const readLogsParams = [
48
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
49
+ { kind: 'flag', name: 'since', type: 'string', required: false, constraint: '' },
50
+ { kind: 'flag', name: 'until', type: 'string', required: false, constraint: '' },
51
+ { kind: 'flag', name: 'level', type: 'enum', choices: ['debug', 'info', 'warn', 'error'], required: false, default: 'info', constraint: '' },
52
+ { kind: 'flag', name: 'follow', type: 'bool', required: false, constraint: '' },
53
+ ];
54
+ const submitParams = [
55
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
56
+ { kind: 'context-file', name: 'result', required: true, constraint: '' },
57
+ { kind: 'flag', name: 'kill-pane', type: 'bool', required: false, constraint: '' },
58
+ ];
59
+ const cancelParams = [
60
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
61
+ ];
62
+ const failParams = [
63
+ { kind: 'positional', name: 'job_id', type: 'string', required: true, constraint: '' },
64
+ ];
65
+ // ---------------------------------------------------------------------------
66
+ // Helpers
67
+ // ---------------------------------------------------------------------------
68
+ let tmpDir;
69
+ function setup() {
70
+ tmpDir = join(tmpdir(), `crtr-job-test-${Date.now()}`);
71
+ mkdirSync(tmpDir, { recursive: true });
72
+ }
73
+ function teardown() {
74
+ if (tmpDir !== undefined) {
75
+ rmSync(tmpDir, { recursive: true, force: true });
76
+ }
77
+ }
78
+ function writeTmpJson(name, obj) {
79
+ const p = join(tmpDir, name);
80
+ writeFileSync(p, JSON.stringify(obj), 'utf8');
81
+ return p;
82
+ }
83
+ // ---------------------------------------------------------------------------
84
+ // job start prompt
85
+ // ---------------------------------------------------------------------------
86
+ describe('job start prompt', () => {
87
+ // stdin is handled by readStdinRaw() which requires actual stdin — we only
88
+ // test the non-stdin flag parsing here.
89
+ test('--cwd flag parsed as string', async () => {
90
+ // stdin param will be read but we can't pipe here — skip stdin assertion,
91
+ // test that cwd parses correctly alongside other tokens.
92
+ // We test the flag-only shape: no stdin params in isolation.
93
+ const flagOnlyParams = [
94
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
95
+ ];
96
+ const result = await parseArgv(flagOnlyParams, ['--cwd', '/tmp/mydir']);
97
+ assert.equal(result['cwd'], '/tmp/mydir');
98
+ });
99
+ test('cwd absent → undefined', async () => {
100
+ const flagOnlyParams = [
101
+ { kind: 'flag', name: 'cwd', type: 'path', required: false, constraint: '' },
102
+ ];
103
+ const result = await parseArgv(flagOnlyParams, []);
104
+ assert.equal(result['cwd'], undefined);
105
+ });
106
+ });
107
+ // ---------------------------------------------------------------------------
108
+ // job start fork
109
+ // ---------------------------------------------------------------------------
110
+ describe('job start fork', () => {
111
+ test('no args parses cleanly', async () => {
112
+ const result = await parseArgv(startForkParams, []);
113
+ assert.equal(result['cwd'], undefined);
114
+ });
115
+ test('--cwd parsed', async () => {
116
+ const result = await parseArgv(startForkParams, ['--cwd', '/workspace']);
117
+ assert.equal(result['cwd'], '/workspace');
118
+ });
119
+ test('unknown positional rejected', async () => {
120
+ await assert.rejects(() => parseArgv(startForkParams, ['extra-pos']), (err) => { assert.match(err.message, /takes no positional/); return true; });
121
+ });
122
+ });
123
+ // ---------------------------------------------------------------------------
124
+ // job start planner
125
+ // ---------------------------------------------------------------------------
126
+ describe('job start planner', () => {
127
+ test('positional spec_path required', async () => {
128
+ await assert.rejects(() => parseArgv(startPlannerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
129
+ });
130
+ test('positional parsed as spec_path (camelCase: specPath? no — underscore stays as-is)', async () => {
131
+ const result = await parseArgv(startPlannerParams, ['/tmp/spec.md']);
132
+ // flagNameToKey converts kebab to camel; underscores are unaffected
133
+ assert.equal(result['spec_path'], '/tmp/spec.md');
134
+ });
135
+ test('--cwd optional', async () => {
136
+ const result = await parseArgv(startPlannerParams, ['/tmp/spec.md', '--cwd', '/src']);
137
+ assert.equal(result['cwd'], '/src');
138
+ assert.equal(result['spec_path'], '/tmp/spec.md');
139
+ });
140
+ });
141
+ // ---------------------------------------------------------------------------
142
+ // job start implementer
143
+ // ---------------------------------------------------------------------------
144
+ describe('job start implementer', () => {
145
+ test('positional plan_path required', async () => {
146
+ await assert.rejects(() => parseArgv(startImplementerParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
147
+ });
148
+ test('positional parsed', async () => {
149
+ const result = await parseArgv(startImplementerParams, ['/tmp/plan.md']);
150
+ assert.equal(result['plan_path'], '/tmp/plan.md');
151
+ });
152
+ });
153
+ // ---------------------------------------------------------------------------
154
+ // job start reviewer
155
+ // ---------------------------------------------------------------------------
156
+ describe('job start reviewer', () => {
157
+ test('positional + --kind required', async () => {
158
+ await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
159
+ });
160
+ test('valid kind: plan', async () => {
161
+ const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'plan']);
162
+ assert.equal(result['artifact_path'], '/tmp/artifact.md');
163
+ assert.equal(result['kind'], 'plan');
164
+ });
165
+ test('valid kind: spec', async () => {
166
+ const result = await parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'spec']);
167
+ assert.equal(result['kind'], 'spec');
168
+ });
169
+ test('invalid kind throws invalid_type', async () => {
170
+ await assert.rejects(() => parseArgv(startReviewerParams, ['/tmp/artifact.md', '--kind', 'bad']), (err) => { assert.match(err.message, /must be one of/); return true; });
171
+ });
172
+ test('--spec-path optional, becomes specPath', async () => {
173
+ const result = await parseArgv(startReviewerParams, [
174
+ '/tmp/artifact.md', '--kind', 'plan', '--spec-path', '/tmp/spec.md',
175
+ ]);
176
+ assert.equal(result['specPath'], '/tmp/spec.md');
177
+ });
178
+ });
179
+ // ---------------------------------------------------------------------------
180
+ // job read list
181
+ // ---------------------------------------------------------------------------
182
+ describe('job read list', () => {
183
+ test('defaults: limit=20, cursor=undefined', async () => {
184
+ const result = await parseArgv(readListParams, []);
185
+ assert.equal(result['limit'], 20);
186
+ assert.equal(result['cursor'], undefined);
187
+ });
188
+ test('--limit N parsed as int', async () => {
189
+ const result = await parseArgv(readListParams, ['--limit', '50']);
190
+ assert.equal(result['limit'], 50);
191
+ });
192
+ test('--limit with non-integer throws', async () => {
193
+ await assert.rejects(() => parseArgv(readListParams, ['--limit', 'abc']), (err) => { assert.match(err.message, /must be an integer/); return true; });
194
+ });
195
+ test('--cursor opaque token', async () => {
196
+ const result = await parseArgv(readListParams, ['--cursor', 'tok_abc123']);
197
+ assert.equal(result['cursor'], 'tok_abc123');
198
+ });
199
+ test('--limit and --cursor together', async () => {
200
+ const result = await parseArgv(readListParams, ['--limit', '5', '--cursor', 'next_page']);
201
+ assert.equal(result['limit'], 5);
202
+ assert.equal(result['cursor'], 'next_page');
203
+ });
204
+ });
205
+ // ---------------------------------------------------------------------------
206
+ // job read status
207
+ // ---------------------------------------------------------------------------
208
+ describe('job read status', () => {
209
+ test('positional job_id required', async () => {
210
+ await assert.rejects(() => parseArgv(readStatusParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
211
+ });
212
+ test('positional job_id parsed', async () => {
213
+ const result = await parseArgv(readStatusParams, ['job-abc-123']);
214
+ assert.equal(result['job_id'], 'job-abc-123');
215
+ });
216
+ });
217
+ // ---------------------------------------------------------------------------
218
+ // job read result
219
+ // ---------------------------------------------------------------------------
220
+ describe('job read result', () => {
221
+ test('positional job_id required', async () => {
222
+ await assert.rejects(() => parseArgv(readResultParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
223
+ });
224
+ test('positional job_id without --wait', async () => {
225
+ const result = await parseArgv(readResultParams, ['job-xyz']);
226
+ assert.equal(result['job_id'], 'job-xyz');
227
+ assert.equal(result['wait'], false);
228
+ });
229
+ test('--wait presence = true', async () => {
230
+ const result = await parseArgv(readResultParams, ['job-xyz', '--wait']);
231
+ assert.equal(result['wait'], true);
232
+ });
233
+ test('--wait=value rejected (bool takes no value)', async () => {
234
+ await assert.rejects(() => parseArgv(readResultParams, ['job-xyz', '--wait=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
235
+ });
236
+ });
237
+ // ---------------------------------------------------------------------------
238
+ // job read logs
239
+ // ---------------------------------------------------------------------------
240
+ describe('job read logs', () => {
241
+ test('positional job_id required', async () => {
242
+ await assert.rejects(() => parseArgv(readLogsParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
243
+ });
244
+ test('defaults: level=info, follow=false', async () => {
245
+ const result = await parseArgv(readLogsParams, ['job-abc']);
246
+ assert.equal(result['job_id'], 'job-abc');
247
+ assert.equal(result['level'], 'info');
248
+ assert.equal(result['follow'], false);
249
+ });
250
+ test('--follow presence = true', async () => {
251
+ const result = await parseArgv(readLogsParams, ['job-abc', '--follow']);
252
+ assert.equal(result['follow'], true);
253
+ });
254
+ test('--follow=value rejected', async () => {
255
+ await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--follow=true']), (err) => { assert.match(err.message, /takes no value/); return true; });
256
+ });
257
+ test('--level valid enum', async () => {
258
+ const result = await parseArgv(readLogsParams, ['job-abc', '--level', 'debug']);
259
+ assert.equal(result['level'], 'debug');
260
+ });
261
+ test('--level invalid enum throws', async () => {
262
+ await assert.rejects(() => parseArgv(readLogsParams, ['job-abc', '--level', 'trace']), (err) => { assert.match(err.message, /must be one of/); return true; });
263
+ });
264
+ test('--since and --until optional strings', async () => {
265
+ const result = await parseArgv(readLogsParams, [
266
+ 'job-abc', '--since', '2025-01-01T00:00:00Z', '--until', '2025-01-02T00:00:00Z',
267
+ ]);
268
+ assert.equal(result['since'], '2025-01-01T00:00:00Z');
269
+ assert.equal(result['until'], '2025-01-02T00:00:00Z');
270
+ });
271
+ });
272
+ // ---------------------------------------------------------------------------
273
+ // job submit
274
+ // ---------------------------------------------------------------------------
275
+ describe('job submit', () => {
276
+ // setup/teardown wraps tests via explicit calls since node:test lacks
277
+ // per-describe lifecycle hooks in older versions.
278
+ test('--context-file with valid JSON object', async () => {
279
+ setup();
280
+ try {
281
+ const p = writeTmpJson('result.json', { status: 'done', summary: 'all good' });
282
+ const result = await parseArgv(submitParams, ['job-abc', '--context-file', p]);
283
+ assert.equal(result['job_id'], 'job-abc');
284
+ assert.deepEqual(result['result'], { status: 'done', summary: 'all good' });
285
+ assert.equal(result['killPane'], false);
286
+ }
287
+ finally {
288
+ teardown();
289
+ }
290
+ });
291
+ test('--kill-pane presence = true, killPane key', async () => {
292
+ setup();
293
+ try {
294
+ const p = writeTmpJson('result.json', { status: 'done' });
295
+ const result = await parseArgv(submitParams, ['job-abc', '--context-file', p, '--kill-pane']);
296
+ assert.equal(result['killPane'], true);
297
+ }
298
+ finally {
299
+ teardown();
300
+ }
301
+ });
302
+ test('missing --context-file throws missing_parameter', async () => {
303
+ await assert.rejects(() => parseArgv(submitParams, ['job-abc']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
304
+ });
305
+ test('missing positional job_id throws missing_parameter', async () => {
306
+ await assert.rejects(() => parseArgv(submitParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
307
+ });
308
+ test('--context-file with non-existent file throws invalid_type', async () => {
309
+ await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--context-file', '/no/such/file.json']), (err) => { assert.match(err.message, /cannot read file/); return true; });
310
+ });
311
+ test('--context-file with invalid JSON throws invalid_type', async () => {
312
+ setup();
313
+ try {
314
+ const p = join(tmpDir, 'bad.json');
315
+ writeFileSync(p, 'not json', 'utf8');
316
+ await assert.rejects(() => parseArgv(submitParams, ['job-abc', '--context-file', p]), (err) => { assert.match(err.message, /not valid JSON/); return true; });
317
+ }
318
+ finally {
319
+ teardown();
320
+ }
321
+ });
322
+ });
323
+ // ---------------------------------------------------------------------------
324
+ // job cancel
325
+ // ---------------------------------------------------------------------------
326
+ describe('job cancel', () => {
327
+ test('positional job_id required', async () => {
328
+ await assert.rejects(() => parseArgv(cancelParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
329
+ });
330
+ test('positional job_id parsed', async () => {
331
+ const result = await parseArgv(cancelParams, ['job-abc-123']);
332
+ assert.equal(result['job_id'], 'job-abc-123');
333
+ });
334
+ });
335
+ // ---------------------------------------------------------------------------
336
+ // job _fail
337
+ // ---------------------------------------------------------------------------
338
+ describe('job _fail', () => {
339
+ test('positional job_id required', async () => {
340
+ await assert.rejects(() => parseArgv(failParams, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
341
+ });
342
+ test('positional job_id parsed', async () => {
343
+ const result = await parseArgv(failParams, ['job-fail-001']);
344
+ assert.equal(result['job_id'], 'job-fail-001');
345
+ });
346
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,218 @@
1
+ // Tests for the pkg subtree argv migration.
2
+ // Exercises parseArgv against the param schemas declared in pkg.ts leaves.
3
+ // No subprocess spawning; no real FS writes. Tests are schema/parsing level only.
4
+ import { test, describe } from 'node:test';
5
+ import assert from 'node:assert/strict';
6
+ import { parseArgv } from '../command.js';
7
+ // ---------------------------------------------------------------------------
8
+ // plugin manage install — positional source + optional flags
9
+ // ---------------------------------------------------------------------------
10
+ describe('pkg plugin manage install', () => {
11
+ const params = [
12
+ { kind: 'positional', name: 'source', type: 'string', required: true, constraint: 'Git URL.' },
13
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
14
+ { kind: 'flag', name: 'ref', type: 'string', required: false, constraint: '' },
15
+ ];
16
+ test('positional source is required', async () => {
17
+ await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
18
+ });
19
+ test('parses positional source', async () => {
20
+ const result = await parseArgv(params, ['https://github.com/org/my-plugin.git']);
21
+ assert.equal(result['source'], 'https://github.com/org/my-plugin.git');
22
+ });
23
+ test('parses positional source with --scope flag', async () => {
24
+ const result = await parseArgv(params, ['https://github.com/org/repo.git', '--scope', 'user']);
25
+ assert.equal(result['source'], 'https://github.com/org/repo.git');
26
+ assert.equal(result['scope'], 'user');
27
+ });
28
+ test('parses --ref flag', async () => {
29
+ const result = await parseArgv(params, ['https://example.com/repo.git', '--ref', 'v1.2.3']);
30
+ assert.equal(result['ref'], 'v1.2.3');
31
+ });
32
+ test('rejects invalid scope enum', async () => {
33
+ await assert.rejects(() => parseArgv(params, ['https://example.com/repo.git', '--scope', 'all']), (err) => { assert.match(err.message, /must be one of/); return true; });
34
+ });
35
+ });
36
+ // ---------------------------------------------------------------------------
37
+ // plugin manage remove / enable / disable — positional name + optional scope
38
+ // ---------------------------------------------------------------------------
39
+ describe('pkg plugin manage remove/enable/disable', () => {
40
+ const params = [
41
+ { kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
42
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
43
+ ];
44
+ test('parses positional name', async () => {
45
+ const result = await parseArgv(params, ['my-plugin']);
46
+ assert.equal(result['name'], 'my-plugin');
47
+ });
48
+ test('parses name with scope', async () => {
49
+ const result = await parseArgv(params, ['my-plugin', '--scope', 'project']);
50
+ assert.equal(result['name'], 'my-plugin');
51
+ assert.equal(result['scope'], 'project');
52
+ });
53
+ test('missing name throws missing_parameter', async () => {
54
+ await assert.rejects(() => parseArgv(params, ['--scope', 'user']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
55
+ });
56
+ });
57
+ // ---------------------------------------------------------------------------
58
+ // plugin manage update — optional --name flag
59
+ // ---------------------------------------------------------------------------
60
+ describe('pkg plugin manage update', () => {
61
+ const params = [
62
+ { kind: 'flag', name: 'name', type: 'string', required: false, constraint: '' },
63
+ ];
64
+ test('no args is valid (update-all path)', async () => {
65
+ const result = await parseArgv(params, []);
66
+ assert.equal(result['name'], undefined);
67
+ });
68
+ test('--name sets the name', async () => {
69
+ const result = await parseArgv(params, ['--name', 'my-plugin']);
70
+ assert.equal(result['name'], 'my-plugin');
71
+ });
72
+ });
73
+ // ---------------------------------------------------------------------------
74
+ // plugin inspect list — --include-disabled bool flag + pagination
75
+ // ---------------------------------------------------------------------------
76
+ describe('pkg plugin inspect list', () => {
77
+ const params = [
78
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project', 'all'], required: false, constraint: '' },
79
+ { kind: 'flag', name: 'include-disabled', type: 'bool', required: false, constraint: '' },
80
+ { kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
81
+ { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
82
+ ];
83
+ test('no args returns defaults', async () => {
84
+ const result = await parseArgv(params, []);
85
+ assert.equal(result['includeDisabled'], false);
86
+ assert.equal(result['limit'], 50);
87
+ assert.equal(result['scope'], undefined);
88
+ assert.equal(result['cursor'], undefined);
89
+ });
90
+ test('--include-disabled presence sets true', async () => {
91
+ const result = await parseArgv(params, ['--include-disabled']);
92
+ assert.equal(result['includeDisabled'], true);
93
+ });
94
+ test('--include-disabled camelCase key', async () => {
95
+ const result = await parseArgv(params, ['--include-disabled']);
96
+ assert.ok('includeDisabled' in result);
97
+ assert.ok(!('include-disabled' in result));
98
+ });
99
+ test('--limit parsed as int', async () => {
100
+ const result = await parseArgv(params, ['--limit', '25']);
101
+ assert.equal(result['limit'], 25);
102
+ });
103
+ test('--cursor passed through', async () => {
104
+ const result = await parseArgv(params, ['--cursor', 'user:some-plugin']);
105
+ assert.equal(result['cursor'], 'user:some-plugin');
106
+ });
107
+ test('--scope all is valid', async () => {
108
+ const result = await parseArgv(params, ['--scope', 'all']);
109
+ assert.equal(result['scope'], 'all');
110
+ });
111
+ });
112
+ // ---------------------------------------------------------------------------
113
+ // plugin inspect show — positional name
114
+ // ---------------------------------------------------------------------------
115
+ describe('pkg plugin inspect show', () => {
116
+ const params = [
117
+ { kind: 'positional', name: 'name', type: 'string', required: true, constraint: 'Plugin name.' },
118
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
119
+ ];
120
+ test('parses positional name', async () => {
121
+ const result = await parseArgv(params, ['my-plugin']);
122
+ assert.equal(result['name'], 'my-plugin');
123
+ });
124
+ test('missing name throws', async () => {
125
+ await assert.rejects(() => parseArgv(params, []), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
126
+ });
127
+ });
128
+ // ---------------------------------------------------------------------------
129
+ // market manage install — --marketplace + --plugin required flags
130
+ // ---------------------------------------------------------------------------
131
+ describe('pkg market manage install', () => {
132
+ const params = [
133
+ { kind: 'flag', name: 'marketplace', type: 'string', required: true, constraint: '' },
134
+ { kind: 'flag', name: 'plugin', type: 'string', required: true, constraint: '' },
135
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
136
+ ];
137
+ test('parses --marketplace and --plugin together', async () => {
138
+ const result = await parseArgv(params, ['--marketplace', 'official', '--plugin', 'my-plugin']);
139
+ assert.equal(result['marketplace'], 'official');
140
+ assert.equal(result['plugin'], 'my-plugin');
141
+ });
142
+ test('missing --marketplace throws', async () => {
143
+ await assert.rejects(() => parseArgv(params, ['--plugin', 'my-plugin']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
144
+ });
145
+ test('missing --plugin throws', async () => {
146
+ await assert.rejects(() => parseArgv(params, ['--marketplace', 'official']), (err) => { assert.match(err.message, /required parameter is missing/); return true; });
147
+ });
148
+ test('parses all three flags', async () => {
149
+ const result = await parseArgv(params, ['--marketplace', 'official', '--plugin', 'my-plugin', '--scope', 'user']);
150
+ assert.equal(result['marketplace'], 'official');
151
+ assert.equal(result['plugin'], 'my-plugin');
152
+ assert.equal(result['scope'], 'user');
153
+ });
154
+ });
155
+ // ---------------------------------------------------------------------------
156
+ // market manage update — optional --marketplace flag
157
+ // ---------------------------------------------------------------------------
158
+ describe('pkg market manage update', () => {
159
+ const params = [
160
+ { kind: 'flag', name: 'marketplace', type: 'string', required: false, constraint: '' },
161
+ ];
162
+ test('no args is valid (update-all path)', async () => {
163
+ const result = await parseArgv(params, []);
164
+ assert.equal(result['marketplace'], undefined);
165
+ });
166
+ test('--marketplace sets name', async () => {
167
+ const result = await parseArgv(params, ['--marketplace', 'official']);
168
+ assert.equal(result['marketplace'], 'official');
169
+ });
170
+ });
171
+ // ---------------------------------------------------------------------------
172
+ // market manage remove — positional name
173
+ // ---------------------------------------------------------------------------
174
+ describe('pkg market manage remove', () => {
175
+ const params = [
176
+ { kind: 'positional', name: 'name', type: 'string', required: true, constraint: '' },
177
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project'], required: false, constraint: '' },
178
+ ];
179
+ test('parses positional name', async () => {
180
+ const result = await parseArgv(params, ['official']);
181
+ assert.equal(result['name'], 'official');
182
+ });
183
+ });
184
+ // ---------------------------------------------------------------------------
185
+ // market inspect list / browse — pagination flags
186
+ // ---------------------------------------------------------------------------
187
+ describe('pkg market inspect list pagination', () => {
188
+ const params = [
189
+ { kind: 'flag', name: 'scope', type: 'enum', choices: ['user', 'project', 'all'], required: false, constraint: '' },
190
+ { kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
191
+ { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
192
+ ];
193
+ test('default limit is 50', async () => {
194
+ const result = await parseArgv(params, []);
195
+ assert.equal(result['limit'], 50);
196
+ });
197
+ test('--limit and --cursor parsed together', async () => {
198
+ const result = await parseArgv(params, ['--limit', '10', '--cursor', 'tok123']);
199
+ assert.equal(result['limit'], 10);
200
+ assert.equal(result['cursor'], 'tok123');
201
+ });
202
+ });
203
+ describe('pkg market inspect browse', () => {
204
+ const params = [
205
+ { kind: 'flag', name: 'marketplace', type: 'string', required: false, constraint: '' },
206
+ { kind: 'flag', name: 'limit', type: 'int', required: false, default: 50, constraint: '' },
207
+ { kind: 'flag', name: 'cursor', type: 'string', required: false, constraint: '' },
208
+ ];
209
+ test('--marketplace optional', async () => {
210
+ const result = await parseArgv(params, []);
211
+ assert.equal(result['marketplace'], undefined);
212
+ });
213
+ test('--marketplace with pagination', async () => {
214
+ const result = await parseArgv(params, ['--marketplace', 'official', '--limit', '20']);
215
+ assert.equal(result['marketplace'], 'official');
216
+ assert.equal(result['limit'], 20);
217
+ });
218
+ });
@@ -0,0 +1 @@
1
+ export {};