amness 0.0.0-stage → 0.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.
Files changed (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -2
  3. package/dist/cli/acpcli.d.ts +7 -0
  4. package/dist/cli/acpcli.js +14 -0
  5. package/dist/cli/amness.d.ts +2 -0
  6. package/dist/cli/amness.js +693 -0
  7. package/dist/cli/benchcli.d.ts +5 -0
  8. package/dist/cli/benchcli.js +160 -0
  9. package/dist/cli/config.d.ts +79 -0
  10. package/dist/cli/config.js +54 -0
  11. package/dist/cli/demo.d.ts +13 -0
  12. package/dist/cli/demo.js +44 -0
  13. package/dist/cli/detect.d.ts +2 -0
  14. package/dist/cli/detect.js +20 -0
  15. package/dist/cli/evalcli.d.ts +15 -0
  16. package/dist/cli/evalcli.js +198 -0
  17. package/dist/cli/format.d.ts +5 -0
  18. package/dist/cli/format.js +55 -0
  19. package/dist/cli/headless.d.ts +2 -0
  20. package/dist/cli/headless.js +169 -0
  21. package/dist/cli/model.d.ts +11 -0
  22. package/dist/cli/model.js +19 -0
  23. package/dist/cli/plugincli.d.ts +2 -0
  24. package/dist/cli/plugincli.js +176 -0
  25. package/dist/cli/schedulecli.d.ts +2 -0
  26. package/dist/cli/schedulecli.js +66 -0
  27. package/dist/cli/state.d.ts +11 -0
  28. package/dist/cli/state.js +19 -0
  29. package/dist/cli/tui/ansi.d.ts +38 -0
  30. package/dist/cli/tui/ansi.js +142 -0
  31. package/dist/cli/tui/app.d.ts +328 -0
  32. package/dist/cli/tui/app.js +1358 -0
  33. package/dist/cli/tui/blocks.d.ts +151 -0
  34. package/dist/cli/tui/blocks.js +128 -0
  35. package/dist/cli/tui/brand.d.ts +9 -0
  36. package/dist/cli/tui/brand.js +16 -0
  37. package/dist/cli/tui/canvas.d.ts +92 -0
  38. package/dist/cli/tui/canvas.js +199 -0
  39. package/dist/cli/tui/cards.d.ts +44 -0
  40. package/dist/cli/tui/cards.js +215 -0
  41. package/dist/cli/tui/clipboard.d.ts +5 -0
  42. package/dist/cli/tui/clipboard.js +29 -0
  43. package/dist/cli/tui/commands.d.ts +10 -0
  44. package/dist/cli/tui/commands.js +649 -0
  45. package/dist/cli/tui/complete.d.ts +39 -0
  46. package/dist/cli/tui/complete.js +113 -0
  47. package/dist/cli/tui/custom.d.ts +9 -0
  48. package/dist/cli/tui/custom.js +34 -0
  49. package/dist/cli/tui/diffview.d.ts +25 -0
  50. package/dist/cli/tui/diffview.js +130 -0
  51. package/dist/cli/tui/editor.d.ts +60 -0
  52. package/dist/cli/tui/editor.js +230 -0
  53. package/dist/cli/tui/full.d.ts +86 -0
  54. package/dist/cli/tui/full.js +630 -0
  55. package/dist/cli/tui/keys.d.ts +31 -0
  56. package/dist/cli/tui/keys.js +134 -0
  57. package/dist/cli/tui/markdown.d.ts +37 -0
  58. package/dist/cli/tui/markdown.js +155 -0
  59. package/dist/cli/tui/mentions.d.ts +15 -0
  60. package/dist/cli/tui/mentions.js +48 -0
  61. package/dist/cli/tui/modal.d.ts +26 -0
  62. package/dist/cli/tui/modal.js +107 -0
  63. package/dist/cli/tui/overlays.d.ts +98 -0
  64. package/dist/cli/tui/overlays.js +237 -0
  65. package/dist/cli/tui/pager.d.ts +15 -0
  66. package/dist/cli/tui/pager.js +66 -0
  67. package/dist/cli/tui/pricing.d.ts +9 -0
  68. package/dist/cli/tui/pricing.js +23 -0
  69. package/dist/cli/tui/rich.d.ts +16 -0
  70. package/dist/cli/tui/rich.js +72 -0
  71. package/dist/cli/tui/screen.d.ts +30 -0
  72. package/dist/cli/tui/screen.js +62 -0
  73. package/dist/cli/tui/session.d.ts +77 -0
  74. package/dist/cli/tui/session.js +103 -0
  75. package/dist/cli/tui/sidebar.d.ts +84 -0
  76. package/dist/cli/tui/sidebar.js +114 -0
  77. package/dist/cli/tui/text.d.ts +15 -0
  78. package/dist/cli/tui/text.js +94 -0
  79. package/dist/cli/tui/theme.d.ts +59 -0
  80. package/dist/cli/tui/theme.js +148 -0
  81. package/dist/cli/tui/themes.d.ts +56 -0
  82. package/dist/cli/tui/themes.js +80 -0
  83. package/dist/cli/tui/tokens.d.ts +39 -0
  84. package/dist/cli/tui/tokens.js +71 -0
  85. package/dist/cli/tui/view.d.ts +60 -0
  86. package/dist/cli/tui/view.js +178 -0
  87. package/dist/cli/ui.d.ts +34 -0
  88. package/dist/cli/ui.js +224 -0
  89. package/dist/src/acp.d.ts +28 -0
  90. package/dist/src/acp.js +111 -0
  91. package/dist/src/agents.d.ts +23 -0
  92. package/dist/src/agents.js +71 -0
  93. package/dist/src/anthropic.d.ts +30 -0
  94. package/dist/src/anthropic.js +91 -0
  95. package/dist/src/approver.d.ts +15 -0
  96. package/dist/src/approver.js +40 -0
  97. package/dist/src/bench.d.ts +67 -0
  98. package/dist/src/bench.js +201 -0
  99. package/dist/src/bundle.d.ts +36 -0
  100. package/dist/src/bundle.js +55 -0
  101. package/dist/src/chaos.d.ts +15 -0
  102. package/dist/src/chaos.js +26 -0
  103. package/dist/src/code.d.ts +187 -0
  104. package/dist/src/code.js +590 -0
  105. package/dist/src/codeindex.d.ts +65 -0
  106. package/dist/src/codeindex.js +300 -0
  107. package/dist/src/deep.d.ts +62 -0
  108. package/dist/src/deep.js +177 -0
  109. package/dist/src/diff.d.ts +8 -0
  110. package/dist/src/diff.js +74 -0
  111. package/dist/src/dynamic.d.ts +42 -0
  112. package/dist/src/dynamic.js +133 -0
  113. package/dist/src/embed.d.ts +8 -0
  114. package/dist/src/embed.js +25 -0
  115. package/dist/src/evalcases.d.ts +11 -0
  116. package/dist/src/evalcases.js +196 -0
  117. package/dist/src/evals.d.ts +197 -0
  118. package/dist/src/evals.js +340 -0
  119. package/dist/src/gitflow.d.ts +27 -0
  120. package/dist/src/gitflow.js +56 -0
  121. package/dist/src/grade.d.ts +42 -0
  122. package/dist/src/grade.js +96 -0
  123. package/dist/src/guidance.d.ts +15 -0
  124. package/dist/src/guidance.js +68 -0
  125. package/dist/src/hardcases.d.ts +2 -0
  126. package/dist/src/hardcases.js +94 -0
  127. package/dist/src/harness.d.ts +411 -0
  128. package/dist/src/harness.js +725 -0
  129. package/dist/src/hooks.d.ts +44 -0
  130. package/dist/src/hooks.js +116 -0
  131. package/dist/src/images.d.ts +13 -0
  132. package/dist/src/images.js +27 -0
  133. package/dist/src/index.d.ts +57 -0
  134. package/dist/src/index.js +57 -0
  135. package/dist/src/ledger.d.ts +58 -0
  136. package/dist/src/ledger.js +78 -0
  137. package/dist/src/live.d.ts +16 -0
  138. package/dist/src/live.js +80 -0
  139. package/dist/src/lsp.d.ts +31 -0
  140. package/dist/src/lsp.js +147 -0
  141. package/dist/src/memory.d.ts +128 -0
  142. package/dist/src/memory.js +390 -0
  143. package/dist/src/metrics.d.ts +3 -0
  144. package/dist/src/metrics.js +42 -0
  145. package/dist/src/mining.d.ts +49 -0
  146. package/dist/src/mining.js +126 -0
  147. package/dist/src/openai.d.ts +50 -0
  148. package/dist/src/openai.js +307 -0
  149. package/dist/src/parallel.d.ts +66 -0
  150. package/dist/src/parallel.js +135 -0
  151. package/dist/src/patch.d.ts +45 -0
  152. package/dist/src/patch.js +219 -0
  153. package/dist/src/permissions.d.ts +46 -0
  154. package/dist/src/permissions.js +245 -0
  155. package/dist/src/plugins.d.ts +148 -0
  156. package/dist/src/plugins.js +225 -0
  157. package/dist/src/pluginscan.d.ts +8 -0
  158. package/dist/src/pluginscan.js +20 -0
  159. package/dist/src/procs.d.ts +41 -0
  160. package/dist/src/procs.js +94 -0
  161. package/dist/src/profiles.d.ts +36 -0
  162. package/dist/src/profiles.js +61 -0
  163. package/dist/src/progress.d.ts +29 -0
  164. package/dist/src/progress.js +75 -0
  165. package/dist/src/prompts.d.ts +10 -0
  166. package/dist/src/prompts.js +28 -0
  167. package/dist/src/ratelimit.d.ts +34 -0
  168. package/dist/src/ratelimit.js +116 -0
  169. package/dist/src/replay.d.ts +25 -0
  170. package/dist/src/replay.js +99 -0
  171. package/dist/src/review.d.ts +20 -0
  172. package/dist/src/review.js +49 -0
  173. package/dist/src/route.d.ts +9 -0
  174. package/dist/src/route.js +30 -0
  175. package/dist/src/router.d.ts +61 -0
  176. package/dist/src/router.js +126 -0
  177. package/dist/src/sandbox.d.ts +13 -0
  178. package/dist/src/sandbox.js +68 -0
  179. package/dist/src/schedule.d.ts +20 -0
  180. package/dist/src/schedule.js +59 -0
  181. package/dist/src/schema.d.ts +13 -0
  182. package/dist/src/schema.js +36 -0
  183. package/dist/src/secrets.d.ts +10 -0
  184. package/dist/src/secrets.js +41 -0
  185. package/dist/src/shell.d.ts +34 -0
  186. package/dist/src/shell.js +95 -0
  187. package/dist/src/skills.d.ts +20 -0
  188. package/dist/src/skills.js +112 -0
  189. package/dist/src/snapshot.d.ts +20 -0
  190. package/dist/src/snapshot.js +93 -0
  191. package/dist/src/stats.d.ts +24 -0
  192. package/dist/src/stats.js +62 -0
  193. package/dist/src/strategies.d.ts +16 -0
  194. package/dist/src/strategies.js +35 -0
  195. package/dist/src/suites.d.ts +30 -0
  196. package/dist/src/suites.js +112 -0
  197. package/dist/src/trace.d.ts +63 -0
  198. package/dist/src/trace.js +153 -0
  199. package/dist/src/tune.d.ts +38 -0
  200. package/dist/src/tune.js +49 -0
  201. package/dist/src/web.d.ts +19 -0
  202. package/dist/src/web.js +56 -0
  203. package/dist/src/worktree.d.ts +24 -0
  204. package/dist/src/worktree.js +85 -0
  205. package/docs/agent-loop.md +222 -0
  206. package/docs/architecture.md +228 -0
  207. package/docs/bench.md +293 -0
  208. package/docs/budgets.md +185 -0
  209. package/docs/build-your-own-harness.md +424 -0
  210. package/docs/cookbook.md +375 -0
  211. package/docs/development.md +150 -0
  212. package/docs/evaluation.md +450 -0
  213. package/docs/features.md +1320 -0
  214. package/docs/getting-started.md +196 -0
  215. package/docs/headless.md +183 -0
  216. package/docs/images/approval.png +0 -0
  217. package/docs/images/chat.png +0 -0
  218. package/docs/images/daylight.png +0 -0
  219. package/docs/images/loop.png +0 -0
  220. package/docs/images/palette.png +0 -0
  221. package/docs/images/running.png +0 -0
  222. package/docs/images/subagent-done.png +0 -0
  223. package/docs/images/subagent-running.png +0 -0
  224. package/docs/images/welcome.png +0 -0
  225. package/docs/index.md +25 -0
  226. package/docs/library.md +276 -0
  227. package/docs/plugins.md +691 -0
  228. package/docs/providers.md +175 -0
  229. package/docs/safety.md +237 -0
  230. package/docs/usage.md +348 -0
  231. package/docs/why.md +155 -0
  232. package/docs/writing-guide.md +39 -0
  233. package/package.json +72 -4
@@ -0,0 +1,55 @@
1
+ import { bold, cyan, dim, gray, green, magenta, red, yellow, blue } from "./ui.js";
2
+ const one = (s, n = 90) => { const l = s.replace(/\s+/g, " ").trim(); return l.length > n ? l.slice(0, n - 1) + "…" : l; };
3
+ const argSummary = (name, raw) => {
4
+ try {
5
+ const a = JSON.parse(raw);
6
+ return one(a.cmd ?? a.path ?? a.query ?? a.url ?? a.description ?? a.note ?? a.pattern ?? a.summary ?? raw, 80);
7
+ }
8
+ catch {
9
+ return one(raw, 80);
10
+ }
11
+ };
12
+ const agentColor = (name) => ({ lead: cyan, general: magenta }[name] ?? blue)(name.padEnd(7));
13
+ /** One readable trace line per harness event. */
14
+ export function formatEvent(e) {
15
+ const who = e.agent ? agentColor(e.agent) + " " : "";
16
+ switch (e.kind) {
17
+ case "tool": {
18
+ const bad = /^(ERROR|DENIED|REJECTED)/.test(e.result) || /^exit=[^0]/.test(e.result);
19
+ return `${who}${bad ? red("✗") : green("✓")} ${e.name} ${dim(argSummary(e.name, e.args))}${bad ? " " + red(one(e.result, 70)) : ""}`;
20
+ }
21
+ case "nudge": return `${who}${yellow("↻ nudge")} ${dim("model described an action instead of doing it → told to act")}`;
22
+ case "repair": return `${who}${yellow("⚒ repair")} ${dim("invalid tool call → error returned to model: " + one(e.detail, 70))}`;
23
+ case "loop": return `${who}${yellow("∞ loop guard")} ${dim("repeated identical call refused")}`;
24
+ case "rejected": return `${who}${yellow("⛔ finish rejected")} ${dim(one(e.detail, 80))}`;
25
+ case "denied": return `${who}${red("⊘ denied")} ${dim(e.detail)}`;
26
+ case "budget": return `${who}${red("$ budget")} ${e.detail}`;
27
+ case "tools": return `${who}${gray("⚙ toolbox changed")} ${dim(one(e.detail, 70))}`;
28
+ case "edit": {
29
+ const body = e.diff.split("\n").filter((l) => !/^(---|\+\+\+)/.test(l));
30
+ const shown = body.slice(0, 40).map((l) => l.startsWith("+") ? green(" " + l) : l.startsWith("-") ? red(" " + l) : l.startsWith("@@") ? cyan(" " + l) : gray(" " + l));
31
+ if (body.length > 40)
32
+ shown.push(dim(` … ${body.length - 40} more lines`));
33
+ return `${who}${yellow("✎")} ${bold(e.path)} ${e.created ? green("new file ") : ""}${green("+" + e.added)} ${red("-" + e.removed)}\n${shown.join("\n")}`;
34
+ }
35
+ case "aborted": return `${who}${yellow("⏹ stopped")} ${dim("run interrupted; progress is saved and can be resumed")}`;
36
+ case "turn":
37
+ case "phase":
38
+ case "trim":
39
+ case "cachebreak":
40
+ case "run":
41
+ case "model":
42
+ case "policy":
43
+ case "toolstart":
44
+ case "text":
45
+ case "thinking":
46
+ case "assistant":
47
+ case "todos":
48
+ case "steer": return "";
49
+ case "rollover": return `${who}${magenta("⟳ context rollover")} ${dim("handoff note written; continuing in a fresh context")}`;
50
+ case "review": return `${who}${e.pass ? green("✔ review") : yellow("✖ review")} ${dim(e.inconclusive ? "inconclusive (accepted)" : e.pass ? "the reviewer found no problems" : e.findings.slice(0, 2).map((f) => one(f, 70)).join("; "))}`;
51
+ case "audit": return e.changed.length || e.notes.length ? `${who}${e.notes.length ? yellow("◐ audit") : green("◉ audit")} ${dim(`${e.changed.length} file(s) changed${e.testsAfterLastEdit === true ? " · tests passed after the last edit" : ""}${e.notes.length ? " · " + e.notes.join("; ") : ""}`)}` : "";
52
+ case "hook": return `${who}${e.blocked ? red("⛔ hook") : yellow("⚙ hook")} ${dim(e.event + ": " + one(e.detail, 90))}`;
53
+ case "finish": return `${who}${green("✔ finish")} ${dim(one(e.summary, 80))}`;
54
+ }
55
+ }
@@ -0,0 +1,2 @@
1
+ export declare const HELP: string;
2
+ export declare function runHeadless(argv: string[]): Promise<number>;
@@ -0,0 +1,169 @@
1
+ // Non-interactive mode for CI, scripts and containers: amness run [flags] "task"
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { Budget, Tracer, Hooks, Permissions, projectConfigFingerprint, createCodeAgent, startLive, setShell, loadPlugins, readImage, visionLikely, bestOfN, parallelParts, isRepo } from "../src/index.js";
5
+ import { PROVIDERS, loadConfig, ready } from "./config.js";
6
+ import { formatEvent } from "./format.js";
7
+ import { createModel } from "./model.js";
8
+ export const HELP = `usage: amness run [flags] "task"
9
+ --cwd <dir> workspace (default .)
10
+ --verify <cmd> command that must exit 0 before the agent may finish
11
+ --trust-project run this project's .amness/hooks.json and plugins and honour its allow rules (they execute code; off by default in headless runs)
12
+ --sandbox <mode> run shell commands under an OS sandbox: workspace (write only in the project and temp folders) or strict (also no network)
13
+ --review an independent reviewer writes acceptance criteria first and checks the work before it is accepted (extra model calls)
14
+ --trace <file> write a JSONL trace of every loop event (also AMNESS_OTLP=<collector url> for OpenTelemetry export)
15
+ --approval-reviewer a second model looks at each risky call first and approves clearly safe ones (without --yes, calls it does not clear are still denied)
16
+ --check <cmd> fast command run after every edit; failures are shown to the agent immediately
17
+ --provider <id> ${Object.keys(PROVIDERS).join(" | ")} --model <id> --base-url <url> --effort none|low|medium|high|xhigh|max --api auto|responses|chat
18
+ --bundle <file> continue from a portable session bundle (any provider/model)
19
+ --export <file> write a portable session bundle when done
20
+ --yes auto-approve risky commands (default: deny them)
21
+ --json machine-readable: one JSON event per line on stdout, final {"kind":"result"} line
22
+ --live [port] serve the live browser view
23
+ --best-of <n> attempt the task up to n times in separate git worktrees (stops at the first attempt that passes --verify, which is required); the winner is applied
24
+ --parts <file.json> run independent parts ([{"name","task"}]) concurrently in worktrees, merged one at a time through --verify; conflicts are reported, not applied
25
+ --image <file> show the model a picture (png, jpg, gif, webp; up to 5 MB, repeat the flag for more, max 4); needs a vision-capable model
26
+ --max-tokens <n> hard token cap (default 300000) --no-memory disable memory
27
+ - as the task: read it from standard input (useful on Windows, where quoting a long task is painful)
28
+ exit code: 0 finished, 1 not finished, 2 usage/config error`;
29
+ export async function runHeadless(argv) {
30
+ if (argv.includes("--help") || argv.includes("-h")) {
31
+ console.log(HELP);
32
+ return 0;
33
+ } // asking for help must never start a model run
34
+ const args = [...argv];
35
+ const val = (f) => { const i = args.indexOf(f); return i < 0 ? undefined : args.splice(i, 2)[1]; };
36
+ const bool = (f) => { const i = args.indexOf(f); return i >= 0 && !!args.splice(i, 1); };
37
+ const cfg = loadConfig();
38
+ const provider = val("--provider");
39
+ if (provider) {
40
+ if (!(provider in PROVIDERS)) {
41
+ console.error(`unknown provider ${provider}\n${HELP}`);
42
+ return 2;
43
+ }
44
+ Object.assign(cfg, { provider, baseUrl: PROVIDERS[provider].baseUrl, model: PROVIDERS[provider].model });
45
+ const k = provider === "anthropic" ? process.env.ANTHROPIC_API_KEY : process.env.OPENAI_API_KEY;
46
+ cfg.apiKey = k ?? (loadConfig().provider === provider ? loadConfig().apiKey : undefined);
47
+ }
48
+ cfg.model = val("--model") ?? cfg.model;
49
+ cfg.baseUrl = val("--base-url") ?? cfg.baseUrl;
50
+ cfg.api = val("--api") ?? cfg.api;
51
+ cfg.effort = val("--effort") ?? cfg.effort;
52
+ const cwd = resolve(val("--cwd") ?? "."), verify = val("--verify"), check = val("--check"), bundle = val("--bundle"), exportTo = val("--export");
53
+ const trustHooks = bool("--trust-project") || bool("--trust-hooks"), yes = bool("--yes"), json = bool("--json"), noMemory = bool("--no-memory");
54
+ const li = args.indexOf("--live");
55
+ let livePort;
56
+ if (li >= 0) {
57
+ const nxt = args[li + 1];
58
+ livePort = /^\d+$/.test(nxt ?? "") ? Number(args.splice(li, 2)[1]) : (args.splice(li, 1), 0);
59
+ }
60
+ const imagePaths = [];
61
+ for (let v = val("--image"); v !== undefined; v = val("--image"))
62
+ imagePaths.push(v);
63
+ const maxTokens = Number(val("--max-tokens") ?? cfg.maxTokens);
64
+ let task = args.join(" ").trim();
65
+ if (task === "-")
66
+ task = (await new Promise((res) => { let s = ""; process.stdin.setEncoding("utf8"); process.stdin.on("data", (d) => (s += d)); process.stdin.on("end", () => res(s)); })).trim(); // `amness run -` reads the task from stdin (no quoting problems on any shell)
67
+ if (!task && !bundle) {
68
+ console.error(HELP);
69
+ return 2;
70
+ }
71
+ if (!ready(cfg)) {
72
+ console.error(`no API key for ${PROVIDERS[cfg.provider].label}. Set ${cfg.provider === "anthropic" ? "ANTHROPIC_API_KEY" : "OPENAI_API_KEY"} or run \`amness settings\`.`);
73
+ return 2;
74
+ }
75
+ if (imagePaths.length && !visionLikely(cfg.provider, cfg.model)) {
76
+ console.error(`${cfg.model} does not look like a vision model; pick one that accepts images or drop --image`);
77
+ return 2;
78
+ }
79
+ let images;
80
+ try {
81
+ images = imagePaths.map((p) => readImage(resolve(p)));
82
+ }
83
+ catch (e) {
84
+ console.error(e.message);
85
+ return 2;
86
+ }
87
+ const live = livePort === undefined ? undefined : await startLive({ port: livePort });
88
+ if (live)
89
+ console.error(`live view: ${live.url}`);
90
+ const approve = yes ? () => true : () => "non-interactive run: risky commands are denied unless --yes is passed";
91
+ const budget = new Budget({ maxTokens });
92
+ const model = createModel(cfg);
93
+ const bestOf = Number(val("--best-of") ?? 0), partsFile = val("--parts");
94
+ if (bestOf || partsFile) {
95
+ if (!verify) {
96
+ console.error("--best-of and --parts need --verify <cmd>: that command is what decides which result is kept");
97
+ return 2;
98
+ }
99
+ if (!(await isRepo(cwd))) {
100
+ console.error("--best-of and --parts need a git repository with at least one commit");
101
+ return 2;
102
+ }
103
+ try {
104
+ if (bestOf) {
105
+ const r = await bestOfN({ repo: cwd, n: bestOf, verify, apply: true, maxTokens, attempt: async (dir) => { const b = new Budget({ maxTokens: Math.max(1, Math.floor(maxTokens / bestOf)) }); const a = createCodeAgent({ model, cwd: dir, verify, approve, sandbox: cfg.sandbox, memory: false, budget: b }); const x = await a.run(task); await a.procs?.dispose(); return { tokens: b.tokens, summary: x.text }; }, onCandidate: (c) => console.error(`attempt ${c.index + 1}: ${c.passed ? "passed verify" : c.error ? "error: " + c.error : "failed verify"} (${c.tokens} tokens)`) });
106
+ if (json)
107
+ console.log(JSON.stringify({ kind: "result", finished: !!r.winner, applied: r.applied, tokens: r.tokens, attempts: r.candidates.length }));
108
+ else
109
+ console.log(r.winner ? `attempt ${r.winner.index + 1} won and was ${r.applied ? "applied" : "NOT applied: " + r.applyError}\n${r.winner.summary ?? ""}` : `no attempt passed ${verify} (${r.candidates.length} tried, ${r.tokens} tokens)`);
110
+ return r.winner && r.applied ? 0 : 1;
111
+ }
112
+ const parts = JSON.parse(readFileSync(resolve(partsFile), "utf8"));
113
+ const { results, tokens } = await parallelParts({ repo: cwd, parts, verify, maxTokens, attempt: async (p, dir) => { const b = new Budget({ maxTokens: Math.max(1, Math.floor(maxTokens / parts.length)) }); const a = createCodeAgent({ model, cwd: dir, verify, approve, sandbox: cfg.sandbox, memory: false, budget: b }); const x = await a.run(p.task); await a.procs?.dispose(); return { tokens: b.tokens, summary: x.text }; }, onPart: (r) => console.error(`${r.part.name}: ${r.status}${r.detail ? " - " + r.detail.split("\n")[0] : ""}`) });
114
+ if (json)
115
+ console.log(JSON.stringify({ kind: "result", tokens, parts: results.map((r) => ({ name: r.part.name, status: r.status })) }));
116
+ else
117
+ for (const r of results)
118
+ console.log(`${r.status.padEnd(14)} ${r.part.name}`);
119
+ return results.every((r) => r.status === "merged") ? 0 : 1;
120
+ }
121
+ catch (e) {
122
+ console.error("error: " + e.message);
123
+ return 1;
124
+ }
125
+ }
126
+ const tracer = val("--trace") || process.env.AMNESS_OTLP ? new Tracer({ file: val("--trace") ? resolve(val("--trace")) : undefined, otlp: process.env.AMNESS_OTLP, sessionId: `run-${Date.now()}`, model: cfg.model }) : undefined;
127
+ setShell(cfg.shell);
128
+ const loadedPlugins = await loadPlugins(cwd, { trustProject: trustHooks });
129
+ for (const w of loadedPlugins.warnings)
130
+ console.error("plugin: " + w);
131
+ if (loadedPlugins.untrusted)
132
+ console.error(`note: ${loadedPlugins.untrusted.count} project plugin file(s) in .amness/plugins were not loaded; pass --trust-project to run them`);
133
+ const agent = createCodeAgent({
134
+ model, modelById: (id) => createModel({ ...cfg, model: id }), cwd, verify, plugins: loadedPlugins.plugins.length ? loadedPlugins.plugins : undefined, check, approve, record: !!tracer || undefined, sandbox: val("--sandbox") ?? cfg.sandbox, review: bool("--review") || undefined, approvalReviewer: bool("--approval-reviewer") || undefined, hooks: ((h) => { if (!trustHooks && projectConfigFingerprint(cwd))
135
+ console.error("note: ignoring the project's hooks and allow rules (pass --trust-project to use them)"); return h.count() ? h : undefined; })(Hooks.load(cwd, { trustProject: trustHooks })), rules: Permissions.load(cwd, { trustProject: trustHooks }), budget, memory: noMemory ? false : undefined, meta: { provider: cfg.provider, model: cfg.model },
136
+ onEvent: (e) => { live?.onEvent(e); tracer?.event(e); if (json)
137
+ console.log(JSON.stringify(e));
138
+ else {
139
+ const l = formatEvent(e);
140
+ if (l)
141
+ console.error(l);
142
+ } },
143
+ });
144
+ try {
145
+ const r = await agent.run(task, { bundle, images });
146
+ if (exportTo) {
147
+ await agent.exportBundle(r, exportTo);
148
+ console.error(`session bundle: ${exportTo}`);
149
+ }
150
+ const out = { kind: "result", finished: r.finished, text: r.text, steps: r.steps, tokens: budget.tokens, stopped: r.stopped };
151
+ if (json)
152
+ console.log(JSON.stringify(out));
153
+ else
154
+ console.log(r.text);
155
+ return r.finished ? 0 : 1;
156
+ }
157
+ catch (e) {
158
+ if (json)
159
+ console.log(JSON.stringify({ kind: "error", message: e.message }));
160
+ else
161
+ console.error("error: " + e.message);
162
+ return 1;
163
+ }
164
+ finally {
165
+ await agent.procs?.dispose();
166
+ await tracer?.close();
167
+ await live?.close();
168
+ }
169
+ }
@@ -0,0 +1,11 @@
1
+ import { type Model } from "../src/index.ts";
2
+ import { type Config } from "./config.ts";
3
+ /** One place for the UI to hear about rate limits, whichever model object hit them. */
4
+ declare let waitListener: ((ms: number, attempt: number, concurrency: number) => void) | undefined;
5
+ export declare const onRateLimitWait: (fn: typeof waitListener) => void;
6
+ /** Build the model for a config (shared by the menu and headless mode). */
7
+ export declare function createModel(cfg: Config, retries?: number, o?: {
8
+ cacheKey?: string; /** told when a rate limit makes calls wait */
9
+ onWait?: (ms: number, attempt: number, concurrency: number) => void;
10
+ }): Model;
11
+ export {};
@@ -0,0 +1,19 @@
1
+ import { anthropic, effortsFor, gateFor, openai, rateLimited } from "../src/index.js";
2
+ import { PROVIDERS } from "./config.js";
3
+ /** One place for the UI to hear about rate limits, whichever model object hit them. */
4
+ let waitListener;
5
+ export const onRateLimitWait = (fn) => { waitListener = fn; };
6
+ /** Build the model for a config (shared by the menu and headless mode). */
7
+ export function createModel(cfg, retries, o = {}) {
8
+ // Every model built for the same endpoint and key shares one gate, so parallel subagents slow down together when the provider says 429.
9
+ const gate = gateFor(`${cfg.provider}|${cfg.baseUrl}|${(cfg.apiKey ?? "").slice(-6)}`, cfg.maxConcurrent ?? 4);
10
+ return rateLimited(rawModel(cfg, retries, o), { gate, onWait: (ms, attempt, g) => { o.onWait?.(ms, attempt, g.max); waitListener?.(ms, attempt, g.max); } });
11
+ }
12
+ function rawModel(cfg, retries, o) {
13
+ return cfg.provider === "anthropic"
14
+ ? anthropic({ model: cfg.model, apiKey: cfg.apiKey, taskBudget: cfg.taskBudget, effort: cfg.effort && !["none", "minimal"].includes(cfg.effort) ? cfg.effort : undefined, baseUrl: cfg.baseUrl === PROVIDERS.anthropic.baseUrl ? undefined : cfg.baseUrl })
15
+ : openai({
16
+ model: cfg.model, apiKey: cfg.apiKey, baseUrl: cfg.baseUrl, retries, api: cfg.api, promptCacheKey: o.cacheKey, compactAt: cfg.compactAt, externalRetry: true,
17
+ effort: cfg.effort && effortsFor(cfg.model).includes(cfg.effort) ? cfg.effort : undefined, // a value this model does not accept is never sent
18
+ });
19
+ }
@@ -0,0 +1,2 @@
1
+ export declare const PLUGIN_HELP = "usage: amness plugin <command>\n new <name> create .amness/plugins/<name>.ts from a template (never overwrites)\n policy <traces...> --plugin <file> replay the tool calls recorded in traces (a file, or a folder such as .amness/traces) through the plugin policies and show what each would allow, ask or deny\n grade <traces...> [--plugin <file>] [--grader a,b] score real recorded sessions (a trace file, or a folder such as .amness/traces) with built-in or plugin graders\n add <file|https-url> copy a plugin file into .amness/plugins/ (never overwrites) and show what it appears to touch; it still needs your approval the first time you start Amness\n list show the plugins that load here (project plugins need --trust-project to be listed as loaded)\nA plugin is a TypeScript file that runs inside Amness: guardrails, loop rules, tools, slash commands and eval variants. See docs/plugins.md.";
2
+ export declare function runPlugin(argv: string[], cwd?: string): Promise<number>;
@@ -0,0 +1,176 @@
1
+ // `amness plugin new <name>` and `amness plugin list`.
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
+ import { basename } from "node:path";
4
+ import { join, resolve } from "node:path";
5
+ import { scanSource, BUILTIN_GRADERS, gradeSessions, sessionFiles, describeProjectPlugins, checkPolicies, importPlugins, loadPlugins, pluginTemplate, projectPluginDir } from "../src/index.js";
6
+ import { dim, green, red } from "./ui.js";
7
+ export const PLUGIN_HELP = `usage: amness plugin <command>
8
+ new <name> create .amness/plugins/<name>.ts from a template (never overwrites)
9
+ policy <traces...> --plugin <file> replay the tool calls recorded in traces (a file, or a folder such as .amness/traces) through the plugin policies and show what each would allow, ask or deny
10
+ grade <traces...> [--plugin <file>] [--grader a,b] score real recorded sessions (a trace file, or a folder such as .amness/traces) with built-in or plugin graders
11
+ add <file|https-url> copy a plugin file into .amness/plugins/ (never overwrites) and show what it appears to touch; it still needs your approval the first time you start Amness
12
+ list show the plugins that load here (project plugins need --trust-project to be listed as loaded)
13
+ A plugin is a TypeScript file that runs inside Amness: guardrails, loop rules, tools, slash commands and eval variants. See docs/plugins.md.`;
14
+ export async function runPlugin(argv, cwd = process.cwd()) {
15
+ const [cmd, name] = argv, root = resolve(cwd);
16
+ if (!cmd || cmd === "--help" || cmd === "-h") {
17
+ console.log(PLUGIN_HELP);
18
+ return cmd ? 0 : 2;
19
+ }
20
+ if (cmd === "new") {
21
+ if (!name || !/^[\w.-]{1,40}$/.test(name)) {
22
+ console.error(red("✗ ") + "give a name made of letters, numbers, . _ - (for example: amness plugin new house-rules)");
23
+ return 2;
24
+ }
25
+ const file = join(projectPluginDir(root), `${name}.ts`);
26
+ if (existsSync(file)) {
27
+ console.error(red("✗ ") + `${file} already exists`);
28
+ return 1;
29
+ }
30
+ mkdirSync(projectPluginDir(root), { recursive: true });
31
+ writeFileSync(file, pluginTemplate(name));
32
+ console.log(green("created ") + file + "\n" + dim("Start the chat: it will ask you to approve this project plugin. Edit it, then /reload-plugins."));
33
+ return 0;
34
+ }
35
+ if (cmd === "list") {
36
+ const r = await loadPlugins(root, { trustProject: argv.includes("--trust-project") });
37
+ for (const p of r.plugins)
38
+ console.log(`${p.name.padEnd(24)} ${p.description ?? ""}`);
39
+ for (const w of r.warnings)
40
+ console.error(red("! ") + w);
41
+ if (r.untrusted) {
42
+ console.log(dim(`${r.untrusted.count} project plugin file(s) not loaded (not approved); add --trust-project to load them here. What they appear to touch (a text scan, not a guarantee):`));
43
+ for (const l of describeProjectPlugins(root))
44
+ console.log(" " + l);
45
+ }
46
+ if (!r.plugins.length && !r.untrusted)
47
+ console.log("no plugins");
48
+ return 0;
49
+ }
50
+ if (cmd === "add") {
51
+ if (!name) {
52
+ console.error(red("✗ ") + "usage: amness plugin add <file or https URL>");
53
+ return 2;
54
+ }
55
+ let text, base;
56
+ try {
57
+ if (/^https:\/\//.test(name)) {
58
+ const r = await fetch(name);
59
+ if (!r.ok)
60
+ throw new Error(`HTTP ${r.status}`);
61
+ text = await r.text();
62
+ base = basename(new URL(name).pathname) || "plugin.ts";
63
+ }
64
+ else if (/^http:\/\//.test(name)) {
65
+ console.error(red("✗ ") + "only https URLs are accepted");
66
+ return 2;
67
+ }
68
+ else {
69
+ text = readFileSync(resolve(name), "utf8");
70
+ base = basename(name);
71
+ }
72
+ }
73
+ catch (e) {
74
+ console.error(red("✗ ") + `could not read ${name}: ${e.message}`);
75
+ return 1;
76
+ }
77
+ if (text.length > 300_000) {
78
+ console.error(red("✗ ") + "that file is too large for a plugin (300 KB limit)");
79
+ return 1;
80
+ }
81
+ if (!/\bexport\s+default\b|\bexport\s*\{[^}]*\bas default\b|module\.exports/.test(text)) {
82
+ console.error(red("✗ ") + "this does not look like a plugin: it has no `export default`");
83
+ return 1;
84
+ }
85
+ const file = join(projectPluginDir(root), /\.(m?[jt]s)$/.test(base) ? base : base + ".ts");
86
+ if (existsSync(file)) {
87
+ console.error(red("✗ ") + `${file} already exists`);
88
+ return 1;
89
+ }
90
+ mkdirSync(projectPluginDir(root), { recursive: true });
91
+ writeFileSync(file, text);
92
+ const caps = scanSource(text);
93
+ console.log(green("added ") + file);
94
+ console.log(caps.length ? "It appears to: " + caps.map((c) => c.label).join("; ") : "It shows no shell, network, file or environment access (a text scan, not a guarantee).");
95
+ console.log(dim("Read it before you trust it. Amness asks for your approval the first time it starts in this project."));
96
+ return 0;
97
+ }
98
+ if (cmd === "grade") {
99
+ const rest = argv.slice(1), take = (f) => { const i = rest.indexOf(f); return i >= 0 ? rest.splice(i, 2)[1] : undefined; };
100
+ const pluginFiles = (take("--plugin") ?? "").split(",").filter(Boolean), pick = (take("--grader") ?? "").split(",").filter(Boolean), inputs = rest.length ? rest : [join(root, ".amness", "traces")];
101
+ const graders = { ...BUILTIN_GRADERS };
102
+ for (const f of pluginFiles) {
103
+ const r = await importPlugins(f);
104
+ for (const w of r.warnings)
105
+ console.error(red("! ") + w);
106
+ for (const p of r.plugins)
107
+ Object.assign(graders, p.graders ?? {});
108
+ }
109
+ const chosen = pick.length ? Object.fromEntries(pick.map((n) => { if (!graders[n])
110
+ throw new Error(`unknown grader "${n}". Known: ${Object.keys(graders).join(", ")}`); return [n, graders[n]]; })) : graders;
111
+ const files = sessionFiles(inputs);
112
+ if (!files.length) {
113
+ console.error(red("✗ ") + "no trace files found");
114
+ return 2;
115
+ }
116
+ const reports = await gradeSessions(files, chosen);
117
+ console.log(`${files.length} session file(s)`);
118
+ for (const r of reports) {
119
+ console.log(`${r.grader.padEnd(18)} mean ${Number.isNaN(r.mean) ? "n/a" : (r.mean * 100).toFixed(0) + "%"} over ${r.sessions} session(s)`);
120
+ for (const l of r.low)
121
+ console.log(dim(` ${(l.score * 100).toFixed(0).padStart(3)}% ${l.session} ${l.task.slice(0, 60)}${l.note ? " - " + l.note : ""}`));
122
+ }
123
+ return 0;
124
+ }
125
+ if (cmd === "policy") {
126
+ const rest = argv.slice(1), pi = rest.indexOf("--plugin"), pluginFiles = pi >= 0 ? (rest.splice(pi, 2)[1] ?? "").split(",").filter(Boolean) : [], inputs = rest;
127
+ if (!pluginFiles.length || !inputs.length) {
128
+ console.error(red("✗ ") + "usage: amness plugin policy <trace file or folder>... --plugin <file>");
129
+ return 2;
130
+ }
131
+ const plugins = [];
132
+ for (const f of pluginFiles) {
133
+ const r = await importPlugins(f);
134
+ for (const w of r.warnings)
135
+ console.error(red("! ") + w);
136
+ plugins.push(...r.plugins);
137
+ }
138
+ if (!plugins.some((p) => p.policy)) {
139
+ console.error(red("✗ ") + "none of these plugins defines a policy");
140
+ return 2;
141
+ }
142
+ const files = inputs.flatMap((p) => (existsSync(p) && statSync(p).isDirectory() ? readdirSync(p).filter((f) => f.endsWith(".jsonl")).map((f) => join(p, f)) : [p]));
143
+ const tally = { allow: 0, ask: 0, deny: 0, none: 0 }, shown = [];
144
+ for (const f of files)
145
+ for (const line of readFileSync(f, "utf8").split("\n")) {
146
+ if (!line.includes('"tool_calls"'))
147
+ continue;
148
+ let ev;
149
+ try {
150
+ ev = JSON.parse(line);
151
+ }
152
+ catch {
153
+ continue;
154
+ }
155
+ for (const c of ev.message?.tool_calls ?? []) {
156
+ let args = {};
157
+ try {
158
+ args = JSON.parse(c.function.arguments || "{}");
159
+ }
160
+ catch { /* keep empty */ }
161
+ const d = await checkPolicies(plugins, { tool: c.function.name, args, cwd: root });
162
+ tally[d?.action ?? "none"]++;
163
+ if (d && d.action !== "allow" && shown.length < 25)
164
+ shown.push(`${d.action.padEnd(5)} ${c.function.name} ${JSON.stringify(args).slice(0, 90)} ${dim(d.reason ?? "")}`);
165
+ }
166
+ }
167
+ console.log(`${files.length} trace file(s): ${tally.deny} denied, ${tally.ask} would ask, ${tally.allow} allowed by the policy, ${tally.none} without an opinion`);
168
+ for (const s of shown)
169
+ console.log(" " + s);
170
+ if (!shown.length)
171
+ console.log(dim(" nothing was denied or asked"));
172
+ return 0;
173
+ }
174
+ console.error(PLUGIN_HELP);
175
+ return 2;
176
+ }
@@ -0,0 +1,2 @@
1
+ export declare const SCHEDULE_HELP = "usage: amness schedule <command>\n add <name> \"<cron>\" \"<task>\" [--verify <cmd>] save a recurring task for this project (5-field cron, e.g. \"0 3 * * 1-5\")\n list show this project's schedules\n remove <name> delete one\n install [--write] print the crontab lines; with --write, put them in your crontab (only a marked block for this project is touched)\n uninstall remove this project's block from your crontab\n Runs are headless (`amness run --json`), risky commands are denied, and each run's output is appended to .amness/schedule-logs/<name>.jsonl.\n Cron must be able to find `amness` and your API key; set them in the crontab environment or use a wrapper script.";
2
+ export declare function runSchedule(argv: string[], cwd?: string): number;
@@ -0,0 +1,66 @@
1
+ // `amness schedule`: recurring headless runs via cron.
2
+ import { execFileSync, spawnSync } from "node:child_process";
3
+ import { resolve } from "node:path";
4
+ import { add, cronLines, ensureLogDir, load, mergeCrontab, remove } from "../src/index.js";
5
+ import { bold, dim, green, red } from "./ui.js";
6
+ export const SCHEDULE_HELP = `usage: amness schedule <command>
7
+ add <name> "<cron>" "<task>" [--verify <cmd>] save a recurring task for this project (5-field cron, e.g. "0 3 * * 1-5")
8
+ list show this project's schedules
9
+ remove <name> delete one
10
+ install [--write] print the crontab lines; with --write, put them in your crontab (only a marked block for this project is touched)
11
+ uninstall remove this project's block from your crontab
12
+ Runs are headless (\`amness run --json\`), risky commands are denied, and each run's output is appended to .amness/schedule-logs/<name>.jsonl.
13
+ Cron must be able to find \`amness\` and your API key; set them in the crontab environment or use a wrapper script.`;
14
+ export function runSchedule(argv, cwd = process.cwd()) {
15
+ const root = resolve(cwd), [cmd, ...rest] = argv;
16
+ const val = (f) => { const i = rest.indexOf(f); return i >= 0 ? rest[i + 1] : undefined; };
17
+ try {
18
+ if (!cmd || cmd === "--help" || cmd === "-h") {
19
+ console.log(SCHEDULE_HELP);
20
+ return cmd ? 0 : 2;
21
+ }
22
+ if (cmd === "add") {
23
+ const [name, cron, task] = rest;
24
+ add(root, { name, cron, task, verify: val("--verify") });
25
+ console.log(`saved "${name}". Run \`amness schedule install\` to see the crontab line.`);
26
+ return 0;
27
+ }
28
+ if (cmd === "list") {
29
+ const l = load(root);
30
+ if (!l.length)
31
+ console.log("no schedules for this project");
32
+ for (const s of l)
33
+ console.log(`${bold(s.name.padEnd(16))} ${s.cron.padEnd(14)} ${s.task}${s.verify ? dim(" verify: " + s.verify) : ""}`);
34
+ return 0;
35
+ }
36
+ if (cmd === "remove") {
37
+ console.log(remove(root, rest[0]) ? `removed "${rest[0]}"` : `no schedule named "${rest[0]}"`);
38
+ return 0;
39
+ }
40
+ if (cmd === "install" || cmd === "uninstall") {
41
+ const lines = cmd === "install" ? cronLines(root, load(root)) : [];
42
+ if (cmd === "install" && !rest.includes("--write")) {
43
+ console.log(lines.length ? lines.join("\n") : "no schedules to install");
44
+ console.log(dim("\n(not written; pass --write to add these to your crontab)"));
45
+ return 0;
46
+ }
47
+ let current = "";
48
+ try {
49
+ current = execFileSync("crontab", ["-l"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
50
+ }
51
+ catch { /* none yet */ }
52
+ ensureLogDir(root);
53
+ const r = spawnSync("crontab", ["-"], { input: mergeCrontab(current, root, lines), encoding: "utf8" });
54
+ if (r.status !== 0)
55
+ throw new Error("crontab refused the update: " + (r.stderr || "unknown error").trim());
56
+ console.log(green(cmd === "install" ? `installed ${lines.length} schedule(s) in your crontab` : "removed this project's schedules from your crontab"));
57
+ return 0;
58
+ }
59
+ console.error(SCHEDULE_HELP);
60
+ return 2;
61
+ }
62
+ catch (e) {
63
+ console.error(red("✗ " + e.message));
64
+ return 2;
65
+ }
66
+ }
@@ -0,0 +1,11 @@
1
+ export type State = {
2
+ lastDir?: string;
3
+ verifyByDir: Record<string, string>;
4
+ tasks: string[]; /** project hooks the user approved: directory -> fingerprint of the hooks.json that was approved */
5
+ hookTrust?: Record<string, string>; /** project plugins the user approved: directory -> fingerprint of the plugin files */
6
+ pluginTrust?: Record<string, string>;
7
+ };
8
+ export declare function loadState(): State;
9
+ export declare function saveState(s: State): void;
10
+ /** Most-recent-first, de-duplicated, capped: the shape readline's `history` wants. */
11
+ export declare function rememberTask(s: State, task: string): State;
@@ -0,0 +1,19 @@
1
+ // Small remembered state so the TUI stops asking things it already knows: last project, per-project verify command, task history.
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { home } from "./config.js";
5
+ const file = () => join(home(), "state.json");
6
+ export function loadState() {
7
+ try {
8
+ if (existsSync(file()))
9
+ return { verifyByDir: {}, tasks: [], ...JSON.parse(readFileSync(file(), "utf8")) };
10
+ }
11
+ catch { /* corrupt: start fresh */ }
12
+ return { verifyByDir: {}, tasks: [] };
13
+ }
14
+ export function saveState(s) { mkdirSync(home(), { recursive: true }); writeFileSync(file(), JSON.stringify(s, null, 2)); }
15
+ /** Most-recent-first, de-duplicated, capped: the shape readline's `history` wants. */
16
+ export function rememberTask(s, task) {
17
+ const t = task.trim();
18
+ return t ? { ...s, tasks: [t, ...s.tasks.filter((x) => x !== t)].slice(0, 50) } : s;
19
+ }
@@ -0,0 +1,38 @@
1
+ /** Colour: -1 = terminal default, 0x000000..0xFFFFFF = truecolor, 0x1000000 + n = 256-palette index n. */
2
+ export declare const DEFAULT = -1;
3
+ export declare const A: {
4
+ readonly bold: 1;
5
+ readonly dim: 2;
6
+ readonly italic: 4;
7
+ readonly underline: 8;
8
+ readonly strike: 16;
9
+ readonly inverse: 32;
10
+ };
11
+ export type Cell = {
12
+ ch: string;
13
+ w: number;
14
+ fg: number;
15
+ bg: number;
16
+ attr: number;
17
+ };
18
+ /** Resolve any colour value to 0xRRGGBB (for dimming/compositing and for screenshots). `def` stands in for the terminal default. */
19
+ export declare function toRgb(c: number, def: number): number;
20
+ export declare const rgb: (r: number, g: number, b: number) => number;
21
+ export declare const mix: (a: number, b: number, t: number) => number;
22
+ type St = {
23
+ fg: number;
24
+ bg: number;
25
+ attr: number;
26
+ };
27
+ /** Apply the parameters of one SGR sequence (the part between `ESC[` and `m`) to a style. */
28
+ export declare function applySgr(st: St, params: string): void;
29
+ /** Parse an ANSI string into styled cells. Wide characters become one cell with w = 2. Unknown escape sequences are dropped. */
30
+ export declare function parseAnsi(s: string, base?: Partial<St>): Cell[];
31
+ /** Cells back to an ANSI string (minimal SGR changes). */
32
+ export declare function cellsToAnsi(cells: Cell[]): string;
33
+ /** Full SGR (reset + everything) to reach a given style from any state. */
34
+ export declare function sgrFor(fg: number, bg: number, attr: number): string;
35
+ /** SGR fragments for a colour (truecolor, 256-palette index, or default). */
36
+ export declare const fgSeq: (c: number) => string;
37
+ export declare const bgSeq: (c: number) => string;
38
+ export {};