ccdeck 3.29.10 → 3.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/agent-dag.js +67 -118
- package/bin/cli/help.js +67 -0
- package/bin/cli/login-item.js +182 -0
- package/bin/cli/one-shot.js +254 -0
- package/bin/cli/package.js +39 -0
- package/bin/cli/pulse.js +98 -0
- package/bin/cli/restart.js +179 -0
- package/bin/cli/screen.js +166 -0
- package/bin/cli/second-start.js +120 -0
- package/bin/cli/startup.js +459 -0
- package/bin/cli/uninstall.js +169 -0
- package/bin/deck.js +112 -1559
- package/dist/web/assets/BrowserWatchModal-DP3YMtEi.js +1 -0
- package/dist/web/assets/UsageHistoryModal-CcdPVXAw.js +1 -0
- package/dist/web/assets/index-3vS6A0vt.css +1 -0
- package/dist/web/assets/index-BVTWHDta.js +298 -0
- package/dist/web/index.html +2 -2
- package/hook/hook.js +15 -14
- package/package.json +1 -1
- package/release-notes.json +28 -0
- package/src/server/account-projects.mjs +25 -11
- package/src/server/account-routes.mjs +254 -0
- package/src/server/activity.mjs +3 -3
- package/src/server/append-budget.mjs +185 -0
- package/src/server/append-failures.mjs +147 -0
- package/src/server/args.mjs +37 -5
- package/src/server/atomic-write.mjs +300 -0
- package/src/server/block-notify.mjs +0 -5
- package/src/server/boot-lock.mjs +1 -1
- package/src/server/brand.mjs +7 -6
- package/src/server/browser-react.mjs +10 -2
- package/src/server/browser-watch-decks.mjs +146 -0
- package/src/server/browser-watch-feed.mjs +69 -0
- package/src/server/browser-watch-log.mjs +173 -0
- package/src/server/browser-watch-mtime.mjs +18 -0
- package/src/server/browser-watch-relay.mjs +112 -0
- package/src/server/browser-watch-routes.mjs +172 -0
- package/src/server/browser-watch-seen.mjs +127 -0
- package/src/server/browser-watch-store.mjs +14 -143
- package/src/server/browser-watch.mjs +278 -626
- package/src/server/canonical-path.mjs +150 -0
- package/src/server/ccusage-failure.mjs +64 -0
- package/src/server/ccusage-install.mjs +459 -0
- package/src/server/ccusage-runner.mjs +271 -0
- package/src/server/ccusage.mjs +21 -756
- package/src/server/claude-accounts.mjs +220 -464
- package/src/server/claude-collector.mjs +189 -0
- package/src/server/claude-dir.mjs +2 -2
- package/src/server/claude-identity.mjs +5 -4
- package/src/server/claude-reset-credits.mjs +130 -0
- package/src/server/claude-verdicts.mjs +174 -0
- package/src/server/clear-route.mjs +191 -0
- package/src/server/codex-auth.mjs +4 -4
- package/src/server/codex-dir.mjs +52 -8
- package/src/server/codex-enrichment.mjs +310 -0
- package/src/server/codex-quota.mjs +5 -3
- package/src/server/codex-translate.mjs +407 -0
- package/src/server/codex-usage.mjs +8 -5
- package/src/server/codex-watch.mjs +496 -0
- package/src/server/cswap-auto-loop.mjs +342 -0
- package/src/server/cswap-auto-readers.mjs +303 -0
- package/src/server/cswap-auto.mjs +51 -609
- package/src/server/cswap-install.mjs +69 -265
- package/src/server/cswap-layout.mjs +242 -0
- package/src/server/deck-home.mjs +1 -1
- package/src/server/deck-prefs.mjs +51 -184
- package/src/server/deck-scope.mjs +52 -0
- package/src/server/desktop-update-routes.mjs +75 -0
- package/src/server/detach.mjs +78 -0
- package/src/server/discovery.mjs +298 -0
- package/src/server/event-log.mjs +328 -0
- package/src/server/event-pipeline.mjs +235 -0
- package/src/server/event-ring.mjs +214 -0
- package/src/server/event-routes.mjs +352 -0
- package/src/server/event-sink.mjs +26 -0
- package/src/server/exec-children.mjs +128 -0
- package/src/server/exec-not-found.mjs +213 -0
- package/src/server/exec-output.mjs +23 -0
- package/src/server/exec-spec.mjs +341 -0
- package/src/server/exec.mjs +74 -661
- package/src/server/health-route.mjs +42 -0
- package/src/server/hook-challenge.mjs +37 -0
- package/src/server/http-io.mjs +104 -0
- package/src/server/index.mjs +172 -7620
- package/src/server/install-layout.mjs +506 -0
- package/src/server/installer.mjs +79 -608
- package/src/server/jsonl-chunks.mjs +139 -0
- package/src/server/lan-accounts.mjs +27 -0
- package/src/server/lan-beacon.mjs +457 -0
- package/src/server/lan-call.mjs +239 -0
- package/src/server/lan-copies.mjs +270 -0
- package/src/server/lan-deck.mjs +583 -0
- package/src/server/lan-dials.mjs +161 -0
- package/src/server/lan-engine.mjs +475 -1024
- package/src/server/lan-hearing.mjs +49 -0
- package/src/server/lan-inbound.mjs +100 -0
- package/src/server/lan-invite-offer.mjs +77 -0
- package/src/server/lan-invite.mjs +207 -0
- package/src/server/lan-lines.mjs +73 -0
- package/src/server/lan-manifest.mjs +74 -0
- package/src/server/lan-reach.mjs +82 -69
- package/src/server/lan-requests.mjs +249 -0
- package/src/server/lan-round-record.mjs +45 -0
- package/src/server/lan-round-timer.mjs +62 -0
- package/src/server/lan-routes.mjs +143 -0
- package/src/server/lan-serve.mjs +112 -0
- package/src/server/lan-socket.mjs +243 -925
- package/src/server/lan-sync.mjs +58 -1026
- package/src/server/lan-tailnet-poll.mjs +57 -0
- package/src/server/lan-turns.mjs +80 -0
- package/src/server/lan-wire.mjs +620 -0
- package/src/server/lifecycle.mjs +421 -0
- package/src/server/listen.mjs +110 -0
- package/src/server/live-decks.mjs +138 -0
- package/src/server/load-average.mjs +32 -0
- package/src/server/log-election.mjs +235 -0
- package/src/server/log-replay.mjs +374 -0
- package/src/server/log-writer.mjs +72 -557
- package/src/server/login-service.mjs +3 -2
- package/src/server/macmon.mjs +1 -1
- package/src/server/memory-files.mjs +163 -0
- package/src/server/memory-metrics.mjs +178 -0
- package/src/server/metrics-history.mjs +94 -0
- package/src/server/metrics-run.mjs +100 -0
- package/src/server/music-routes.mjs +134 -0
- package/src/server/network-metrics.mjs +5 -3
- package/src/server/network-sampler.mjs +256 -0
- package/src/server/npm-latest.mjs +385 -0
- package/src/server/npm-upgrade.mjs +352 -0
- package/src/server/npx.mjs +41 -7
- package/src/server/output-watch.mjs +5 -4
- package/src/server/pinned-build.mjs +108 -0
- package/src/server/prefs-path.mjs +14 -0
- package/src/server/prefs-refusal.mjs +77 -0
- package/src/server/prefs-routes.mjs +76 -0
- package/src/server/prefs-set-aside.mjs +86 -0
- package/src/server/prefs-state.mjs +51 -0
- package/src/server/process-command.mjs +104 -0
- package/src/server/process-list.mjs +547 -0
- package/src/server/pulse-line.mjs +132 -0
- package/src/server/purge-key.mjs +7 -7
- package/src/server/quota-cli.mjs +174 -0
- package/src/server/quota-oauth.mjs +190 -0
- package/src/server/quota-shape.mjs +229 -0
- package/src/server/quota-store-resets.mjs +206 -0
- package/src/server/quota.mjs +123 -509
- package/src/server/relay-guard.mjs +3 -3
- package/src/server/request-gates.mjs +441 -0
- package/src/server/restart-note.mjs +180 -0
- package/src/server/retire-sound-hook.mjs +3 -3
- package/src/server/ring-bounds.mjs +215 -0
- package/src/server/running-deck.mjs +1 -1
- package/src/server/running-version.mjs +24 -0
- package/src/server/self-update.mjs +39 -1371
- package/src/server/session-enrichment.mjs +622 -0
- package/src/server/session-read-gate.mjs +49 -0
- package/src/server/session-tracking.mjs +242 -0
- package/src/server/sse-clients.mjs +222 -0
- package/src/server/stale-discovery.mjs +30 -0
- package/src/server/static-serve.mjs +81 -0
- package/src/server/supervisor.mjs +73 -12
- package/src/server/system-metrics.mjs +169 -1935
- package/src/server/term.mjs +11 -249
- package/src/server/thermal-metrics.mjs +520 -0
- package/src/server/thermal-sampler.mjs +204 -0
- package/src/server/token-redact.mjs +123 -0
- package/src/server/transcript-gate.mjs +107 -0
- package/src/server/transcript-scan.mjs +563 -0
- package/src/server/usage-routes.mjs +93 -0
- package/src/server/uv-bootstrap.mjs +1 -1
- package/src/server/wordmark.mjs +127 -0
- package/dist/web/assets/BrowserWatchModal-CMn28FHb.js +0 -1
- package/dist/web/assets/UsageHistoryModal-C-sPhsKT.js +0 -1
- package/dist/web/assets/index-C1YFI9f4.js +0 -298
- package/dist/web/assets/index-Ck2RAla3.css +0 -1
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
// Which ccusage runs, and running it once.
|
|
2
|
+
//
|
|
3
|
+
// Three paths answer, in an order getRunner explains below: a copy the user
|
|
4
|
+
// named or has on PATH, the deck's own managed install (ccusage-install.mjs),
|
|
5
|
+
// and `npx -y ccusage@latest` when neither is there. None of them is handed
|
|
6
|
+
// to a shell. What to ask ccusage and what to make of its answer are
|
|
7
|
+
// ccusage.mjs's; this module only turns an argument vector into a child and
|
|
8
|
+
// its stdout, and retries the one failure a rebuilt install can fix.
|
|
9
|
+
import { createRequire } from "node:module";
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { killTree, pathLookup, spawnSpec } from "./exec.mjs";
|
|
13
|
+
import { note, stamp, tagged } from "./ccusage-failure.mjs";
|
|
14
|
+
import {
|
|
15
|
+
discardDamagedInstall, installsDisabled, lastInstallError, maybeBackgroundUpdate, resolveEntry,
|
|
16
|
+
startInstall, touchMarker, winShim,
|
|
17
|
+
} from "./ccusage-install.mjs";
|
|
18
|
+
|
|
19
|
+
const TIMEOUT_MS = 90_000;
|
|
20
|
+
|
|
21
|
+
// npx is the same kind of shim as npm, and the fallback run needs the same
|
|
22
|
+
// treatment for a second, sharper reason: its argument vector carries a
|
|
23
|
+
// user-supplied `--since`. `shell: true` handed `[file, ...args].join(" ")` to
|
|
24
|
+
// /bin/sh -c, so `GET /api/ccusage?since=1;id;` ran `id` — and because that
|
|
25
|
+
// route is a GET, any page open in the user's browser could aim it at the
|
|
26
|
+
// loopback port with no CORS, no preflight and no need to read the answer.
|
|
27
|
+
// spawnSpec quotes each argument into the cmd.exe line on Windows and spawns
|
|
28
|
+
// the vector untouched everywhere else, so nothing in it is ever parsed as
|
|
29
|
+
// syntax. Naming the file `npx.cmd` on Windows is what makes that work: only a
|
|
30
|
+
// .cmd/.bat file routes through cmd.exe, and a bare `npx` there is not a file
|
|
31
|
+
// spawn can launch at all (PATHEXT is a shell's job), so asking for the
|
|
32
|
+
// extensionless name would trade a shell injection for an ENOENT. What it must
|
|
33
|
+
// NOT be is the bare `npx.cmd` this said until #456 — see winShim.
|
|
34
|
+
function npxSpec(args, platform = process.platform, deps) {
|
|
35
|
+
return spawnSpec(platform === "win32" ? winShim("npx.cmd", deps) : "npx", args, platform);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* What `spawn` gets for the portable `npx -y ccusage@latest <args>` fallback.
|
|
40
|
+
* Exported for tests: the platform is a parameter so the Windows command line
|
|
41
|
+
* can be checked from any OS, and `deps` stands in for the Windows filesystem
|
|
42
|
+
* the shim lookup asks about.
|
|
43
|
+
*/
|
|
44
|
+
export const fallbackSpec = (args = [], platform = process.platform, deps) =>
|
|
45
|
+
npxSpec(["-y", "ccusage@latest", ...args], platform, deps);
|
|
46
|
+
|
|
47
|
+
// ── the user's own copy ─────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A ccusage the USER put somewhere, either by naming it outright or by having
|
|
51
|
+
* it on PATH — or null when there is no such thing.
|
|
52
|
+
*
|
|
53
|
+
* This is #433, and the case for it is not that a PATH search is nice to have.
|
|
54
|
+
* The deck has been telling people "put ccusage on PATH yourself" in
|
|
55
|
+
* admin-failure.ts for as long as that sentence has existed, and nothing
|
|
56
|
+
* anywhere ever looked: a user who did exactly what they were told, and could
|
|
57
|
+
* prove it with `ccusage --version` in their own shell, got the identical
|
|
58
|
+
* failure on the next click. Two ways out of that, and the other one is to
|
|
59
|
+
* delete the promise. It is kept because there is a configuration that needs it
|
|
60
|
+
* and has no other:
|
|
61
|
+
*
|
|
62
|
+
* - `AGENTS_DECK_NO_INSTALL=1` is documented as "never install or update
|
|
63
|
+
* claude-swap / ccusage". Someone who sets it AND installs ccusage
|
|
64
|
+
* themselves has done the only thing that flag can sensibly mean, and until
|
|
65
|
+
* now the deck answered "ccusage is not installed" while it was installed
|
|
66
|
+
* and on their PATH. There was NO combination of settings that made a
|
|
67
|
+
* self-managed ccusage work.
|
|
68
|
+
* - The #432 machine — npm and npx both unusable on disk — cannot create the
|
|
69
|
+
* managed install and cannot run the npx fallback. A copy the user already
|
|
70
|
+
* has is the only route left, and it was the one route the deck refused to
|
|
71
|
+
* look down.
|
|
72
|
+
*
|
|
73
|
+
* The override wins over PATH the way AGENTS_DECK_CSWAP does over cswap's own
|
|
74
|
+
* search, and is never remembered, because somebody debugging a bad resolution
|
|
75
|
+
* needs a change to it to take effect on the next click rather than the next
|
|
76
|
+
* restart. Nothing here is cached for the same reason, and it is affordable:
|
|
77
|
+
* a lookup is a handful of stats, once per uncached fetch, against a process
|
|
78
|
+
* spawn that follows it.
|
|
79
|
+
*
|
|
80
|
+
* `platform` and `deps` are parameters, and this is exported, for the reason
|
|
81
|
+
* every other Windows answer in this module is: the PATHEXT walk and the
|
|
82
|
+
* `.cmd` spelling have to be checkable from a machine that cannot run Windows.
|
|
83
|
+
*/
|
|
84
|
+
export function userCcusage(platform = process.platform, deps, env = process.env) {
|
|
85
|
+
const named = env.AGENTS_DECK_CCUSAGE;
|
|
86
|
+
if (named) {
|
|
87
|
+
// A path the user typed is used as typed — pathLookup would refuse it
|
|
88
|
+
// anyway, since re-rooting a name that carries a directory is a way to run
|
|
89
|
+
// something other than what was asked for. Whether it EXISTS is the
|
|
90
|
+
// caller's question, because "the path you gave me is not there" is a
|
|
91
|
+
// different sentence from "you have no ccusage".
|
|
92
|
+
return { file: String(named), named: true };
|
|
93
|
+
}
|
|
94
|
+
const found = pathLookup("ccusage", platform, deps);
|
|
95
|
+
return found ? { file: found, named: false } : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Does the file an explicit override names actually exist? Split out so the
|
|
99
|
+
* existence check is injectable alongside the lookup above. */
|
|
100
|
+
export const overrideIsThere = (file, { exists = existsSync } = {}) => {
|
|
101
|
+
try {
|
|
102
|
+
return exists(file);
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
// Ensure a runnable ccusage. Returns { kind:"node", entry } for the managed
|
|
109
|
+
// install, { kind:"path", file } for a copy the user provided, or { kind:"npx" }
|
|
110
|
+
// as the portable fallback.
|
|
111
|
+
//
|
|
112
|
+
// The order is the whole design decision, so it is stated rather than implied.
|
|
113
|
+
//
|
|
114
|
+
// An explicit AGENTS_DECK_CCUSAGE is first and is never fallen through: a user
|
|
115
|
+
// who pointed the deck at a file and got silently ignored has been given a
|
|
116
|
+
// setting that does nothing, which is worse than not having one. If it does not
|
|
117
|
+
// resolve, the failure names THAT — "npx is not on this deck's PATH" would be
|
|
118
|
+
// both true and completely beside the point.
|
|
119
|
+
//
|
|
120
|
+
// The managed install then comes BEFORE the PATH copy, which is the opposite of
|
|
121
|
+
// what #433 proposed, and deliberately. Preferring PATH would silently change
|
|
122
|
+
// which ccusage runs on every machine that already has both — a deck that works
|
|
123
|
+
// today would start running a copy it has never run, and an old global ccusage
|
|
124
|
+
// without `daily --json` would turn a working panel into a broken one for no
|
|
125
|
+
// reason the user asked for. The PATH copy is an ESCAPE ROUTE, and it wants to
|
|
126
|
+
// be reached exactly when the managed install is not there; someone who wants
|
|
127
|
+
// their own copy to win over the deck's says so with AGENTS_DECK_CCUSAGE, which
|
|
128
|
+
// is unambiguous in a way a precedence rule never is.
|
|
129
|
+
//
|
|
130
|
+
// PATH comes before INSTALLING, though. Downloading a second copy of a tool
|
|
131
|
+
// that is already on the machine is not something to do to somebody, and it is
|
|
132
|
+
// what makes the order identical with and without AGENTS_DECK_NO_INSTALL=1 —
|
|
133
|
+
// that flag now removes a step rather than changing the sequence.
|
|
134
|
+
async function getRunner() {
|
|
135
|
+
const mine = userCcusage();
|
|
136
|
+
if (mine?.named) {
|
|
137
|
+
if (!overrideIsThere(mine.file)) {
|
|
138
|
+
throw tagged("bad_override",
|
|
139
|
+
`AGENTS_DECK_CCUSAGE points at ${mine.file}, and there is no such file`);
|
|
140
|
+
}
|
|
141
|
+
return { kind: "path", file: mine.file, named: true };
|
|
142
|
+
}
|
|
143
|
+
let resolved = resolveEntry();
|
|
144
|
+
if (resolved) {
|
|
145
|
+
maybeBackgroundUpdate(resolved.version);
|
|
146
|
+
return { kind: "node", entry: resolved.entry };
|
|
147
|
+
}
|
|
148
|
+
if (mine) return { kind: "path", file: mine.file, named: false };
|
|
149
|
+
// Nothing installed, nothing of the user's to run, and installs are
|
|
150
|
+
// forbidden. The npx fallback is not an escape hatch — `npx -y ccusage@latest`
|
|
151
|
+
// downloads and runs the same package — so there is no runner to hand back.
|
|
152
|
+
// Fail with the reason, which the usage-history modal shows, instead of
|
|
153
|
+
// quietly installing.
|
|
154
|
+
if (installsDisabled()) {
|
|
155
|
+
throw tagged("no_install", "ccusage is not installed, and installs are off (AGENTS_DECK_NO_INSTALL=1)");
|
|
156
|
+
}
|
|
157
|
+
// Cold: install once (deduped across concurrent callers).
|
|
158
|
+
await startInstall("latest");
|
|
159
|
+
resolved = resolveEntry();
|
|
160
|
+
if (resolved) { touchMarker(); return { kind: "node", entry: resolved.entry }; }
|
|
161
|
+
// npm unavailable / offline → fall back to npx, carrying WHY the managed
|
|
162
|
+
// install is not here. Without that the modal can only describe the fallback,
|
|
163
|
+
// and the fallback is the second thing that failed.
|
|
164
|
+
return { kind: "npx", installError: lastInstallError() };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── invocation ──────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* What `spawn` gets for a ccusage the user provided.
|
|
171
|
+
*
|
|
172
|
+
* `file` is always an absolute path by the time it reaches here — pathLookup
|
|
173
|
+
* resolves the directory and an override is a path the user typed — and that is
|
|
174
|
+
* load-bearing rather than tidy. On Windows the thing on PATH is `ccusage.cmd`,
|
|
175
|
+
* a batch file, so spawnSpec routes it through cmd.exe; a batch file launched
|
|
176
|
+
* by BARE name computes `%~dp0` from the deck's working directory and goes
|
|
177
|
+
* looking for its payload there, which is #456 exactly. Resolving first and
|
|
178
|
+
* quoting second is the same order the npm and npx shims go through.
|
|
179
|
+
*
|
|
180
|
+
* Exported for tests: the platform is a parameter so the Windows command line
|
|
181
|
+
* can be checked from any OS.
|
|
182
|
+
*/
|
|
183
|
+
export const userSpec = (file, args = [], platform = process.platform) =>
|
|
184
|
+
spawnSpec(file, args, platform);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The platform binary ccusage's own cli.js would run, resolved the way cli.js
|
|
188
|
+
* resolves it (`@ccusage/ccusage-<platform>-<arch>/bin/ccusage[.exe]`, from
|
|
189
|
+
* the package's own directory), or null when there is none.
|
|
190
|
+
*
|
|
191
|
+
* WINDOWS ONLY, and for one reason: cli.js starts that binary WITHOUT hiding
|
|
192
|
+
* its console. The deck runs with no console of its own (detached, or inside
|
|
193
|
+
* the desktop app), so the binary is given a new one — and with Windows
|
|
194
|
+
* Terminal as the default terminal that is a window flashing up on every
|
|
195
|
+
* usage read, measured on a Windows 10 box as a CASCADIA_HOSTING_WINDOW_CLASS
|
|
196
|
+
* hosting ccusage. Running the binary directly, with windowsHide, is what
|
|
197
|
+
* cli.js does minus the window. Elsewhere nothing flashes, and the wrapper
|
|
198
|
+
* stays in the path it has always been in.
|
|
199
|
+
*
|
|
200
|
+
* Exported for tests; `resolve` is injectable.
|
|
201
|
+
*/
|
|
202
|
+
export function nativeCcusage(entry, platform = process.platform, arch = process.arch, resolve = null) {
|
|
203
|
+
if (platform !== "win32" || typeof entry !== "string" || !entry) return null;
|
|
204
|
+
const pkg = arch === "arm64" ? "@ccusage/ccusage-win32-arm64" : arch === "x64" ? "@ccusage/ccusage-win32-x64" : null;
|
|
205
|
+
if (!pkg) return null;
|
|
206
|
+
try {
|
|
207
|
+
const req = resolve ?? createRequire(entry).resolve;
|
|
208
|
+
return req(`${pkg}/bin/ccusage.exe`);
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// One attempt with one runner. No branch gets a shell. The managed install is
|
|
215
|
+
// `node <entry> …`, which never needed one; the user's own copy and the npx
|
|
216
|
+
// fallback are routed through spawnSpec instead — see npxSpec and userSpec, and
|
|
217
|
+
// note that `args` here ends in whatever /api/ccusage was asked for.
|
|
218
|
+
function runOnce(runner, args) {
|
|
219
|
+
const native = runner.kind === "node" ? nativeCcusage(runner.entry) : null;
|
|
220
|
+
const { file, args: full, opts } = native
|
|
221
|
+
? { file: native, args, opts: {} }
|
|
222
|
+
: runner.kind === "node"
|
|
223
|
+
? { file: process.execPath, args: [runner.entry, ...args], opts: {} }
|
|
224
|
+
: runner.kind === "path"
|
|
225
|
+
? userSpec(runner.file, args)
|
|
226
|
+
: fallbackSpec(args);
|
|
227
|
+
return new Promise((resolve, reject) => {
|
|
228
|
+
const child = spawn(file, full, { windowsHide: true, ...opts });
|
|
229
|
+
let out = "", err = "";
|
|
230
|
+
const timer = setTimeout(() => {
|
|
231
|
+
// The npx fallback goes through cmd.exe on Windows, so `child` is the
|
|
232
|
+
// wrapper there and npx is a grandchild that a plain kill would leave
|
|
233
|
+
// downloading.
|
|
234
|
+
killTree(child);
|
|
235
|
+
reject(tagged("timeout", "ccusage timed out"));
|
|
236
|
+
}, TIMEOUT_MS);
|
|
237
|
+
child.stdout.on("data", d => { out += d; });
|
|
238
|
+
child.stderr.on("data", d => { err += d; });
|
|
239
|
+
child.on("error", e => { clearTimeout(timer); reject(e); });
|
|
240
|
+
child.on("close", code => {
|
|
241
|
+
clearTimeout(timer);
|
|
242
|
+
if (code === 0) resolve(out);
|
|
243
|
+
else reject(new Error(err.trim() || `ccusage exited ${code}`));
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Run ccusage with the given args, resolve raw stdout AND the runner that
|
|
249
|
+
// produced it. One retry, and only for the one failure that is otherwise
|
|
250
|
+
// permanent — see discardDamagedInstall. The second getRunner() is what rebuilds
|
|
251
|
+
// the install, or falls through to npx when npm cannot.
|
|
252
|
+
//
|
|
253
|
+
// The runner comes back with the output because the caller has to say which
|
|
254
|
+
// path answered: `extractJson` in ccusage.mjs can fail on a run that started
|
|
255
|
+
// perfectly well, and "ccusage ran but printed no usage data" reads
|
|
256
|
+
// differently depending on which ccusage that was.
|
|
257
|
+
export async function runCcusage(args) {
|
|
258
|
+
const runner = await getRunner();
|
|
259
|
+
try {
|
|
260
|
+
return { out: await runOnce(runner, args), runner };
|
|
261
|
+
} catch (err) {
|
|
262
|
+
if (!discardDamagedInstall(runner, err)) throw stamp(err, runner);
|
|
263
|
+
note("managed install was unusable, rebuilding it", err);
|
|
264
|
+
const rebuilt = await getRunner();
|
|
265
|
+
try {
|
|
266
|
+
return { out: await runOnce(rebuilt, args), runner: rebuilt };
|
|
267
|
+
} catch (again) {
|
|
268
|
+
throw stamp(again, rebuilt);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|