@enactprotocol/cli 2.1.23 → 2.1.28
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/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init/templates/agent-agents.d.ts +1 -1
- package/dist/commands/init/templates/agent-agents.d.ts.map +1 -1
- package/dist/commands/init/templates/agent-agents.js +25 -12
- package/dist/commands/init/templates/agent-agents.js.map +1 -1
- package/dist/commands/init/templates/claude.d.ts +1 -1
- package/dist/commands/init/templates/claude.d.ts.map +1 -1
- package/dist/commands/init/templates/claude.js +1 -1
- package/dist/commands/init/templates/tool-agents.d.ts +1 -1
- package/dist/commands/init/templates/tool-agents.d.ts.map +1 -1
- package/dist/commands/init/templates/tool-agents.js +8 -8
- package/dist/commands/install/index.d.ts.map +1 -1
- package/dist/commands/install/index.js +9 -1
- package/dist/commands/install/index.js.map +1 -1
- package/dist/commands/mcp/index.d.ts.map +1 -1
- package/dist/commands/mcp/index.js +204 -53
- package/dist/commands/mcp/index.js.map +1 -1
- package/dist/commands/run/index.d.ts.map +1 -1
- package/dist/commands/run/index.js +297 -38
- package/dist/commands/run/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/errors.d.ts +8 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +13 -2
- package/dist/utils/errors.js.map +1 -1
- package/package.json +5 -5
- package/src/commands/index.ts +2 -0
- package/src/commands/init/templates/agent-agents.ts +25 -12
- package/src/commands/init/templates/claude.ts +1 -1
- package/src/commands/init/templates/tool-agents.ts +8 -8
- package/src/commands/install/index.ts +11 -0
- package/src/commands/learn/index.ts +6 -11
- package/src/commands/mcp/index.ts +768 -0
- package/src/commands/run/README.md +51 -1
- package/src/commands/run/index.ts +374 -35
- package/src/index.ts +4 -1
- package/src/utils/errors.ts +26 -6
- package/tests/commands/init.test.ts +1 -1
- package/tests/commands/run.test.ts +260 -0
- package/tests/utils/errors.test.ts +36 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -140,6 +140,94 @@ describe("run command", () => {
|
|
|
140
140
|
const localOpt = opts.find((o) => o.long === "--local");
|
|
141
141
|
expect(localOpt).toBeDefined();
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
test("has --remote option", () => {
|
|
145
|
+
const program = new Command();
|
|
146
|
+
configureRunCommand(program);
|
|
147
|
+
|
|
148
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
149
|
+
const opts = runCmd?.options ?? [];
|
|
150
|
+
const remoteOpt = opts.find((o) => o.long === "--remote");
|
|
151
|
+
expect(remoteOpt).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("--remote option has short flag -r", () => {
|
|
155
|
+
const program = new Command();
|
|
156
|
+
configureRunCommand(program);
|
|
157
|
+
|
|
158
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
159
|
+
const opts = runCmd?.options ?? [];
|
|
160
|
+
const remoteOpt = opts.find((o) => o.long === "--remote");
|
|
161
|
+
expect(remoteOpt?.short).toBe("-r");
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("tool resolution logic", () => {
|
|
166
|
+
// Test the resolution logic patterns for local vs remote tools
|
|
167
|
+
|
|
168
|
+
test("path-like targets are local only (./)", () => {
|
|
169
|
+
const tool = "./hello";
|
|
170
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
171
|
+
expect(isRegistryFormat).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("path-like targets are local only (../)", () => {
|
|
175
|
+
const tool = "../tools/hello";
|
|
176
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
177
|
+
expect(isRegistryFormat).toBe(false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("path-like targets are local only (/abs)", () => {
|
|
181
|
+
const tool = "/absolute/path/hello";
|
|
182
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
183
|
+
expect(isRegistryFormat).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("simple names without slash are local only", () => {
|
|
187
|
+
const tool = "hello";
|
|
188
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
189
|
+
expect(isRegistryFormat).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("namespace/tool format can be registry", () => {
|
|
193
|
+
const tool = "user/hello";
|
|
194
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
195
|
+
expect(isRegistryFormat).toBe(true);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("nested namespace/ns/tool format can be registry", () => {
|
|
199
|
+
const tool = "org/namespace/hello";
|
|
200
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
201
|
+
expect(isRegistryFormat).toBe(true);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("--remote with simple name should be rejected", () => {
|
|
205
|
+
const tool = "hello";
|
|
206
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
207
|
+
const remoteFlag = true;
|
|
208
|
+
|
|
209
|
+
// --remote requires registry format
|
|
210
|
+
const isValid = !remoteFlag || isRegistryFormat;
|
|
211
|
+
expect(isValid).toBe(false);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("--remote with namespace/tool is valid", () => {
|
|
215
|
+
const tool = "user/hello";
|
|
216
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
217
|
+
const remoteFlag = true;
|
|
218
|
+
|
|
219
|
+
const isValid = !remoteFlag || isRegistryFormat;
|
|
220
|
+
expect(isValid).toBe(true);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("--remote with path should be rejected", () => {
|
|
224
|
+
const tool = "./hello";
|
|
225
|
+
const isRegistryFormat = tool.includes("/") && !tool.startsWith("/") && !tool.startsWith(".");
|
|
226
|
+
const remoteFlag = true;
|
|
227
|
+
|
|
228
|
+
const isValid = !remoteFlag || isRegistryFormat;
|
|
229
|
+
expect(isValid).toBe(false);
|
|
230
|
+
});
|
|
143
231
|
});
|
|
144
232
|
|
|
145
233
|
describe("input parsing helpers", () => {
|
|
@@ -239,6 +327,178 @@ describe("run command", () => {
|
|
|
239
327
|
});
|
|
240
328
|
});
|
|
241
329
|
|
|
330
|
+
describe("--output option configuration", () => {
|
|
331
|
+
test("has --output option", () => {
|
|
332
|
+
const program = new Command();
|
|
333
|
+
configureRunCommand(program);
|
|
334
|
+
|
|
335
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
336
|
+
const opts = runCmd?.options ?? [];
|
|
337
|
+
const outputOpt = opts.find((o) => o.long === "--output");
|
|
338
|
+
expect(outputOpt).toBeDefined();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("--output option has short flag -o", () => {
|
|
342
|
+
const program = new Command();
|
|
343
|
+
configureRunCommand(program);
|
|
344
|
+
|
|
345
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
346
|
+
const opts = runCmd?.options ?? [];
|
|
347
|
+
const outputOpt = opts.find((o) => o.long === "--output");
|
|
348
|
+
expect(outputOpt?.short).toBe("-o");
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("--output option takes a path argument", () => {
|
|
352
|
+
const program = new Command();
|
|
353
|
+
configureRunCommand(program);
|
|
354
|
+
|
|
355
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
356
|
+
const opts = runCmd?.options ?? [];
|
|
357
|
+
const outputOpt = opts.find((o) => o.long === "--output");
|
|
358
|
+
// Non-variadic options should not be variadic
|
|
359
|
+
expect(outputOpt?.variadic).toBeFalsy();
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
describe("input path parsing patterns", () => {
|
|
364
|
+
// Test the input parsing logic patterns (--input can be key=value or path)
|
|
365
|
+
|
|
366
|
+
test("key=value is detected as parameter, not path", () => {
|
|
367
|
+
const input = "name=Alice";
|
|
368
|
+
const eqIndex = input.indexOf("=");
|
|
369
|
+
const looksLikePath =
|
|
370
|
+
input.startsWith("./") || input.startsWith("../") || input.startsWith("/");
|
|
371
|
+
|
|
372
|
+
expect(eqIndex).toBeGreaterThan(0);
|
|
373
|
+
expect(looksLikePath).toBe(false);
|
|
374
|
+
// This should be treated as a key=value parameter
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("./path is detected as input path", () => {
|
|
378
|
+
const input = "./data";
|
|
379
|
+
const looksLikePath =
|
|
380
|
+
input.startsWith("./") || input.startsWith("../") || input.startsWith("/");
|
|
381
|
+
|
|
382
|
+
expect(looksLikePath).toBe(true);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("../path is detected as input path", () => {
|
|
386
|
+
const input = "../parent/data";
|
|
387
|
+
const looksLikePath =
|
|
388
|
+
input.startsWith("./") || input.startsWith("../") || input.startsWith("/");
|
|
389
|
+
|
|
390
|
+
expect(looksLikePath).toBe(true);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("/absolute/path is detected as input path", () => {
|
|
394
|
+
const input = "/absolute/data";
|
|
395
|
+
const looksLikePath =
|
|
396
|
+
input.startsWith("./") || input.startsWith("../") || input.startsWith("/");
|
|
397
|
+
|
|
398
|
+
expect(looksLikePath).toBe(true);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("name=./path is detected as named input", () => {
|
|
402
|
+
const input = "left=./old";
|
|
403
|
+
const eqIndex = input.indexOf("=");
|
|
404
|
+
const key = input.slice(0, eqIndex);
|
|
405
|
+
const value = input.slice(eqIndex + 1);
|
|
406
|
+
|
|
407
|
+
expect(key).toBe("left");
|
|
408
|
+
expect(value).toBe("./old");
|
|
409
|
+
expect(value.startsWith("./")).toBe(true);
|
|
410
|
+
// This should be treated as a named input path
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe("input target paths", () => {
|
|
415
|
+
test("single unnamed input goes to /input", () => {
|
|
416
|
+
const inputName = undefined;
|
|
417
|
+
|
|
418
|
+
const target = inputName !== undefined ? `/inputs/${inputName}` : "/input";
|
|
419
|
+
|
|
420
|
+
expect(target).toBe("/input");
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("named input goes to /inputs/<name>", () => {
|
|
424
|
+
const inputName = "left";
|
|
425
|
+
|
|
426
|
+
const target = `/inputs/${inputName}`;
|
|
427
|
+
expect(target).toBe("/inputs/left");
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
test("single file input goes to /input/<filename>", () => {
|
|
431
|
+
const inputType = "file";
|
|
432
|
+
const inputName = undefined;
|
|
433
|
+
const filename = "data.csv";
|
|
434
|
+
|
|
435
|
+
const target =
|
|
436
|
+
inputName !== undefined
|
|
437
|
+
? `/inputs/${inputName}`
|
|
438
|
+
: inputType === "file"
|
|
439
|
+
? `/input/${filename}`
|
|
440
|
+
: "/input";
|
|
441
|
+
|
|
442
|
+
expect(target).toBe("/input/data.csv");
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
describe("--apply option configuration", () => {
|
|
447
|
+
test("has --apply option", () => {
|
|
448
|
+
const program = new Command();
|
|
449
|
+
configureRunCommand(program);
|
|
450
|
+
|
|
451
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
452
|
+
const opts = runCmd?.options ?? [];
|
|
453
|
+
const applyOpt = opts.find((o) => o.long === "--apply");
|
|
454
|
+
expect(applyOpt).toBeDefined();
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
test("--apply option is a boolean flag", () => {
|
|
458
|
+
const program = new Command();
|
|
459
|
+
configureRunCommand(program);
|
|
460
|
+
|
|
461
|
+
const runCmd = program.commands.find((cmd) => cmd.name() === "run");
|
|
462
|
+
const opts = runCmd?.options ?? [];
|
|
463
|
+
const applyOpt = opts.find((o) => o.long === "--apply");
|
|
464
|
+
// Boolean flags don't have required or optional args
|
|
465
|
+
expect(applyOpt?.required).toBeFalsy();
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
describe("--apply validation logic", () => {
|
|
470
|
+
test("--apply requires a directory input", () => {
|
|
471
|
+
// When using --apply, you need exactly one unnamed directory input
|
|
472
|
+
const inputPaths = [{ path: "/some/dir", type: "directory" as const }];
|
|
473
|
+
const hasValidInput = inputPaths.some((p) => p.type === "directory");
|
|
474
|
+
expect(hasValidInput).toBe(true);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("--apply rejects file-only inputs", () => {
|
|
478
|
+
const inputPaths: { path: string; type: "directory" | "file" }[] = [
|
|
479
|
+
{ path: "/some/file.txt", type: "file" as "directory" | "file" },
|
|
480
|
+
];
|
|
481
|
+
const dirInputs = inputPaths.filter((p) => p.type === "directory");
|
|
482
|
+
expect(dirInputs.length).toBe(0);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
test("--apply requires output path", () => {
|
|
486
|
+
// Validation check: --apply needs --output
|
|
487
|
+
const options = { apply: true, output: undefined };
|
|
488
|
+
const isValid = options.apply ? options.output !== undefined : true;
|
|
489
|
+
expect(isValid).toBe(false);
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("--apply with matching input/output is valid", () => {
|
|
493
|
+
const options = { apply: true, output: "/some/dir" };
|
|
494
|
+
const inputPath = "/some/dir";
|
|
495
|
+
const outputPath = options.output;
|
|
496
|
+
|
|
497
|
+
// When input and output match, it's an in-place apply
|
|
498
|
+
expect(inputPath).toBe(outputPath);
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
|
|
242
502
|
describe("input file handling", () => {
|
|
243
503
|
test("JSON input file can be parsed", () => {
|
|
244
504
|
// Create a test JSON file
|
|
@@ -65,6 +65,42 @@ describe("error classes", () => {
|
|
|
65
65
|
expect(err.exitCode).toBe(EXIT_TOOL_NOT_FOUND);
|
|
66
66
|
expect(err.suggestion).toContain("owner/namespace/tool");
|
|
67
67
|
});
|
|
68
|
+
|
|
69
|
+
test("includes reason when provided", () => {
|
|
70
|
+
const err = new ToolNotFoundError("my-tool", {
|
|
71
|
+
reason: "Manifest validation failed",
|
|
72
|
+
});
|
|
73
|
+
expect(err.message).toContain("Tool not found: my-tool");
|
|
74
|
+
expect(err.message).toContain("Manifest validation failed");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("includes searched locations when provided", () => {
|
|
78
|
+
const err = new ToolNotFoundError("my-tool", {
|
|
79
|
+
searchedLocations: ["/path/to/tools", "/other/path"],
|
|
80
|
+
});
|
|
81
|
+
expect(err.message).toContain("Searched locations:");
|
|
82
|
+
expect(err.message).toContain("/path/to/tools");
|
|
83
|
+
expect(err.message).toContain("/other/path");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("provides different suggestion when localOnly is true", () => {
|
|
87
|
+
const err = new ToolNotFoundError("my-tool", { localOnly: true });
|
|
88
|
+
expect(err.suggestion).toContain("Remove --local flag");
|
|
89
|
+
expect(err.suggestion).not.toContain("owner/namespace/tool");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("handles all options together", () => {
|
|
93
|
+
const err = new ToolNotFoundError("my-tool", {
|
|
94
|
+
reason: "No manifest found",
|
|
95
|
+
searchedLocations: ["/a", "/b"],
|
|
96
|
+
localOnly: true,
|
|
97
|
+
});
|
|
98
|
+
expect(err.message).toContain("my-tool");
|
|
99
|
+
expect(err.message).toContain("No manifest found");
|
|
100
|
+
expect(err.message).toContain("/a");
|
|
101
|
+
expect(err.message).toContain("/b");
|
|
102
|
+
expect(err.suggestion).toContain("Remove --local flag");
|
|
103
|
+
});
|
|
68
104
|
});
|
|
69
105
|
|
|
70
106
|
describe("ManifestError", () => {
|