@cruxy/cli 1.0.2 → 1.0.4

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 (245) hide show
  1. package/dist/agent/loop.js +3 -0
  2. package/dist/agent/session.js +2 -1
  3. package/dist/theme/tokens.js +3 -0
  4. package/dist/usage/collect.js +41 -2
  5. package/dist/usage/index.js +1 -1
  6. package/dist/usage/summary.js +19 -0
  7. package/dist/usage/types.js +9 -0
  8. package/package.json +1 -1
  9. package/dist/agent/budget.d.ts +0 -52
  10. package/dist/agent/index.d.ts +0 -3
  11. package/dist/agent/loop.d.ts +0 -159
  12. package/dist/agent/prompts.d.ts +0 -69
  13. package/dist/agent/session.d.ts +0 -212
  14. package/dist/approval/classify.d.ts +0 -18
  15. package/dist/approval/index.d.ts +0 -6
  16. package/dist/approval/mutex.d.ts +0 -45
  17. package/dist/approval/policy.d.ts +0 -43
  18. package/dist/approval/prompt.d.ts +0 -48
  19. package/dist/approval/service.d.ts +0 -36
  20. package/dist/approval/types.d.ts +0 -82
  21. package/dist/brand/index.d.ts +0 -1
  22. package/dist/brand/voice.d.ts +0 -94
  23. package/dist/checkpoint/capture.d.ts +0 -17
  24. package/dist/checkpoint/gate-hook.d.ts +0 -28
  25. package/dist/checkpoint/gate.d.ts +0 -71
  26. package/dist/checkpoint/git-store.d.ts +0 -61
  27. package/dist/checkpoint/index.d.ts +0 -10
  28. package/dist/checkpoint/restore.d.ts +0 -23
  29. package/dist/checkpoint/service.d.ts +0 -89
  30. package/dist/checkpoint/set-rollback.d.ts +0 -51
  31. package/dist/checkpoint/set.d.ts +0 -44
  32. package/dist/checkpoint/shadow-store.d.ts +0 -23
  33. package/dist/checkpoint/types.d.ts +0 -164
  34. package/dist/cli/commands/checkpoint.d.ts +0 -7
  35. package/dist/cli/commands/config.d.ts +0 -2
  36. package/dist/cli/commands/hooks.d.ts +0 -8
  37. package/dist/cli/commands/index.d.ts +0 -7
  38. package/dist/cli/commands/init.d.ts +0 -7
  39. package/dist/cli/commands/login.d.ts +0 -8
  40. package/dist/cli/commands/mcp.d.ts +0 -9
  41. package/dist/cli/commands/memory.d.ts +0 -8
  42. package/dist/cli/commands/pr.d.ts +0 -8
  43. package/dist/cli/commands/rollback.d.ts +0 -18
  44. package/dist/cli/commands/run.d.ts +0 -2
  45. package/dist/cli/commands/skills.d.ts +0 -8
  46. package/dist/cli/commands/test.d.ts +0 -9
  47. package/dist/cli/commands/usage.d.ts +0 -9
  48. package/dist/cli/onboard.d.ts +0 -25
  49. package/dist/cli/program.d.ts +0 -2
  50. package/dist/cli/repl.d.ts +0 -27
  51. package/dist/cli/session-factory.d.ts +0 -42
  52. package/dist/cli/stream-print.d.ts +0 -14
  53. package/dist/components/autocomplete.d.ts +0 -32
  54. package/dist/components/frame.d.ts +0 -28
  55. package/dist/components/fuzzy.d.ts +0 -61
  56. package/dist/components/index.d.ts +0 -6
  57. package/dist/components/input.d.ts +0 -78
  58. package/dist/components/keys.d.ts +0 -48
  59. package/dist/components/select.d.ts +0 -28
  60. package/dist/config/credentials.d.ts +0 -18
  61. package/dist/config/index.d.ts +0 -5
  62. package/dist/config/manager.d.ts +0 -39
  63. package/dist/config/owner-only.d.ts +0 -19
  64. package/dist/config/paths.d.ts +0 -9
  65. package/dist/config/project.d.ts +0 -10
  66. package/dist/config/schema.d.ts +0 -2143
  67. package/dist/constants.d.ts +0 -65
  68. package/dist/errors/boundary.d.ts +0 -43
  69. package/dist/errors/constructors.d.ts +0 -411
  70. package/dist/errors/format.d.ts +0 -31
  71. package/dist/errors/index.d.ts +0 -4
  72. package/dist/errors/types.d.ts +0 -270
  73. package/dist/hooks/config.d.ts +0 -21
  74. package/dist/hooks/index.d.ts +0 -7
  75. package/dist/hooks/router.d.ts +0 -58
  76. package/dist/hooks/runner.d.ts +0 -88
  77. package/dist/hooks/service.d.ts +0 -38
  78. package/dist/hooks/slash.d.ts +0 -48
  79. package/dist/hooks/trust.d.ts +0 -46
  80. package/dist/hooks/types.d.ts +0 -147
  81. package/dist/index.d.ts +0 -2
  82. package/dist/indexing/chunker.d.ts +0 -28
  83. package/dist/indexing/embedder.d.ts +0 -98
  84. package/dist/indexing/index.d.ts +0 -9
  85. package/dist/indexing/indexer.d.ts +0 -45
  86. package/dist/indexing/retriever.d.ts +0 -61
  87. package/dist/indexing/service.d.ts +0 -49
  88. package/dist/indexing/store.d.ts +0 -103
  89. package/dist/indexing/types.d.ts +0 -78
  90. package/dist/indexing/util.d.ts +0 -34
  91. package/dist/indexing/walker.d.ts +0 -53
  92. package/dist/jobs/approval-queue.d.ts +0 -85
  93. package/dist/jobs/dispatch-tool.d.ts +0 -34
  94. package/dist/jobs/index.d.ts +0 -6
  95. package/dist/jobs/log-buffer.d.ts +0 -31
  96. package/dist/jobs/log-renderer.d.ts +0 -32
  97. package/dist/jobs/manager.d.ts +0 -139
  98. package/dist/jobs/types.d.ts +0 -81
  99. package/dist/lsp/client.d.ts +0 -25
  100. package/dist/lsp/index.d.ts +0 -8
  101. package/dist/lsp/pool.d.ts +0 -48
  102. package/dist/lsp/registry.d.ts +0 -38
  103. package/dist/lsp/server.d.ts +0 -48
  104. package/dist/lsp/service.d.ts +0 -44
  105. package/dist/lsp/tools/common.d.ts +0 -50
  106. package/dist/lsp/tools/find-definition.d.ts +0 -23
  107. package/dist/lsp/tools/find-references.d.ts +0 -23
  108. package/dist/lsp/tools/get-diagnostics.d.ts +0 -17
  109. package/dist/lsp/tools/hover.d.ts +0 -23
  110. package/dist/lsp/tools/index.d.ts +0 -4
  111. package/dist/lsp/transport.d.ts +0 -39
  112. package/dist/lsp/types.d.ts +0 -107
  113. package/dist/mcp/adapter.d.ts +0 -44
  114. package/dist/mcp/bounds.d.ts +0 -35
  115. package/dist/mcp/client.d.ts +0 -19
  116. package/dist/mcp/demarcate.d.ts +0 -12
  117. package/dist/mcp/http-transport.d.ts +0 -89
  118. package/dist/mcp/index.d.ts +0 -12
  119. package/dist/mcp/service.d.ts +0 -71
  120. package/dist/mcp/sibling-banner.d.ts +0 -25
  121. package/dist/mcp/transport.d.ts +0 -30
  122. package/dist/mcp/trust-gate.d.ts +0 -59
  123. package/dist/mcp/trust.d.ts +0 -62
  124. package/dist/mcp/types.d.ts +0 -62
  125. package/dist/mcp/url-guard.d.ts +0 -48
  126. package/dist/memory/index.d.ts +0 -7
  127. package/dist/memory/recall.d.ts +0 -56
  128. package/dist/memory/remember-tool.d.ts +0 -28
  129. package/dist/memory/secrets.d.ts +0 -29
  130. package/dist/memory/service.d.ts +0 -92
  131. package/dist/memory/store.d.ts +0 -32
  132. package/dist/memory/trust.d.ts +0 -52
  133. package/dist/memory/types.d.ts +0 -101
  134. package/dist/net/ip-guard.d.ts +0 -55
  135. package/dist/onboarding/detect.d.ts +0 -26
  136. package/dist/onboarding/flow.d.ts +0 -28
  137. package/dist/onboarding/index.d.ts +0 -5
  138. package/dist/onboarding/io.d.ts +0 -9
  139. package/dist/onboarding/steps.d.ts +0 -17
  140. package/dist/onboarding/types.d.ts +0 -81
  141. package/dist/plan/approve.d.ts +0 -16
  142. package/dist/plan/execute.d.ts +0 -28
  143. package/dist/plan/index.d.ts +0 -7
  144. package/dist/plan/policy.d.ts +0 -26
  145. package/dist/plan/render.d.ts +0 -5
  146. package/dist/plan/service.d.ts +0 -58
  147. package/dist/plan/submit-plan.d.ts +0 -33
  148. package/dist/plan/types.d.ts +0 -60
  149. package/dist/render/capabilities.d.ts +0 -31
  150. package/dist/render/diff.d.ts +0 -18
  151. package/dist/render/highlight.d.ts +0 -47
  152. package/dist/render/index.d.ts +0 -22
  153. package/dist/render/layout.d.ts +0 -59
  154. package/dist/render/motion.d.ts +0 -76
  155. package/dist/render/plain-renderer.d.ts +0 -39
  156. package/dist/render/resize.d.ts +0 -36
  157. package/dist/render/screen-reader-renderer.d.ts +0 -45
  158. package/dist/render/state.d.ts +0 -49
  159. package/dist/render/tty-renderer.d.ts +0 -106
  160. package/dist/render/types.d.ts +0 -201
  161. package/dist/routing/index.d.ts +0 -2
  162. package/dist/routing/resolve.d.ts +0 -17
  163. package/dist/routing/router.d.ts +0 -47
  164. package/dist/routing/types.d.ts +0 -42
  165. package/dist/sandbox/detect.d.ts +0 -22
  166. package/dist/sandbox/docker-runtime.d.ts +0 -32
  167. package/dist/sandbox/index.d.ts +0 -7
  168. package/dist/sandbox/policy.d.ts +0 -26
  169. package/dist/sandbox/service.d.ts +0 -64
  170. package/dist/sandbox/types.d.ts +0 -123
  171. package/dist/skills/index.d.ts +0 -4
  172. package/dist/skills/loader.d.ts +0 -43
  173. package/dist/skills/parser.d.ts +0 -31
  174. package/dist/skills/service.d.ts +0 -41
  175. package/dist/skills/types.d.ts +0 -94
  176. package/dist/subagent/index.d.ts +0 -5
  177. package/dist/subagent/orchestrator.d.ts +0 -169
  178. package/dist/subagent/registry-scope.d.ts +0 -41
  179. package/dist/subagent/semaphore.d.ts +0 -56
  180. package/dist/subagent/spawn-tool.d.ts +0 -86
  181. package/dist/subagent/types.d.ts +0 -65
  182. package/dist/testing/detect.d.ts +0 -3
  183. package/dist/testing/index.d.ts +0 -5
  184. package/dist/testing/parse.d.ts +0 -33
  185. package/dist/testing/run-tests-tool.d.ts +0 -64
  186. package/dist/testing/runner.d.ts +0 -26
  187. package/dist/testing/sandbox-runner.d.ts +0 -16
  188. package/dist/testing/types.d.ts +0 -66
  189. package/dist/theme/index.d.ts +0 -2
  190. package/dist/theme/resolve.d.ts +0 -43
  191. package/dist/theme/tokens.d.ts +0 -119
  192. package/dist/tools/create-pull-request.d.ts +0 -27
  193. package/dist/tools/file/apply-patch.d.ts +0 -94
  194. package/dist/tools/file/edit-file.d.ts +0 -12
  195. package/dist/tools/file/glob.d.ts +0 -17
  196. package/dist/tools/file/grep-files.d.ts +0 -43
  197. package/dist/tools/file/index.d.ts +0 -7
  198. package/dist/tools/file/match.d.ts +0 -43
  199. package/dist/tools/file/paths.d.ts +0 -160
  200. package/dist/tools/file/read-file.d.ts +0 -8
  201. package/dist/tools/file/write-file.d.ts +0 -10
  202. package/dist/tools/git-status.d.ts +0 -15
  203. package/dist/tools/index.d.ts +0 -9
  204. package/dist/tools/list-files.d.ts +0 -13
  205. package/dist/tools/list-skills.d.ts +0 -9
  206. package/dist/tools/load-skill.d.ts +0 -21
  207. package/dist/tools/registry.d.ts +0 -23
  208. package/dist/tools/search-codebase.d.ts +0 -35
  209. package/dist/tools/shell/exec.d.ts +0 -53
  210. package/dist/tools/shell/index.d.ts +0 -1
  211. package/dist/tools/shell/resolve-shell.d.ts +0 -81
  212. package/dist/tools/shell/run-command.d.ts +0 -14
  213. package/dist/tools/types.d.ts +0 -257
  214. package/dist/usage/collect.d.ts +0 -40
  215. package/dist/usage/cost.d.ts +0 -19
  216. package/dist/usage/index.d.ts +0 -15
  217. package/dist/usage/store.d.ts +0 -37
  218. package/dist/usage/summary.d.ts +0 -32
  219. package/dist/usage/types.d.ts +0 -220
  220. package/dist/utils/child-tree.d.ts +0 -33
  221. package/dist/utils/git.d.ts +0 -17
  222. package/dist/utils/logger.d.ts +0 -18
  223. package/dist/utils/process-tree.d.ts +0 -16
  224. package/dist/vcs/auth.d.ts +0 -22
  225. package/dist/vcs/generate.d.ts +0 -74
  226. package/dist/vcs/git.d.ts +0 -60
  227. package/dist/vcs/github.d.ts +0 -50
  228. package/dist/vcs/guidance.d.ts +0 -20
  229. package/dist/vcs/index.d.ts +0 -7
  230. package/dist/vcs/service.d.ts +0 -61
  231. package/dist/vcs/types.d.ts +0 -73
  232. package/dist/web/demarcate.d.ts +0 -13
  233. package/dist/web/fetch.d.ts +0 -11
  234. package/dist/web/index.d.ts +0 -7
  235. package/dist/web/provider.d.ts +0 -29
  236. package/dist/web/search.d.ts +0 -17
  237. package/dist/web/ssrf.d.ts +0 -41
  238. package/dist/web/tools.d.ts +0 -20
  239. package/dist/web/types.d.ts +0 -64
  240. package/dist/workspace/add-root.d.ts +0 -27
  241. package/dist/workspace/index.d.ts +0 -6
  242. package/dist/workspace/resolve.d.ts +0 -54
  243. package/dist/workspace/select.d.ts +0 -41
  244. package/dist/workspace/types.d.ts +0 -30
  245. package/dist/workspace/workspace.d.ts +0 -61
@@ -1,5 +1,6 @@
1
1
  import { CruxyError, providerUnsupported } from "../errors/index.js";
2
2
  import { resolveTaskModel, } from "../routing/index.js";
3
+ import { accumulateCacheTokens } from "../usage/collect.js";
3
4
  import { buildSystemPrompt } from "./prompts.js";
4
5
  import { resolveShell } from "../tools/shell/resolve-shell.js";
5
6
  /** Tools whose successful call is a file change (drives the `on-file-change`
@@ -155,12 +156,14 @@ async function driveLoop(args, renderer, routed) {
155
156
  case "usage":
156
157
  usage.input_tokens = ev.usage.input_tokens || usage.input_tokens;
157
158
  usage.output_tokens += ev.usage.output_tokens;
159
+ accumulateCacheTokens(usage, ev.usage);
158
160
  // Mirror the accumulation into the per-request figure the telemetry
159
161
  // callback reports (same last-non-zero-in / summed-out semantics).
160
162
  sawUsage = true;
161
163
  reqUsage.input_tokens =
162
164
  ev.usage.input_tokens || reqUsage.input_tokens;
163
165
  reqUsage.output_tokens += ev.usage.output_tokens;
166
+ accumulateCacheTokens(reqUsage, ev.usage);
164
167
  break;
165
168
  case "message_stop":
166
169
  // Turn complete; the stream ends after this.
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { loadProjectInstructions } from "../config/index.js";
3
3
  import { resolveTaskModel } from "../routing/index.js";
4
- import { UsageCollector, } from "../usage/index.js";
4
+ import { UsageCollector, accumulateCacheTokens, } from "../usage/index.js";
5
5
  import { Budget } from "./budget.js";
6
6
  import { runAgent, } from "./loop.js";
7
7
  import { SUMMARY_SYSTEM, COMPACTION_MARKER } from "./prompts.js";
@@ -345,6 +345,7 @@ export class Session {
345
345
  case "usage":
346
346
  usage.input_tokens = ev.usage.input_tokens || usage.input_tokens;
347
347
  usage.output_tokens += ev.usage.output_tokens;
348
+ accumulateCacheTokens(usage, ev.usage);
348
349
  sawUsage = true;
349
350
  break;
350
351
  case "error":
@@ -20,6 +20,7 @@ export const UNICODE_GLYPHS = {
20
20
  arrow: "→",
21
21
  caretUp: "↑",
22
22
  caretDown: "↓",
23
+ cached: "↻",
23
24
  cursorBar: "▏",
24
25
  bullet: "•",
25
26
  sep: "·",
@@ -42,6 +43,7 @@ export const ASCII_GLYPHS = {
42
43
  arrow: "->",
43
44
  caretUp: "^",
44
45
  caretDown: "v",
46
+ cached: "",
45
47
  cursorBar: "|",
46
48
  bullet: "*",
47
49
  sep: "-",
@@ -68,6 +70,7 @@ export const SCREEN_READER_GLYPHS = {
68
70
  arrow: "->",
69
71
  caretUp: "up",
70
72
  caretDown: "down",
73
+ cached: "",
71
74
  cursorBar: "",
72
75
  bullet: "-",
73
76
  sep: "-",
@@ -1,3 +1,32 @@
1
+ /**
2
+ * Usage collection (C.22). Accumulates per-request usage exactly as the agent
3
+ * loop reports it — one {@link UsageEntry} per completed model request — and
4
+ * emits a {@link UsageRecord} for the run.
5
+ *
6
+ * The one honesty invariant: `usage: undefined` (the loop's signal that the
7
+ * provider returned NO usage event for a request) is recorded as `undefined`
8
+ * token counts — the honest "unknown". A provider-reported `0` arrives as a real
9
+ * `Usage` and is stored as `0`. Nothing is estimated, re-tokenized, or
10
+ * zero-filled, and this module makes ZERO network calls.
11
+ */
12
+ /**
13
+ * Fold a streamed {@link Usage} event's cache counters into an accumulator.
14
+ *
15
+ * Phase-A prompt caching (Anthropic dev path only) reports `cache_read_*` /
16
+ * `cache_creation_*` ONCE per request but echoes the same value on both the
17
+ * `message_start` and terminal `message_delta` usage events — so we ASSIGN
18
+ * (never `+=`) to avoid double-counting within a request. `undefined` is left
19
+ * untouched: a provider that doesn't cache (the cruxy gateway, OpenAI-compat)
20
+ * never sets these, so the accumulator's fields stay `undefined` — the honest
21
+ * "unknown", never a fabricated `0`. A real reported `0` (cache in play, no read
22
+ * this request) is captured as `0`.
23
+ */
24
+ export function accumulateCacheTokens(acc, ev) {
25
+ if (ev.cache_read_input_tokens !== undefined)
26
+ acc.cache_read_input_tokens = ev.cache_read_input_tokens;
27
+ if (ev.cache_creation_input_tokens !== undefined)
28
+ acc.cache_creation_input_tokens = ev.cache_creation_input_tokens;
29
+ }
1
30
  const systemClock = () => new Date().toISOString();
2
31
  export class UsageCollector {
3
32
  now;
@@ -11,10 +40,20 @@ export class UsageCollector {
11
40
  * nothing. A real reported `0` is preserved as `0`.
12
41
  */
13
42
  record(req) {
43
+ const u = req.usage;
14
44
  this.entries.push({
15
45
  tier: req.tier,
16
- inputTokens: req.usage?.input_tokens,
17
- outputTokens: req.usage?.output_tokens,
46
+ inputTokens: u?.input_tokens,
47
+ outputTokens: u?.output_tokens,
48
+ // Cache counters are conditionally spread so an unreported field stays
49
+ // absent from the persisted entry (undefined ≠ 0) — only the Anthropic
50
+ // dev path ever populates them.
51
+ ...(u?.cache_read_input_tokens !== undefined
52
+ ? { cacheReadTokens: u.cache_read_input_tokens }
53
+ : {}),
54
+ ...(u?.cache_creation_input_tokens !== undefined
55
+ ? { cacheCreationTokens: u.cache_creation_input_tokens }
56
+ : {}),
18
57
  at: this.now(),
19
58
  });
20
59
  }
@@ -9,7 +9,7 @@
9
9
  * ships nothing that sends. Asserted by the runtime + static no-phone-home tests.
10
10
  */
11
11
  export * from "./types.js";
12
- export { UsageCollector } from "./collect.js";
12
+ export { UsageCollector, accumulateCacheTokens, } from "./collect.js";
13
13
  export { costFor, priceForTier } from "./cost.js";
14
14
  export { loadUsage, appendRun, usageStorePath } from "./store.js";
15
15
  export { summarizeRuns, renderSummary, formatCost, } from "./summary.js";
@@ -5,6 +5,8 @@ export function summarizeRuns(runs, opts) {
5
5
  const byTier = new Map();
6
6
  let totalInputTokens;
7
7
  let totalOutputTokens;
8
+ let totalCacheReadTokens;
9
+ let totalCacheCreationTokens;
8
10
  let requests = 0;
9
11
  let requestsWithoutUsage = 0;
10
12
  const addKnown = (acc, v) => (v === undefined ? acc : (acc ?? 0) + v);
@@ -16,6 +18,8 @@ export function summarizeRuns(runs, opts) {
16
18
  requestsWithoutUsage++;
17
19
  totalInputTokens = addKnown(totalInputTokens, e.inputTokens);
18
20
  totalOutputTokens = addKnown(totalOutputTokens, e.outputTokens);
21
+ totalCacheReadTokens = addKnown(totalCacheReadTokens, e.cacheReadTokens);
22
+ totalCacheCreationTokens = addKnown(totalCacheCreationTokens, e.cacheCreationTokens);
19
23
  // Per-tier attribution only for entries that carry a tier. Untiered
20
24
  // requests (routing inert) still count toward totals — the total stays
21
25
  // honest — but there is no tier label to bucket them under.
@@ -29,6 +33,8 @@ export function summarizeRuns(runs, opts) {
29
33
  b.requestsWithoutUsage++;
30
34
  b.inputTokens = addKnown(b.inputTokens, e.inputTokens);
31
35
  b.outputTokens = addKnown(b.outputTokens, e.outputTokens);
36
+ b.cacheReadTokens = addKnown(b.cacheReadTokens, e.cacheReadTokens);
37
+ b.cacheCreationTokens = addKnown(b.cacheCreationTokens, e.cacheCreationTokens);
32
38
  byTier.set(e.tier, b);
33
39
  }
34
40
  }
@@ -37,6 +43,8 @@ export function summarizeRuns(runs, opts) {
37
43
  tier,
38
44
  inputTokens: b.inputTokens,
39
45
  outputTokens: b.outputTokens,
46
+ cacheReadTokens: b.cacheReadTokens,
47
+ cacheCreationTokens: b.cacheCreationTokens,
40
48
  requests: b.requests,
41
49
  requestsWithoutUsage: b.requestsWithoutUsage,
42
50
  cost: costFor(tier, b.inputTokens, b.outputTokens, opts.prices),
@@ -50,6 +58,8 @@ export function summarizeRuns(runs, opts) {
50
58
  perTier,
51
59
  totalInputTokens,
52
60
  totalOutputTokens,
61
+ totalCacheReadTokens,
62
+ totalCacheCreationTokens,
53
63
  totalCost,
54
64
  priced,
55
65
  currency: opts.currency,
@@ -106,6 +116,15 @@ export function renderSummary(summary, t) {
106
116
  parts.push(totalKnown
107
117
  ? `${t.strong("total")} ${tokenText(summary.totalInputTokens, summary.totalOutputTokens, t)}${t.muted(totalCost)}`
108
118
  : `${t.strong("total")} —`);
119
+ // Cache reads (phase-A caching, Anthropic dev path): shown ONLY when the
120
+ // provider actually reported cache usage — `undefined` on the cruxy gateway
121
+ // and every OpenAI-compat path, so normal users never see this segment. A
122
+ // reported 0 IS shown (`cached ↻0`): honest proof the counter is flowing,
123
+ // e.g. the first request of a run that writes the cache but reads nothing.
124
+ if (summary.totalCacheReadTokens !== undefined) {
125
+ const glyph = t.glyph.cached;
126
+ parts.push(t.muted(`cached ${glyph}${formatTokens(summary.totalCacheReadTokens)}`));
127
+ }
109
128
  // The honesty guard on display: a visible note whenever any request went
110
129
  // unreported, so the total above is never read as the whole story.
111
130
  if (summary.requestsWithoutUsage > 0) {
@@ -22,6 +22,15 @@ export const UsageEntrySchema = z
22
22
  inputTokens: z.number().int().nonnegative().optional(),
23
23
  /** Provider-reported completion tokens; `undefined` ⇔ no usage was reported. */
24
24
  outputTokens: z.number().int().nonnegative().optional(),
25
+ /**
26
+ * Prompt-cache tokens READ from a warm cache (phase-A caching, Anthropic dev
27
+ * path). `undefined` ⇔ the provider doesn't cache / reported nothing (the
28
+ * cruxy gateway today) — never zero-filled. Optional, so older on-disk
29
+ * entries without the field still parse.
30
+ */
31
+ cacheReadTokens: z.number().int().nonnegative().optional(),
32
+ /** Prompt-cache tokens WRITTEN this request (the ~1.25× write premium); same `undefined`-≠-0 rule. */
33
+ cacheCreationTokens: z.number().int().nonnegative().optional(),
25
34
  /** ISO-8601 timestamp the request completed. */
26
35
  at: z.string(),
27
36
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cruxy/cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "an agentic coding CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,52 +0,0 @@
1
- import type { Usage } from "@cruxy/sdk";
2
- import type { LoopBudget } from "./loop.js";
3
- /**
4
- * The live budget primitive for the agent loop: iteration + token + optional
5
- * wall-clock caps, checked by {@link runAgent} before every model turn (see
6
- * `LoopBudget`). A tripped cap stops the run with a human-readable reason and
7
- * the coherent partial history — the loop never runs unbounded.
8
- *
9
- * It lives beside the loop it guards: the loop defines the `LoopBudget` seam,
10
- * this is its one concrete implementation, and the subagent orchestrator, the
11
- * job manager, and {@link Session} (per-turn token guard) all consume it.
12
- */
13
- /**
14
- * Hard caps a run executes under. `maxTokens` is always finite; `maxIterations`
15
- * is finite for a subagent (bounded by construction) but may be
16
- * `Number.POSITIVE_INFINITY` for a token-only guard where iteration count is
17
- * bounded elsewhere (a main turn is already capped by `agent.maxIterations` in
18
- * the loop). `timeoutMs` is an optional wall-clock backstop on top.
19
- */
20
- export interface BudgetLimits {
21
- /** Cap on the run's model turns (may be `Infinity` for a token-only guard). */
22
- maxIterations: number;
23
- /** Cap on the run's combined input+output tokens. */
24
- maxTokens: number;
25
- /** Optional wall-clock cap in milliseconds. */
26
- timeoutMs?: number;
27
- }
28
- /**
29
- * Resolve the effective limits for one spawn: start from the configured
30
- * ceilings and let overrides only *narrow* them. A request above a ceiling is
31
- * clamped down, not honored — "budget overrides within limits" by construction.
32
- */
33
- export declare function resolveBudget(defaults: BudgetLimits, overrides?: Partial<BudgetLimits>): BudgetLimits;
34
- /**
35
- * A live budget for one run. The wall clock starts at construction (spawn /
36
- * turn start); the clock source is injectable so tests never sleep.
37
- */
38
- export declare class Budget implements LoopBudget {
39
- private readonly limits;
40
- private readonly now;
41
- private readonly startedAt;
42
- constructor(limits: BudgetLimits, now?: () => number);
43
- /**
44
- * The reason to stop before the next model turn, or `null` to continue.
45
- * Checked at iteration boundaries — the in-flight turn always completes, so
46
- * overshoot is bounded by one turn.
47
- */
48
- exceeded(state: {
49
- iterations: number;
50
- usage: Usage;
51
- }): string | null;
52
- }
@@ -1,3 +0,0 @@
1
- export * from "./loop.js";
2
- export * from "./session.js";
3
- export * from "./prompts.js";
@@ -1,159 +0,0 @@
1
- import type { Message, Provider, Usage } from "@cruxy/sdk";
2
- import type { CruxyConfig } from "../config/index.js";
3
- import type { HookEvent } from "../hooks/index.js";
4
- import type { StreamRenderer } from "../render/index.js";
5
- import { type Router, type TaskClass } from "../routing/index.js";
6
- import type { ToolContext } from "../tools/index.js";
7
- import { ToolRegistry } from "../tools/index.js";
8
- /** Optional per-fire context (C.26 step 5). For tool-scoped events the loop
9
- * passes the raw tool-call `input` so a multi-root {@link HookRouter} can resolve
10
- * the ONE acting root and fire only that root's hooks. Session lifecycle events
11
- * (`before-run`/`after-run`) carry no hint — they have no acting root and fan
12
- * every trusted root. Ignored by the single-root runner. */
13
- export interface HookFireHint {
14
- input?: unknown;
15
- }
16
- /** The lifecycle-hook firing seam (C.19). Structural so the loop stays
17
- * decoupled from the concrete `HookRunner`. `fire` resolves when hooks pass (or
18
- * advisory ones fail) and throws `CRUXY_E_HOOK_FAILED` on a blocking failure. */
19
- export interface LifecycleHookRunner {
20
- fire(event: HookEvent, ctx: ToolContext, hint?: HookFireHint): Promise<void>;
21
- }
22
- export interface RunAgentArgs {
23
- /**
24
- * The full running conversation. The caller owns history and must append the
25
- * user turn before calling; `runAgent` does not fabricate the initial array.
26
- */
27
- messages: Message[];
28
- /** A constructed provider to stream from. */
29
- provider: Provider;
30
- /** The tool catalogue advertised to the model and dispatched against. */
31
- registry: ToolRegistry;
32
- /** Resolved CLI configuration (turn ceiling, etc.). */
33
- config: CruxyConfig;
34
- /** Ambient capabilities handed to each tool. */
35
- ctx: ToolContext;
36
- /**
37
- * The render seam (U.2): assistant text streams through `renderer.write`
38
- * delta by delta (each non-empty segment closed via `endSegment`), and
39
- * tool-call progress is surfaced as transient `status` + committed `note`
40
- * lines. When omitted, behavior is unchanged (one buffered print per turn,
41
- * no tool-call chrome). The loop never touches stdout directly.
42
- */
43
- renderer?: StreamRenderer;
44
- /** Git context (branch + dirty) for the system prompt's Environment section. */
45
- git?: {
46
- branch: string;
47
- dirty: boolean;
48
- } | null;
49
- /** Project instructions (e.g. from CRUXY.md) folded into the system prompt. */
50
- projectInstructions?: string | null;
51
- /** Persistent memory (C.29): the pre-rendered recall block for the system
52
- * prompt, or null. Reference data only — never influences the U.3 gate. */
53
- recalledMemory?: string | null;
54
- /** Plan mode's propose phase (C.31): inject the plan-first system directive. */
55
- planMode?: boolean;
56
- /** Subagent runs (C.14): inject the bounded-subtask system directive. */
57
- subagent?: boolean;
58
- /**
59
- * Optional hard budget (C.14): checked before every model turn; a non-null
60
- * reason stops the loop with `stop: "budget"` and the partial history. The
61
- * in-flight turn (model call + its tool executions) always completes, so
62
- * histories stay coherent — overshoot is bounded by one turn.
63
- */
64
- budget?: LoopBudget;
65
- /**
66
- * Lifecycle hooks (C.19). When set, `before-tool` fires before each tool call
67
- * (a blocking failure fails the call closed — the tool does NOT run), and
68
- * `after-tool` / `on-file-change` fire after. Omitted for subagents and the
69
- * no-hooks path, so their tools never fire hooks.
70
- */
71
- hooks?: LifecycleHookRunner;
72
- /**
73
- * Multi-model routing (C.30): when set, the tier for `taskClass` is resolved
74
- * ONCE up front (fail loud before any model turn) and its wire model overrides
75
- * the provider default for every turn in this run; the tier is surfaced on the
76
- * live line. Omitted → no override, the provider's default model is used
77
- * (unchanged behavior).
78
- */
79
- router?: Router;
80
- /** The declared task class for routing; defaults to `main-turn`. Ignored
81
- * unless `router` is set. */
82
- taskClass?: TaskClass;
83
- /**
84
- * Usage telemetry (C.22): fired ONCE per completed model request with the
85
- * routing tier (C.30) and the provider's usage for THAT request — or
86
- * `usage: undefined` when the provider emitted no usage event, so the caller
87
- * records it as unknown (never a fabricated zero). LOCAL accounting only:
88
- * this is a callback into the process, nothing is transmitted. Omitted → no
89
- * collection, behavior unchanged.
90
- */
91
- onRequestUsage?: (req: {
92
- tier?: string;
93
- usage?: Usage;
94
- }) => void;
95
- /**
96
- * Cooperative cancellation (C.33). When the signal aborts, the loop stops at
97
- * the NEXT turn boundary and returns `stop: "aborted"` with the coherent
98
- * partial history — the in-flight turn (model call + its tool executions)
99
- * always completes first, exactly like a tripped {@link budget}, so overshoot
100
- * is bounded by one turn. Used by the parallel orchestrator to cancel sibling
101
- * subagents on a fatal failure or Ctrl-C; omitted → no cancellation (unchanged).
102
- * The same signal reaches tools via `ctx.signal`, so an in-flight shell child
103
- * is kill-tree'd rather than orphaned.
104
- */
105
- signal?: AbortSignal;
106
- /**
107
- * Mid-loop compaction seam (build item 3). Called at the top of every
108
- * iteration with the running history; returns the history to continue from —
109
- * unchanged when under threshold, or with its older prefix summarized away
110
- * when over. Supplied by {@link Session}, which reuses its own
111
- * threshold/cut/summarize machinery, so the loop gains no summarization
112
- * knowledge and no logic is duplicated. Omitted → history is never compacted
113
- * mid-loop (unchanged behavior; subagents pass nothing).
114
- *
115
- * This is what keeps a long autonomous turn — where `send` compacts only once
116
- * up front and never again — within the context window: without it a run that
117
- * drives dozens of tool calls (each result appended, some tens of KB) grows
118
- * unbounded until the next `send`, which in one-shot never comes.
119
- */
120
- compact?: (messages: Message[]) => Promise<Message[]>;
121
- }
122
- /**
123
- * The budget seam for {@link runAgent}: implementations track their own caps
124
- * (iterations, tokens, wall clock — see `subagent/budget.ts`); the loop only
125
- * asks "may I start another turn?". Kept a one-method interface so future
126
- * callers (C.22 cost tracking) can slot in without touching the loop again.
127
- */
128
- export interface LoopBudget {
129
- /**
130
- * Return a human-readable reason to stop *before* the next model turn, or
131
- * `null` to continue. `iterations` = model turns completed so far.
132
- */
133
- exceeded(state: {
134
- iterations: number;
135
- usage: Usage;
136
- }): string | null;
137
- }
138
- export interface AgentResult {
139
- /** The full conversation, including assistant tool calls and tool results. */
140
- messages: Message[];
141
- /** Number of model turns consumed. */
142
- iterations: number;
143
- /** Why the loop ended. */
144
- stop: "completed" | "max_iterations" | "budget" | "aborted";
145
- /** Which cap tripped, when `stop === "budget"` (from {@link LoopBudget}). */
146
- stopReason?: string;
147
- /** Accumulated token usage (stashed for cost tracking in C.22). */
148
- usage: Usage;
149
- }
150
- /**
151
- * Drive the model/tool loop over an existing conversation. Streams each turn,
152
- * renders assistant text, reassembles tool calls, executes them, feeds the
153
- * results back, and repeats until the model stops calling tools or the turn
154
- * ceiling is hit.
155
- *
156
- * Requires a tool-capable provider — it throws up front otherwise rather than
157
- * silently running tool-less.
158
- */
159
- export declare function runAgent(args: RunAgentArgs): Promise<AgentResult>;
@@ -1,69 +0,0 @@
1
- /**
2
- * cruxy-code agent prompts.
3
- *
4
- * These are ORIGINAL prompts written for cruxy — not copied from any other
5
- * tool. The system prompt is assembled at runtime from a static core plus a
6
- * dynamic environment block, so the model always knows where it is, what it
7
- * can do, and how it's expected to behave.
8
- */
9
- import type { ShellDialect } from "../tools/shell/resolve-shell.js";
10
- export interface ToolSummary {
11
- name: string;
12
- }
13
- export interface PromptContext {
14
- /** Absolute working directory the agent is rooted in. */
15
- cwd: string;
16
- /** node `process.platform`, e.g. "linux", "darwin", "win32". */
17
- platform: string;
18
- /** Provider/model string, for the model's self-awareness. */
19
- model: string;
20
- /** Current ISO date (so the model isn't guessing). */
21
- date: string;
22
- /** Tools available this session (from the tool registry). */
23
- tools: ToolSummary[];
24
- /** Optional git context, when the cwd is a repo. */
25
- git?: {
26
- branch: string;
27
- dirty: boolean;
28
- } | null;
29
- /** Optional extra instructions (e.g. from a project CRUXY.md). */
30
- projectInstructions?: string | null;
31
- /**
32
- * Persistent memory (C.29): the pre-rendered, demarcated recall block injected
33
- * at session start. It is REFERENCE DATA, not instructions — the block carries
34
- * its own data-only framing (see memory/recall.ts) and is appended as an
35
- * ordinary section; the U.3 gate never reads it. Null when nothing is recalled.
36
- */
37
- recalledMemory?: string | null;
38
- /** Plan mode's propose phase (C.31): inject the plan-first directive. */
39
- planMode?: boolean;
40
- /** Subagent run (C.14): inject the bounded-subtask directive. */
41
- subagent?: boolean;
42
- /**
43
- * The resolved shell's command dialect (from resolve-shell.ts). When it is not
44
- * a POSIX shell — e.g. PowerShell, because no Git Bash was found on Windows —
45
- * a directive tells the model to emit that dialect instead of bash. Absent /
46
- * "posix" → no directive (bash-isms are correct there; a nudge would be wrong).
47
- */
48
- shellDialect?: ShellDialect;
49
- }
50
- /** Assemble the full system prompt for a session. */
51
- export declare function buildSystemPrompt(ctx: PromptContext): string;
52
- /**
53
- * Compact reminder injected after tool results when the loop has run long, to
54
- * keep the model anchored to the original objective and the verify step.
55
- */
56
- export declare const PROGRESS_REMINDER = "Reminder: stay focused on the original task. Before declaring done, verify your change actually works (build/tests/lint), then summarize what changed.";
57
- /**
58
- * System prompt for the side conversation that compacts an over-long history
59
- * (see Session.compact). It runs as a standalone, tool-less completion over a
60
- * rendered transcript — the goal is a synopsis dense enough that the main loop
61
- * can continue without the verbatim prefix.
62
- */
63
- export declare const SUMMARY_SYSTEM = "You are compacting a coding assistant's conversation to fit within its context window. Summarize the conversation so far into a compact synopsis that preserves: decisions made and their rationale, concrete file paths and identifiers touched, the current state of the work, and any open or pending tasks. Be specific and terse \u2014 omit pleasantries and restated instructions. Output only the synopsis.";
64
- /**
65
- * Marker embedded in the synthetic messages that replace a compacted prefix, so
66
- * they're recognizable in the history (and fold cleanly into a later
67
- * re-summarization rather than being mistaken for live conversation).
68
- */
69
- export declare const COMPACTION_MARKER = "[conversation compacted]";