@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.51
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash-classifier.js +400 -4
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/diff-capture.js +112 -3
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +86 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +155 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +897 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1045 -36
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +284 -2
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1897 -37
- package/dist/core/repl/slash-commands.js +430 -15
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +80 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +3241 -343
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +412 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +32 -0
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +203 -4
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +51 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +218 -3
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +313 -35
- package/dist/tui/repl-splash-art.js +1 -1
- package/dist/tui/repl-splash-mascot.js +32 -8
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +85 -5
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +52 -3
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +13 -7
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi cost` / `pugi usage` command handler — L19 sprint (2026-05-27).
|
|
3
|
+
*
|
|
4
|
+
* Shared backend for three operator surfaces:
|
|
5
|
+
*
|
|
6
|
+
* - `pugi cost` current session (default)
|
|
7
|
+
* - `pugi cost --all-sessions` 30-day rolling aggregate
|
|
8
|
+
* - `pugi cost --reset --yes` wipe current session counter (operator-only)
|
|
9
|
+
* - `pugi usage` alias of `pugi cost`
|
|
10
|
+
* - `/cost` REPL slash same handler, in-REPL output
|
|
11
|
+
* - `/usage` REPL slash same handler, alias of /cost
|
|
12
|
+
*
|
|
13
|
+
* Why a separate command from the existing `pugi budget`:
|
|
14
|
+
*
|
|
15
|
+
* - `pugi budget` walks `.pugi/events.jsonl` and bills against the
|
|
16
|
+
* event-log heuristic (per-command / per-persona attribution). It
|
|
17
|
+
* is the right surface for "what did this brief / this persona
|
|
18
|
+
* spend?". It does not break down by model and it does not persist
|
|
19
|
+
* a cross-session aggregate.
|
|
20
|
+
*
|
|
21
|
+
* - `pugi cost` (this command) reads the persisted `.pugi/cost.json`
|
|
22
|
+
* written by the `CostTracker`. It is the right surface for "what
|
|
23
|
+
* did this model spend?" and "what did I spend across the last 30
|
|
24
|
+
* days?". Token + USD figures are sourced from the rate card, which
|
|
25
|
+
* distinguishes hosted Claude (per-token billed) from open-weight
|
|
26
|
+
* Qwen / Kimi / DeepSeek (infra cost only).
|
|
27
|
+
*
|
|
28
|
+
* Both commands intentionally coexist — they answer adjacent but distinct
|
|
29
|
+
* operator questions. The L19 spec calls out `/cost` and `/usage` by
|
|
30
|
+
* name; the budget surface is unaffected.
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
33
|
+
import { resolve } from 'node:path';
|
|
34
|
+
import { createCostTracker, totalTokens, totalUsd, } from '../../core/cost/tracker.js';
|
|
35
|
+
import { buildCostView, renderCostTableText } from '../../tui/cost-table.js';
|
|
36
|
+
/**
|
|
37
|
+
* Parsed flag bundle. Exported for the test surface; production callers
|
|
38
|
+
* never touch it directly — `runCostCommand` owns parsing.
|
|
39
|
+
*/
|
|
40
|
+
export function parseCostFlags(args) {
|
|
41
|
+
const flags = {
|
|
42
|
+
allSessions: false,
|
|
43
|
+
reset: false,
|
|
44
|
+
yes: false,
|
|
45
|
+
json: false,
|
|
46
|
+
windowDays: 30,
|
|
47
|
+
};
|
|
48
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
49
|
+
const arg = args[i] ?? '';
|
|
50
|
+
if (arg === '--all-sessions')
|
|
51
|
+
flags.allSessions = true;
|
|
52
|
+
else if (arg === '--reset')
|
|
53
|
+
flags.reset = true;
|
|
54
|
+
else if (arg === '--yes' || arg === '-y')
|
|
55
|
+
flags.yes = true;
|
|
56
|
+
else if (arg === '--json')
|
|
57
|
+
flags.json = true;
|
|
58
|
+
else if (arg.startsWith('--window=')) {
|
|
59
|
+
const raw = Number.parseInt(arg.slice('--window='.length), 10);
|
|
60
|
+
if (Number.isFinite(raw) && raw > 0 && raw <= 365)
|
|
61
|
+
flags.windowDays = raw;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return flags;
|
|
65
|
+
}
|
|
66
|
+
export async function runCostCommand(args, ctx) {
|
|
67
|
+
const flags = parseCostFlags(args);
|
|
68
|
+
const sessionId = ctx.sessionId ?? deriveSessionIdFromEvents(ctx.workspaceRoot) ?? 'no-session';
|
|
69
|
+
const tracker = createCostTracker({
|
|
70
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
71
|
+
sessionIdProvider: () => sessionId,
|
|
72
|
+
now: ctx.now,
|
|
73
|
+
});
|
|
74
|
+
// --reset: clear the current session counter. Operator-only — refuses
|
|
75
|
+
// without `--yes` so a typo / shell completion never wipes the meter.
|
|
76
|
+
if (flags.reset) {
|
|
77
|
+
if (!flags.yes) {
|
|
78
|
+
ctx.writeOutput({ command: 'cost', status: 'reset_pending_confirmation' }, 'pugi cost --reset clears the current session counter. Re-run with --yes to confirm.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const wiped = tracker.resetCurrent();
|
|
82
|
+
const payload = {
|
|
83
|
+
command: 'cost',
|
|
84
|
+
status: 'reset_ok',
|
|
85
|
+
wiped: wiped ?? null,
|
|
86
|
+
};
|
|
87
|
+
ctx.writeOutput(payload, wiped
|
|
88
|
+
? `Cleared session ${wiped.sessionId} (${Object.keys(wiped.models).length} model(s) wiped).`
|
|
89
|
+
: 'No current session counter to clear.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const aggregate = flags.allSessions ? tracker.aggregateWithin(flags.windowDays) : (tracker.current() ?? emptyAggregate(sessionId, ctx.now ?? Date.now));
|
|
93
|
+
const tier = ctx.resolveTier ? await safeResolveTier(ctx.resolveTier) : null;
|
|
94
|
+
const heading = flags.allSessions
|
|
95
|
+
? `Pugi cost / usage — aggregate (last ${flags.windowDays} days)`
|
|
96
|
+
: buildSessionHeading(aggregate, ctx.now ?? Date.now);
|
|
97
|
+
const view = buildCostView({ aggregate, heading, tier: tier ?? undefined });
|
|
98
|
+
const text = renderCostTableText(view);
|
|
99
|
+
ctx.writeOutput({
|
|
100
|
+
command: flags.allSessions ? 'cost.aggregate' : 'cost.session',
|
|
101
|
+
status: 'ok',
|
|
102
|
+
window: flags.allSessions ? `${flags.windowDays}d` : 'current',
|
|
103
|
+
tokens: {
|
|
104
|
+
input: view.totalInputTokens,
|
|
105
|
+
output: view.totalOutputTokens,
|
|
106
|
+
},
|
|
107
|
+
dollars: view.totalUsd,
|
|
108
|
+
perModel: view.rows.map((row) => ({
|
|
109
|
+
model: row.model,
|
|
110
|
+
input: row.inputTokens,
|
|
111
|
+
output: row.outputTokens,
|
|
112
|
+
usd: row.usd,
|
|
113
|
+
note: row.note ?? null,
|
|
114
|
+
})),
|
|
115
|
+
tier: tier ?? null,
|
|
116
|
+
}, text);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Render-only helper for the REPL slash. The slash dispatcher inside
|
|
120
|
+
* `session.ts` owns the side-effect of pushing system lines; this
|
|
121
|
+
* function builds the view and the text rendition so the slash handler
|
|
122
|
+
* can fan the lines into the existing `appendSystemLine` queue.
|
|
123
|
+
*
|
|
124
|
+
* Exposed here (not in the Ink module) so the slash path never imports
|
|
125
|
+
* Ink/React — keeps the REPL bundle slim and the slash handler async-free.
|
|
126
|
+
*/
|
|
127
|
+
export function renderCostForSlash(input) {
|
|
128
|
+
const aggregate = input.allSessions
|
|
129
|
+
? input.tracker.aggregateWithin(input.windowDays)
|
|
130
|
+
: (input.tracker.current() ?? emptyAggregate('no-session', input.now));
|
|
131
|
+
const heading = input.allSessions
|
|
132
|
+
? `Pugi cost / usage — aggregate (last ${input.windowDays} days)`
|
|
133
|
+
: buildSessionHeading(aggregate, input.now);
|
|
134
|
+
const view = buildCostView({ aggregate, heading, tier: input.tier ?? undefined });
|
|
135
|
+
return { view, lines: renderCostTableText(view).split('\n') };
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Derive a session id from `.pugi/events.jsonl` when the caller does not
|
|
139
|
+
* pass one. Walks the file once and picks the most recent `session.start`
|
|
140
|
+
* event's id. Falls back to `null` when the file is missing / corrupted
|
|
141
|
+
* — the caller substitutes a `'no-session'` placeholder so the table
|
|
142
|
+
* still renders an empty state instead of crashing.
|
|
143
|
+
*/
|
|
144
|
+
function deriveSessionIdFromEvents(workspaceRoot) {
|
|
145
|
+
const path = resolve(workspaceRoot, '.pugi/events.jsonl');
|
|
146
|
+
if (!existsSync(path))
|
|
147
|
+
return null;
|
|
148
|
+
try {
|
|
149
|
+
const raw = readFileSync(path, 'utf8');
|
|
150
|
+
const lines = raw.split('\n').filter((line) => line.trim().length > 0);
|
|
151
|
+
// Walk from newest to oldest — `session.start` is rare, no reason to
|
|
152
|
+
// scan the whole file when the answer is at the tail.
|
|
153
|
+
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
154
|
+
try {
|
|
155
|
+
const parsed = JSON.parse(lines[i]);
|
|
156
|
+
if (parsed.type === 'session' && parsed.name === 'start' && typeof parsed.sessionId === 'string') {
|
|
157
|
+
return parsed.sessionId;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// partial-write lines are ignored
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// best-effort; absent events.jsonl is a normal first-boot state
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function buildSessionHeading(aggregate, now) {
|
|
171
|
+
if (!aggregate || aggregate.sessionId === 'no-session' || aggregate.sessionId === 'aggregate') {
|
|
172
|
+
return 'Pugi cost / usage — no active session';
|
|
173
|
+
}
|
|
174
|
+
const start = Date.parse(aggregate.startedAt);
|
|
175
|
+
if (!Number.isFinite(start)) {
|
|
176
|
+
return `Pugi cost / usage — session ${aggregate.sessionId}`;
|
|
177
|
+
}
|
|
178
|
+
const elapsedMin = Math.max(0, Math.floor((now() - start) / 60_000));
|
|
179
|
+
return `Pugi cost / usage — session ${aggregate.sessionId} (${elapsedMin} min)`;
|
|
180
|
+
}
|
|
181
|
+
function emptyAggregate(sessionId, now) {
|
|
182
|
+
return {
|
|
183
|
+
sessionId,
|
|
184
|
+
startedAt: new Date(now()).toISOString(),
|
|
185
|
+
models: {},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
async function safeResolveTier(resolver) {
|
|
189
|
+
try {
|
|
190
|
+
return await resolver();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Re-export aggregate helpers so the cli.ts wire-up can read totals
|
|
197
|
+
// without reaching into the tracker module directly.
|
|
198
|
+
export { totalUsd, totalTokens };
|
|
199
|
+
//# sourceMappingURL=cost.js.map
|
|
@@ -6,13 +6,20 @@
|
|
|
6
6
|
* handlers table) does not need a try/catch wrapper.
|
|
7
7
|
*/
|
|
8
8
|
export async function runDelegateCommand(args, ctx) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Extract the optional `--wait` flag from positional args. Accept it
|
|
10
|
+
// in any position so `pugi delegate --wait dev "..."` and
|
|
11
|
+
// `pugi delegate dev "..." --wait` both work; positional ordering of
|
|
12
|
+
// slug + brief is preserved by filtering the flag out before the
|
|
13
|
+
// slug/brief split (Claude P2 fix 2026-05-25).
|
|
14
|
+
const wait = args.some((a) => a === '--wait');
|
|
15
|
+
const positional = args.filter((a) => a !== '--wait');
|
|
16
|
+
const slug = positional[0];
|
|
17
|
+
const brief = positional.slice(1).join(' ').trim();
|
|
11
18
|
if (!slug || !brief) {
|
|
12
19
|
ctx.writeOutput({
|
|
13
20
|
ok: false,
|
|
14
|
-
error: 'Usage: pugi delegate <persona-slug> "<one-sentence brief>"',
|
|
15
|
-
}, 'Usage: pugi delegate <persona-slug> "<one-sentence brief>"');
|
|
21
|
+
error: 'Usage: pugi delegate [--wait] <persona-slug> "<one-sentence brief>"',
|
|
22
|
+
}, 'Usage: pugi delegate [--wait] <persona-slug> "<one-sentence brief>"');
|
|
16
23
|
process.exitCode = 2;
|
|
17
24
|
return;
|
|
18
25
|
}
|
|
@@ -47,14 +54,66 @@ export async function runDelegateCommand(args, ctx) {
|
|
|
47
54
|
brief,
|
|
48
55
|
});
|
|
49
56
|
switch (result.status) {
|
|
50
|
-
case 'ok':
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
case 'ok': {
|
|
58
|
+
const ok = result.response;
|
|
59
|
+
if (!wait) {
|
|
60
|
+
ctx.writeOutput({
|
|
61
|
+
ok: true,
|
|
62
|
+
sessionId: opened.sessionId,
|
|
63
|
+
dispatchId: ok.dispatchId,
|
|
64
|
+
personaSlug: ok.personaSlug,
|
|
65
|
+
}, `dispatched ${ok.personaSlug} (dispatchId=${ok.dispatchId}); stream via GET /sessions/${opened.sessionId}/stream.`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// --wait: subscribe to SSE until the persona reaches a terminal
|
|
69
|
+
// event. The waiter contract is provider-shaped so tests can pass
|
|
70
|
+
// a fake without standing up fetch.
|
|
71
|
+
const waiter = ctx.waitForTerminal ?? waitForDelegateTerminal;
|
|
72
|
+
const outcome = await waiter(config, opened.sessionId, ok.personaSlug);
|
|
73
|
+
switch (outcome.kind) {
|
|
74
|
+
case 'completed':
|
|
75
|
+
ctx.writeOutput({
|
|
76
|
+
ok: true,
|
|
77
|
+
sessionId: opened.sessionId,
|
|
78
|
+
dispatchId: ok.dispatchId,
|
|
79
|
+
personaSlug: outcome.personaSlug,
|
|
80
|
+
status: 'completed',
|
|
81
|
+
}, `completed ${outcome.personaSlug} (dispatchId=${ok.dispatchId}).`);
|
|
82
|
+
return;
|
|
83
|
+
case 'blocked':
|
|
84
|
+
ctx.writeOutput({
|
|
85
|
+
ok: false,
|
|
86
|
+
sessionId: opened.sessionId,
|
|
87
|
+
dispatchId: ok.dispatchId,
|
|
88
|
+
personaSlug: outcome.personaSlug,
|
|
89
|
+
status: 'blocked',
|
|
90
|
+
detail: outcome.detail,
|
|
91
|
+
}, `blocked ${outcome.personaSlug}: ${outcome.detail}`);
|
|
92
|
+
process.exitCode = 5;
|
|
93
|
+
return;
|
|
94
|
+
case 'failed':
|
|
95
|
+
ctx.writeOutput({
|
|
96
|
+
ok: false,
|
|
97
|
+
sessionId: opened.sessionId,
|
|
98
|
+
dispatchId: ok.dispatchId,
|
|
99
|
+
personaSlug: outcome.personaSlug,
|
|
100
|
+
status: 'failed',
|
|
101
|
+
error: outcome.error,
|
|
102
|
+
}, `failed ${outcome.personaSlug}: ${outcome.error}`);
|
|
103
|
+
process.exitCode = 5;
|
|
104
|
+
return;
|
|
105
|
+
case 'stream_error':
|
|
106
|
+
ctx.writeOutput({
|
|
107
|
+
ok: false,
|
|
108
|
+
sessionId: opened.sessionId,
|
|
109
|
+
dispatchId: ok.dispatchId,
|
|
110
|
+
error: outcome.error,
|
|
111
|
+
}, `pugi delegate --wait: ${outcome.error}`);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
57
115
|
return;
|
|
116
|
+
}
|
|
58
117
|
case 'unknown_persona':
|
|
59
118
|
ctx.writeOutput({ ok: false, error: result.message, code: result.code }, `pugi delegate: ${result.message}`);
|
|
60
119
|
process.exitCode = 3;
|
|
@@ -78,4 +137,176 @@ export async function runDelegateCommand(args, ctx) {
|
|
|
78
137
|
return;
|
|
79
138
|
}
|
|
80
139
|
}
|
|
140
|
+
/* ------------------------------------------------------------------ */
|
|
141
|
+
/* --wait waiter */
|
|
142
|
+
/* ------------------------------------------------------------------ */
|
|
143
|
+
/**
|
|
144
|
+
* Subscribe to the session SSE stream and resolve when the named
|
|
145
|
+
* persona reaches a terminal event (agent.completed / agent.blocked /
|
|
146
|
+
* agent.failed). Falls back to a `stream_error` outcome when the
|
|
147
|
+
* stream closes before any terminal event arrives or when the
|
|
148
|
+
* underlying fetch fails.
|
|
149
|
+
*
|
|
150
|
+
* The waiter is intentionally a single-call helper (not a long-lived
|
|
151
|
+
* subscriber) - the scripted `pugi delegate --wait` caller wants to
|
|
152
|
+
* exit on the first terminal event for THIS persona, not maintain a
|
|
153
|
+
* persistent connection.
|
|
154
|
+
*
|
|
155
|
+
* Why we parse the lifecycle in this file (not via the repl-render
|
|
156
|
+
* subscribe helper): repl-render carries Ink/React deps the
|
|
157
|
+
* non-interactive delegate path does not want to load. Duplicating the
|
|
158
|
+
* minimal SSE-line parser here keeps the command lean (this is the same
|
|
159
|
+
* shape `apps/admin-api/src/pugi/sessions.controller.ts` writes to the
|
|
160
|
+
* wire).
|
|
161
|
+
*/
|
|
162
|
+
export async function waitForDelegateTerminal(config, sessionId, targetPersonaSlug) {
|
|
163
|
+
const url = `${config.apiUrl.replace(/\/+$/, '')}/api/pugi/sessions/${encodeURIComponent(sessionId)}/stream`;
|
|
164
|
+
const controller = new AbortController();
|
|
165
|
+
// Hard-cap the waiter at 5 minutes so a stalled server cannot keep
|
|
166
|
+
// a scripted caller hanging forever; the dispatcher's per-turn budget
|
|
167
|
+
// is well under this.
|
|
168
|
+
const timeout = setTimeout(() => controller.abort(), 5 * 60 * 1000);
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(url, {
|
|
171
|
+
method: 'GET',
|
|
172
|
+
headers: {
|
|
173
|
+
Accept: 'text/event-stream',
|
|
174
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
175
|
+
},
|
|
176
|
+
signal: controller.signal,
|
|
177
|
+
});
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
return { kind: 'stream_error', error: `HTTP ${response.status}` };
|
|
180
|
+
}
|
|
181
|
+
if (!response.body) {
|
|
182
|
+
return { kind: 'stream_error', error: 'no response body' };
|
|
183
|
+
}
|
|
184
|
+
// Track the most-recent persona slug seen on agent.spawned so the
|
|
185
|
+
// terminal event (which carries only taskId) can be matched to the
|
|
186
|
+
// delegate target without parsing the random nonce suffix.
|
|
187
|
+
const taskToPersona = new Map();
|
|
188
|
+
// Buffer `agent.message` frames per taskId so the terminal event can
|
|
189
|
+
// surface the persona's full reply (Gemini P1 fix, PR #608). Keyed by
|
|
190
|
+
// taskId so a session producing multiple parallel persona dispatches
|
|
191
|
+
// does not cross-contaminate transcripts.
|
|
192
|
+
const transcriptByTask = new Map();
|
|
193
|
+
const reader = response.body.getReader();
|
|
194
|
+
const decoder = new TextDecoder('utf-8');
|
|
195
|
+
let buffer = '';
|
|
196
|
+
let currentData = '';
|
|
197
|
+
while (true) {
|
|
198
|
+
const { value, done } = await reader.read();
|
|
199
|
+
if (done) {
|
|
200
|
+
return { kind: 'stream_error', error: 'stream ended without terminal event' };
|
|
201
|
+
}
|
|
202
|
+
buffer += decoder.decode(value, { stream: true });
|
|
203
|
+
let newlineIndex;
|
|
204
|
+
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
205
|
+
const rawLine = buffer.slice(0, newlineIndex).replace(/\r$/, '');
|
|
206
|
+
buffer = buffer.slice(newlineIndex + 1);
|
|
207
|
+
if (rawLine.length === 0) {
|
|
208
|
+
if (currentData.length > 0) {
|
|
209
|
+
const parsed = parseDelegateFrame(currentData);
|
|
210
|
+
if (parsed) {
|
|
211
|
+
if (parsed.type === 'agent.spawned') {
|
|
212
|
+
taskToPersona.set(parsed.taskId, parsed.personaSlug);
|
|
213
|
+
}
|
|
214
|
+
else if (parsed.type === 'agent.message') {
|
|
215
|
+
// Buffer the unfiltered reply by taskId. The terminal
|
|
216
|
+
// event below joins these into the `transcript` field
|
|
217
|
+
// when the matched persona reaches `agent.completed`.
|
|
218
|
+
const existing = transcriptByTask.get(parsed.taskId);
|
|
219
|
+
if (existing) {
|
|
220
|
+
existing.push(parsed.content);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
transcriptByTask.set(parsed.taskId, [parsed.content]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else if (parsed.type === 'agent.completed' ||
|
|
227
|
+
parsed.type === 'agent.blocked' ||
|
|
228
|
+
parsed.type === 'agent.failed') {
|
|
229
|
+
const slug = taskToPersona.get(parsed.taskId) ?? targetPersonaSlug;
|
|
230
|
+
if (slug === targetPersonaSlug) {
|
|
231
|
+
controller.abort();
|
|
232
|
+
if (parsed.type === 'agent.completed') {
|
|
233
|
+
const frames = transcriptByTask.get(parsed.taskId) ?? [];
|
|
234
|
+
const transcript = frames.join('\n\n');
|
|
235
|
+
return {
|
|
236
|
+
kind: 'completed',
|
|
237
|
+
personaSlug: slug,
|
|
238
|
+
taskId: parsed.taskId,
|
|
239
|
+
transcript,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
if (parsed.type === 'agent.blocked') {
|
|
243
|
+
return {
|
|
244
|
+
kind: 'blocked',
|
|
245
|
+
personaSlug: slug,
|
|
246
|
+
taskId: parsed.taskId,
|
|
247
|
+
detail: parsed.detail,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
kind: 'failed',
|
|
252
|
+
personaSlug: slug,
|
|
253
|
+
taskId: parsed.taskId,
|
|
254
|
+
error: parsed.error,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
currentData = '';
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (rawLine.startsWith(':'))
|
|
264
|
+
continue;
|
|
265
|
+
const colonIndex = rawLine.indexOf(':');
|
|
266
|
+
const field = colonIndex === -1 ? rawLine : rawLine.slice(0, colonIndex);
|
|
267
|
+
const value = colonIndex === -1 ? '' : rawLine.slice(colonIndex + 1).replace(/^ /, '');
|
|
268
|
+
if (field === 'data') {
|
|
269
|
+
currentData = currentData.length === 0 ? value : `${currentData}\n${value}`;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch (err) {
|
|
275
|
+
if (controller.signal.aborted) {
|
|
276
|
+
return { kind: 'stream_error', error: 'wait timed out' };
|
|
277
|
+
}
|
|
278
|
+
return { kind: 'stream_error', error: err.message };
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
clearTimeout(timeout);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
function parseDelegateFrame(data) {
|
|
285
|
+
try {
|
|
286
|
+
const raw = JSON.parse(data);
|
|
287
|
+
const type = raw.type;
|
|
288
|
+
const taskId = typeof raw.taskId === 'string' ? raw.taskId : null;
|
|
289
|
+
if (!taskId)
|
|
290
|
+
return null;
|
|
291
|
+
if (type === 'agent.spawned' && typeof raw.personaSlug === 'string') {
|
|
292
|
+
return { type, taskId, personaSlug: raw.personaSlug };
|
|
293
|
+
}
|
|
294
|
+
if (type === 'agent.message' && typeof raw.content === 'string') {
|
|
295
|
+
return { type, taskId, content: raw.content };
|
|
296
|
+
}
|
|
297
|
+
if (type === 'agent.completed') {
|
|
298
|
+
return { type, taskId };
|
|
299
|
+
}
|
|
300
|
+
if (type === 'agent.blocked') {
|
|
301
|
+
return { type, taskId, detail: typeof raw.detail === 'string' ? raw.detail : '' };
|
|
302
|
+
}
|
|
303
|
+
if (type === 'agent.failed') {
|
|
304
|
+
return { type, taskId, error: typeof raw.error === 'string' ? raw.error : '' };
|
|
305
|
+
}
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
81
312
|
//# sourceMappingURL=delegate.js.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi dispatch <sub>` — fork-subagent cache-handoff surface (Leak L10 — 2026-05-27).
|
|
3
|
+
*
|
|
4
|
+
* Sub-commands:
|
|
5
|
+
*
|
|
6
|
+
* - `pugi dispatch list-cache-refs` — show active cache-inherit refs
|
|
7
|
+
* persisted under `.pugi/cache-refs/`. Renders an aligned table or
|
|
8
|
+
* JSON depending on `--json`.
|
|
9
|
+
*
|
|
10
|
+
* - `pugi dispatch clear-cache-refs [--older-than 1h] [-v]` — GC stale
|
|
11
|
+
* refs. Without `--older-than`, defaults to 24h (the same cutoff
|
|
12
|
+
* the REPL boot-time auto-sweep uses). The flag accepts duration
|
|
13
|
+
* strings the `parseDuration` helper recognises (`1h`, `30m`,
|
|
14
|
+
* `7d`, `500ms`, ...).
|
|
15
|
+
*
|
|
16
|
+
* The command lives in its own module (not inline in cli.ts) so the
|
|
17
|
+
* dispatch table stays narrow and tests can drive it without spinning
|
|
18
|
+
* up the full CLI.
|
|
19
|
+
*/
|
|
20
|
+
import { cleanupStaleCacheRefs, parseDuration, } from '../../core/dispatch/cache-cleanup.js';
|
|
21
|
+
import { listCacheRefs } from '../../core/dispatch/cache-handoff.js';
|
|
22
|
+
const USAGE = [
|
|
23
|
+
'Usage:',
|
|
24
|
+
' pugi dispatch list-cache-refs',
|
|
25
|
+
' Show every active subagent cache-inherit',
|
|
26
|
+
' reference persisted under .pugi/cache-refs/.',
|
|
27
|
+
'',
|
|
28
|
+
' pugi dispatch clear-cache-refs [--older-than <duration>] [-v]',
|
|
29
|
+
' Evict stale refs. Accepts 500ms / 30s / 5m /',
|
|
30
|
+
' 1h / 7d. Default --older-than 24h.',
|
|
31
|
+
].join('\n');
|
|
32
|
+
export async function runDispatchCommand(args, ctx) {
|
|
33
|
+
const sub = args[0];
|
|
34
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
35
|
+
ctx.writeOutput({ command: 'dispatch', usage: USAGE.split('\n') }, USAGE);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
switch (sub) {
|
|
39
|
+
case 'list-cache-refs':
|
|
40
|
+
return runListCacheRefs(ctx);
|
|
41
|
+
case 'clear-cache-refs':
|
|
42
|
+
return runClearCacheRefs(args.slice(1), ctx);
|
|
43
|
+
default:
|
|
44
|
+
ctx.writeOutput({
|
|
45
|
+
ok: false,
|
|
46
|
+
error: `unknown subcommand: ${sub}`,
|
|
47
|
+
usage: USAGE.split('\n'),
|
|
48
|
+
}, `pugi dispatch: unknown subcommand '${sub}'\n\n${USAGE}`);
|
|
49
|
+
process.exitCode = 2;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function runListCacheRefs(ctx) {
|
|
54
|
+
const refs = listCacheRefs(ctx.workspaceRoot);
|
|
55
|
+
if (ctx.json) {
|
|
56
|
+
ctx.writeOutput({ ok: true, count: refs.length, refs }, '');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (refs.length === 0) {
|
|
60
|
+
ctx.writeOutput({ ok: true, count: 0, refs: [] }, 'No active cache-inherit refs under .pugi/cache-refs/.');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
// Aligned column table. Widths derived from the data — no hardcoded
|
|
64
|
+
// sizes so a long childAgentId does not overflow the layout.
|
|
65
|
+
const header = ['CHILD AGENT', 'CACHE ID', 'PARENT SESSION', 'CREATED'];
|
|
66
|
+
const rows = refs.map((r) => [
|
|
67
|
+
r.childAgentId,
|
|
68
|
+
r.cacheId,
|
|
69
|
+
r.parentSessionId,
|
|
70
|
+
r.createdAt,
|
|
71
|
+
]);
|
|
72
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((row) => (row[i] ?? '').length)));
|
|
73
|
+
const lines = [];
|
|
74
|
+
lines.push(header.map((h, i) => h.padEnd(widths[i] ?? 0)).join(' '));
|
|
75
|
+
lines.push(widths.map((w) => '-'.repeat(w)).join(' '));
|
|
76
|
+
for (const row of rows) {
|
|
77
|
+
lines.push(row.map((cell, i) => (cell ?? '').padEnd(widths[i] ?? 0)).join(' '));
|
|
78
|
+
}
|
|
79
|
+
ctx.writeOutput({ ok: true, count: refs.length, refs }, lines.join('\n'));
|
|
80
|
+
}
|
|
81
|
+
function runClearCacheRefs(args, ctx) {
|
|
82
|
+
let olderThanArg;
|
|
83
|
+
let verbose = false;
|
|
84
|
+
for (let i = 0; i < args.length; i++) {
|
|
85
|
+
const arg = args[i];
|
|
86
|
+
if (arg === '--older-than') {
|
|
87
|
+
olderThanArg = args[i + 1];
|
|
88
|
+
i++;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (arg && arg.startsWith('--older-than=')) {
|
|
92
|
+
olderThanArg = arg.slice('--older-than='.length);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (arg === '-v' || arg === '--verbose') {
|
|
96
|
+
verbose = true;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Default 24h matches the REPL boot-time auto-sweep cutoff.
|
|
101
|
+
const olderThanMs = olderThanArg ? parseDuration(olderThanArg) : 24 * 60 * 60 * 1000;
|
|
102
|
+
if (olderThanMs === null) {
|
|
103
|
+
ctx.writeOutput({
|
|
104
|
+
ok: false,
|
|
105
|
+
error: `invalid --older-than value: ${olderThanArg ?? '(missing)'}`,
|
|
106
|
+
usage: USAGE.split('\n'),
|
|
107
|
+
}, `pugi dispatch clear-cache-refs: invalid --older-than '${olderThanArg ?? '(missing)'}'\n\n${USAGE}`);
|
|
108
|
+
process.exitCode = 2;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const result = cleanupStaleCacheRefs(ctx.workspaceRoot, {
|
|
112
|
+
olderThanMs,
|
|
113
|
+
verbose,
|
|
114
|
+
});
|
|
115
|
+
const summary = `Removed ${result.removedCount} ref(s) (${result.corruptCount} corrupt), kept ${result.keptCount}.`;
|
|
116
|
+
ctx.writeOutput({
|
|
117
|
+
ok: true,
|
|
118
|
+
olderThanMs,
|
|
119
|
+
removedCount: result.removedCount,
|
|
120
|
+
keptCount: result.keptCount,
|
|
121
|
+
corruptCount: result.corruptCount,
|
|
122
|
+
removed: result.removed,
|
|
123
|
+
corrupt: result.corrupt,
|
|
124
|
+
}, summary);
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=dispatch.js.map
|