getprismo 0.1.25 → 0.1.27
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 +23 -1
- package/docs/mcp.md +1 -0
- package/lib/prismo-dev/doctor.js +12 -0
- package/lib/prismo-dev/firewall.js +119 -0
- package/lib/prismo-dev/mcp.js +24 -2
- package/lib/prismo-dev/report.js +24 -0
- package/lib/prismo-dev/usage-watch.js +23 -20
- package/lib/prismo-dev/watch-live.js +103 -0
- package/lib/prismo-dev/watch-render.js +65 -0
- package/lib/prismo-dev-scan.js +42 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -492,6 +492,14 @@ Run npx getprismo optimize, then start from .prismo/architecture-summary.md.
|
|
|
492
492
|
|
|
493
493
|
timeline shows exactly what leaked, what repeated, and what to do differently next time.
|
|
494
494
|
|
|
495
|
+
to turn a postmortem into a safer next-session policy, run:
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
npx getprismo cc timeline --firewall --task auth-bug
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
this writes `.prismo/timeline-firewall-suggestions.md`, `.prismo/context-firewall.suggested.md`, `.prismo/allowed-context.suggested.txt`, and `.prismo/blocked-context.suggested.txt` from the latest session evidence. it does not overwrite your active firewall; it gives you a per-task allow/block recommendation for the next session.
|
|
502
|
+
|
|
495
503
|
---
|
|
496
504
|
|
|
497
505
|
## how doctor improves a repo
|
|
@@ -552,6 +560,16 @@ watch reads local session logs from codex and claude code. it detects:
|
|
|
552
560
|
|
|
553
561
|
watch tells you the single most useful action to take right now. usually: start a fresh session, or switch to a scoped context pack.
|
|
554
562
|
|
|
563
|
+
if you run multiple agents in the same repo, use:
|
|
564
|
+
|
|
565
|
+
```bash
|
|
566
|
+
npx getprismo watch --agents
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
multi-agent watch shows every visible local Codex/Claude Code session for the repo, ranks each agent by context pressure, and flags coordination risks like two agents repeatedly loading the same file, shared artifact leaks, multiple high-pressure sessions, or agents that should move noisy commands into `shield`.
|
|
570
|
+
|
|
571
|
+
the same multi-agent coordination signal is included in `usage --json`, `scan --usage --json`, doctor output, and the generated markdown report whenever multiple local sessions are visible for the repo.
|
|
572
|
+
|
|
555
573
|
`watch --rescue` prints a paste-ready prompt for the active coding session. use it when the agent is looping, reading too many files, or flooding context with logs:
|
|
556
574
|
|
|
557
575
|
```bash
|
|
@@ -644,6 +662,8 @@ npx getprismo doctor --json # machine-readable output
|
|
|
644
662
|
```bash
|
|
645
663
|
npx getprismo watch # live refresh
|
|
646
664
|
npx getprismo watch --once # single snapshot
|
|
665
|
+
npx getprismo watch --agents # multi-agent coordination view
|
|
666
|
+
npx getprismo watch --agents --json # machine-readable multi-agent state
|
|
647
667
|
npx getprismo watch --once --report # write .prismo/watch-report.md
|
|
648
668
|
npx getprismo watch --once --json # machine-readable
|
|
649
669
|
npx getprismo watch --auto # guardrails + throttle + 600k budget
|
|
@@ -681,6 +701,7 @@ npx getprismo mcp /path/to/repo
|
|
|
681
701
|
- `prismo_scan`
|
|
682
702
|
- `prismo_doctor_dry_run`
|
|
683
703
|
- `prismo_watch_snapshot`
|
|
704
|
+
- `prismo_multi_agent_watch`
|
|
684
705
|
- `prismo_shield_run`
|
|
685
706
|
- `prismo_shield_search`
|
|
686
707
|
- `prismo_shield_last`
|
|
@@ -688,7 +709,7 @@ npx getprismo mcp /path/to/repo
|
|
|
688
709
|
- `prismo_firewall`
|
|
689
710
|
- `prismo_cc_timeline`
|
|
690
711
|
|
|
691
|
-
This lets an MCP-compatible agent search prior shielded test/build output, request scoped context packs,
|
|
712
|
+
This lets an MCP-compatible agent search prior shielded test/build output, request scoped context packs, inspect token-waste signals, or coordinate multiple local agents without pasting giant logs into the conversation.
|
|
692
713
|
|
|
693
714
|
Generic MCP client config:
|
|
694
715
|
|
|
@@ -723,6 +744,7 @@ For local development from this repo:
|
|
|
723
744
|
```bash
|
|
724
745
|
npx getprismo cc # latest session cost
|
|
725
746
|
npx getprismo cc timeline # event timeline for latest session
|
|
747
|
+
npx getprismo cc timeline --firewall --task auth-bug # suggest next-session firewall rules
|
|
726
748
|
npx getprismo cc list # list recent sessions
|
|
727
749
|
npx getprismo cc last 5 # last 5 sessions
|
|
728
750
|
npx getprismo cc all # everything
|
package/docs/mcp.md
CHANGED
|
@@ -49,6 +49,7 @@ For local development from this repo:
|
|
|
49
49
|
- `prismo_scan`: scan repo context/token waste
|
|
50
50
|
- `prismo_doctor_dry_run`: preview doctor payoff without writing files
|
|
51
51
|
- `prismo_watch_snapshot`: inspect live context pressure
|
|
52
|
+
- `prismo_multi_agent_watch`: inspect coordination risks across parallel local agents
|
|
52
53
|
- `prismo_shield_run`: run a noisy command and store full output locally
|
|
53
54
|
- `prismo_shield_search`: search stored shield output
|
|
54
55
|
- `prismo_shield_last`: list recent shielded command runs
|
package/lib/prismo-dev/doctor.js
CHANGED
|
@@ -324,6 +324,7 @@ function toDoctorJsonPayload(result) {
|
|
|
324
324
|
confidence: usage.confidence,
|
|
325
325
|
totals: usage.totals,
|
|
326
326
|
sources: usage.sources,
|
|
327
|
+
multiAgent: usage.multiAgent || null,
|
|
327
328
|
}
|
|
328
329
|
: null;
|
|
329
330
|
return {
|
|
@@ -381,6 +382,13 @@ function renderDoctorTerminal(result) {
|
|
|
381
382
|
if (result.before.realUsage && result.before.realUsage.sessions.length) {
|
|
382
383
|
lines.push(`Local usage: ${formatTokenCount(result.before.realUsage.totals.displayTokens)} tokens across ${result.before.realUsage.sessions.length} recent session(s)`);
|
|
383
384
|
}
|
|
385
|
+
const multiAgent = result.before.realUsage && result.before.realUsage.multiAgent;
|
|
386
|
+
if (multiAgent && multiAgent.agentCount > 1) {
|
|
387
|
+
lines.push(`Multi-agent: ${multiAgent.agentCount} agents visible; highest pressure ${multiAgent.highestPressure}`);
|
|
388
|
+
if (multiAgent.coordinationWarnings && multiAgent.coordinationWarnings[0]) {
|
|
389
|
+
lines.push(`Coordination warning: ${multiAgent.coordinationWarnings[0]}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
384
392
|
if (!(result.dryRun && result.applySuggestions)) lines.push(`Estimated exposed context reduction: ${result.exposedTokenReductionPercent}%`);
|
|
385
393
|
if (!result.dryRun) {
|
|
386
394
|
lines.push(`Payoff: ${scoreDelta > 0 ? `repo is ${scoreDelta} points cleaner for AI coding sessions` : "safe fixes applied; remaining risk needs manual cleanup"}`);
|
|
@@ -457,6 +465,10 @@ function renderDevTerminal(result) {
|
|
|
457
465
|
} else if (result.scan.realUsage) {
|
|
458
466
|
lines.push("Real local usage: no matching local sessions found for this repo");
|
|
459
467
|
}
|
|
468
|
+
const multiAgent = result.scan.realUsage && result.scan.realUsage.multiAgent;
|
|
469
|
+
if (multiAgent && multiAgent.agentCount > 1) {
|
|
470
|
+
lines.push(`Multi-agent: ${multiAgent.agentCount} agents visible; highest pressure ${multiAgent.highestPressure}`);
|
|
471
|
+
}
|
|
460
472
|
lines.push("");
|
|
461
473
|
lines.push("Generated:");
|
|
462
474
|
result.optimize.generatedFiles.slice(0, 8).forEach((file) => lines.push(`- ${file}`));
|
|
@@ -99,6 +99,58 @@ function buildBlockedContext(ctx) {
|
|
|
99
99
|
return uniq(blocked).slice(0, 80);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function isGeneratedLike(value) {
|
|
103
|
+
const text = String(value || "").replace(/\\/g, "/").toLowerCase();
|
|
104
|
+
return /(^|\/)(node_modules|dist|build|coverage|\.next|__pycache__|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)(\/|$)/.test(text)
|
|
105
|
+
|| /(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lockb)$/.test(text)
|
|
106
|
+
|| /\.(log|jsonl|ndjson|map|pyc)$/.test(text);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function firewallPatternForPath(value) {
|
|
110
|
+
const text = String(value || "").replace(/\\/g, "/").replace(/\s+\(\d+x\)$/, "");
|
|
111
|
+
if (!text) return null;
|
|
112
|
+
if (/(^|\/)__pycache__(\/|$)/.test(text)) return "**/__pycache__/**";
|
|
113
|
+
if (/(^|\/)(node_modules|dist|build|coverage|\.next|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)\//.test(text)) {
|
|
114
|
+
const match = text.match(/(^|\/)(node_modules|dist|build|coverage|\.next|logs?|events?|source-streams?|session-dumps?|playwright-report|test-results)\//);
|
|
115
|
+
const prefix = text.slice(0, match.index + match[0].length).replace(/\/$/, "");
|
|
116
|
+
return `${prefix}/**`;
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function timelineEventPath(event) {
|
|
122
|
+
const detail = String(event?.detail || "");
|
|
123
|
+
const match = detail.match(/^(.+?)\s+\(\d+x\)$/);
|
|
124
|
+
return (match ? match[1] : detail).trim();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildTimelineFirewallSuggestions(ctx, session, taskScope) {
|
|
128
|
+
const baseAllowed = buildAllowedContext(ctx, taskScope);
|
|
129
|
+
const baseBlocked = buildBlockedContext(ctx);
|
|
130
|
+
const timeline = session?.timeline || [];
|
|
131
|
+
const repeated = session?.repeatedPathMentions || [];
|
|
132
|
+
const artifacts = session?.generatedArtifacts || [];
|
|
133
|
+
|
|
134
|
+
const sessionAllowed = repeated
|
|
135
|
+
.map((item) => item.value)
|
|
136
|
+
.filter((value) => value && !isGeneratedLike(value))
|
|
137
|
+
.map(globForFile)
|
|
138
|
+
.slice(0, 20);
|
|
139
|
+
|
|
140
|
+
const sessionBlocked = [
|
|
141
|
+
...artifacts.map((item) => item.value),
|
|
142
|
+
...timeline.filter((event) => event.type === "artifact-leak").map(timelineEventPath),
|
|
143
|
+
...repeated.map((item) => item.value).filter(isGeneratedLike),
|
|
144
|
+
].map(firewallPatternForPath).filter(Boolean);
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
allowed: uniq([...sessionAllowed, ...baseAllowed]).slice(0, 80),
|
|
148
|
+
blocked: uniq([...sessionBlocked, ...baseBlocked]).slice(0, 100),
|
|
149
|
+
sessionAllowed: uniq(sessionAllowed),
|
|
150
|
+
sessionBlocked: uniq(sessionBlocked),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
102
154
|
function renderLines(title, items) {
|
|
103
155
|
return [`# ${title}`, "", ...items.map((item) => item)].join("\n") + "\n";
|
|
104
156
|
}
|
|
@@ -187,6 +239,71 @@ function runFirewall(rootDir = process.cwd(), options = {}) {
|
|
|
187
239
|
return result;
|
|
188
240
|
}
|
|
189
241
|
|
|
242
|
+
function renderTimelineFirewallSuggestions(result) {
|
|
243
|
+
const lines = [];
|
|
244
|
+
lines.push("# Prismo Timeline Firewall Suggestions");
|
|
245
|
+
lines.push("");
|
|
246
|
+
lines.push(`Generated: ${result.generatedAt}`);
|
|
247
|
+
lines.push(`Task: ${result.task}`);
|
|
248
|
+
lines.push(`Scope: ${result.scope}`);
|
|
249
|
+
if (result.sessionId) lines.push(`Session: ${result.sessionId}`);
|
|
250
|
+
lines.push("");
|
|
251
|
+
lines.push("These suggestions came from `cc timeline` session evidence. They are safe recommendation files; Prismo does not overwrite your active firewall unless you copy/apply them.");
|
|
252
|
+
lines.push("");
|
|
253
|
+
lines.push("## Session-Derived Allowed Context");
|
|
254
|
+
lines.push("");
|
|
255
|
+
if (result.sessionAllowed.length) result.sessionAllowed.forEach((item) => lines.push(`- ${item}`));
|
|
256
|
+
else lines.push("- No repeated source files were strong enough to promote.");
|
|
257
|
+
lines.push("");
|
|
258
|
+
lines.push("## Session-Derived Blocked Context");
|
|
259
|
+
lines.push("");
|
|
260
|
+
if (result.sessionBlocked.length) result.sessionBlocked.forEach((item) => lines.push(`- ${item}`));
|
|
261
|
+
else lines.push("- No generated/noisy paths were strong enough to add.");
|
|
262
|
+
lines.push("");
|
|
263
|
+
lines.push("## Next Session Prompt");
|
|
264
|
+
lines.push("");
|
|
265
|
+
lines.push("```text");
|
|
266
|
+
lines.push(`Use .prismo/context-firewall.suggested.md as the starting context policy for this ${result.task} task.`);
|
|
267
|
+
lines.push("Start from allowed context first. Do not read blocked paths unless you explain why they are required.");
|
|
268
|
+
lines.push("If you need wider context, name the exact file and reason before reading.");
|
|
269
|
+
lines.push("```");
|
|
270
|
+
lines.push("");
|
|
271
|
+
return lines.join("\n");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function runTimelineFirewallSuggestions(rootDir = process.cwd(), session = null, options = {}) {
|
|
275
|
+
const ctx = createOptimizeContext(rootDir, options.scope || null);
|
|
276
|
+
const task = options.task || options.scope || "timeline-followup";
|
|
277
|
+
const scope = inferTaskScope(task, ctx);
|
|
278
|
+
const suggestions = buildTimelineFirewallSuggestions(ctx, session, scope);
|
|
279
|
+
const result = {
|
|
280
|
+
root: ctx.root,
|
|
281
|
+
task,
|
|
282
|
+
scope,
|
|
283
|
+
sessionId: session?.sessionId || null,
|
|
284
|
+
allowed: suggestions.allowed,
|
|
285
|
+
blocked: suggestions.blocked,
|
|
286
|
+
sessionAllowed: suggestions.sessionAllowed,
|
|
287
|
+
sessionBlocked: suggestions.sessionBlocked,
|
|
288
|
+
generatedAt: new Date().toISOString(),
|
|
289
|
+
generatedFiles: [
|
|
290
|
+
".prismo/timeline-firewall-suggestions.md",
|
|
291
|
+
".prismo/context-firewall.suggested.md",
|
|
292
|
+
".prismo/allowed-context.suggested.txt",
|
|
293
|
+
".prismo/blocked-context.suggested.txt",
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (!options.dryRun) {
|
|
298
|
+
writeGeneratedFile(ctx.root, ".prismo/timeline-firewall-suggestions.md", renderTimelineFirewallSuggestions(result));
|
|
299
|
+
writeGeneratedFile(ctx.root, ".prismo/context-firewall.suggested.md", renderFirewallPolicy(result));
|
|
300
|
+
writeGeneratedFile(ctx.root, ".prismo/allowed-context.suggested.txt", renderLines("Allowed Context Suggestions", result.allowed));
|
|
301
|
+
writeGeneratedFile(ctx.root, ".prismo/blocked-context.suggested.txt", renderLines("Blocked Context Suggestions", result.blocked));
|
|
302
|
+
}
|
|
303
|
+
result.dryRun = Boolean(options.dryRun);
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
|
|
190
307
|
function renderFirewallTerminal(result) {
|
|
191
308
|
const lines = [];
|
|
192
309
|
lines.push("");
|
|
@@ -218,6 +335,8 @@ function renderFirewallTerminal(result) {
|
|
|
218
335
|
renderFirewallPolicy,
|
|
219
336
|
renderFirewallPrompt,
|
|
220
337
|
renderFirewallTerminal,
|
|
338
|
+
renderTimelineFirewallSuggestions,
|
|
221
339
|
runFirewall,
|
|
340
|
+
runTimelineFirewallSuggestions,
|
|
222
341
|
};
|
|
223
342
|
};
|
package/lib/prismo-dev/mcp.js
CHANGED
|
@@ -69,6 +69,11 @@ function createMcpTools(deps) {
|
|
|
69
69
|
tool: { type: "string", enum: ["all", "codex", "claude"], description: "Which local session logs to inspect." },
|
|
70
70
|
limit: limitProperty,
|
|
71
71
|
}),
|
|
72
|
+
makeTool("prismo_multi_agent_watch", "Return multi-agent coordination risks across visible local Codex/Claude sessions.", {
|
|
73
|
+
path: pathProperty,
|
|
74
|
+
tool: { type: "string", enum: ["all", "codex", "claude"], description: "Which local session logs to inspect." },
|
|
75
|
+
limit: limitProperty,
|
|
76
|
+
}),
|
|
72
77
|
makeTool("prismo_shield_run", "Run a noisy command through Prismo shield and store full output locally.", {
|
|
73
78
|
path: pathProperty,
|
|
74
79
|
command: {
|
|
@@ -131,11 +136,27 @@ function createMcpTools(deps) {
|
|
|
131
136
|
const summary = getUsageSummary({
|
|
132
137
|
cwd: target,
|
|
133
138
|
limit: Number(args.limit) || 3,
|
|
134
|
-
|
|
139
|
+
tool: args.tool || "all",
|
|
135
140
|
});
|
|
136
141
|
return createTextResult(summary);
|
|
137
142
|
}
|
|
138
143
|
|
|
144
|
+
if (name === "prismo_multi_agent_watch") {
|
|
145
|
+
const summary = getUsageSummary({
|
|
146
|
+
cwd: target,
|
|
147
|
+
limit: Number(args.limit) || 8,
|
|
148
|
+
tool: args.tool || "all",
|
|
149
|
+
});
|
|
150
|
+
return createTextResult({
|
|
151
|
+
schemaVersion: 1,
|
|
152
|
+
generatedAt: summary.generatedAt,
|
|
153
|
+
scannedPath: summary.scannedPath,
|
|
154
|
+
tool: summary.tool,
|
|
155
|
+
totals: summary.totals,
|
|
156
|
+
multiAgent: summary.multiAgent,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
139
160
|
if (name === "prismo_shield_run") {
|
|
140
161
|
return createTextResult(runShield(target, args.command));
|
|
141
162
|
}
|
|
@@ -265,6 +286,7 @@ async function runMcpDoctor(deps) {
|
|
|
265
286
|
"prismo_scan",
|
|
266
287
|
"prismo_doctor_dry_run",
|
|
267
288
|
"prismo_watch_snapshot",
|
|
289
|
+
"prismo_multi_agent_watch",
|
|
268
290
|
"prismo_shield_run",
|
|
269
291
|
"prismo_shield_search",
|
|
270
292
|
"prismo_shield_last",
|
|
@@ -315,7 +337,7 @@ async function runMcpDoctor(deps) {
|
|
|
315
337
|
next: [
|
|
316
338
|
"Add the config snippet to your MCP-compatible client.",
|
|
317
339
|
"Restart the client and confirm prismodev appears in the MCP tool list.",
|
|
318
|
-
"Ask the agent to call prismo_scan or prismo_shield_run.",
|
|
340
|
+
"Ask the agent to call prismo_scan, prismo_multi_agent_watch, or prismo_shield_run.",
|
|
319
341
|
],
|
|
320
342
|
};
|
|
321
343
|
}
|
package/lib/prismo-dev/report.js
CHANGED
|
@@ -45,6 +45,10 @@ function renderTerminalReport(result, options = {}) {
|
|
|
45
45
|
if (result.realUsage && result.realUsage.sessions.length) {
|
|
46
46
|
lines.push(`- Real local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens across ${result.realUsage.sessions.length} session(s)`);
|
|
47
47
|
lines.push(`- Usage confidence: ${result.realUsage.confidence}`);
|
|
48
|
+
if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
|
|
49
|
+
lines.push(`- Multi-agent: ${result.realUsage.multiAgent.agentCount} agents visible; highest pressure ${result.realUsage.multiAgent.highestPressure}`);
|
|
50
|
+
if (result.realUsage.multiAgent.coordinationWarnings[0]) lines.push(`- Coordination warning: ${result.realUsage.multiAgent.coordinationWarnings[0]}`);
|
|
51
|
+
}
|
|
48
52
|
} else if (result.realUsage) {
|
|
49
53
|
lines.push("- Real local usage: no matching local Codex/Claude Code sessions found for this repo");
|
|
50
54
|
}
|
|
@@ -116,6 +120,9 @@ function renderOptimizerFitTerminal(result, options = {}) {
|
|
|
116
120
|
lines.push(`Primary bottleneck: ${color(fit.summary, tone, useColor)}`);
|
|
117
121
|
if (result.realUsage && result.realUsage.sessions.length) {
|
|
118
122
|
lines.push(`Local usage: ${formatTokenCount(result.realUsage.totals.displayTokens)} tokens across ${result.realUsage.sessions.length} session(s)`);
|
|
123
|
+
if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
|
|
124
|
+
lines.push(`Multi-agent: ${result.realUsage.multiAgent.agentCount} agents visible; highest pressure ${result.realUsage.multiAgent.highestPressure}`);
|
|
125
|
+
}
|
|
119
126
|
} else if (result.realUsage) {
|
|
120
127
|
lines.push("Local usage: no matching local Claude/Codex sessions found");
|
|
121
128
|
}
|
|
@@ -261,6 +268,9 @@ function renderMarkdownReport(result) {
|
|
|
261
268
|
if (result.realUsage) {
|
|
262
269
|
lines.push(`- **Real Local Usage:** ${result.realUsage.totals.displayTokens.toLocaleString()} tokens across ${result.realUsage.sessions.length} session(s)`);
|
|
263
270
|
lines.push(`- **Usage Confidence:** ${result.realUsage.confidence}`);
|
|
271
|
+
if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
|
|
272
|
+
lines.push(`- **Multi-Agent:** ${result.realUsage.multiAgent.agentCount} agents visible; highest pressure ${result.realUsage.multiAgent.highestPressure}`);
|
|
273
|
+
}
|
|
264
274
|
}
|
|
265
275
|
lines.push("");
|
|
266
276
|
lines.push("Estimates are based on local file-size and configuration heuristics. They are not provider billing data and are not guaranteed savings.");
|
|
@@ -346,6 +356,20 @@ function renderMarkdownReport(result) {
|
|
|
346
356
|
lines.push(`- Estimated tool/output tokens: ${result.realUsage.totals.toolTokens.toLocaleString()}`);
|
|
347
357
|
lines.push(`- Confidence: ${result.realUsage.confidence}`);
|
|
348
358
|
lines.push("");
|
|
359
|
+
if (result.realUsage.multiAgent && result.realUsage.multiAgent.agentCount > 1) {
|
|
360
|
+
lines.push("### Multi-Agent Coordination");
|
|
361
|
+
lines.push("");
|
|
362
|
+
lines.push(`- Agents visible: ${result.realUsage.multiAgent.agentCount}`);
|
|
363
|
+
lines.push(`- Highest pressure: ${result.realUsage.multiAgent.highestPressure}`);
|
|
364
|
+
result.realUsage.multiAgent.coordinationWarnings.slice(0, 5).forEach((warning) => lines.push(`- ${warning}`));
|
|
365
|
+
if (result.realUsage.multiAgent.sharedFiles.length) {
|
|
366
|
+
lines.push(`- Shared repeated files: ${result.realUsage.multiAgent.sharedFiles.slice(0, 5).map((item) => `\`${item.path}\` (${item.agents} agents)`).join(", ")}`);
|
|
367
|
+
}
|
|
368
|
+
if (result.realUsage.multiAgent.sharedArtifacts.length) {
|
|
369
|
+
lines.push(`- Shared artifact leaks: ${result.realUsage.multiAgent.sharedArtifacts.slice(0, 5).map((item) => `${item.type} (${item.agents} agents)`).join(", ")}`);
|
|
370
|
+
}
|
|
371
|
+
lines.push("");
|
|
372
|
+
}
|
|
349
373
|
result.realUsage.sessions.slice(0, 5).forEach((session, index) => {
|
|
350
374
|
lines.push(`${index + 1}. ${session.tool} - ${session.title || session.sessionId}`);
|
|
351
375
|
lines.push(` - Tokens: ${session.displayTokens.toLocaleString()} (${session.confidence})`);
|
|
@@ -53,6 +53,7 @@ const {
|
|
|
53
53
|
|
|
54
54
|
const {
|
|
55
55
|
buildLiveSessionView,
|
|
56
|
+
buildMultiAgentView,
|
|
56
57
|
} = require("./watch-live")({
|
|
57
58
|
NPX_COMMAND,
|
|
58
59
|
formatTokenCount,
|
|
@@ -65,7 +66,7 @@ const {
|
|
|
65
66
|
getAllClaudeSessionFiles,
|
|
66
67
|
getClaudeSessionFiles,
|
|
67
68
|
getCodexSessionFiles,
|
|
68
|
-
getUsageSummary,
|
|
69
|
+
getUsageSummary: getBaseUsageSummary,
|
|
69
70
|
} = require("./usage-sessions")({
|
|
70
71
|
fs,
|
|
71
72
|
os,
|
|
@@ -76,15 +77,25 @@ const {
|
|
|
76
77
|
readIfText,
|
|
77
78
|
});
|
|
78
79
|
|
|
80
|
+
function getUsageSummary(options = {}) {
|
|
81
|
+
const summary = getBaseUsageSummary(options);
|
|
82
|
+
if ((summary.sessions || []).length > 1) {
|
|
83
|
+
summary.multiAgent = buildMultiAgentView(summary);
|
|
84
|
+
}
|
|
85
|
+
return summary;
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
const {
|
|
80
89
|
renderContextThrottle,
|
|
81
90
|
renderLiveGuardrails,
|
|
91
|
+
renderMultiAgentWatchTerminal,
|
|
82
92
|
renderRescuePrompt,
|
|
83
93
|
renderUsageTerminal,
|
|
84
94
|
renderWatchTerminal,
|
|
85
95
|
} = require("./watch-render")({
|
|
86
96
|
NPX_COMMAND,
|
|
87
97
|
buildLiveSessionView,
|
|
98
|
+
buildMultiAgentView,
|
|
88
99
|
color,
|
|
89
100
|
formatTokenCount,
|
|
90
101
|
getActionableRepeatedPaths,
|
|
@@ -199,24 +210,6 @@ function parseScopeAndTarget(args, valueFlags = new Set()) {
|
|
|
199
210
|
return { scope: null, target: positional[0] || process.cwd() };
|
|
200
211
|
}
|
|
201
212
|
|
|
202
|
-
function formatTokenCount(value) {
|
|
203
|
-
const n = Number(value || 0);
|
|
204
|
-
if (n >= 1000000) return `${(n / 1000000).toFixed(2)}M`;
|
|
205
|
-
if (n >= 1000) return `${Math.round(n / 1000)}k`;
|
|
206
|
-
return String(Math.round(n));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function formatMoney(value) {
|
|
210
|
-
const n = Number(value || 0);
|
|
211
|
-
if (n >= 1) return `$${n.toFixed(2)}`;
|
|
212
|
-
return `$${n.toFixed(4)}`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
213
|
function writeLiveFile(root, relPath, contents) {
|
|
221
214
|
const fullPath = path.join(root || process.cwd(), relPath);
|
|
222
215
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
@@ -432,6 +425,7 @@ function compactUsageSummary(summary) {
|
|
|
432
425
|
totals: summary.totals,
|
|
433
426
|
sources: summary.sources,
|
|
434
427
|
sessions: (summary.sessions || []).map(compactWatchSession),
|
|
428
|
+
multiAgent: summary.multiAgent || ((summary.sessions || []).length > 1 ? buildMultiAgentView(summary) : null),
|
|
435
429
|
};
|
|
436
430
|
}
|
|
437
431
|
|
|
@@ -484,6 +478,7 @@ function toWatchJsonPayload(summary) {
|
|
|
484
478
|
sources: summary.sources,
|
|
485
479
|
sessions: summary.sessions.map(compactWatchSession),
|
|
486
480
|
live: summary.live || buildLiveSessionView(summary),
|
|
481
|
+
multiAgent: summary.multiAgent || (summary.agents ? buildMultiAgentView(summary) : null),
|
|
487
482
|
auto: Boolean(summary.auto),
|
|
488
483
|
rescuePrompt: summary.includeRescuePrompt ? renderRescuePrompt(summary) : null,
|
|
489
484
|
guardrailsPath: summary.guardrailsPath || null,
|
|
@@ -502,7 +497,11 @@ async function watchUsage(options = {}) {
|
|
|
502
497
|
for (let i = 0; i < iterations; i += 1) {
|
|
503
498
|
const summary = getUsageSummary(options);
|
|
504
499
|
summary.auto = Boolean(options.auto);
|
|
500
|
+
summary.agents = Boolean(options.agents);
|
|
505
501
|
summary.live = buildLiveSessionView(summary);
|
|
502
|
+
if (options.agents) {
|
|
503
|
+
summary.multiAgent = buildMultiAgentView(summary);
|
|
504
|
+
}
|
|
506
505
|
if (options.guardrails) {
|
|
507
506
|
const written = writeLiveGuardrails(summary);
|
|
508
507
|
summary.guardrailsPath = written.guardrailsPath;
|
|
@@ -524,7 +523,11 @@ async function watchUsage(options = {}) {
|
|
|
524
523
|
}
|
|
525
524
|
summary.redactPaths = Boolean(options.redactPaths);
|
|
526
525
|
summary.includeRescuePrompt = Boolean(options.rescue);
|
|
527
|
-
if (options.
|
|
526
|
+
if (options.agents && !options.json) {
|
|
527
|
+
console.clear();
|
|
528
|
+
console.log(renderMultiAgentWatchTerminal(summary));
|
|
529
|
+
if (!options.once) console.log(`\nRefreshing every ${Math.round(intervalMs / 1000)}s. Press Ctrl+C to stop.`);
|
|
530
|
+
} else if (options.rescue && !options.json) {
|
|
528
531
|
console.log(renderRescuePrompt(summary));
|
|
529
532
|
} else if (options.json) {
|
|
530
533
|
console.log(JSON.stringify(toWatchJsonPayload(summary), null, 2));
|
|
@@ -345,8 +345,111 @@ module.exports = function createWatchLive(deps) {
|
|
|
345
345
|
};
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
function agentLabel(session, index) {
|
|
349
|
+
const tool = session.tool === "claude-code" ? "claude" : session.tool || "agent";
|
|
350
|
+
const id = String(session.sessionId || index + 1).slice(0, 8);
|
|
351
|
+
return `${tool}-${id}`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function buildMultiAgentView(summary) {
|
|
355
|
+
const agents = (summary.sessions || []).map((session, index) => {
|
|
356
|
+
const singleSummary = {
|
|
357
|
+
...summary,
|
|
358
|
+
sessions: [session],
|
|
359
|
+
totals: {
|
|
360
|
+
sessions: 1,
|
|
361
|
+
displayTokens: session.displayTokens || 0,
|
|
362
|
+
contextTokens: session.contextTokens || 0,
|
|
363
|
+
estimatedTokens: session.estimatedTotalTokens || 0,
|
|
364
|
+
exactTokens: session.exactAvailable ? session.exactTotalTokens || 0 : 0,
|
|
365
|
+
toolTokens: session.estimatedToolTokens || 0,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
const live = buildLiveSessionView(singleSummary);
|
|
369
|
+
return {
|
|
370
|
+
label: agentLabel(session, index),
|
|
371
|
+
tool: session.tool,
|
|
372
|
+
sessionId: session.sessionId,
|
|
373
|
+
title: session.title,
|
|
374
|
+
model: session.model,
|
|
375
|
+
updatedAt: session.updatedAt,
|
|
376
|
+
contextPressure: live.contextPressure,
|
|
377
|
+
tokens: session.displayTokens || 0,
|
|
378
|
+
contextTokens: session.contextTokens || 0,
|
|
379
|
+
toolOutputTokens: session.estimatedToolTokens || 0,
|
|
380
|
+
turns: session.turns || 0,
|
|
381
|
+
toolCalls: session.toolCalls || 0,
|
|
382
|
+
liveAction: live.liveAction,
|
|
383
|
+
warnings: live.warnings,
|
|
384
|
+
repeatedFiles: live.activeSession?.actionableRepeatedPaths || [],
|
|
385
|
+
artifactGroups: live.activeSession?.generatedArtifactGroups || [],
|
|
386
|
+
repeatedCommands: session.repeatedCommands || [],
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const warnings = [];
|
|
391
|
+
const highPressureAgents = agents.filter((agent) => agent.contextPressure === "High");
|
|
392
|
+
if (highPressureAgents.length >= 2) {
|
|
393
|
+
warnings.push(`${highPressureAgents.length} agents are under high context pressure; pause broad exploration and split work by task boundary.`);
|
|
394
|
+
}
|
|
395
|
+
const noisyAgents = agents.filter((agent) => ["tool-output-flood", "possible-loop"].includes(agent.liveAction?.cause));
|
|
396
|
+
if (noisyAgents.length) {
|
|
397
|
+
warnings.push(`${noisyAgents.length} agent${noisyAgents.length === 1 ? "" : "s"} should route noisy commands through shield before rerunning.`);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const fileOwners = new Map();
|
|
401
|
+
for (const agent of agents) {
|
|
402
|
+
for (const item of agent.repeatedFiles || []) {
|
|
403
|
+
const owners = fileOwners.get(item.value) || [];
|
|
404
|
+
owners.push({ agent: agent.label, count: item.count });
|
|
405
|
+
fileOwners.set(item.value, owners);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const sharedFiles = Array.from(fileOwners.entries())
|
|
409
|
+
.filter(([, owners]) => owners.length >= 2)
|
|
410
|
+
.map(([path, owners]) => ({ path, owners }))
|
|
411
|
+
.slice(0, 5);
|
|
412
|
+
for (const item of sharedFiles.slice(0, 3)) {
|
|
413
|
+
warnings.push(`${item.path} is repeatedly entering context across ${item.owners.length} agents.`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const artifactOwners = new Map();
|
|
417
|
+
for (const agent of agents) {
|
|
418
|
+
for (const group of agent.artifactGroups || []) {
|
|
419
|
+
const owners = artifactOwners.get(group.type) || [];
|
|
420
|
+
owners.push({ agent: agent.label, count: group.count, example: group.examples?.[0] || null });
|
|
421
|
+
artifactOwners.set(group.type, owners);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const sharedArtifacts = Array.from(artifactOwners.entries())
|
|
425
|
+
.filter(([, owners]) => owners.length >= 2)
|
|
426
|
+
.map(([type, owners]) => ({ type, owners }))
|
|
427
|
+
.slice(0, 5);
|
|
428
|
+
for (const item of sharedArtifacts.slice(0, 2)) {
|
|
429
|
+
warnings.push(`${item.type} artifacts appear across ${item.owners.length} agents; add/verify ignore coverage before continuing.`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const recommendedActions = [];
|
|
433
|
+
if (noisyAgents.length) recommendedActions.push(`${NPX_COMMAND} shield -- <noisy command>`);
|
|
434
|
+
if (sharedFiles.length || sharedArtifacts.length) recommendedActions.push(`${NPX_COMMAND} doctor --apply-suggestions --dry-run`);
|
|
435
|
+
if (highPressureAgents.length) recommendedActions.push("Ask each high-pressure agent for a compact handoff summary, then restart scoped sessions.");
|
|
436
|
+
if (!recommendedActions.length) recommendedActions.push("Keep agents scoped to separate files/tasks and continue watching.");
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
enabled: true,
|
|
440
|
+
agentCount: agents.length,
|
|
441
|
+
agents,
|
|
442
|
+
highestPressure: agents.reduce((pressure, agent) => (getRiskRank(agent.contextPressure) > getRiskRank(pressure) ? agent.contextPressure : pressure), "Low"),
|
|
443
|
+
coordinationWarnings: Array.from(new Set(warnings)).slice(0, 8),
|
|
444
|
+
sharedFiles,
|
|
445
|
+
sharedArtifacts,
|
|
446
|
+
recommendedActions: Array.from(new Set(recommendedActions)).slice(0, 5),
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
348
450
|
return {
|
|
349
451
|
buildLiveSessionView,
|
|
452
|
+
buildMultiAgentView,
|
|
350
453
|
getRiskRank,
|
|
351
454
|
getTokenBudgetStatus,
|
|
352
455
|
};
|
|
@@ -2,6 +2,7 @@ module.exports = function createWatchRender(deps) {
|
|
|
2
2
|
const {
|
|
3
3
|
NPX_COMMAND,
|
|
4
4
|
buildLiveSessionView,
|
|
5
|
+
buildMultiAgentView,
|
|
5
6
|
color,
|
|
6
7
|
formatTokenCount,
|
|
7
8
|
getActionableRepeatedPaths,
|
|
@@ -33,6 +34,15 @@ function renderUsageTerminal(summary, title = "Prismo Usage") {
|
|
|
33
34
|
}
|
|
34
35
|
lines.push("");
|
|
35
36
|
lines.push("Notes: exact means the local tool log exposed token fields. Estimated means Prismo used local text size heuristics only.");
|
|
37
|
+
const multi = summary.multiAgent || ((summary.sessions || []).length > 1 && buildMultiAgentView ? buildMultiAgentView(summary) : null);
|
|
38
|
+
if (multi && multi.agentCount > 1) {
|
|
39
|
+
lines.push("");
|
|
40
|
+
lines.push("Multi-Agent Coordination:");
|
|
41
|
+
lines.push(`- Agents visible: ${multi.agentCount}`);
|
|
42
|
+
lines.push(`- Highest pressure: ${multi.highestPressure}`);
|
|
43
|
+
if (multi.coordinationWarnings.length) lines.push(`- Top warning: ${multi.coordinationWarnings[0]}`);
|
|
44
|
+
lines.push(`- Live view: ${NPX_COMMAND} watch --agents`);
|
|
45
|
+
}
|
|
36
46
|
return lines.join("\n");
|
|
37
47
|
}
|
|
38
48
|
function renderWatchTerminal(summary) {
|
|
@@ -107,6 +117,60 @@ function renderWatchTerminal(summary) {
|
|
|
107
117
|
lines.push("Expected project instruction files are muted unless they combine with stronger context-pressure signals.");
|
|
108
118
|
return lines.join("\n");
|
|
109
119
|
}
|
|
120
|
+
function renderMultiAgentWatchTerminal(summary) {
|
|
121
|
+
const multi = summary.multiAgent;
|
|
122
|
+
const lines = [];
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(color("Prismo Multi-Agent Watch", "bold"));
|
|
125
|
+
lines.push("");
|
|
126
|
+
if (!multi || !multi.agentCount) {
|
|
127
|
+
lines.push("Agents: 0");
|
|
128
|
+
lines.push("- No local Codex/Claude Code sessions detected for this repo yet.");
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push("Suggested Action");
|
|
131
|
+
lines.push(`Run: ${NPX_COMMAND} setup`);
|
|
132
|
+
return lines.join("\n");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const pressureTone = multi.highestPressure === "High" ? "red" : multi.highestPressure === "Medium" ? "yellow" : "green";
|
|
136
|
+
lines.push(`Agents: ${multi.agentCount}`);
|
|
137
|
+
lines.push(`Highest Pressure: ${color(String(multi.highestPressure).toUpperCase(), pressureTone)}`);
|
|
138
|
+
lines.push("");
|
|
139
|
+
lines.push("Active Agents");
|
|
140
|
+
multi.agents.slice(0, 8).forEach((agent) => {
|
|
141
|
+
const tone = agent.contextPressure === "High" ? "red" : agent.contextPressure === "Medium" ? "yellow" : "green";
|
|
142
|
+
lines.push(`- ${agent.label} ${agent.tool} ${color(agent.contextPressure.toUpperCase(), tone)} ${formatTokenCount(agent.tokens)} tokens ${agent.liveAction.cause}`);
|
|
143
|
+
if (agent.updatedAt) lines.push(` updated: ${agent.updatedAt}`);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
lines.push("");
|
|
147
|
+
lines.push("Coordination Warnings");
|
|
148
|
+
if (multi.coordinationWarnings.length) multi.coordinationWarnings.forEach((warning) => lines.push(`- ${warning}`));
|
|
149
|
+
else lines.push("- No cross-agent coordination risks detected.");
|
|
150
|
+
|
|
151
|
+
if (multi.sharedFiles.length) {
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push("Shared Repeated Files");
|
|
154
|
+
multi.sharedFiles.slice(0, 5).forEach((item) => {
|
|
155
|
+
lines.push(`- ${item.path} (${item.owners.map((owner) => `${owner.agent}:${owner.count}x`).join(", ")})`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (multi.sharedArtifacts.length) {
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push("Shared Artifact Leaks");
|
|
162
|
+
multi.sharedArtifacts.slice(0, 5).forEach((item) => {
|
|
163
|
+
lines.push(`- ${item.type} (${item.owners.map((owner) => `${owner.agent}:${owner.count}x`).join(", ")})`);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("Do This Now");
|
|
169
|
+
multi.recommendedActions.forEach((action, index) => lines.push(`${index + 1}. ${action}`));
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push("Signals are local estimates from available coding-agent logs. Use this view to coordinate parallel agents before they duplicate work or flood context.");
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|
|
110
174
|
function renderRescuePrompt(summary) {
|
|
111
175
|
const live = summary.live || buildLiveSessionView(summary);
|
|
112
176
|
const active = live.activeSession;
|
|
@@ -283,6 +347,7 @@ function renderContextThrottle(summary) {
|
|
|
283
347
|
return {
|
|
284
348
|
renderContextThrottle,
|
|
285
349
|
renderLiveGuardrails,
|
|
350
|
+
renderMultiAgentWatchTerminal,
|
|
286
351
|
renderRescuePrompt,
|
|
287
352
|
renderUsageTerminal,
|
|
288
353
|
renderWatchTerminal,
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -202,6 +202,7 @@ const {
|
|
|
202
202
|
const {
|
|
203
203
|
renderFirewallTerminal,
|
|
204
204
|
runFirewall,
|
|
205
|
+
runTimelineFirewallSuggestions,
|
|
205
206
|
} = require("./prismo-dev/firewall")({
|
|
206
207
|
fs,
|
|
207
208
|
path,
|
|
@@ -263,9 +264,9 @@ Usage:
|
|
|
263
264
|
prismo scan [--fix] [--ci] [--json] [--usage] [--optimizer-fit] [--report-card] [--simple] [--no-report] [path]
|
|
264
265
|
prismo optimize [scope] [--json] [path]
|
|
265
266
|
prismo context [scope] [--json] [path]
|
|
266
|
-
prismo cc [list|last N|all] [--json] [--limit N] [path]
|
|
267
|
+
prismo cc [list|last N|all|timeline] [--json] [--limit N] [--firewall] [--task TASK] [path]
|
|
267
268
|
prismo usage [codex|claude|all] [--json] [--limit N] [path]
|
|
268
|
-
prismo watch [codex|claude|all] [--json] [--once] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
269
|
+
prismo watch [codex|claude|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
269
270
|
prismo demo
|
|
270
271
|
|
|
271
272
|
Commands:
|
|
@@ -295,12 +296,15 @@ Options:
|
|
|
295
296
|
--simple Print a plain-English scan summary for first-time or non-technical users.
|
|
296
297
|
--no-report Do not write .prismo/prismo-dev-report.md.
|
|
297
298
|
--limit N Number of recent local sessions to show.
|
|
299
|
+
--firewall Generate cc timeline-derived firewall suggestion files.
|
|
300
|
+
--task TASK Name the task for timeline-derived firewall suggestions.
|
|
298
301
|
--interval N Refresh interval in seconds for watch mode.
|
|
299
302
|
--dry-run Preview doctor/fix actions without writing files.
|
|
300
303
|
--apply-ignores-only Only create/suggest AI ignore files in doctor mode.
|
|
301
304
|
--apply-suggestions Append missing recommended ignore rules with backups.
|
|
302
305
|
--no-context-packs Skip .prismo context-pack generation in doctor mode.
|
|
303
306
|
--report Write .prismo/watch-report.md in watch mode.
|
|
307
|
+
--agents Show multi-agent coordination view for parallel local sessions.
|
|
304
308
|
--rescue Print a paste-ready live-session rescue prompt in watch mode.
|
|
305
309
|
--guardrails Write/update .prismo/live-guardrails.md and .prismo/live-rescue-prompt.md.
|
|
306
310
|
--throttle Write/update .prismo/live-context-throttle.md in watch mode.
|
|
@@ -376,7 +380,7 @@ Output:
|
|
|
376
380
|
|
|
377
381
|
Usage:
|
|
378
382
|
prismo cc [--json] [path]
|
|
379
|
-
prismo cc timeline [--json] [path]
|
|
383
|
+
prismo cc timeline [--json] [--firewall] [--task TASK] [path]
|
|
380
384
|
prismo cc list [--json] [--limit N] [path]
|
|
381
385
|
prismo cc last N [--json] [path]
|
|
382
386
|
prismo cc all [--json] [path]
|
|
@@ -384,6 +388,7 @@ Usage:
|
|
|
384
388
|
Examples:
|
|
385
389
|
prismo cc
|
|
386
390
|
prismo cc timeline
|
|
391
|
+
prismo cc timeline --firewall --task auth-bug
|
|
387
392
|
prismo cc list
|
|
388
393
|
prismo cc last 5
|
|
389
394
|
prismo cc all --json
|
|
@@ -392,6 +397,7 @@ Examples:
|
|
|
392
397
|
Reads ~/.claude/projects session logs and estimates Claude API token cost from input, output, cache write, and cache read tokens.
|
|
393
398
|
Adds Prismo diagnosis: cost drivers, estimated avoidable spend, and context-optimization next actions.
|
|
394
399
|
timeline shows context spikes, repeated commands, artifact leaks, and tool-output pressure for the latest session.
|
|
400
|
+
--firewall writes .prismo/timeline-firewall-suggestions.md and suggested allow/block files from timeline evidence.
|
|
395
401
|
Without a path, cc commands read all Claude Code projects. Passing a path filters to that project.`,
|
|
396
402
|
usage: `Prismo Usage
|
|
397
403
|
|
|
@@ -405,11 +411,12 @@ Examples:
|
|
|
405
411
|
watch: `Prismo Watch
|
|
406
412
|
|
|
407
413
|
Usage:
|
|
408
|
-
prismo watch [codex|claude|all] [--json] [--once] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
414
|
+
prismo watch [codex|claude|all] [--json] [--once] [--agents] [--report] [--rescue] [--guardrails] [--throttle] [--events] [--no-events] [--auto] [--budget N] [--redact-paths] [--interval N] [path]
|
|
409
415
|
|
|
410
416
|
Examples:
|
|
411
417
|
prismo watch codex
|
|
412
418
|
prismo watch claude --once --json
|
|
419
|
+
prismo watch --agents --once
|
|
413
420
|
prismo watch --once --report
|
|
414
421
|
prismo watch --once --redact-paths
|
|
415
422
|
prismo watch --rescue
|
|
@@ -421,6 +428,7 @@ Examples:
|
|
|
421
428
|
|
|
422
429
|
Output:
|
|
423
430
|
Shows context pressure, recent growth, likely context leaks, repeated reads/commands, loop suspicion, one suggested action, and live intervention steps.
|
|
431
|
+
--agents shows all visible local sessions for this repo and coordination warnings.
|
|
424
432
|
--rescue prints a paste-ready prompt to recover a noisy or looping active session.
|
|
425
433
|
--guardrails continuously updates .prismo/live-guardrails.md and .prismo/live-rescue-prompt.md for the active session.
|
|
426
434
|
--throttle writes a stricter live context budget file for the current agent session.
|
|
@@ -756,7 +764,17 @@ async function runCli(argv) {
|
|
|
756
764
|
if (command === "cc") {
|
|
757
765
|
const json = rest.includes("--json");
|
|
758
766
|
const limitIndex = rest.indexOf("--limit");
|
|
759
|
-
const
|
|
767
|
+
const firewall = rest.includes("--firewall");
|
|
768
|
+
const taskIndex = rest.indexOf("--task");
|
|
769
|
+
const firewallTask = taskIndex >= 0 && rest[taskIndex + 1] && !rest[taskIndex + 1].startsWith("-")
|
|
770
|
+
? rest[taskIndex + 1]
|
|
771
|
+
: "timeline-followup";
|
|
772
|
+
const ccArgs = rest.filter((_, index) => {
|
|
773
|
+
if (index === rest.indexOf("--firewall")) return false;
|
|
774
|
+
if (taskIndex >= 0 && (index === taskIndex || index === taskIndex + 1)) return false;
|
|
775
|
+
return true;
|
|
776
|
+
});
|
|
777
|
+
const positional = getPositionals(ccArgs, new Set(["--limit"]));
|
|
760
778
|
const subcommand = positional[0] && ["list", "last", "all", "timeline"].includes(positional[0].toLowerCase()) ? positional[0].toLowerCase() : "latest";
|
|
761
779
|
const lastCount = subcommand === "last" ? parsePositiveInt(positional[1], 5) : null;
|
|
762
780
|
const limit = subcommand === "list"
|
|
@@ -777,6 +795,9 @@ async function runCli(argv) {
|
|
|
777
795
|
if (json) {
|
|
778
796
|
if (subcommand === "timeline") {
|
|
779
797
|
const latest = summary.sessions[0] || null;
|
|
798
|
+
const firewallSuggestions = firewall && latest
|
|
799
|
+
? runTimelineFirewallSuggestions(path.resolve(target), latest, { task: firewallTask, dryRun: false })
|
|
800
|
+
: null;
|
|
780
801
|
console.log(JSON.stringify({
|
|
781
802
|
schemaVersion: 1,
|
|
782
803
|
generatedAt: summary.generatedAt,
|
|
@@ -793,6 +814,7 @@ async function runCli(argv) {
|
|
|
793
814
|
}
|
|
794
815
|
: null,
|
|
795
816
|
timeline: latest ? latest.timeline || [] : [],
|
|
817
|
+
firewallSuggestions,
|
|
796
818
|
suggestedAction: latest?.prismo?.recommendations?.[0] || `${NPX_COMMAND} doctor`,
|
|
797
819
|
}, null, 2));
|
|
798
820
|
return;
|
|
@@ -800,7 +822,20 @@ async function runCli(argv) {
|
|
|
800
822
|
console.log(JSON.stringify(summary, null, 2));
|
|
801
823
|
return;
|
|
802
824
|
}
|
|
803
|
-
|
|
825
|
+
const output = [renderClaudeCostTerminal(summary)];
|
|
826
|
+
if (subcommand === "timeline" && firewall) {
|
|
827
|
+
const latest = summary.sessions[0] || null;
|
|
828
|
+
if (latest) {
|
|
829
|
+
const suggestions = runTimelineFirewallSuggestions(path.resolve(target), latest, { task: firewallTask, dryRun: false });
|
|
830
|
+
output.push("");
|
|
831
|
+
output.push("Timeline Firewall Suggestions");
|
|
832
|
+
output.push(`Wrote: ${suggestions.generatedFiles.join(", ")}`);
|
|
833
|
+
output.push(`Session-derived allowed: ${suggestions.sessionAllowed.length}`);
|
|
834
|
+
output.push(`Session-derived blocked: ${suggestions.sessionBlocked.length}`);
|
|
835
|
+
output.push("Tell your agent: Use .prismo/context-firewall.suggested.md for the next scoped session.");
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
console.log(output.join("\n"));
|
|
804
839
|
return;
|
|
805
840
|
}
|
|
806
841
|
|
|
@@ -825,6 +860,7 @@ async function runCli(argv) {
|
|
|
825
860
|
limit,
|
|
826
861
|
tokenBudget,
|
|
827
862
|
auto,
|
|
863
|
+
agents: rest.includes("--agents"),
|
|
828
864
|
json,
|
|
829
865
|
once: rest.includes("--once"),
|
|
830
866
|
report: rest.includes("--report"),
|
package/package.json
CHANGED