@heyditto/cli 2.0.1 → 2.1.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/README.md CHANGED
@@ -42,6 +42,17 @@ You can also reorder your `PATH` so the npm global bin comes before `/usr/bin`,
42
42
 
43
43
  ## Auth
44
44
 
45
+ Humans sign in through the browser:
46
+
47
+ ```bash
48
+ heyditto login
49
+ ```
50
+
51
+ This prints a short code, opens `https://heyditto.ai/device` (pre-filled with the
52
+ code), and saves the resulting key to your CLI config directory once you approve
53
+ it. Pass a key as an argument (`heyditto login ditto_mcp_…`), pipe it with
54
+ `--stdin`, or use `--paste` for the older copy-a-key page.
55
+
45
56
  Agents can self-provision without a browser, email, or OTP:
46
57
 
47
58
  ```bash
@@ -111,6 +122,17 @@ heyditto graphs subscribers
111
122
  heyditto graphs sharing (--enable|--disable) [--title <t>] [--description <d>]
112
123
  heyditto init [--name <name>] [--subscribe <@graph>]... [<@graph>...] [--json]
113
124
  heyditto login [<key>] [--paste] [--stdin]
125
+ heyditto endpoints [--set-default <slug>] [--clear-default]
126
+ heyditto claude [options] [-- <claude args>]
127
+ heyditto codex [options] [-- <codex args>]
128
+ heyditto sessions [--json] [--all]
129
+ heyditto sessions rm <id>
130
+ heyditto session new [<name>...] [--id <id>]
131
+ heyditto session list [--all]
132
+ heyditto session use <id>
133
+ heyditto session current
134
+ heyditto session end
135
+ heyditto agents [--output <format>]
114
136
  heyditto logout
115
137
  heyditto status [--output <format>]
116
138
  heyditto config
@@ -289,10 +311,109 @@ graph. Choose it up front — renaming after init isn't yet supported. See
289
311
 
290
312
  Print a Claude Desktop / Cursor / generic-MCP-client config snippet for the Ditto memory server.
291
313
 
314
+ ## Coding agents
315
+
316
+ `heyditto claude` and `heyditto codex` launch Claude Code or Codex through one of
317
+ your Ditto **inference endpoints** (Settings → Developer → Inference endpoints
318
+ in the Ditto app). Each launch:
319
+
320
+ 1. lists your endpoints and lets you pick one (or uses `--endpoint` / the saved default),
321
+ 2. mints a temporary endpoint key (optionally capped with `--budget <tokens>`),
322
+ 3. starts the agent with that key and a fresh `X-Ditto-Session-Id`, so the
323
+ session becomes its own thread with full traces under the endpoint,
324
+ 4. **revokes the key when the agent exits** (Ctrl+C included). The thread and
325
+ traces are kept; `--keep-key` opts out, `--expires` sets a server-side
326
+ safety expiry (default `1d`).
327
+
328
+ ```bash
329
+ npx -y @heyditto/cli login
330
+ npx -y @heyditto/cli claude --endpoint my-endpoint
331
+
332
+ heyditto endpoints --set-default my-endpoint # skip the picker next time
333
+ heyditto claude # interactive Claude Code
334
+ heyditto codex --yellow # Codex, auto-accept edits
335
+ heyditto claude --yolo --worktree fix-login # bypass prompts in <repo>/.worktrees/fix-login
336
+ heyditto codex -p "summarize this repo" --json # headless (codex exec)
337
+ heyditto claude -p "list TODOs" --output-format json --max-turns 3
338
+ heyditto claude --resume # reopen the last session in the same thread
339
+ heyditto claude -- --verbose # anything after -- goes to the agent
340
+ ```
341
+
342
+ Options shared by both commands:
343
+
344
+ | Flag | Meaning |
345
+ | --- | --- |
346
+ | `-e, --endpoint <slug>` | endpoint to route through (default: saved default, else a picker) |
347
+ | `--budget <tokens>` | spend cap for this session's key, in Ditto tokens |
348
+ | `--expires <1h…never>` | server-side key expiry; the key is still revoked on exit unless `--keep-key` |
349
+ | `--session <id>` | reuse a Ditto session id so traces land in an existing thread |
350
+ | `--resume [id]` / `-c, --continue` | resume a local session (`heyditto sessions`) / the agent's most recent conversation |
351
+ | `--yolo` / `--yellow` / `--plan` | bypass permissions / auto-accept edits / plan mode (Claude only) |
352
+ | `-p, --prompt <text>` | headless run: `claude -p` or `codex exec` |
353
+ | `-m, --model <id>` | model to request (Codex defaults to the endpoint slug; Claude's ids follow the endpoint's routes) |
354
+ | `-w, --worktree [name]` | run inside `<repo>/.worktrees/<name>` on a branch of that name; `.worktrees/` is added to `.gitignore` |
355
+ | `--name <label>` | key name shown in the app (default `cli:<agent>:<hostname>`) |
356
+ | `--dry-run` | print the command, args and env with the key masked; mints nothing |
357
+
358
+ Unknown flags and everything after `--` are forwarded to the agent, so Claude's
359
+ and Codex's own options keep working.
360
+
361
+ How the wiring works:
362
+
363
+ - **Claude Code** gets `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN` (a bearer
364
+ token, which skips Claude's "use this API key?" prompt) and
365
+ `ANTHROPIC_CUSTOM_HEADERS` with the session id. Any inherited
366
+ `ANTHROPIC_API_KEY` is removed from the child environment.
367
+ - **Codex** speaks the Responses API only, so the endpoint is injected as a
368
+ `ditto` model provider via `-c` overrides (nothing is written to
369
+ `~/.codex/config.toml`) with the key in `DITTO_INFERENCE_API_KEY`.
370
+
371
+ ### `endpoints`
372
+
373
+ List your inference endpoints with model and spend. `--set-default <slug>` picks
374
+ the one used when `--endpoint` is omitted; `--output json` includes the gateway
375
+ base URL.
376
+
377
+ ### `sessions`
378
+
379
+ List the coding-agent sessions launched from this machine (stored under
380
+ `~/.config/heyditto/cli/sessions/`). `heyditto sessions rm <id>` forgets a local
381
+ record; the Ditto thread and traces are unaffected.
382
+
383
+ ### `session`
384
+
385
+ Explicit MCP sessions. Without one, the server groups your saves and searches
386
+ into a time-based *implicit* session (activity within a cooldown, auto-named
387
+ when it goes quiet). `heyditto session new [name]` pins an explicit session:
388
+ every MCP request from then on carries `X-Ditto-Session-Id`, and the name goes
389
+ out once as `X-Ditto-Session-Name` so the thread gets that title. Saves and
390
+ searches land in one thread inside the agent your key is attached to.
391
+
392
+ ```bash
393
+ heyditto session new "refactor auth module" # prints the id and makes it active
394
+ heyditto save "Decided to keep JWT refresh in the gateway"
395
+ heyditto session current # the active id (exit 1 when none)
396
+ heyditto session list # local history, * marks the active one
397
+ heyditto session end # back to implicit sessions
398
+ heyditto session use 9e9a93c3 # reactivate by id or unique prefix
399
+ ```
400
+
401
+ `DITTO_SESSION_ID=<id>` pins a session for one shell or script (it overrides the
402
+ saved one and never sends a name). Sessions are tracked locally in
403
+ `~/.config/heyditto/cli/mcp-sessions.json`; the server keeps the threads.
404
+
405
+ ### `agents`
406
+
407
+ List your Ditto agents (`GET /api/v5/chat-agents`): id, kind (`main`, `chat`,
408
+ `inference_endpoint`, `mcp`, `connector`), name, thread count, last activity and
409
+ the live connections (API keys, OAuth grants, endpoints) writing into each.
410
+
292
411
  ## Environment
293
412
 
294
413
  - `DITTO_API_KEY` (optional) — MCP API key override. Agents can instead run `heyditto init --json` for no-human setup.
295
414
  - `DITTO_API_BASE` (optional) — API base URL. Defaults to `https://api.heyditto.ai`. Useful for local dev (`http://localhost:3400`).
415
+ - `DITTO_SESSION_ID` (optional) — pin an explicit MCP session id for this shell (see `session`).
416
+ - `DITTO_CONFIG_DIR` (optional) — config directory for the saved key, default endpoint and session records. Defaults to `$XDG_CONFIG_HOME/heyditto/cli` or `~/.config/heyditto/cli`.
296
417
 
297
418
  ## Output
298
419
 
@@ -0,0 +1,11 @@
1
+ import { type HarnessPlan, type PlanInput } from "./types.js";
2
+ /**
3
+ * Builds the `claude` invocation.
4
+ *
5
+ * Auth goes through ANTHROPIC_AUTH_TOKEN (sent as `Authorization: Bearer`):
6
+ * unlike ANTHROPIC_API_KEY it never triggers the interactive "use this API
7
+ * key? No (recommended)" approval prompt, so the same plan works headless.
8
+ * Any inherited ANTHROPIC_API_KEY is removed so it cannot re-trigger that
9
+ * prompt or add a stray x-api-key header.
10
+ */
11
+ export declare function planClaude(input: PlanInput): HarnessPlan;
@@ -0,0 +1,48 @@
1
+ import { SESSION_HEADER, apiRootOf } from "./types.js";
2
+ /**
3
+ * Builds the `claude` invocation.
4
+ *
5
+ * Auth goes through ANTHROPIC_AUTH_TOKEN (sent as `Authorization: Bearer`):
6
+ * unlike ANTHROPIC_API_KEY it never triggers the interactive "use this API
7
+ * key? No (recommended)" approval prompt, so the same plan works headless.
8
+ * Any inherited ANTHROPIC_API_KEY is removed so it cannot re-trigger that
9
+ * prompt or add a stray x-api-key header.
10
+ */
11
+ export function planClaude(input) {
12
+ const args = [];
13
+ if (input.yolo)
14
+ args.push("--dangerously-skip-permissions");
15
+ else if (input.yellow)
16
+ args.push("--permission-mode", "acceptEdits");
17
+ else if (input.plan)
18
+ args.push("--permission-mode", "plan");
19
+ if (input.resumeId)
20
+ args.push("--resume", input.resumeId);
21
+ else if (input.resumeLast)
22
+ args.push("--continue");
23
+ else
24
+ args.push("--session-id", input.sessionId);
25
+ if (input.model)
26
+ args.push("--model", input.model);
27
+ if (input.prompt !== undefined)
28
+ args.push("-p", input.prompt);
29
+ args.push(...input.passthrough);
30
+ const headers = [input.env.ANTHROPIC_CUSTOM_HEADERS?.trim(), `${SESSION_HEADER}: ${input.sessionId}`]
31
+ .filter((h) => Boolean(h))
32
+ .join("\n");
33
+ const envSet = {
34
+ ANTHROPIC_BASE_URL: apiRootOf(input.baseUrl),
35
+ ANTHROPIC_AUTH_TOKEN: input.apiKey,
36
+ ANTHROPIC_CUSTOM_HEADERS: headers,
37
+ };
38
+ if (input.model)
39
+ envSet.ANTHROPIC_MODEL = input.model;
40
+ return {
41
+ command: "claude",
42
+ args,
43
+ envSet,
44
+ envUnset: ["ANTHROPIC_API_KEY"],
45
+ installHint: "install Claude Code: npm i -g @anthropic-ai/claude-code (or see https://code.claude.com)",
46
+ };
47
+ }
48
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/agents/claude.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzF;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAgB;IACzC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,KAAK,CAAC,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;SACvD,IAAI,KAAK,CAAC,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC;SAChE,IAAI,KAAK,CAAC,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAE5D,IAAI,KAAK,CAAC,QAAQ;QAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;SACrD,IAAI,KAAK,CAAC,UAAU;QAAE,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;;QAC9C,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IAEhD,IAAI,KAAK,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9D,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAEhC,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,EAAE,EAAE,GAAG,cAAc,KAAK,KAAK,CAAC,SAAS,EAAE,CAAC;SAClG,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;SACtC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,MAAM,GAA2B;QACrC,kBAAkB,EAAE,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC;QAC5C,oBAAoB,EAAE,KAAK,CAAC,MAAM;QAClC,wBAAwB,EAAE,OAAO;KAClC,CAAC;IACF,IAAI,KAAK,CAAC,KAAK;QAAE,MAAM,CAAC,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC;IAEtD,OAAO;QACL,OAAO,EAAE,QAAQ;QACjB,IAAI;QACJ,MAAM;QACN,QAAQ,EAAE,CAAC,mBAAmB,CAAC;QAC/B,WAAW,EAAE,0FAA0F;KACxG,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { type HarnessPlan, type PlanInput } from "./types.js";
2
+ /** Quotes a string as a TOML basic string for `codex -c key=value`. */
3
+ export declare function tomlString(value: string): string;
4
+ /**
5
+ * Builds the `codex` invocation.
6
+ *
7
+ * Codex ignores OPENAI_BASE_URL while a ChatGPT login exists and only speaks
8
+ * the Responses wire, so the endpoint is injected as a dedicated `ditto`
9
+ * model provider through `-c` overrides (nothing is written to
10
+ * ~/.codex/config.toml). The session header rides on the provider's
11
+ * http_headers so every request lands in the same Ditto thread.
12
+ */
13
+ export declare function planCodex(input: PlanInput): HarnessPlan;
@@ -0,0 +1,48 @@
1
+ import { CODEX_KEY_ENV, SESSION_HEADER } from "./types.js";
2
+ /** Quotes a string as a TOML basic string for `codex -c key=value`. */
3
+ export function tomlString(value) {
4
+ return JSON.stringify(value);
5
+ }
6
+ /**
7
+ * Builds the `codex` invocation.
8
+ *
9
+ * Codex ignores OPENAI_BASE_URL while a ChatGPT login exists and only speaks
10
+ * the Responses wire, so the endpoint is injected as a dedicated `ditto`
11
+ * model provider through `-c` overrides (nothing is written to
12
+ * ~/.codex/config.toml). The session header rides on the provider's
13
+ * http_headers so every request lands in the same Ditto thread.
14
+ */
15
+ export function planCodex(input) {
16
+ const args = [];
17
+ if (input.prompt !== undefined)
18
+ args.push("exec");
19
+ else if (input.resumeId || input.resumeLast)
20
+ args.push("resume");
21
+ args.push("-c", `model_provider=${tomlString("ditto")}`, "-c", `model_providers.ditto.name=${tomlString("Ditto")}`, "-c", `model_providers.ditto.base_url=${tomlString(input.baseUrl)}`, "-c", `model_providers.ditto.env_key=${tomlString(CODEX_KEY_ENV)}`, "-c", `model_providers.ditto.wire_api=${tomlString("responses")}`, "-c", `model_providers.ditto.http_headers={${tomlString(SESSION_HEADER)}=${tomlString(input.sessionId)}}`);
22
+ if (input.model)
23
+ args.push("-m", input.model);
24
+ if (input.yolo)
25
+ args.push("--dangerously-bypass-approvals-and-sandbox");
26
+ else if (input.yellow)
27
+ args.push("-a", "on-request", "-s", "workspace-write");
28
+ if (input.prompt !== undefined) {
29
+ args.push("--skip-git-repo-check", ...input.passthrough, input.prompt);
30
+ }
31
+ else if (input.resumeId) {
32
+ args.push(...input.passthrough, input.resumeId);
33
+ }
34
+ else if (input.resumeLast) {
35
+ args.push("--last", ...input.passthrough);
36
+ }
37
+ else {
38
+ args.push(...input.passthrough);
39
+ }
40
+ return {
41
+ command: "codex",
42
+ args,
43
+ envSet: { [CODEX_KEY_ENV]: input.apiKey },
44
+ envUnset: [],
45
+ installHint: "install Codex: npm i -g @openai/codex (or brew install codex)",
46
+ };
47
+ }
48
+ //# sourceMappingURL=codex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/agents/codex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAoC,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7F,uEAAuE;AACvE,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAC,KAAgB;IACxC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;QAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SAC7C,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,UAAU;QAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEjE,IAAI,CAAC,IAAI,CACP,IAAI,EACJ,kBAAkB,UAAU,CAAC,OAAO,CAAC,EAAE,EACvC,IAAI,EACJ,8BAA8B,UAAU,CAAC,OAAO,CAAC,EAAE,EACnD,IAAI,EACJ,kCAAkC,UAAU,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAC7D,IAAI,EACJ,iCAAiC,UAAU,CAAC,aAAa,CAAC,EAAE,EAC5D,IAAI,EACJ,kCAAkC,UAAU,CAAC,WAAW,CAAC,EAAE,EAC3D,IAAI,EACJ,uCAAuC,UAAU,CAAC,cAAc,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CACpG,CAAC;IACF,IAAI,KAAK,CAAC,KAAK;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAE9C,IAAI,KAAK,CAAC,IAAI;QAAE,IAAI,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;SACnE,IAAI,KAAK,CAAC,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC;IAE9E,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,uBAAuB,EAAE,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACzE,CAAC;SAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IAClD,CAAC;SAAM,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC,CAAC;IAClC,CAAC;IAED,OAAO;QACL,OAAO,EAAE,OAAO;QAChB,IAAI;QACJ,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE;QACzC,QAAQ,EAAE,EAAE;QACZ,WAAW,EAAE,+DAA+D;KAC7E,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type Harness } from "./types.js";
2
+ export interface LaunchOptions {
3
+ endpoint?: string;
4
+ budget?: string;
5
+ expires?: string;
6
+ keepKey?: boolean;
7
+ session?: string;
8
+ resume?: string | boolean;
9
+ continue?: boolean;
10
+ yolo?: boolean;
11
+ yellow?: boolean;
12
+ plan?: boolean;
13
+ prompt?: string;
14
+ model?: string;
15
+ worktree?: string | boolean;
16
+ name?: string;
17
+ dryRun?: boolean;
18
+ }
19
+ /** Launches a coding harness against a Ditto inference endpoint with a temporary key. */
20
+ export declare function launchHarness(harness: Harness, rawArgs: string[], options: LaunchOptions): Promise<void>;
@@ -0,0 +1,274 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { randomUUID } from "node:crypto";
3
+ import os from "node:os";
4
+ import { createInterface } from "node:readline/promises";
5
+ import { createKey, listEndpoints, revokeKey, } from "../api.js";
6
+ import { readStoredAuth } from "../store.js";
7
+ import { planClaude } from "./claude.js";
8
+ import { planCodex } from "./codex.js";
9
+ import { latestSession, readSession, writeSession } from "./sessions.js";
10
+ import { KEY_EXPIRIES, childEnv, maskKey, stripSeparator, } from "./types.js";
11
+ import { defaultWorktreeName, ensureWorktree } from "./worktree.js";
12
+ const DRY_RUN_KEY = "ditto_inf_<minted-at-launch>";
13
+ function log(line) {
14
+ process.stderr.write(`ditto: ${line}\n`);
15
+ }
16
+ function planFor(harness, input) {
17
+ return harness === "claude" ? planClaude(input) : planCodex(input);
18
+ }
19
+ function binaryAvailable(command) {
20
+ const probe = process.platform === "win32" ? "where" : "which";
21
+ return spawnSync(probe, [command], { stdio: "ignore" }).status === 0;
22
+ }
23
+ function parseBudget(raw) {
24
+ if (raw === undefined)
25
+ return undefined;
26
+ const n = Number(raw.replace(/[_,]/g, ""));
27
+ if (!Number.isInteger(n) || n <= 0)
28
+ throw new Error(`--budget must be a positive integer token count, got "${raw}"`);
29
+ return n;
30
+ }
31
+ function parseExpiry(raw) {
32
+ const value = (raw ?? "1d").trim();
33
+ if (KEY_EXPIRIES.includes(value))
34
+ return value;
35
+ throw new Error(`--expires must be one of: ${KEY_EXPIRIES.join(", ")}`);
36
+ }
37
+ function formatSpend(e) {
38
+ const used = e.spentTokens ?? 0;
39
+ if (e.spendLimitTokens == null || e.spendLimitTokens < 0)
40
+ return `${used.toLocaleString()} tokens used, no limit`;
41
+ return `${used.toLocaleString()} / ${e.spendLimitTokens.toLocaleString()} tokens${e.spendPeriod && e.spendPeriod !== "never" ? ` per ${e.spendPeriod.replace(/ly$/, "")}` : ""}`;
42
+ }
43
+ async function pickEndpoint(endpoints) {
44
+ if (!process.stdin.isTTY || !process.stderr.isTTY) {
45
+ throw new Error("no endpoint selected. Pass --endpoint <slug>, or set a default with `heyditto endpoints --set-default <slug>`.");
46
+ }
47
+ process.stderr.write("Ditto inference endpoints:\n\n");
48
+ endpoints.forEach((e, i) => {
49
+ process.stderr.write(` ${i + 1}) ${e.slug} ${e.name !== e.slug ? `(${e.name}) ` : ""}model=${e.model}\n`);
50
+ process.stderr.write(` ${formatSpend(e)}${e.recordTrace ? " · traces on" : " · traces off"}\n`);
51
+ });
52
+ process.stderr.write("\n");
53
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
54
+ try {
55
+ for (;;) {
56
+ const answer = (await rl.question(`Connect to which endpoint? [1-${endpoints.length}] `)).trim();
57
+ const idx = Number(answer);
58
+ if (Number.isInteger(idx) && idx >= 1 && idx <= endpoints.length)
59
+ return endpoints[idx - 1];
60
+ const bySlug = endpoints.find((e) => e.slug === answer || e.id === answer);
61
+ if (bySlug)
62
+ return bySlug;
63
+ process.stderr.write("Pick a number from the list.\n");
64
+ }
65
+ }
66
+ finally {
67
+ rl.close();
68
+ }
69
+ }
70
+ async function resolveEndpoint(wanted, endpoints) {
71
+ if (endpoints.length === 0) {
72
+ throw new Error("you have no inference endpoints yet. Create one in the Ditto app: Settings → Developer → Inference endpoints.");
73
+ }
74
+ const stored = (await readStoredAuth())?.defaultEndpoint;
75
+ const target = wanted?.trim() || stored?.trim();
76
+ if (target) {
77
+ const match = endpoints.find((e) => e.slug === target || e.id === target);
78
+ if (match)
79
+ return match;
80
+ if (wanted) {
81
+ throw new Error(`no endpoint named "${target}". Available: ${endpoints.map((e) => e.slug).join(", ")}`);
82
+ }
83
+ log(`default endpoint "${target}" no longer exists; pick another`);
84
+ }
85
+ if (endpoints.length === 1)
86
+ return endpoints[0];
87
+ return pickEndpoint(endpoints);
88
+ }
89
+ function forwardSignals(child) {
90
+ // Ctrl+C reaches the child directly through the shared terminal; the
91
+ // wrapper only has to survive it so it can revoke the key afterwards.
92
+ const onInt = () => {
93
+ /* keep running until the harness exits */
94
+ };
95
+ const onTerm = () => child.kill("SIGTERM");
96
+ const onHup = () => child.kill("SIGHUP");
97
+ process.on("SIGINT", onInt);
98
+ process.on("SIGTERM", onTerm);
99
+ process.on("SIGHUP", onHup);
100
+ return () => {
101
+ process.off("SIGINT", onInt);
102
+ process.off("SIGTERM", onTerm);
103
+ process.off("SIGHUP", onHup);
104
+ };
105
+ }
106
+ function waitForExit(child) {
107
+ return new Promise((resolve, reject) => {
108
+ child.on("error", reject);
109
+ child.on("exit", (code, signal) => resolve(code ?? (signal ? 128 + (os.constants.signals[signal] ?? 0) : null)));
110
+ });
111
+ }
112
+ /** Launches a coding harness against a Ditto inference endpoint with a temporary key. */
113
+ export async function launchHarness(harness, rawArgs, options) {
114
+ const passthrough = stripSeparator(rawArgs);
115
+ const budget = parseBudget(options.budget);
116
+ const expiresIn = parseExpiry(options.expires);
117
+ if (options.plan && harness !== "claude")
118
+ throw new Error("--plan is only supported for claude");
119
+ if ([options.yolo, options.yellow, options.plan].filter(Boolean).length > 1) {
120
+ throw new Error("choose only one of --yolo, --yellow, --plan");
121
+ }
122
+ if (options.resume !== undefined && options.continue)
123
+ throw new Error("use either --resume or --continue");
124
+ if (options.resume !== undefined && options.session)
125
+ throw new Error("--session cannot be combined with --resume");
126
+ if (!options.dryRun && !binaryAvailable(harness)) {
127
+ const hint = planFor(harness, {
128
+ baseUrl: "",
129
+ apiKey: "",
130
+ sessionId: "",
131
+ passthrough: [],
132
+ env: {},
133
+ }).installHint;
134
+ throw new Error(`"${harness}" is not on your PATH; ${hint}`);
135
+ }
136
+ // Resume: reuse the local record's Ditto session id so the traces stay in
137
+ // the same thread; a fresh key is minted every launch.
138
+ let record;
139
+ let resumeLast = Boolean(options.continue);
140
+ if (options.resume !== undefined) {
141
+ record =
142
+ typeof options.resume === "string"
143
+ ? await readSession(options.resume)
144
+ : await latestSession(harness, process.cwd());
145
+ if (!record) {
146
+ throw new Error(typeof options.resume === "string"
147
+ ? `no local session "${options.resume}"; see \`heyditto sessions\``
148
+ : `no previous ${harness} session to resume; see \`heyditto sessions\``);
149
+ }
150
+ if (record.harness !== harness)
151
+ throw new Error(`session ${record.id} was a ${record.harness} session`);
152
+ if (!record.harnessSessionId)
153
+ resumeLast = true;
154
+ }
155
+ const catalog = await listEndpoints();
156
+ const endpoint = await resolveEndpoint(options.endpoint ?? record?.endpointSlug, catalog.endpoints);
157
+ let cwd = record?.worktree ?? process.cwd();
158
+ let worktreePath = record?.worktree;
159
+ if (options.worktree !== undefined && options.worktree !== false) {
160
+ const name = typeof options.worktree === "string" ? options.worktree : defaultWorktreeName(harness);
161
+ if (options.dryRun) {
162
+ log(`would create worktree .worktrees/${name}`);
163
+ }
164
+ else {
165
+ const wt = await ensureWorktree(process.cwd(), name);
166
+ log(`${wt.created ? "created" : "reusing"} worktree ${wt.path} (branch ${wt.branch})`);
167
+ cwd = wt.path;
168
+ worktreePath = wt.path;
169
+ }
170
+ }
171
+ const sessionId = record?.id ?? options.session?.trim() ?? randomUUID();
172
+ if (!/^[A-Za-z0-9._:@-]{1,128}$/.test(sessionId)) {
173
+ throw new Error("--session must match ^[A-Za-z0-9._:@-]{1,128}$");
174
+ }
175
+ const harnessSessionId = record?.harnessSessionId ?? (harness === "claude" && !resumeLast ? sessionId : undefined);
176
+ const model = options.model ?? record?.model ?? (harness === "codex" ? endpoint.slug : undefined);
177
+ const now = new Date().toISOString();
178
+ const keyName = options.name?.trim() || `cli:${harness}:${os.hostname()}`;
179
+ let key;
180
+ if (!options.dryRun) {
181
+ key = await createKey(endpoint.id, {
182
+ name: keyName,
183
+ expiresIn,
184
+ ...(budget !== undefined ? { spendLimitTokens: budget, spendPeriod: "never" } : {}),
185
+ });
186
+ }
187
+ const plan = planFor(harness, {
188
+ baseUrl: catalog.baseUrl,
189
+ apiKey: key?.key ?? DRY_RUN_KEY,
190
+ sessionId,
191
+ model,
192
+ resumeId: record ? record.harnessSessionId : undefined,
193
+ resumeLast,
194
+ prompt: options.prompt,
195
+ yolo: options.yolo,
196
+ yellow: options.yellow,
197
+ plan: options.plan,
198
+ passthrough,
199
+ env: process.env,
200
+ });
201
+ const banner = [
202
+ `endpoint=${endpoint.slug}`,
203
+ `model=${model ?? "(harness default → endpoint routes)"}`,
204
+ `key=${key ? `…${key.keyHint || maskKey(key.key ?? "")}` : "(dry run)"}`,
205
+ `expires=${expiresIn}${options.keepKey ? "" : " (revoked on exit)"}`,
206
+ budget !== undefined ? `budget=${budget.toLocaleString()} tokens` : undefined,
207
+ `session=${sessionId}`,
208
+ ]
209
+ .filter(Boolean)
210
+ .join(" ");
211
+ log(banner);
212
+ log(`traces: Ditto app → Settings → Developer → Inference endpoints → ${endpoint.slug}`);
213
+ if (options.dryRun) {
214
+ const env = Object.fromEntries(Object.entries(plan.envSet).map(([k, v]) => [k, v === DRY_RUN_KEY ? "<key>" : v]));
215
+ process.stdout.write(`${JSON.stringify({
216
+ harness,
217
+ command: plan.command,
218
+ args: plan.args,
219
+ env,
220
+ unsetEnv: plan.envUnset,
221
+ cwd,
222
+ endpoint: { id: endpoint.id, slug: endpoint.slug, model: endpoint.model },
223
+ sessionId,
224
+ key: { name: keyName, expiresIn, spendLimitTokens: budget ?? null },
225
+ }, null, 2)}\n`);
226
+ return;
227
+ }
228
+ if (!key)
229
+ throw new Error("internal: key was not minted");
230
+ const nextRecord = {
231
+ id: sessionId,
232
+ harness,
233
+ endpointId: endpoint.id,
234
+ endpointSlug: endpoint.slug,
235
+ keyId: key.id,
236
+ keyHint: key.keyHint,
237
+ harnessSessionId,
238
+ cwd: record?.cwd ?? process.cwd(),
239
+ worktree: worktreePath,
240
+ model,
241
+ createdAt: record?.createdAt ?? now,
242
+ lastLaunchedAt: now,
243
+ launches: (record?.launches ?? 0) + 1,
244
+ };
245
+ await writeSession(nextRecord);
246
+ const child = spawn(plan.command, plan.args, {
247
+ stdio: "inherit",
248
+ env: childEnv(plan, process.env),
249
+ cwd,
250
+ });
251
+ const restore = forwardSignals(child);
252
+ let exitCode = null;
253
+ try {
254
+ exitCode = await waitForExit(child);
255
+ }
256
+ finally {
257
+ restore();
258
+ if (options.keepKey) {
259
+ log(`kept key …${key.keyHint} (expires ${expiresIn}); revoke it from the Ditto app when done`);
260
+ }
261
+ else {
262
+ try {
263
+ await revokeKey(endpoint.id, key.id);
264
+ log(`revoked session key …${key.keyHint}; thread kept (resume with: heyditto ${harness} --resume ${sessionId})`);
265
+ }
266
+ catch (err) {
267
+ log(`could not revoke key …${key.keyHint} (${err instanceof Error ? err.message : String(err)}); it expires in ${expiresIn}`);
268
+ }
269
+ }
270
+ await writeSession({ ...nextRecord, endedAt: new Date().toISOString(), exitCode });
271
+ }
272
+ process.exitCode = exitCode ?? 1;
273
+ }
274
+ //# sourceMappingURL=launch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launch.js","sourceRoot":"","sources":["../../src/agents/launch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { Harness } from "./types.js";
2
+ /** One coding-agent session launched by the CLI; the Ditto session id is the file name. */
3
+ export interface SessionRecord {
4
+ id: string;
5
+ harness: Harness;
6
+ endpointId: string;
7
+ endpointSlug: string;
8
+ /** Last key minted for this session; revoked on exit unless --keep-key. */
9
+ keyId?: string;
10
+ keyHint?: string;
11
+ /** Harness-native id used to resume (Claude's --session-id; Codex thread id when known). */
12
+ harnessSessionId?: string;
13
+ cwd: string;
14
+ worktree?: string;
15
+ model?: string;
16
+ createdAt: string;
17
+ lastLaunchedAt: string;
18
+ endedAt?: string;
19
+ exitCode?: number | null;
20
+ launches: number;
21
+ }
22
+ export declare function writeSession(record: SessionRecord): Promise<void>;
23
+ export declare function readSession(id: string): Promise<SessionRecord | undefined>;
24
+ export declare function removeSession(id: string): Promise<boolean>;
25
+ /** All local records, most recently launched first. */
26
+ export declare function listSessions(): Promise<SessionRecord[]>;
27
+ /** Most recent record for a harness, preferring one launched from the same directory. */
28
+ export declare function latestSession(harness: Harness, cwd: string): Promise<SessionRecord | undefined>;