@cruxy/cli 1.2.0 → 1.3.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/dist/agent/context.js +178 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/loop.js +41 -2
- package/dist/agent/mode.js +103 -0
- package/dist/agent/prompts.js +1 -1
- package/dist/agent/session.js +185 -72
- package/dist/approval/classify.js +204 -0
- package/dist/approval/policy.js +41 -3
- package/dist/approval/prompt.js +49 -22
- package/dist/checkpoint/gate.js +12 -0
- package/dist/cli/commands/run.js +374 -227
- package/dist/cli/commands/usage.js +45 -45
- package/dist/cli/onboard.js +2 -1
- package/dist/cli/program.js +60 -18
- package/dist/cli/repl.js +67 -249
- package/dist/cli/session-commands.js +755 -0
- package/dist/cli/session-factory.js +198 -76
- package/dist/cli/suggest.js +77 -0
- package/dist/components/fuzzy.js +3 -3
- package/dist/components/input.js +17 -2
- package/dist/components/keys.js +27 -3
- package/dist/components/select.js +3 -3
- package/dist/config/project.js +53 -1
- package/dist/config/schema.js +49 -16
- package/dist/jobs/log-renderer.js +47 -0
- package/dist/onboarding/steps.js +13 -22
- package/dist/plan/approve.js +36 -24
- package/dist/plan/execute.js +9 -7
- package/dist/plan/render.js +10 -23
- package/dist/plan/service.js +4 -1
- package/dist/render/capabilities.js +30 -1
- package/dist/render/context-view.js +106 -0
- package/dist/render/diff.js +198 -12
- package/dist/render/index.js +31 -5
- package/dist/render/plain-renderer.js +38 -2
- package/dist/render/plan-view.js +108 -0
- package/dist/render/resize.js +7 -2
- package/dist/render/status-view.js +66 -0
- package/dist/render/test-view.js +89 -0
- package/dist/render/tty-renderer.js +40 -0
- package/dist/routing/index.js +1 -0
- package/dist/routing/router.js +13 -4
- package/dist/routing/session-model.js +109 -0
- package/dist/routing/types.js +14 -0
- package/dist/session/export.js +88 -0
- package/dist/session/index.js +20 -0
- package/dist/session/list.js +137 -0
- package/dist/session/log.js +137 -0
- package/dist/session/paths.js +73 -0
- package/dist/session/replay.js +169 -0
- package/dist/session/resume.js +128 -0
- package/dist/session/types.js +223 -0
- package/dist/subagent/orchestrator.js +23 -0
- package/dist/testing/run-tests-tool.js +8 -0
- package/dist/tools/registry.js +3 -3
- package/dist/tui/app.js +385 -0
- package/dist/tui/approval-overlay.js +160 -0
- package/dist/tui/context-gauge.js +48 -0
- package/dist/tui/git-status.js +63 -0
- package/dist/tui/index.js +10 -0
- package/dist/tui/layout.js +269 -0
- package/dist/tui/overlay.js +105 -0
- package/dist/tui/palette.js +73 -0
- package/dist/tui/panels.js +235 -0
- package/dist/tui/renderer.js +776 -0
- package/dist/tui/supports.js +20 -0
- package/dist/tui/tool-versions.js +129 -0
- package/dist/usage/collect.js +21 -3
- package/dist/usage/index.js +10 -2
- package/dist/usage/report.js +76 -0
- package/dist/usage/store.js +7 -1
- package/dist/usage/summary.js +106 -17
- package/dist/usage/types.js +73 -4
- package/dist/usage/weighted.js +77 -0
- package/dist/utils/git.js +50 -4
- package/package.json +2 -2
- package/dist/usage/cost.js +0 -29
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weighted tokens (C.22) — the unit the cruxy gateway actually meters a
|
|
3
|
+
* subscriber in.
|
|
4
|
+
*
|
|
5
|
+
* A subscriber is not billed per token and not billed in dollars: the gateway
|
|
6
|
+
* counts `(billable_input + output) × multiplier` per request and checks that
|
|
7
|
+
* against two windows. This module computes THAT number, locally, from figures
|
|
8
|
+
* already on disk — so `cruxy usage` can state consumption in the same unit the
|
|
9
|
+
* meter uses instead of a currency nobody is charged in.
|
|
10
|
+
*
|
|
11
|
+
* WHAT THIS IS NOT: it is not headroom, and it must never be rendered as such.
|
|
12
|
+
* A cap needs the user's plan, and how much of that plan every OTHER surface
|
|
13
|
+
* (web chat, desktop, phone) has already spent — neither of which is on this
|
|
14
|
+
* machine. All this module can honestly say is "this is what the runs in this
|
|
15
|
+
* CLI's own store weigh". Anything phrased as remaining/left/available would be
|
|
16
|
+
* a number this process cannot possibly know.
|
|
17
|
+
*
|
|
18
|
+
* No network: the multipliers are a compiled-in constant, not a fetched one.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Weighted-token multipliers, keyed by tier — mirroring `multipliers` in the
|
|
22
|
+
* gateway's `internal/budget/config.go`, which is the source of truth.
|
|
23
|
+
*
|
|
24
|
+
* MIRRORED, THEREFORE DRIFTABLE. Nothing checks these against the server at
|
|
25
|
+
* runtime (that would be a network call this module is forbidden to make), so a
|
|
26
|
+
* gateway-side reweighting silently makes an old CLI's arithmetic stale. That
|
|
27
|
+
* is the accepted cost of computing offline, and it is why every rendering of
|
|
28
|
+
* this figure is framed as what THIS CLI reckons it used — never as an
|
|
29
|
+
* authoritative balance. If they drift, the server is right and this is wrong.
|
|
30
|
+
*
|
|
31
|
+
* Only the chat tiers are listed. The gateway also weighs its embed/guard/rerank
|
|
32
|
+
* models at 0.1×, but the CLI's chat path cannot produce a request on one, so
|
|
33
|
+
* including them would model traffic that never reaches this store.
|
|
34
|
+
*/
|
|
35
|
+
export const TIER_MULTIPLIERS = {
|
|
36
|
+
mira: 1.0,
|
|
37
|
+
vaani: 3.25,
|
|
38
|
+
kavi: 3.8,
|
|
39
|
+
};
|
|
40
|
+
/** The multiplier for a tier, or `undefined` when the tier isn't a known one. */
|
|
41
|
+
export function multiplierForTier(tier) {
|
|
42
|
+
return TIER_MULTIPLIERS[tier];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Weighted tokens for one request's figures, or `undefined` when the request
|
|
46
|
+
* cannot be weighed honestly.
|
|
47
|
+
*
|
|
48
|
+
* BOTH terms are required, and this is the whole discipline of the function.
|
|
49
|
+
* The meter's formula has two addends; substituting 0 for an unknown one does
|
|
50
|
+
* not produce an approximate answer, it produces a confidently understated one —
|
|
51
|
+
* and understating consumption is the direction that flatters, so it is exactly
|
|
52
|
+
* the error that would go unnoticed. `costFor` could price one known side
|
|
53
|
+
* because the raw token counts stayed visible beside it; here the weighted
|
|
54
|
+
* figure IS the deliverable, so a partial one is worse than none. Callers count
|
|
55
|
+
* the `undefined`s and surface them instead.
|
|
56
|
+
*
|
|
57
|
+
* Three ways a request is unweighable, all of them real and all treated alike:
|
|
58
|
+
* - no tier, or a tier this build doesn't know (a non-cruxy provider — those
|
|
59
|
+
* tokens never touched the weighted pool, so weighing them would be fiction);
|
|
60
|
+
* - no `billable_input_tokens` (a gateway too old to send it). Note that
|
|
61
|
+
* `inputTokens` is NOT a fallback: it includes cache-read tokens, which the
|
|
62
|
+
* meter excludes, so it would overstate every cached request — the precise
|
|
63
|
+
* inverse of the caching win the field exists to show;
|
|
64
|
+
* - no output count.
|
|
65
|
+
*
|
|
66
|
+
* A reported `0` on either side is a real measurement and weighs in as 0.
|
|
67
|
+
*/
|
|
68
|
+
export function weightedFor(tier, billableInputTokens, outputTokens) {
|
|
69
|
+
if (tier === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
const multiplier = multiplierForTier(tier);
|
|
72
|
+
if (multiplier === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (billableInputTokens === undefined || outputTokens === undefined)
|
|
75
|
+
return undefined;
|
|
76
|
+
return (billableInputTokens + outputTokens) * multiplier;
|
|
77
|
+
}
|
package/dist/utils/git.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
1
|
+
import { execFile, spawnSync } from "node:child_process";
|
|
2
2
|
/** Hard ceiling on a git invocation; a hung git must never stall startup. */
|
|
3
3
|
const GIT_TIMEOUT_MS = 5000;
|
|
4
4
|
/**
|
|
@@ -17,6 +17,35 @@ function runGit(args, cwd) {
|
|
|
17
17
|
}
|
|
18
18
|
return res.stdout;
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* {@link runGit} without blocking the event loop. Same contract in every other
|
|
22
|
+
* respect: `null` on any failure, never throws, same timeout.
|
|
23
|
+
*
|
|
24
|
+
* This exists because `spawnSync` is measurably expensive — around 45ms warm for
|
|
25
|
+
* the branch+status pair — and the TUI needs this data while a model response is
|
|
26
|
+
* streaming. Blocking the loop for 45ms mid-stream stutters the paint; blocking
|
|
27
|
+
* it from the paint path itself would do so on every frame.
|
|
28
|
+
*/
|
|
29
|
+
function runGitAsync(args, cwd) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
execFile("git", args, { cwd, encoding: "utf8", timeout: GIT_TIMEOUT_MS, windowsHide: true }, (err, stdout) => resolve(err ? null : stdout));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Number of changed paths in `git status --porcelain` output.
|
|
36
|
+
*
|
|
37
|
+
* One line is one path, INCLUDING a rename (`R old -> new`), which is one
|
|
38
|
+
* change and not two. Blank lines are ignored so a trailing newline — always
|
|
39
|
+
* present on non-empty output — cannot inflate the count by one.
|
|
40
|
+
*/
|
|
41
|
+
export function countChanges(status) {
|
|
42
|
+
return status.split("\n").filter((line) => line.trim() !== "").length;
|
|
43
|
+
}
|
|
44
|
+
/** Shared shaping so the sync and async paths cannot disagree. */
|
|
45
|
+
function toGitInfo(branch, status) {
|
|
46
|
+
const changed = countChanges(status);
|
|
47
|
+
return { branch: branch.trim(), dirty: changed > 0, changed };
|
|
48
|
+
}
|
|
20
49
|
/**
|
|
21
50
|
* Branch name plus the raw `git status --porcelain` text for `cwd`, or `null`
|
|
22
51
|
* when it isn't a git repository (or git is unavailable). Backs the `git_status`
|
|
@@ -32,12 +61,29 @@ export function getGitStatus(cwd) {
|
|
|
32
61
|
return { branch: branch.trim(), status };
|
|
33
62
|
}
|
|
34
63
|
/**
|
|
35
|
-
* Compact git context for the system prompt: current branch
|
|
36
|
-
*
|
|
64
|
+
* Compact git context for the system prompt: current branch, whether the working
|
|
65
|
+
* tree has uncommitted changes, and how many paths changed. `null` when not a
|
|
66
|
+
* repo / git missing.
|
|
67
|
+
*
|
|
68
|
+
* The change count costs no extra subprocess — it is derived from the porcelain
|
|
69
|
+
* output {@link getGitStatus} already fetched.
|
|
37
70
|
*/
|
|
38
71
|
export function getGitInfo(cwd) {
|
|
39
72
|
const info = getGitStatus(cwd);
|
|
40
73
|
if (info === null)
|
|
41
74
|
return null;
|
|
42
|
-
return
|
|
75
|
+
return toGitInfo(info.branch, info.status);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* {@link getGitInfo} without blocking the event loop — what the TUI rail uses.
|
|
79
|
+
* Identical result, identical `null` semantics.
|
|
80
|
+
*/
|
|
81
|
+
export async function getGitInfoAsync(cwd) {
|
|
82
|
+
const branch = await runGitAsync(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
|
|
83
|
+
if (branch === null)
|
|
84
|
+
return null;
|
|
85
|
+
const status = await runGitAsync(["status", "--porcelain"], cwd);
|
|
86
|
+
if (status === null)
|
|
87
|
+
return null;
|
|
88
|
+
return toGitInfo(branch, status);
|
|
43
89
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxy/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "an agentic coding CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"undici": "^6.21.0",
|
|
37
37
|
"zod": "^3.23.8",
|
|
38
38
|
"zod-to-json-schema": "^3.23.5",
|
|
39
|
-
"@cruxy/sdk": "0.4.
|
|
39
|
+
"@cruxy/sdk": "0.4.1"
|
|
40
40
|
},
|
|
41
41
|
"optionalDependencies": {
|
|
42
42
|
"better-sqlite3": "^12.11.1"
|
package/dist/usage/cost.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Token → cost mapping (C.22). The whole discipline lives in one rule: a cost is
|
|
3
|
-
* produced ONLY when the tier has a configured price AND at least one token side
|
|
4
|
-
* is known. Otherwise the result is `undefined` — cost is omitted, tokens are
|
|
5
|
-
* still shown, and NO dollar figure is ever fabricated. Prices are per MILLION
|
|
6
|
-
* tokens (see {@link TierPrice}). Keyed by tier only (U.8 gag). No network.
|
|
7
|
-
*/
|
|
8
|
-
/** The configured price for a tier, or `undefined` when the tier is unpriced. */
|
|
9
|
-
export function priceForTier(tier, prices) {
|
|
10
|
-
return prices[tier];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Compute the cost of a tier's usage, or `undefined` when it cannot be stated
|
|
14
|
-
* honestly:
|
|
15
|
-
* - no configured price for the tier → `undefined` (cost omitted).
|
|
16
|
-
* - both token counts unknown → `undefined` (nothing real to price).
|
|
17
|
-
* A known side is priced; an unknown side contributes nothing (never a
|
|
18
|
-
* fabricated 0-token charge). tokens/1e6 × price, summed.
|
|
19
|
-
*/
|
|
20
|
-
export function costFor(tier, inputTokens, outputTokens, prices) {
|
|
21
|
-
const price = priceForTier(tier, prices);
|
|
22
|
-
if (!price)
|
|
23
|
-
return undefined;
|
|
24
|
-
if (inputTokens === undefined && outputTokens === undefined)
|
|
25
|
-
return undefined;
|
|
26
|
-
const inCost = inputTokens !== undefined ? (inputTokens / 1_000_000) * price.input : 0;
|
|
27
|
-
const outCost = outputTokens !== undefined ? (outputTokens / 1_000_000) * price.output : 0;
|
|
28
|
-
return inCost + outCost;
|
|
29
|
-
}
|