Skip to content

Commit 14695e0

Browse files
committed
refactor(cli): make call tool-invocation only
1 parent d3519a2 commit 14695e0

4 files changed

Lines changed: 22 additions & 109 deletions

File tree

README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,9 @@ const issues = await tools.github.issues.list({
7878
});
7979
```
8080

81-
Run code via the CLI:
81+
Use tools via the CLI:
8282

8383
```bash
84-
executor call --file script.ts
85-
executor call 'return await tools.discover({ query: "send email" })'
8684
executor tools search "send email"
8785
executor call gmail send '{"to":"alice@example.com","subject":"Hi"}'
8886
executor call github issues create '{"owner":"octocat","repo":"Hello-World","title":"Hi"}'
@@ -106,14 +104,11 @@ executor daemon status # show daemon status
106104
executor daemon stop # stop daemon
107105
executor daemon restart # restart daemon
108106
executor mcp # start MCP endpoint
109-
executor call --file script.ts # execute a file
110-
executor call '<code>' # execute inline code
111-
executor call --stdin # execute from stdin
107+
executor call <path...> '{"k":"v"}' # invoke a tool by path segments
112108
executor resume --execution-id <id> # resume paused execution
113109
executor tools search "<query>" # search tools by intent
114110
executor tools sources # list configured sources + tool counts
115111
executor tools describe <path> # show tool TypeScript/JSON schema
116-
executor call <path...> '{"k":"v"}' # invoke a tool by path segments
117112
```
118113

119114
## Developing locally

apps/cli/src/main.ts

Lines changed: 13 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ import {
7373
buildSearchToolsCode,
7474
extractExecutionId,
7575
extractExecutionResult,
76-
isLikelyToolPathToken,
7776
parseJsonObjectInput,
7877
} from "./tooling";
7978

@@ -529,46 +528,6 @@ const runStdioMcpSession = () =>
529528
);
530529
});
531530

532-
// ---------------------------------------------------------------------------
533-
// Code resolution — positional arg > --file > stdin
534-
// ---------------------------------------------------------------------------
535-
536-
const readCode = (input: {
537-
code: Option.Option<string>;
538-
file: Option.Option<string>;
539-
stdin: boolean;
540-
}): Effect.Effect<string, Error, FileSystem.FileSystem> =>
541-
Effect.gen(function* () {
542-
const fs = yield* FileSystem.FileSystem;
543-
const code = Option.getOrUndefined(input.code);
544-
if (code && code.trim().length > 0) return code;
545-
546-
const file = Option.getOrUndefined(input.file);
547-
if (file && file.trim().length > 0) {
548-
const contents = yield* fs.readFileString(file).pipe(
549-
Effect.mapError((cause) => new Error(`Failed to read file: ${String(cause)}`)),
550-
);
551-
if (contents.trim().length > 0) return contents;
552-
}
553-
554-
if (input.stdin || !process.stdin.isTTY) {
555-
const chunks: string[] = [];
556-
process.stdin.setEncoding("utf8");
557-
const contents = yield* Effect.tryPromise({
558-
try: async () => {
559-
for await (const chunk of process.stdin) chunks.push(chunk as string);
560-
return chunks.join("");
561-
},
562-
catch: (e) => new Error(`Failed to read stdin: ${e}`),
563-
});
564-
if (contents.trim().length > 0) return contents;
565-
}
566-
567-
return yield* Effect.fail(
568-
new Error("No code provided. Pass code as an argument, --file, or pipe to stdin."),
569-
);
570-
});
571-
572531
const scope = Options.text("scope").pipe(
573532
Options.optional,
574533
Options.withDescription("Path to workspace directory containing executor.jsonc"),
@@ -610,67 +569,28 @@ const resolveToolInvocation = (input: {
610569
const callCommand = Command.make(
611570
"call",
612571
{
613-
codeOrTool: Args.text({ name: "code-or-tool" }).pipe(Args.repeated),
614-
file: Options.text("file").pipe(Options.optional),
615-
stdin: Options.boolean("stdin").pipe(Options.withDefault(false)),
572+
pathParts: Args.text({ name: "tool-path-segment" }).pipe(Args.repeated),
616573
baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_BASE_URL)),
617574
scope,
618575
},
619-
({ codeOrTool, file, stdin, baseUrl, scope }) =>
576+
({ pathParts, baseUrl, scope }) =>
620577
Effect.gen(function* () {
621578
applyScope(scope);
622-
const singleArg = codeOrTool.length === 1 ? codeOrTool[0] : undefined;
623-
const singleArgLooksLikeToolPath =
624-
singleArg !== undefined && singleArg.includes(".") && isLikelyToolPathToken(singleArg);
625-
const isToolInvocation =
626-
!stdin &&
627-
Option.isNone(file) &&
628-
(codeOrTool.length > 1 || singleArgLooksLikeToolPath);
629-
630-
if (isToolInvocation) {
631-
const { path, args } = yield* resolveToolInvocation({
632-
rawPathParts: codeOrTool,
633-
});
634-
const code = yield* Effect.try({
635-
try: () => buildInvokeToolCode(path, args),
636-
catch: (cause) =>
637-
cause instanceof Error ? cause : new Error(`Invalid tool path: ${String(cause)}`),
638-
});
639-
640-
const outcome = yield* executeCode({ baseUrl, code });
641-
yield* printExecutionOutcome({ baseUrl, outcome });
642-
return;
643-
}
644-
645-
const inlineCode = singleArg !== undefined ? Option.some(singleArg) : Option.none<string>();
646-
const resolvedCode = yield* readCode({ code: inlineCode, file, stdin });
647-
const daemonUrl = yield* ensureDaemon(baseUrl);
648-
649-
const client = yield* makeApiClient(daemonUrl);
650-
const result = yield* client.executions.execute({ payload: { code: resolvedCode } });
579+
const { path, args } = yield* resolveToolInvocation({
580+
rawPathParts: pathParts,
581+
});
582+
const code = yield* Effect.try({
583+
try: () => buildInvokeToolCode(path, args),
584+
catch: (cause) =>
585+
cause instanceof Error ? cause : new Error(`Invalid tool path: ${String(cause)}`),
586+
});
651587

652-
if (result.status === "completed") {
653-
if (result.isError) {
654-
console.error(result.text);
655-
process.exit(1);
656-
} else {
657-
console.log(result.text);
658-
process.exit(0);
659-
}
660-
} else {
661-
console.log(result.text);
662-
const executionId = (result.structured as Record<string, unknown> | undefined)?.executionId;
663-
if (executionId) {
664-
console.log(
665-
`\nTo resume:\n ${cliPrefix} resume --execution-id ${executionId} --action accept`,
666-
);
667-
}
668-
process.exit(0);
669-
}
588+
const outcome = yield* executeCode({ baseUrl, code });
589+
yield* printExecutionOutcome({ baseUrl, outcome });
670590
}),
671591
).pipe(
672592
Command.withDescription(
673-
"Execute JavaScript or invoke a tool path (e.g. `executor call github issues create '{\"title\":\"Hi\"}'`)",
593+
"Invoke a tool path (e.g. `executor call github issues create '{\"title\":\"Hi\"}'`)",
674594
),
675595
);
676596

apps/cli/src/tooling.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@ export const buildToolPath = (parts: ReadonlyArray<string>): string => {
1616
if (segments.length === 0) {
1717
throw new Error("Tool path must include at least one segment");
1818
}
19+
if (segments.some((segment) => !TOOL_PATH_TOKEN.test(segment))) {
20+
throw new Error(
21+
"Tool path segments must contain only letters, numbers, '.', '_' or '-'",
22+
);
23+
}
1924
return segments.join(".");
2025
};
2126

22-
export const isLikelyToolPathToken = (raw: string): boolean => {
23-
const value = raw.trim();
24-
return value.length > 0 && TOOL_PATH_TOKEN.test(value);
25-
};
26-
2727
const buildToolAccessExpression = (toolPath: string): string => {
2828
const segments = toToolPathSegments([toolPath]);
2929
if (segments.length === 0) {

tests/tools-cli.test.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
buildSearchToolsCode,
99
extractExecutionId,
1010
extractExecutionResult,
11-
isLikelyToolPathToken,
1211
parseJsonObjectInput,
1312
} from "../apps/cli/src/tooling";
1413

@@ -45,9 +44,8 @@ describe("CLI tooling helpers", () => {
4544
expect(buildToolPath(["github.issues", "create"])).toBe("github.issues.create");
4645
});
4746

48-
it("detects likely tool path tokens", () => {
49-
expect(isLikelyToolPathToken("github.issues.create")).toBe(true);
50-
expect(isLikelyToolPathToken("return await tools.search({})")).toBe(false);
47+
it("rejects invalid tool-path segments", () => {
48+
expect(() => buildToolPath(["github", "issues", "create now"])).toThrow();
5149
});
5250

5351
it("builds search and sources code snippets", () => {

0 commit comments

Comments
 (0)