oh-my-fable 0.1.2 → 0.2.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
@@ -133,6 +133,14 @@ OpenAI, Ollama, LM Studio, OpenRouter, Groq… — `ollama("llama3.1")` for a lo
133
133
  model with no key), both over `fetch`, no SDK. Or bring any model by implementing
134
134
  the `Provider` interface (three methods).
135
135
 
136
+ `AnthropicProvider` works with the current flagship models (`claude-opus-4-8`,
137
+ `claude-fable-5`) out of the box — it drops the `temperature` parameter they
138
+ reject — and **prompt-caches the system+tools prefix by default**, so a long
139
+ durable run pays ~10× less on the context it replays every step. Opt into
140
+ `{ thinking: "adaptive", effort: "high" }` for harder planning. The `claude`
141
+ provider can return real `--output-format json` cost/usage and run Claude's own
142
+ tools (`{ tools: true, permissionMode: "acceptEdits" }`).
143
+
136
144
  ## Or use it from the terminal
137
145
 
138
146
  Don't want to write code? It ships a CLI (zero extra deps):
@@ -140,7 +148,11 @@ Don't want to write code? It ships a CLI (zero extra deps):
140
148
  ```bash
141
149
  npx oh-my-fable demo # watch crash → resume, no API key
142
150
 
143
- # already use Claude Code or Codex? drive it uses that login, no separate key:
151
+ # already pay for Claude Code? drive it as a DURABLE, TOOL-USING agent — your
152
+ # login, no separate API key, $0 per token. Claude edits files & runs commands:
153
+ npx oh-my-fable run "refactor utils.ts and run the tests" --provider claude --cli-tools
154
+
155
+ # pure-reasoning over the same login (no tools):
144
156
  npx oh-my-fable run "outline a talk on durable agents" --provider claude
145
157
 
146
158
  # or a LOCAL model (Ollama / LM Studio), also no key:
@@ -151,26 +163,33 @@ export ANTHROPIC_API_KEY=sk-...
151
163
  npx oh-my-fable run "summarize README.md into SUMMARY.md" --tools fs
152
164
 
153
165
  npx oh-my-fable list # your saved runs
166
+ npx oh-my-fable show run_abc123 # the run's plan, steps & budget as a timeline
154
167
  npx oh-my-fable resume run_abc123 # continue one from its checkpoint
155
168
  ```
156
169
 
157
170
  **You don't need an Anthropic API key.** Pick how it talks to a model:
158
171
 
159
- | `--provider` | uses | key? |
160
- | --- | --- | --- |
161
- | `claude` / `codex` | your Claude Code / Codex CLI login | **none** rides the CLI's auth |
162
- | `ollama` | a local Ollama model | **none** |
163
- | `--base-url <url>` | LM Studio / OpenRouter / Groq / any OpenAI-compatible | per that server |
164
- | `openai` | OpenAI | `OPENAI_API_KEY` |
165
- | *(default)* | Anthropic | `ANTHROPIC_API_KEY` |
172
+ | `--provider` | uses | key? | tools? |
173
+ | --- | --- | --- | --- |
174
+ | `claude` | your Claude Code login | **none** | `--cli-tools` Claude runs Read/Write/Edit/Bash itself |
175
+ | `codex` | your Codex CLI login | **none** | `--cli-tools` → workspace-write |
176
+ | `ollama` | a local Ollama model | **none** | `--tools fs` (harness-run) |
177
+ | `--base-url <url>` | LM Studio / OpenRouter / Groq / any OpenAI-compatible | per that server | `--tools fs` |
178
+ | `openai` | OpenAI | `OPENAI_API_KEY` | `--tools fs` |
179
+ | *(default)* | Anthropic | `ANTHROPIC_API_KEY` | `--tools fs` |
180
+
181
+ **Two ways to give an agent hands:**
166
182
 
167
- (The CLI-driven providers are text-only great for planning/reasoning runs; they
168
- don't expose `--tools`.)
183
+ - `--cli-tools` (claude/codex) the CLI runs its **own** tools (file edits, shell)
184
+ on your subscription. oh-my-fable stays the durable planner/reflector around it:
185
+ it plans, checkpoints every step, and reflects — Claude does the work. Tune with
186
+ `--permission-mode acceptEdits|dontAsk|plan` and `--allow "Read,Edit,Bash(npm test)"`.
187
+ - `--tools fs` (API providers) — the harness gives the agent a sandboxed
188
+ `read_file`/`write_file`/`list_dir`, confined to the working directory.
169
189
 
170
- You watch the plan form and each step get reflected on, live. `--tools fs` gives
171
- the agent a sandboxed `read_file`/`write_file`/`list_dir` (confined to the working
172
- directory) so a terminal run can actually produce files; without it, runs are
173
- pure-reasoning. Every run is checkpointed, so `resume <runId>` always works.
190
+ You watch the plan form and each step get reflected on, live. Every run is
191
+ checkpointed, so `resume <runId>` always works — and `show <runId>` prints the
192
+ whole run (plan, steps, budget) from its serialized `RunContext`.
174
193
 
175
194
  ## Tools
176
195
 
@@ -238,7 +257,7 @@ architecture writeup is in [`ARCHITECTURE.md`](./ARCHITECTURE.md).
238
257
 
239
258
  ## Roadmap
240
259
 
241
- - A web dashboard that tails a run's events and lets you resume from any checkpoint.
260
+ - A web dashboard that tails a run's events and lets you resume from any checkpoint (`show <runId>` is the CLI version of this today).
242
261
  - More providers in-repo (OpenAI-compatible, local) — though it's a 3-method interface.
243
262
  - Parallel step execution for independent branches of the plan DAG.
244
263
  - Human-in-the-loop: pause for approval as a first-class step status.
package/dist/cli.cjs CHANGED
@@ -812,6 +812,10 @@ var reply = {
812
812
  };
813
813
 
814
814
  // src/providers/anthropic.ts
815
+ function modelRejectsSampling(model) {
816
+ const m = model.toLowerCase();
817
+ return m.includes("opus-4-7") || m.includes("opus-4-8") || m.includes("fable") || m.includes("mythos");
818
+ }
815
819
  function coalesce(messages) {
816
820
  const out = [];
817
821
  for (const m of messages) {
@@ -830,6 +834,9 @@ var AnthropicProvider = class {
830
834
  version;
831
835
  maxRetries;
832
836
  defaultMaxTokens;
837
+ cache;
838
+ thinking;
839
+ effort;
833
840
  constructor(opts = {}) {
834
841
  this.apiKey = opts.apiKey ?? process.env["ANTHROPIC_API_KEY"] ?? "";
835
842
  this.model = opts.model ?? "claude-sonnet-4-6";
@@ -837,6 +844,9 @@ var AnthropicProvider = class {
837
844
  this.version = opts.version ?? "2023-06-01";
838
845
  this.maxRetries = opts.maxRetries ?? 4;
839
846
  this.defaultMaxTokens = opts.defaultMaxTokens ?? 4096;
847
+ this.cache = opts.cache ?? true;
848
+ this.thinking = opts.thinking;
849
+ this.effort = opts.effort;
840
850
  if (!this.apiKey) {
841
851
  throw new Error("AnthropicProvider needs an API key (pass { apiKey } or set ANTHROPIC_API_KEY).");
842
852
  }
@@ -852,12 +862,18 @@ var AnthropicProvider = class {
852
862
  const body = {
853
863
  model: this.model,
854
864
  max_tokens: req.maxTokens ?? this.defaultMaxTokens,
855
- temperature: req.temperature ?? 1,
856
865
  messages: convo
857
866
  };
867
+ if (typeof req.temperature === "number" && !this.thinking && !modelRejectsSampling(this.model)) {
868
+ body["temperature"] = req.temperature;
869
+ }
870
+ if (this.thinking === "adaptive") body["thinking"] = { type: "adaptive" };
871
+ if (this.effort) body["output_config"] = { effort: this.effort };
858
872
  let sys = system;
859
873
  if (req.responseFormat === "json") sys = (sys ? sys + "\n\n" : "") + "Output ONLY valid JSON. No prose, no code fences.";
860
- if (sys) body["system"] = sys;
874
+ if (sys) {
875
+ body["system"] = this.cache ? [{ type: "text", text: sys, cache_control: { type: "ephemeral" } }] : sys;
876
+ }
861
877
  if (req.tools?.length) {
862
878
  body["tools"] = req.tools.map((t) => ({ name: t.name, description: t.description, input_schema: t.parameters }));
863
879
  }
@@ -894,12 +910,14 @@ var AnthropicProvider = class {
894
910
  if (block.type === "text" && block.text) content += block.text;
895
911
  else if (block.type === "tool_use" && block.name) toolCalls.push({ id: block.id ?? block.name, name: block.name, input: block.input });
896
912
  }
913
+ const u = data.usage ?? {};
914
+ const tokensIn = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
897
915
  const map = { end_turn: "end", tool_use: "tool_use", max_tokens: "max_tokens", stop_sequence: "end" };
898
916
  return {
899
917
  content,
900
918
  toolCalls: toolCalls.length ? toolCalls : void 0,
901
- tokensIn: data.usage?.input_tokens ?? 0,
902
- tokensOut: data.usage?.output_tokens ?? 0,
919
+ tokensIn,
920
+ tokensOut: u.output_tokens ?? 0,
903
921
  stopReason: map[data.stop_reason ?? ""] ?? "end"
904
922
  };
905
923
  }
@@ -986,9 +1004,10 @@ function ollama(model, opts = {}) {
986
1004
 
987
1005
  // src/providers/cli.ts
988
1006
  var import_node_child_process = require("child_process");
989
- function flatten(messages) {
1007
+ function flatten(messages, includeSystem) {
990
1008
  const parts = [];
991
1009
  for (const m of messages) {
1010
+ if (m.role === "system" && !includeSystem) continue;
992
1011
  if (m.role === "assistant") parts.push(`Assistant: ${m.content}`);
993
1012
  else parts.push(m.content);
994
1013
  }
@@ -1031,6 +1050,10 @@ var CliProvider = class {
1031
1050
  parse;
1032
1051
  env;
1033
1052
  timeoutMs;
1053
+ extraArgs;
1054
+ requestArgs;
1055
+ parseResult;
1056
+ systemInPrompt;
1034
1057
  constructor(opts) {
1035
1058
  this.command = opts.command;
1036
1059
  this.args = opts.args ?? [];
@@ -1039,14 +1062,33 @@ var CliProvider = class {
1039
1062
  this.env = opts.env;
1040
1063
  this.timeoutMs = opts.timeoutMs ?? 12e4;
1041
1064
  this.name = opts.label ?? `cli:${opts.command}`;
1065
+ this.extraArgs = opts.extraArgs ?? [];
1066
+ this.requestArgs = opts.requestArgs;
1067
+ this.parseResult = opts.parseResult;
1068
+ this.systemInPrompt = opts.systemInPrompt ?? true;
1042
1069
  }
1043
1070
  estimateTokens(messages) {
1044
1071
  return estimateTokens(messages);
1045
1072
  }
1046
1073
  async complete(req) {
1047
- const prompt = flatten(req.messages);
1048
- const args = this.promptVia === "arg" ? [...this.args, prompt] : this.args;
1049
- const stdout = await runCli(this.command, args, this.promptVia === "stdin" ? prompt : null, this.timeoutMs, this.env);
1074
+ const prompt = flatten(req.messages, this.systemInPrompt);
1075
+ const reqArgs = this.requestArgs ? this.requestArgs(req) : [];
1076
+ const argv = [...this.args, ...this.extraArgs, ...reqArgs];
1077
+ const finalArgs = this.promptVia === "arg" ? [...argv, prompt] : argv;
1078
+ const stdout = await runCli(this.command, finalArgs, this.promptVia === "stdin" ? prompt : null, this.timeoutMs, this.env);
1079
+ if (this.parseResult) {
1080
+ const r = this.parseResult(stdout);
1081
+ const content2 = r.content ?? "";
1082
+ return {
1083
+ content: content2,
1084
+ toolCalls: r.toolCalls,
1085
+ tokensIn: r.tokensIn ?? estimateTokens(req.messages),
1086
+ tokensOut: r.tokensOut ?? Math.ceil(content2.length / 4),
1087
+ stopReason: r.stopReason ?? "end",
1088
+ sessionId: r.sessionId,
1089
+ costUsd: r.costUsd
1090
+ };
1091
+ }
1050
1092
  const content = this.parse(stdout);
1051
1093
  return {
1052
1094
  content,
@@ -1056,11 +1098,85 @@ var CliProvider = class {
1056
1098
  };
1057
1099
  }
1058
1100
  };
1101
+ var num = (x) => typeof x === "number" && Number.isFinite(x) ? x : 0;
1102
+ function parseClaudeJson(stdout) {
1103
+ let data;
1104
+ try {
1105
+ data = JSON.parse(stdout);
1106
+ } catch {
1107
+ return { content: stdout.trim() };
1108
+ }
1109
+ let content;
1110
+ if (data["structured_output"] !== void 0) {
1111
+ const so = data["structured_output"];
1112
+ content = typeof so === "string" ? so : JSON.stringify(so);
1113
+ } else {
1114
+ content = typeof data["result"] === "string" ? data["result"] : stdout.trim();
1115
+ }
1116
+ const usage = data["usage"] ?? {};
1117
+ const tokensIn = num(usage["input_tokens"]) + num(usage["cache_read_input_tokens"]) + num(usage["cache_creation_input_tokens"]);
1118
+ const tokensOut = num(usage["output_tokens"]);
1119
+ const out = { content };
1120
+ if (tokensIn) out.tokensIn = tokensIn;
1121
+ if (tokensOut) out.tokensOut = tokensOut;
1122
+ if (typeof data["session_id"] === "string") out.sessionId = data["session_id"];
1123
+ if (typeof data["total_cost_usd"] === "number") out.costUsd = data["total_cost_usd"];
1124
+ return out;
1125
+ }
1126
+ function claudeRequestArgs(req, opts) {
1127
+ const args = [];
1128
+ if (opts.json) args.push("--output-format", "json");
1129
+ if (opts.appendSystem) {
1130
+ let sys = req.messages.filter((m) => m.role === "system").map((m) => m.content).join("\n\n");
1131
+ if (req.responseFormat === "json") sys = (sys ? sys + "\n\n" : "") + "Output ONLY valid JSON. No prose, no code fences.";
1132
+ if (sys) args.push("--append-system-prompt", sys);
1133
+ }
1134
+ if (opts.jsonSchema && req.responseFormat === "json") args.push("--json-schema", JSON.stringify(opts.jsonSchema));
1135
+ return args;
1136
+ }
1137
+ var DEFAULT_CLAUDE_TOOLS = ["Read", "Write", "Edit", "Glob", "Grep"];
1059
1138
  function claudeCode(opts = {}) {
1060
- return new CliProvider({ command: "claude", args: ["-p"], promptVia: "arg", label: "claude-code", ...opts });
1139
+ const json = opts.json ?? true;
1140
+ const appendSystem = opts.appendSystem ?? true;
1141
+ const extra = [];
1142
+ if (opts.model) extra.push("--model", opts.model);
1143
+ if (opts.tools) {
1144
+ const allow = Array.isArray(opts.tools) ? opts.tools : DEFAULT_CLAUDE_TOOLS;
1145
+ extra.push("--allowedTools", allow.join(","), "--permission-mode", opts.permissionMode ?? "acceptEdits");
1146
+ } else if (opts.permissionMode) {
1147
+ extra.push("--permission-mode", opts.permissionMode);
1148
+ }
1149
+ for (const d of opts.addDirs ?? []) extra.push("--add-dir", d);
1150
+ if (opts.resumeSessionId) extra.push("--resume", opts.resumeSessionId);
1151
+ return new CliProvider({
1152
+ command: "claude",
1153
+ args: ["-p"],
1154
+ promptVia: "arg",
1155
+ label: "claude-code",
1156
+ timeoutMs: opts.timeoutMs,
1157
+ env: opts.env,
1158
+ extraArgs: extra,
1159
+ requestArgs: (req) => claudeRequestArgs(req, { json, appendSystem, jsonSchema: opts.jsonSchema }),
1160
+ parseResult: json ? parseClaudeJson : void 0,
1161
+ systemInPrompt: !appendSystem
1162
+ });
1061
1163
  }
1062
1164
  function codexCli(opts = {}) {
1063
- return new CliProvider({ command: "codex", args: ["exec"], promptVia: "arg", label: "codex", ...opts });
1165
+ const extra = [];
1166
+ if (opts.model) extra.push("--model", opts.model);
1167
+ const sandbox = opts.sandbox ?? (opts.tools ? "workspace-write" : void 0);
1168
+ if (sandbox) extra.push("--sandbox", sandbox);
1169
+ const approval = opts.approval ?? (opts.tools ? "never" : void 0);
1170
+ if (approval) extra.push("--ask-for-approval", approval);
1171
+ return new CliProvider({
1172
+ command: "codex",
1173
+ args: ["exec"],
1174
+ promptVia: "arg",
1175
+ label: "codex",
1176
+ timeoutMs: opts.timeoutMs,
1177
+ env: opts.env,
1178
+ extraArgs: extra
1179
+ });
1064
1180
  }
1065
1181
 
1066
1182
  // src/index.ts
@@ -1090,7 +1206,7 @@ async function runWith(ctx, config) {
1090
1206
 
1091
1207
  // src/cli.ts
1092
1208
  init_store();
1093
- var VERSION = "0.1.0";
1209
+ var VERSION = "0.2.0";
1094
1210
  var useColor = process.stdout.isTTY && !process.env["NO_COLOR"];
1095
1211
  var c = (code) => (s) => useColor ? `\x1B[${code}m${s}\x1B[0m` : s;
1096
1212
  var cyan = c("36");
@@ -1169,9 +1285,13 @@ function makeProvider(flags) {
1169
1285
  const provider = str("provider");
1170
1286
  const baseUrl = str("base-url");
1171
1287
  const apiKey = str("api-key");
1288
+ const permissionMode = str("permission-mode");
1289
+ const allowList = typeof flags["allow"] === "string" ? flags["allow"].split(",").map((s) => s.trim()).filter(Boolean) : void 0;
1290
+ const cliTools = flags["cli-tools"] === true;
1291
+ const toolsOpt = allowList ?? (cliTools ? true : void 0);
1172
1292
  try {
1173
- if (provider === "claude" || provider === "claude-code") return claudeCode();
1174
- if (provider === "codex") return codexCli();
1293
+ if (provider === "claude" || provider === "claude-code") return claudeCode({ model, tools: toolsOpt, permissionMode });
1294
+ if (provider === "codex") return codexCli({ model, tools: cliTools || !!allowList });
1175
1295
  if (provider === "ollama") return ollama(model ?? "llama3.1", baseUrl ? { baseUrl } : {});
1176
1296
  if (provider === "openai") {
1177
1297
  return new OpenAICompatProvider({ baseUrl: baseUrl ?? "https://api.openai.com/v1", apiKey: apiKey ?? process.env["OPENAI_API_KEY"], model: model ?? "gpt-4o-mini", label: "openai" });
@@ -1258,6 +1378,42 @@ async function cmdList(args) {
1258
1378
  }
1259
1379
  process.stdout.write("\n");
1260
1380
  }
1381
+ async function cmdShow(args) {
1382
+ const runId = args._[0];
1383
+ if (!runId) fail("Give a run id: oh-my-fable show <runId> (see `oh-my-fable list`)");
1384
+ const store = new FileStore(typeof args.flags["runs-dir"] === "string" ? args.flags["runs-dir"] : "runs");
1385
+ const ctx = await store.load(runId);
1386
+ if (!ctx) fail(`no run ${runId} \u2014 try \`oh-my-fable list\``);
1387
+ const icon = { done: green("\u2714"), failed: red("\u2717"), running: yellow("\u25B6"), skipped: dim("\u2013"), pending: dim("\xB7") };
1388
+ const p = ctx.plan;
1389
+ const planColor = p.status === "done" ? green : p.status === "failed" ? red : yellow;
1390
+ process.stdout.write(`
1391
+ ${dim("\u2500\u2500 run")} ${mag(ctx.runId)} ${dim("\u2500\u2500")}
1392
+ `);
1393
+ process.stdout.write(` ${bold("goal")} ${ctx.goal.description}
1394
+ `);
1395
+ if (ctx.goal.successCriteria?.length) process.stdout.write(` ${dim("done when")} ${dim(ctx.goal.successCriteria.join("; "))}
1396
+ `);
1397
+ process.stdout.write(` ${bold("plan")} ${planColor(p.status)} ${dim(`\xB7 rev ${p.revision} \xB7 ${p.steps.length} steps`)}
1398
+
1399
+ `);
1400
+ for (const s of p.steps) {
1401
+ process.stdout.write(` ${icon[s.status] ?? "\xB7"} ${s.intent}${s.attempts > 1 ? dim(` \xD7${s.attempts}`) : ""}
1402
+ `);
1403
+ if (s.result) process.stdout.write(` ${dim("\u21B3 " + s.result.replace(/\s+/g, " ").slice(0, 120))}
1404
+ `);
1405
+ }
1406
+ const b = ctx.budget;
1407
+ const mins = Math.max(0, Math.round((Date.parse(ctx.updatedAt) - Date.parse(ctx.createdAt)) / 6e4));
1408
+ process.stdout.write(`
1409
+ ${dim(`budget: ${b.steps} steps \xB7 ${b.tokens.toLocaleString()} tokens \xB7 ${b.replans} replans \xB7 ~${mins}m`)}
1410
+ `);
1411
+ if (ctx.digests.length) process.stdout.write(` ${dim(`compacted: ${ctx.digests.length} digest${ctx.digests.length === 1 ? "" : "s"}`)}
1412
+ `);
1413
+ if (p.status !== "done") process.stdout.write(` ${dim("resume:")} ${cyan(`oh-my-fable resume ${ctx.runId}`)}
1414
+ `);
1415
+ process.stdout.write("\n");
1416
+ }
1261
1417
  async function cmdDemo() {
1262
1418
  const { MemoryStore: MemoryStore2 } = await Promise.resolve().then(() => (init_store(), store_exports));
1263
1419
  const store = new MemoryStore2();
@@ -1322,28 +1478,36 @@ function help() {
1322
1478
  ${bold("oh-my-fable")} ${dim("v" + VERSION)} \u2014 give an agent a goal; it plans, self-corrects, and survives crashes.
1323
1479
 
1324
1480
  ${bold("Usage")}
1325
- oh-my-fable run "<goal>" run an agent on a goal (needs ANTHROPIC_API_KEY)
1481
+ oh-my-fable run "<goal>" run an agent on a goal
1326
1482
  oh-my-fable resume <runId> continue a crashed/halted run from its checkpoint
1483
+ oh-my-fable show <runId> print a run's plan, steps, and budget as a timeline
1327
1484
  oh-my-fable list list saved runs
1328
1485
  oh-my-fable demo watch crash \u2192 resume, scripted (no API key)
1329
1486
 
1330
- ${bold("Model")} ${dim("(default: Anthropic, needs ANTHROPIC_API_KEY)")}
1331
- --provider claude drive your Claude Code CLI \u2014 uses its login, NO separate key
1332
- --provider codex drive your Codex CLI \u2014 same idea
1487
+ ${bold("Model")} ${dim("\u2014 no API key needed; ride a CLI login you already have")}
1488
+ --provider claude drive your Claude Code login \u2014 NO separate API key
1489
+ --provider claude --cli-tools \u2026and let Claude edit files / run tools itself (durable agent on your sub)
1490
+ --provider codex --cli-tools drive your Codex login, workspace-write
1333
1491
  --provider ollama --model llama3.1 a LOCAL model \u2014 no API key, no cost
1334
1492
  --provider openai --model gpt-4o-mini OpenAI (OPENAI_API_KEY)
1335
1493
  --base-url <url> --model <id> [--api-key] any OpenAI-compatible server (LM Studio, OpenRouter, Groq, \u2026)
1494
+ ${dim("(default: Anthropic API, needs ANTHROPIC_API_KEY)")}
1336
1495
 
1337
1496
  ${bold("Options for run")}
1338
- --success "a; b" success criteria (semicolon-separated)
1339
- --tools fs allow sandboxed read_file/write_file/list_dir (default: none)
1340
- --max-steps <n> step budget --max-tokens <n> token budget
1341
- --runs-dir <dir> where checkpoints live (default: runs/)
1342
- --quiet no live event stream
1497
+ --model <id> model/alias for the chosen provider (e.g. opus, sonnet, llama3.1)
1498
+ --cli-tools let a CLI provider run its own tools (claude/codex); pairs with --permission-mode
1499
+ --permission-mode <m> claude: acceptEdits (default) | dontAsk | plan
1500
+ --allow "Read,Edit" claude: exact tool allowlist instead of the file-only default
1501
+ --success "a; b" success criteria (semicolon-separated)
1502
+ --tools fs give the harness sandboxed read_file/write_file/list_dir (API providers)
1503
+ --max-steps <n> step budget --max-tokens <n> token budget
1504
+ --runs-dir <dir> where checkpoints live (default: runs/)
1505
+ --quiet no live event stream
1343
1506
 
1344
1507
  ${bold("Examples")}
1508
+ oh-my-fable run "refactor utils.ts and run the tests" --provider claude --cli-tools ${dim("# no API key")}
1345
1509
  oh-my-fable run "outline a talk on durable agents" --provider ollama --model llama3.1
1346
- oh-my-fable run "summarize README.md into SUMMARY.md" --tools fs ${dim("# uses Anthropic")}
1510
+ oh-my-fable show run_abc123 ${dim("# inspect any saved run")}
1347
1511
  oh-my-fable demo ${dim("# no key at all")}
1348
1512
 
1349
1513
  ${dim(`It's also a library: import { run, AnthropicProvider } from "oh-my-fable".`)}
@@ -1361,6 +1525,8 @@ async function main() {
1361
1525
  return cmdResume(args);
1362
1526
  case "list":
1363
1527
  return cmdList(args);
1528
+ case "show":
1529
+ return cmdShow(args);
1364
1530
  case "demo":
1365
1531
  return cmdDemo();
1366
1532
  default: