@alavida/agentpack 0.1.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.
@@ -0,0 +1,491 @@
1
+ import { Command } from 'commander';
2
+ import {
3
+ inspectSkillDependencies,
4
+ inspectMissingSkillDependencies,
5
+ inspectSkillsStatus,
6
+ inspectRegistryConfig,
7
+ inspectSkill,
8
+ inspectSkillsEnv,
9
+ inspectStaleSkill,
10
+ installSkills,
11
+ listOutdatedSkills,
12
+ listStaleSkills,
13
+ resolveInstallTargets,
14
+ startSkillDev,
15
+ unlinkSkill,
16
+ uninstallSkills,
17
+ validateSkills,
18
+ } from '../lib/skills.js';
19
+ import { output } from '../utils/output.js';
20
+ import { EXIT_CODES } from '../utils/errors.js';
21
+
22
+ export function skillsCommand() {
23
+ const cmd = new Command('skills')
24
+ .description('Inspect and manage package-backed skills');
25
+
26
+ cmd
27
+ .command('dev')
28
+ .description('Link one local packaged skill for local Claude and agent discovery')
29
+ .option('--no-sync', 'Skip syncing managed package dependencies from requires')
30
+ .argument('<target>', 'Packaged skill directory or SKILL.md path')
31
+ .action((target, opts, command) => {
32
+ const globalOpts = command.optsWithGlobals();
33
+ const session = startSkillDev(target, {
34
+ sync: opts.sync,
35
+ onStart(result) {
36
+ if (globalOpts.json) {
37
+ output.json(result);
38
+ return;
39
+ }
40
+
41
+ output.write(`Linked Skill: ${result.name}`);
42
+ output.write(`Path: ${result.path}`);
43
+ output.write(`Synced Added: ${result.synced.added.length}`);
44
+ output.write(`Synced Removed: ${result.synced.removed.length}`);
45
+ output.write(`Linked Skills: ${result.linkedSkills.length}`);
46
+ for (const link of result.links) {
47
+ output.write(`Linked: ${link}`);
48
+ }
49
+ output.write('Note: if your current agent session was already running, start a fresh session to pick up newly linked skills.');
50
+ if (result.unresolved.length > 0) {
51
+ output.write('Unresolved Dependencies:');
52
+ for (const dependency of result.unresolved) {
53
+ output.write(`- ${dependency}`);
54
+ }
55
+ output.write('Are you sure those skills are installed or available locally?');
56
+ }
57
+ },
58
+ onRebuild(result) {
59
+ if (result?.error) {
60
+ output.error(`Skill dev rebuild failed: ${result.error.message}`);
61
+ return;
62
+ }
63
+
64
+ output.write(`Reloaded Skill: ${result.name}`);
65
+ output.write(`Path: ${result.path}`);
66
+ },
67
+ });
68
+
69
+ const stop = () => {
70
+ session.close();
71
+ process.exit(0);
72
+ };
73
+
74
+ process.once('SIGTERM', stop);
75
+ process.once('SIGINT', stop);
76
+ });
77
+
78
+ cmd
79
+ .command('unlink')
80
+ .description('Remove one locally linked skill from Claude and agent discovery paths')
81
+ .argument('<name>', 'Skill frontmatter name')
82
+ .action((name, opts, command) => {
83
+ const globalOpts = command.optsWithGlobals();
84
+ const result = unlinkSkill(name);
85
+
86
+ if (globalOpts.json) {
87
+ output.json(result);
88
+ return;
89
+ }
90
+
91
+ output.write(`Unlinked Skill: ${result.name}`);
92
+ for (const removed of result.removed) {
93
+ output.write(`Removed: ${removed}`);
94
+ }
95
+ });
96
+
97
+ cmd
98
+ .command('status')
99
+ .description('Show environment health for installed skills, outdated packages, and registry config')
100
+ .action(async (opts, command) => {
101
+ const globalOpts = command.optsWithGlobals();
102
+ const result = await inspectSkillsStatus();
103
+
104
+ if (globalOpts.json) {
105
+ output.json(result);
106
+ return;
107
+ }
108
+
109
+ output.write(`Health: ${result.health}`);
110
+ output.write(`Installed Skills: ${result.installedCount}`);
111
+ output.write(`Direct Skills: ${result.directCount}`);
112
+ output.write(`Transitive Skills: ${result.transitiveCount}`);
113
+ output.write(`Outdated Skills: ${result.outdatedCount}`);
114
+ output.write(`Deprecated Skills: ${result.deprecatedCount}`);
115
+ output.write(`Incomplete Skills: ${result.incompleteCount}`);
116
+ output.write(`Registry Configured: ${result.registry.configured}`);
117
+
118
+ if (result.outdated.length > 0) {
119
+ output.write('');
120
+ output.write('Outdated:');
121
+ for (const skill of result.outdated) {
122
+ output.write(`- ${skill.packageName}`);
123
+ output.write(` current: ${skill.currentVersion}`);
124
+ output.write(` available: ${skill.availableVersion}`);
125
+ output.write(` source: ${skill.source}`);
126
+ }
127
+ }
128
+
129
+ if (result.deprecated.length > 0) {
130
+ output.write('');
131
+ output.write('Deprecated:');
132
+ for (const skill of result.deprecated) {
133
+ output.write(`- ${skill.packageName}`);
134
+ output.write(` status: ${skill.status}`);
135
+ if (skill.replacement) output.write(` replacement: ${skill.replacement}`);
136
+ }
137
+ }
138
+
139
+ if (result.incomplete.length > 0) {
140
+ output.write('');
141
+ output.write('Incomplete:');
142
+ for (const skill of result.incomplete) {
143
+ output.write(`- ${skill.packageName}`);
144
+ for (const missing of skill.missing) {
145
+ output.write(` missing: ${missing.packageName}`);
146
+ output.write(` recommended: ${missing.recommendedCommand}`);
147
+ }
148
+ }
149
+ }
150
+ });
151
+
152
+ cmd
153
+ .command('registry')
154
+ .description('Inspect repo-local npm registry configuration for managed private skill packages')
155
+ .action((opts, command) => {
156
+ const globalOpts = command.optsWithGlobals();
157
+ const result = inspectRegistryConfig();
158
+
159
+ if (globalOpts.json) {
160
+ output.json(result);
161
+ return;
162
+ }
163
+
164
+ output.write(`Scope: ${result.scope}`);
165
+ output.write(`Configured: ${result.configured}`);
166
+ output.write(`Registry: ${result.registry || 'missing'}`);
167
+ output.write(`Always Auth: ${result.alwaysAuth}`);
168
+ if (result.npmrcPath) output.write(`Path: ${result.npmrcPath}`);
169
+
170
+ if (result.auth.mode === 'env') {
171
+ output.write('Auth: environment variable reference');
172
+ output.write(`Auth Key: ${result.auth.key}`);
173
+ } else if (result.auth.mode === 'literal') {
174
+ output.write('Auth: literal token value');
175
+ } else {
176
+ output.write('Auth: missing');
177
+ }
178
+ });
179
+
180
+ cmd
181
+ .command('dependencies')
182
+ .description('Show direct and reverse skill dependencies for one authored or installed skill')
183
+ .argument('<target>', 'Packaged skill directory, SKILL.md path, or package name')
184
+ .action((target, opts, command) => {
185
+ const globalOpts = command.optsWithGlobals();
186
+ const result = inspectSkillDependencies(target);
187
+
188
+ if (globalOpts.json) {
189
+ output.json(result);
190
+ return;
191
+ }
192
+
193
+ output.write(`Skill: ${result.packageName}`);
194
+ output.write(`Graph: ${result.graph}`);
195
+ if (result.packageVersion) output.write(`Version: ${result.packageVersion}`);
196
+ if (result.status) output.write(`Status: ${result.status}`);
197
+ if (result.graph === 'installed') output.write(`Direct: ${result.direct}`);
198
+ output.write(`Path: ${result.skillFile}`);
199
+
200
+ output.write('');
201
+ output.write('Direct Dependencies:');
202
+ if (result.dependencies.length === 0) {
203
+ output.write('- none');
204
+ } else {
205
+ for (const dependency of result.dependencies) {
206
+ output.write(`- ${dependency.packageName}${dependency.status ? ` (${dependency.status})` : ''}`);
207
+ }
208
+ }
209
+
210
+ output.write('');
211
+ output.write('Reverse Dependencies:');
212
+ if (result.reverseDependencies.length === 0) {
213
+ output.write('- none');
214
+ } else {
215
+ for (const dependency of result.reverseDependencies) {
216
+ output.write(`- ${dependency.packageName}${dependency.status ? ` (${dependency.status})` : ''}`);
217
+ }
218
+ }
219
+ });
220
+
221
+ cmd
222
+ .command('missing')
223
+ .description('Show local or installed skills with unmet required skill dependencies')
224
+ .argument('[target]', 'Optional installed package name or skill path')
225
+ .action((target, opts, command) => {
226
+ const globalOpts = command.optsWithGlobals();
227
+ const result = inspectMissingSkillDependencies({ target });
228
+
229
+ if (globalOpts.json) {
230
+ output.json(result);
231
+ return;
232
+ }
233
+
234
+ output.write(`Skills With Missing Dependencies: ${result.count}`);
235
+ if (result.count === 0) return;
236
+
237
+ for (const skill of result.skills) {
238
+ output.write('');
239
+ output.write(`- ${skill.packageName || skill.skillFile || skill.name}`);
240
+ for (const missing of skill.missing) {
241
+ output.write(` - ${missing.packageName}`);
242
+ output.write(` recommended: ${missing.recommendedCommand}`);
243
+ }
244
+ }
245
+ });
246
+
247
+ cmd
248
+ .command('inspect')
249
+ .description('Inspect one packaged or local skill')
250
+ .argument('<target>', 'Skill directory, SKILL.md path, or package name')
251
+ .action((target, opts, command) => {
252
+ const globalOpts = command.optsWithGlobals();
253
+ const result = inspectSkill(target);
254
+
255
+ if (globalOpts.json) {
256
+ output.json(result);
257
+ return;
258
+ }
259
+
260
+ output.write(`Skill: ${result.name}`);
261
+ if (result.description) output.write(`Description: ${result.description}`);
262
+ if (result.packageName) output.write(`Package: ${result.packageName}`);
263
+ if (result.packageVersion) output.write(`Version: ${result.packageVersion}`);
264
+ if (result.status) output.write(`Status: ${result.status}`);
265
+ if (result.replacement) output.write(`Replacement: ${result.replacement}`);
266
+ if (result.message) output.write(`Message: ${result.message}`);
267
+ output.write(`Path: ${result.skillFile}`);
268
+
269
+ output.write('');
270
+ output.write('Sources:');
271
+ if (result.sources.length === 0) {
272
+ output.write('- none');
273
+ } else {
274
+ for (const source of result.sources) output.write(`- ${source}`);
275
+ }
276
+
277
+ output.write('');
278
+ output.write('Requires:');
279
+ if (result.requires.length === 0) {
280
+ output.write('- none');
281
+ } else {
282
+ for (const requirement of result.requires) output.write(`- ${requirement}`);
283
+ }
284
+ });
285
+
286
+ cmd
287
+ .command('stale')
288
+ .description('Show stale packaged skills from recorded build-state')
289
+ .argument('[target]', 'Optional package name or skill path')
290
+ .action((target, opts, command) => {
291
+ const globalOpts = command.optsWithGlobals();
292
+
293
+ if (target) {
294
+ const result = inspectStaleSkill(target);
295
+
296
+ if (globalOpts.json) {
297
+ output.json(result);
298
+ return;
299
+ }
300
+
301
+ output.write(`Skill: ${result.packageName}`);
302
+ output.write(`Path: ${result.skillPath}`);
303
+ output.write('');
304
+ output.write('Changed Sources:');
305
+ for (const change of result.changedSources) {
306
+ output.write(`- ${change.path}`);
307
+ output.write(` Recorded: ${change.recorded}`);
308
+ output.write(` Current: ${change.current}`);
309
+ }
310
+ return;
311
+ }
312
+
313
+ const results = listStaleSkills();
314
+
315
+ if (globalOpts.json) {
316
+ output.json({
317
+ count: results.length,
318
+ skills: results,
319
+ });
320
+ return;
321
+ }
322
+
323
+ output.write(`Stale Skills: ${results.length}`);
324
+ if (results.length === 0) return;
325
+
326
+ for (const result of results) {
327
+ output.write('');
328
+ output.write(`- ${result.packageName}`);
329
+ output.write(` path: ${result.skillPath}`);
330
+ output.write(` changed_sources: ${result.changedSources.length}`);
331
+ }
332
+ });
333
+
334
+ cmd
335
+ .command('validate')
336
+ .description('Validate one packaged skill or all authored packaged skills')
337
+ .argument('[target]', 'Optional packaged skill directory, SKILL.md path, or package name')
338
+ .action((target, opts, command) => {
339
+ const globalOpts = command.optsWithGlobals();
340
+ const result = validateSkills(target);
341
+
342
+ if (globalOpts.json) {
343
+ output.json(
344
+ target
345
+ ? result.skills[0]
346
+ : result
347
+ );
348
+ if (!result.valid) process.exitCode = EXIT_CODES.VALIDATION;
349
+ return;
350
+ }
351
+
352
+ if (target) {
353
+ const skill = result.skills[0];
354
+ output.write(`Skill: ${skill.packageName || skill.packagePath}`);
355
+ output.write(`Status: ${skill.valid ? 'valid' : 'invalid'}`);
356
+ output.write(`Issues: ${skill.issues.length}`);
357
+ if (skill.valid && skill.nextSteps.length > 0) {
358
+ output.write('');
359
+ output.write('Next Steps:');
360
+ for (const step of skill.nextSteps) {
361
+ output.write(`- ${step.command}`);
362
+ if (step.registry) output.write(` registry: ${step.registry}`);
363
+ }
364
+ }
365
+ if (skill.issues.length > 0) {
366
+ output.write('');
367
+ output.write('Validation Issues:');
368
+ for (const issue of skill.issues) {
369
+ output.write(`- ${issue.code}: ${issue.message}`);
370
+ if (issue.path) output.write(` path: ${issue.path}`);
371
+ if (issue.dependency) output.write(` dependency: ${issue.dependency}`);
372
+ }
373
+ }
374
+ } else {
375
+ output.write(`Validated Skills: ${result.count}`);
376
+ output.write(`Valid Skills: ${result.validCount}`);
377
+ output.write(`Invalid Skills: ${result.invalidCount}`);
378
+
379
+ if (result.invalidCount > 0) {
380
+ for (const skill of result.skills.filter((entry) => !entry.valid)) {
381
+ output.write('');
382
+ output.write(`- ${skill.packageName || skill.packagePath}`);
383
+ for (const issue of skill.issues) {
384
+ output.write(` ${issue.code}: ${issue.message}`);
385
+ }
386
+ }
387
+ }
388
+ }
389
+
390
+ if (!result.valid) process.exitCode = EXIT_CODES.VALIDATION;
391
+ });
392
+
393
+ cmd
394
+ .command('install')
395
+ .description('Install one packaged skill and materialize the resolved graph')
396
+ .argument('[target]', 'Packaged skill directory or package target')
397
+ .action((target, opts, command) => {
398
+ const globalOpts = command.optsWithGlobals();
399
+ const targets = resolveInstallTargets({
400
+ target,
401
+ workbench: globalOpts.workbench,
402
+ });
403
+ const result = installSkills(targets);
404
+
405
+ if (globalOpts.json) {
406
+ output.json(result);
407
+ return;
408
+ }
409
+
410
+ const installs = Object.entries(result.installs).sort(([a], [b]) => a.localeCompare(b));
411
+ output.write(`Installed Skills: ${installs.length}`);
412
+ for (const [packageName, install] of installs) {
413
+ output.write('');
414
+ output.write(`- ${packageName}`);
415
+ output.write(` direct: ${install.direct}`);
416
+ output.write(` version: ${install.package_version}`);
417
+ }
418
+ });
419
+
420
+ cmd
421
+ .command('env')
422
+ .description('Show installed and materialized skills for this repo')
423
+ .action((opts, command) => {
424
+ const globalOpts = command.optsWithGlobals();
425
+ const result = inspectSkillsEnv();
426
+
427
+ if (globalOpts.json) {
428
+ output.json(result);
429
+ return;
430
+ }
431
+
432
+ output.write(`Installed Skills: ${result.installs.length}`);
433
+ for (const install of result.installs) {
434
+ output.write('');
435
+ output.write(`- ${install.packageName}`);
436
+ output.write(` direct: ${install.direct}`);
437
+ output.write(` version: ${install.packageVersion}`);
438
+ output.write(` source: ${install.sourcePackagePath}`);
439
+ for (const materialization of install.materializations) {
440
+ output.write(` materialized: ${materialization.target} (${materialization.mode})`);
441
+ }
442
+ }
443
+ });
444
+
445
+ cmd
446
+ .command('outdated')
447
+ .description('Show installed packaged skills with newer versions available in the current discovery root')
448
+ .action(async (opts, command) => {
449
+ const globalOpts = command.optsWithGlobals();
450
+ const result = await listOutdatedSkills();
451
+
452
+ if (globalOpts.json) {
453
+ output.json(result);
454
+ return;
455
+ }
456
+
457
+ output.write(`Outdated Skills: ${result.count}`);
458
+ if (result.count === 0) return;
459
+
460
+ for (const skill of result.skills) {
461
+ output.write('');
462
+ output.write(`- ${skill.packageName}`);
463
+ output.write(` current: ${skill.currentVersion}`);
464
+ output.write(` available: ${skill.availableVersion}`);
465
+ output.write(` type: ${skill.updateType}`);
466
+ output.write(` source: ${skill.source}`);
467
+ output.write(` recommended: ${skill.recommendedCommand}`);
468
+ }
469
+ });
470
+
471
+ cmd
472
+ .command('uninstall')
473
+ .description('Uninstall one direct skill package and reconcile runtime state')
474
+ .argument('<target>', 'Installed skill package name')
475
+ .action((target, opts, command) => {
476
+ const globalOpts = command.optsWithGlobals();
477
+ const result = uninstallSkills(target);
478
+
479
+ if (globalOpts.json) {
480
+ output.json(result);
481
+ return;
482
+ }
483
+
484
+ output.write(`Removed Skills: ${result.removed.length}`);
485
+ for (const packageName of result.removed) {
486
+ output.write(`- ${packageName}`);
487
+ }
488
+ });
489
+
490
+ return cmd;
491
+ }
@@ -0,0 +1,167 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, dirname, resolve, relative } from 'node:path';
3
+ import { NotFoundError } from '../utils/errors.js';
4
+
5
+ /**
6
+ * Walk up from startDir looking for a directory containing `marker`.
7
+ * Returns the directory path or null.
8
+ */
9
+ function walkUp(startDir, marker) {
10
+ let dir = resolve(startDir);
11
+ const root = dirname(dir) === dir ? dir : undefined; // filesystem root
12
+
13
+ while (true) {
14
+ if (existsSync(join(dir, marker))) {
15
+ return dir;
16
+ }
17
+ const parent = dirname(dir);
18
+ if (parent === dir) return null; // reached filesystem root
19
+ dir = parent;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Find the git repo root by walking up from cwd.
25
+ * Throws NotFoundError (exit 4) if not in a git repo.
26
+ */
27
+ export function findRepoRoot(cwd = process.cwd()) {
28
+ const root = walkUp(cwd, '.git');
29
+ if (!root) {
30
+ throw new NotFoundError(
31
+ 'Not inside a git repository. Run from inside an agentpack repo.',
32
+ { code: 'repo_not_found', suggestion: 'cd into your agentpack knowledge base repo' }
33
+ );
34
+ }
35
+ return root;
36
+ }
37
+
38
+ /**
39
+ * Find workbench context by walking up from cwd looking for workbench.json.
40
+ * Returns { path, config } or null if not inside a workbench.
41
+ * Stops at repo root (won't walk above .git).
42
+ */
43
+ export function findWorkbenchContext(cwd = process.cwd()) {
44
+ const repoRoot = findRepoRoot(cwd);
45
+ let dir = resolve(cwd);
46
+
47
+ while (true) {
48
+ const wbPath = join(dir, 'workbench.json');
49
+ if (existsSync(wbPath)) {
50
+ const config = JSON.parse(readFileSync(wbPath, 'utf-8'));
51
+ return {
52
+ path: dir,
53
+ relativePath: relative(repoRoot, dir),
54
+ config,
55
+ };
56
+ }
57
+ // Don't walk above repo root
58
+ if (dir === repoRoot) return null;
59
+ const parent = dirname(dir);
60
+ if (parent === dir) return null;
61
+ dir = parent;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Recursively find all workbench.json files under a directory.
67
+ */
68
+ function findWorkbenchFiles(dir) {
69
+ const results = [];
70
+ let entries;
71
+ try {
72
+ entries = readdirSync(dir, { withFileTypes: true });
73
+ } catch {
74
+ return results;
75
+ }
76
+ for (const entry of entries) {
77
+ if (entry.name === 'node_modules' || entry.name === '.git') continue;
78
+ // Skip .claude/worktrees/ — temporary agent isolation, not canonical workbench locations
79
+ if (entry.name === 'worktrees' && dir.endsWith('.claude')) continue;
80
+ const fullPath = join(dir, entry.name);
81
+ if (entry.isDirectory()) {
82
+ results.push(...findWorkbenchFiles(fullPath));
83
+ } else if (entry.name === 'workbench.json') {
84
+ results.push(fullPath);
85
+ }
86
+ }
87
+ return results;
88
+ }
89
+
90
+ /**
91
+ * Discover all workbenches in the repo.
92
+ * Returns array of { name, path, relativePath, config }.
93
+ */
94
+ export function findAllWorkbenches(cwd = process.cwd()) {
95
+ const repoRoot = findRepoRoot(cwd);
96
+ const files = findWorkbenchFiles(repoRoot);
97
+
98
+ return files.map((filePath) => {
99
+ const wbDir = dirname(filePath);
100
+ const config = JSON.parse(readFileSync(filePath, 'utf-8'));
101
+ const relPath = relative(repoRoot, wbDir);
102
+ // Derive name from directory name
103
+ const name = relPath.split('/').pop();
104
+ return {
105
+ name,
106
+ path: wbDir,
107
+ relativePath: relPath,
108
+ config,
109
+ };
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Resolve a --workbench flag value to a workbench path.
115
+ * First tries as a marketplace.json alias, then as a direct path.
116
+ */
117
+ export function resolveWorkbenchFlag(nameOrPath, cwd = process.cwd()) {
118
+ const repoRoot = findRepoRoot(cwd);
119
+
120
+ // Try marketplace.json alias first
121
+ const marketplacePath = join(repoRoot, '.claude-plugin', 'marketplace.json');
122
+ if (existsSync(marketplacePath)) {
123
+ const marketplace = JSON.parse(readFileSync(marketplacePath, 'utf-8'));
124
+ const plugin = (marketplace.plugins || []).find((p) => p.name === nameOrPath);
125
+ if (plugin && plugin.source) {
126
+ const resolved = resolve(repoRoot, plugin.source);
127
+ if (existsSync(join(resolved, 'workbench.json'))) {
128
+ const config = JSON.parse(readFileSync(join(resolved, 'workbench.json'), 'utf-8'));
129
+ return {
130
+ path: resolved,
131
+ relativePath: relative(repoRoot, resolved),
132
+ config,
133
+ };
134
+ }
135
+ }
136
+ }
137
+
138
+ // Try as direct path (absolute or relative to cwd)
139
+ const directPath = resolve(cwd, nameOrPath);
140
+ if (existsSync(join(directPath, 'workbench.json'))) {
141
+ const config = JSON.parse(readFileSync(join(directPath, 'workbench.json'), 'utf-8'));
142
+ return {
143
+ path: directPath,
144
+ relativePath: relative(repoRoot, directPath),
145
+ config,
146
+ };
147
+ }
148
+
149
+ // Try as relative to repo root
150
+ const fromRoot = resolve(repoRoot, nameOrPath);
151
+ if (fromRoot !== directPath && existsSync(join(fromRoot, 'workbench.json'))) {
152
+ const config = JSON.parse(readFileSync(join(fromRoot, 'workbench.json'), 'utf-8'));
153
+ return {
154
+ path: fromRoot,
155
+ relativePath: relative(repoRoot, fromRoot),
156
+ config,
157
+ };
158
+ }
159
+
160
+ throw new NotFoundError(
161
+ `Workbench "${nameOrPath}" not found. Not a marketplace alias or valid path.`,
162
+ {
163
+ code: 'workbench_not_found',
164
+ suggestion: 'Pass a path to a directory containing workbench.json, or run the appropriate marketplace/workbench discovery command if you expose one.',
165
+ }
166
+ );
167
+ }