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 +34 -15
- package/dist/cli.cjs +189 -23
- package/dist/cli.js +189 -23
- package/dist/index.cjs +134 -10
- package/dist/index.d.cts +94 -7
- package/dist/index.d.ts +94 -7
- package/dist/index.js +130 -10
- package/package.json +1 -1
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
|
|
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`
|
|
162
|
-
| `
|
|
163
|
-
|
|
|
164
|
-
|
|
|
165
|
-
|
|
|
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
|
-
|
|
168
|
-
|
|
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.
|
|
171
|
-
|
|
172
|
-
|
|
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)
|
|
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
|
|
902
|
-
tokensOut:
|
|
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
|
|
1049
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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("
|
|
1331
|
-
--provider claude drive your Claude Code
|
|
1332
|
-
--provider
|
|
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
|
-
--
|
|
1339
|
-
--tools
|
|
1340
|
-
--
|
|
1341
|
-
--
|
|
1342
|
-
--
|
|
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
|
|
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:
|