getprismo 0.1.54 → 0.1.56
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/lib/prismo-dev/agent.js +3 -3
- package/lib/prismo-dev/cli.js +40 -8
- package/lib/prismo-dev/cloud-sync.js +23 -3
- package/lib/prismo-dev/connector.js +6 -3
- package/lib/prismo-dev/help.js +4 -0
- package/lib/prismo-dev/sessions-report.js +196 -0
- package/lib/prismo-dev-scan.js +16 -0
- package/package.json +1 -1
package/lib/prismo-dev/agent.js
CHANGED
|
@@ -905,9 +905,9 @@ module.exports = function createAgent(deps) {
|
|
|
905
905
|
async function runAgent(rootDir = process.cwd(), options = {}) {
|
|
906
906
|
if (!options.watch) return runAgentOnce(rootDir, options);
|
|
907
907
|
|
|
908
|
-
const intervalMs = Math.max(
|
|
909
|
-
const syncIntervalMs = Math.max(
|
|
910
|
-
const detectIntervalMs = Math.max(
|
|
908
|
+
const intervalMs = Math.max(15, Number(options.interval || 60)) * 1000;
|
|
909
|
+
const syncIntervalMs = Math.max(60, Number(options.syncInterval || 300)) * 1000;
|
|
910
|
+
const detectIntervalMs = Math.max(300, Number(options.detectInterval || 900)) * 1000;
|
|
911
911
|
const plannerIntervalMs = Math.max(60, Number(options.plannerInterval || 600)) * 1000;
|
|
912
912
|
let running = true;
|
|
913
913
|
let sleepResolve = null;
|
package/lib/prismo-dev/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ const { printHelp, printCommandHelp } = require("./help");
|
|
|
3
3
|
|
|
4
4
|
const VALID_COMMANDS = new Set([
|
|
5
5
|
"dev", "init", "doctor", "firewall", "benchmark", "shield", "mcp",
|
|
6
|
-
"connect", "sync", "status", "disconnect", "agent", "connector", "setup", "scan", "digest",
|
|
6
|
+
"connect", "sync", "status", "disconnect", "agent", "connector", "setup", "scan", "digest", "sessions", "report",
|
|
7
7
|
"optimize", "context", "cc", "cursor", "receipt", "instructions",
|
|
8
8
|
"timeline", "replay", "boundaries", "usage", "guard", "watch", "demo", "repair",
|
|
9
9
|
"enforce", "bridge", "hook", "protect",
|
|
@@ -69,6 +69,10 @@ function createCli(deps) {
|
|
|
69
69
|
runDisconnect,
|
|
70
70
|
runStatus,
|
|
71
71
|
runSync,
|
|
72
|
+
buildSessionsView,
|
|
73
|
+
renderSessionsTerminal,
|
|
74
|
+
buildLocalReport,
|
|
75
|
+
renderLocalReportTerminal,
|
|
72
76
|
renderGuardTerminal,
|
|
73
77
|
runGuard,
|
|
74
78
|
REPAIR_CAUSES,
|
|
@@ -353,8 +357,8 @@ function createCli(deps) {
|
|
|
353
357
|
});
|
|
354
358
|
if (result.connected && !rest.includes("--no-agent") && runConnectorInstall) {
|
|
355
359
|
result.connector = runConnectorInstall(target, {
|
|
356
|
-
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null,
|
|
357
|
-
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null,
|
|
360
|
+
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 60),
|
|
361
|
+
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 300),
|
|
358
362
|
mode: modeValue,
|
|
359
363
|
dryRun: rest.includes("--dry-run"),
|
|
360
364
|
});
|
|
@@ -385,8 +389,8 @@ function createCli(deps) {
|
|
|
385
389
|
let result;
|
|
386
390
|
if (action === "install") {
|
|
387
391
|
result = runConnectorInstall(target, {
|
|
388
|
-
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null,
|
|
389
|
-
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null,
|
|
392
|
+
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 60),
|
|
393
|
+
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 300),
|
|
390
394
|
mode: modeValue,
|
|
391
395
|
dryRun: rest.includes("--dry-run"),
|
|
392
396
|
});
|
|
@@ -458,9 +462,9 @@ function createCli(deps) {
|
|
|
458
462
|
autoDetect: !rest.includes("--no-detect"),
|
|
459
463
|
planRepairs: !rest.includes("--no-planner"),
|
|
460
464
|
noSync: rest.includes("--no-sync"),
|
|
461
|
-
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null,
|
|
462
|
-
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null,
|
|
463
|
-
detectInterval: parsePositiveInt(detectIntervalIndex >= 0 ? rest[detectIntervalIndex + 1] : null,
|
|
465
|
+
interval: parsePositiveInt(intervalIndex >= 0 ? rest[intervalIndex + 1] : null, 60),
|
|
466
|
+
syncInterval: parsePositiveInt(syncIntervalIndex >= 0 ? rest[syncIntervalIndex + 1] : null, 300),
|
|
467
|
+
detectInterval: parsePositiveInt(detectIntervalIndex >= 0 ? rest[detectIntervalIndex + 1] : null, 900),
|
|
464
468
|
plannerInterval: parsePositiveInt(plannerIntervalIndex >= 0 ? rest[plannerIntervalIndex + 1] : null, 600),
|
|
465
469
|
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 5),
|
|
466
470
|
syncLimit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 20),
|
|
@@ -474,6 +478,34 @@ function createCli(deps) {
|
|
|
474
478
|
return;
|
|
475
479
|
}
|
|
476
480
|
|
|
481
|
+
if (command === "sessions") {
|
|
482
|
+
const json = rest.includes("--json");
|
|
483
|
+
const limitIndex = rest.indexOf("--limit");
|
|
484
|
+
const toolIndex = rest.indexOf("--tool");
|
|
485
|
+
const target = getPositionals(rest, new Set(["--limit", "--tool"]))[0] || process.cwd();
|
|
486
|
+
const view = buildSessionsView(target, {
|
|
487
|
+
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 10),
|
|
488
|
+
tool: toolIndex >= 0 ? rest[toolIndex + 1] : "all",
|
|
489
|
+
allRepos: rest.includes("--all-repos"),
|
|
490
|
+
});
|
|
491
|
+
if (json) console.log(JSON.stringify(view, null, 2));
|
|
492
|
+
else console.log(renderSessionsTerminal(view));
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (command === "report") {
|
|
497
|
+
const json = rest.includes("--json");
|
|
498
|
+
const limitIndex = rest.indexOf("--limit");
|
|
499
|
+
const target = getPositionals(rest, new Set(["--limit"]))[0] || process.cwd();
|
|
500
|
+
const report = buildLocalReport(target, {
|
|
501
|
+
limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 10),
|
|
502
|
+
allRepos: rest.includes("--all-repos"),
|
|
503
|
+
});
|
|
504
|
+
if (json) console.log(JSON.stringify(report, null, 2));
|
|
505
|
+
else console.log(renderLocalReportTerminal(report));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
477
509
|
if (command === "digest") {
|
|
478
510
|
const json = rest.includes("--json");
|
|
479
511
|
const daysIndex = rest.indexOf("--days");
|
|
@@ -378,6 +378,13 @@ module.exports = function createCloudSync(deps) {
|
|
|
378
378
|
};
|
|
379
379
|
}
|
|
380
380
|
|
|
381
|
+
function sessionsFingerprint(sessions) {
|
|
382
|
+
const parts = (sessions || [])
|
|
383
|
+
.map((s) => `${s.sessionId || ""}:${s.updatedAt || ""}:${s.tokens?.display || 0}:${s.waste?.wastedTokens || 0}`)
|
|
384
|
+
.sort();
|
|
385
|
+
return `${parts.length}|${parts.join("|")}`;
|
|
386
|
+
}
|
|
387
|
+
|
|
381
388
|
async function runSync(rootDir = process.cwd(), options = {}) {
|
|
382
389
|
const config = loadConfig();
|
|
383
390
|
const payload = buildSyncPayload(rootDir, {
|
|
@@ -408,16 +415,29 @@ module.exports = function createCloudSync(deps) {
|
|
|
408
415
|
};
|
|
409
416
|
}
|
|
410
417
|
const endpoint = options.endpoint || `${String(config.apiUrl || DEFAULT_API_URL).replace(/\/$/, "")}/v1/dev/sessions/sync`;
|
|
418
|
+
|
|
419
|
+
// Skip the upload when nothing changed since the last sync. The connector
|
|
420
|
+
// re-runs on a short interval, but session data only changes when the user
|
|
421
|
+
// is actually coding, so most syncs are no-ops; sending them anyway is the
|
|
422
|
+
// main driver of needless backend load and network egress.
|
|
423
|
+
const fingerprint = sessionsFingerprint(payload.sessions);
|
|
424
|
+
if (!options.force) {
|
|
425
|
+
const previous = readJson(statePath());
|
|
426
|
+
if (previous && previous.fingerprint === fingerprint) {
|
|
427
|
+
return { schemaVersion: 1, command: "sync", synced: true, skipped: true, reason: "unchanged", statePath: statePath(), aggregate: payload.aggregate };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
411
431
|
const response = await requestJson("POST", endpoint, config.token, payload, options.timeoutMs || 8000);
|
|
412
|
-
|
|
432
|
+
writeJson(statePath(), {
|
|
413
433
|
schemaVersion: 1,
|
|
414
434
|
lastSyncAt: new Date().toISOString(),
|
|
415
435
|
endpoint,
|
|
436
|
+
fingerprint,
|
|
416
437
|
repo: payload.repo,
|
|
417
438
|
aggregate: payload.aggregate,
|
|
418
439
|
response: response.data,
|
|
419
|
-
};
|
|
420
|
-
writeJson(statePath(), state);
|
|
440
|
+
});
|
|
421
441
|
return {
|
|
422
442
|
schemaVersion: 1,
|
|
423
443
|
command: "sync",
|
|
@@ -78,9 +78,12 @@ module.exports = function createConnector(deps) {
|
|
|
78
78
|
|
|
79
79
|
function writeRunner(rootDir, options = {}) {
|
|
80
80
|
const root = path.resolve(rootDir || process.cwd());
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
// Background poll cadence. Kept conservative so an always-on connector
|
|
82
|
+
// stays light on API/database traffic; agent sessions change on the order
|
|
83
|
+
// of minutes, not seconds, so fast polling only adds idle load.
|
|
84
|
+
const interval = Math.max(15, Number(options.interval || 60));
|
|
85
|
+
const syncInterval = Math.max(60, Number(options.syncInterval || 300));
|
|
86
|
+
const detectInterval = Math.max(300, Number(options.detectInterval || 900));
|
|
84
87
|
const mode = options.mode || "autopilot";
|
|
85
88
|
fs.mkdirSync(connectorDir(), { recursive: true });
|
|
86
89
|
const command = `${BACKGROUND_COMMAND} agent --watch --interval ${interval} --sync-interval ${syncInterval} --detect-interval ${detectInterval} --mode ${shellEscape(mode)} ${shellEscape(root)}`;
|
package/lib/prismo-dev/help.js
CHANGED
|
@@ -17,6 +17,8 @@ Usage:
|
|
|
17
17
|
prismo bridge [--json] [path]
|
|
18
18
|
prismo sync [--json] [--dry-run] [--watch] [--all-repos] [--interval N] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
19
19
|
prismo status [--json]
|
|
20
|
+
prismo sessions [--json] [--all-repos] [--limit N] [--tool all|codex|claude|cursor] [path]
|
|
21
|
+
prismo report [--json] [--all-repos] [--limit N] [path]
|
|
20
22
|
prismo digest [--json] [--days N]
|
|
21
23
|
prismo disconnect [--json]
|
|
22
24
|
prismo agent [--json] [--once] [--watch] [--interval N] [--sync-interval N] [--limit N] [--mode MODE] [path]
|
|
@@ -54,6 +56,8 @@ Commands:
|
|
|
54
56
|
bridge Explain optional agent bridge mode and live interception levels.
|
|
55
57
|
sync Send safe aggregate local agent telemetry to Prismo; use --watch for background-style sync.
|
|
56
58
|
status Show local PrismoDev connection and last sync state.
|
|
59
|
+
sessions List recent local agent sessions with tokens, waste, risk, and top cause (add --all-repos for every repo).
|
|
60
|
+
report Local waste snapshot: observed/wasted tokens, top causes, repeated reads/commands, and the next action. No connection needed.
|
|
57
61
|
digest Print the launch report: verified saved tokens/dollars first, with live prevention labeled estimated.
|
|
58
62
|
disconnect Remove the local PrismoDev cloud connection.
|
|
59
63
|
agent Claim and execute safe workspace actions queued from Prismo Cloud.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
module.exports = function createSessionsReport(deps) {
|
|
2
|
+
const {
|
|
3
|
+
path,
|
|
4
|
+
getUsageSummary,
|
|
5
|
+
estimateWaste,
|
|
6
|
+
formatTokenCount,
|
|
7
|
+
} = deps;
|
|
8
|
+
|
|
9
|
+
const CAUSE_LABELS = {
|
|
10
|
+
"tool-output-flood": "Tool-output floods",
|
|
11
|
+
"repeated-file-reads": "Repeated file reads",
|
|
12
|
+
"generated-artifacts": "Generated artifacts",
|
|
13
|
+
"context-loop": "Context loops",
|
|
14
|
+
"long-session-buildup": "Long-session buildup",
|
|
15
|
+
"low-signal": "No strong waste signal",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const NEXT_ACTION = {
|
|
19
|
+
"tool-output-flood": "Run noisy commands through `prismo shield -- <command>` so output stays out of context.",
|
|
20
|
+
"repeated-file-reads": "Run `prismo repair repeated-file-reads`, then start sessions from .prismo context packs.",
|
|
21
|
+
"generated-artifacts": "Run `prismo repair generated-artifacts` to ignore build output and artifacts.",
|
|
22
|
+
"context-loop": "Run `prismo repair context-loop` and stop retrying failing commands.",
|
|
23
|
+
"long-session-buildup": "Split work at task boundaries; start fresh sessions from `prismo context`.",
|
|
24
|
+
"low-signal": "Nothing urgent. Run `prismo doctor` for a baseline.",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function repoBasename(session, fallbackRoot) {
|
|
28
|
+
const cwd = session && session.cwd ? session.cwd : fallbackRoot;
|
|
29
|
+
try {
|
|
30
|
+
return path.basename(path.resolve(cwd || process.cwd()));
|
|
31
|
+
} catch {
|
|
32
|
+
return "unknown";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function collect(rootDir, options = {}) {
|
|
37
|
+
const root = path.resolve(rootDir || process.cwd());
|
|
38
|
+
const summary = getUsageSummary({
|
|
39
|
+
cwd: root,
|
|
40
|
+
tool: options.tool || "all",
|
|
41
|
+
limit: options.limit || 10,
|
|
42
|
+
allRepos: Boolean(options.allRepos),
|
|
43
|
+
});
|
|
44
|
+
const sessions = (summary.sessions || []).map((session) => {
|
|
45
|
+
const waste = estimateWaste(session);
|
|
46
|
+
return {
|
|
47
|
+
tool: session.tool || "unknown",
|
|
48
|
+
repo: repoBasename(session, root),
|
|
49
|
+
title: session.title || null,
|
|
50
|
+
model: session.model || null,
|
|
51
|
+
updatedAt: session.updatedAt || session.startedAt || null,
|
|
52
|
+
risk: session.contextRisk || "Unknown",
|
|
53
|
+
tokens: Number(waste.tokens || 0),
|
|
54
|
+
wastedTokens: Number(waste.wastedTokens || 0),
|
|
55
|
+
wastePercent: Number(waste.wastePercent || 0),
|
|
56
|
+
topCause: waste.topCause || "low-signal",
|
|
57
|
+
repeatedCommands: session.repeatedCommands || [],
|
|
58
|
+
repeatedPaths: session.repeatedPathMentions || [],
|
|
59
|
+
loopSuspicion: Boolean(session.loopSuspicion),
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
return { root, sessions, generatedAt: new Date().toISOString() };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function buildSessionsView(rootDir, options = {}) {
|
|
66
|
+
const { root, sessions, generatedAt } = collect(rootDir, options);
|
|
67
|
+
const totals = sessions.reduce((acc, s) => {
|
|
68
|
+
acc.sessions += 1;
|
|
69
|
+
acc.tokens += s.tokens;
|
|
70
|
+
acc.wastedTokens += s.wastedTokens;
|
|
71
|
+
return acc;
|
|
72
|
+
}, { sessions: 0, tokens: 0, wastedTokens: 0 });
|
|
73
|
+
totals.wastePercent = totals.tokens > 0 ? Math.round((totals.wastedTokens / totals.tokens) * 100) : 0;
|
|
74
|
+
return {
|
|
75
|
+
schemaVersion: 1,
|
|
76
|
+
command: "sessions",
|
|
77
|
+
scannedPath: root,
|
|
78
|
+
allRepos: Boolean(options.allRepos),
|
|
79
|
+
sessions: sessions.sort((a, b) => String(b.updatedAt || "").localeCompare(String(a.updatedAt || ""))),
|
|
80
|
+
totals,
|
|
81
|
+
generatedAt,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function renderSessionsTerminal(view) {
|
|
86
|
+
const lines = [];
|
|
87
|
+
lines.push("");
|
|
88
|
+
lines.push("PrismoDev Sessions");
|
|
89
|
+
lines.push("");
|
|
90
|
+
if (!view.sessions.length) {
|
|
91
|
+
lines.push("No recent local agent sessions found.");
|
|
92
|
+
lines.push(view.allRepos ? "" : "Tip: add --all-repos to look across every repo on this machine.");
|
|
93
|
+
return lines.join("\n").trimEnd();
|
|
94
|
+
}
|
|
95
|
+
for (const s of view.sessions) {
|
|
96
|
+
const when = s.updatedAt ? new Date(s.updatedAt).toLocaleString() : "unknown time";
|
|
97
|
+
lines.push(`${s.tool.padEnd(12)} ${s.repo} · ${when}`);
|
|
98
|
+
lines.push(` ${formatTokenCount(s.tokens)} tokens | ~${formatTokenCount(s.wastedTokens)} wasted (${s.wastePercent}%) | ${s.risk} risk | ${CAUSE_LABELS[s.topCause] || s.topCause}`);
|
|
99
|
+
}
|
|
100
|
+
lines.push("");
|
|
101
|
+
lines.push(`Totals: ${view.totals.sessions} session(s) · ${formatTokenCount(view.totals.tokens)} tokens · ~${formatTokenCount(view.totals.wastedTokens)} likely wasted (${view.totals.wastePercent}%)`);
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildLocalReport(rootDir, options = {}) {
|
|
106
|
+
const { root, sessions, generatedAt } = collect(rootDir, { ...options, allRepos: options.allRepos });
|
|
107
|
+
const observed = sessions.reduce((sum, s) => sum + s.tokens, 0);
|
|
108
|
+
const wasted = sessions.reduce((sum, s) => sum + s.wastedTokens, 0);
|
|
109
|
+
|
|
110
|
+
const causeTotals = new Map();
|
|
111
|
+
for (const s of sessions) {
|
|
112
|
+
if (s.topCause === "low-signal") continue;
|
|
113
|
+
causeTotals.set(s.topCause, (causeTotals.get(s.topCause) || 0) + s.wastedTokens);
|
|
114
|
+
}
|
|
115
|
+
const topCauses = Array.from(causeTotals, ([cause, tokens]) => ({
|
|
116
|
+
cause,
|
|
117
|
+
label: CAUSE_LABELS[cause] || cause,
|
|
118
|
+
tokens,
|
|
119
|
+
})).sort((a, b) => b.tokens - a.tokens).slice(0, 5);
|
|
120
|
+
|
|
121
|
+
const repeatedReads = aggregate(sessions, (s) => s.repeatedPaths).slice(0, 5);
|
|
122
|
+
const repeatedCommands = aggregate(sessions, (s) => s.repeatedCommands).slice(0, 5);
|
|
123
|
+
const loopSessions = sessions.filter((s) => s.loopSuspicion).length;
|
|
124
|
+
const primaryCause = topCauses[0] ? topCauses[0].cause : "low-signal";
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
schemaVersion: 1,
|
|
128
|
+
command: "report",
|
|
129
|
+
scannedPath: root,
|
|
130
|
+
allRepos: Boolean(options.allRepos),
|
|
131
|
+
sessions: sessions.length,
|
|
132
|
+
observedTokens: observed,
|
|
133
|
+
wastedTokens: wasted,
|
|
134
|
+
wastePercent: observed > 0 ? Math.round((wasted / observed) * 100) : 0,
|
|
135
|
+
topCauses,
|
|
136
|
+
repeatedReads,
|
|
137
|
+
repeatedCommands,
|
|
138
|
+
loopSessions,
|
|
139
|
+
nextAction: NEXT_ACTION[primaryCause] || NEXT_ACTION["low-signal"],
|
|
140
|
+
note: "Local estimate from your own session logs. Connect with `prismo connect` for verified, dollar-denominated savings.",
|
|
141
|
+
generatedAt,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function aggregate(sessions, pick) {
|
|
146
|
+
const totals = new Map();
|
|
147
|
+
for (const s of sessions) {
|
|
148
|
+
for (const item of pick(s) || []) {
|
|
149
|
+
if (!item || !item.value) continue;
|
|
150
|
+
totals.set(item.value, (totals.get(item.value) || 0) + Number(item.count || 0));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return Array.from(totals, ([value, count]) => ({ value, count })).sort((a, b) => b.count - a.count);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function renderLocalReportTerminal(report) {
|
|
157
|
+
const lines = [];
|
|
158
|
+
lines.push("");
|
|
159
|
+
lines.push("PrismoDev Report");
|
|
160
|
+
lines.push("");
|
|
161
|
+
if (!report.sessions) {
|
|
162
|
+
lines.push("No recent local agent sessions found.");
|
|
163
|
+
lines.push(report.allRepos ? "" : "Tip: add --all-repos to look across every repo on this machine.");
|
|
164
|
+
return lines.join("\n").trimEnd();
|
|
165
|
+
}
|
|
166
|
+
lines.push(`Observed: ${formatTokenCount(report.observedTokens)} tokens across ${report.sessions} session(s)`);
|
|
167
|
+
lines.push(`Likely wasted: ${formatTokenCount(report.wastedTokens)} (${report.wastePercent}%)`);
|
|
168
|
+
if (report.loopSessions) lines.push(`Loop-suspect sessions: ${report.loopSessions}`);
|
|
169
|
+
if (report.topCauses.length) {
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push("Top causes:");
|
|
172
|
+
report.topCauses.forEach((c) => lines.push(`- ${c.label}: ~${formatTokenCount(c.tokens)} tokens`));
|
|
173
|
+
}
|
|
174
|
+
if (report.repeatedReads.length) {
|
|
175
|
+
lines.push("");
|
|
176
|
+
lines.push("Most repeated reads:");
|
|
177
|
+
report.repeatedReads.forEach((r) => lines.push(`- ${r.value} (${r.count}x)`));
|
|
178
|
+
}
|
|
179
|
+
if (report.repeatedCommands.length) {
|
|
180
|
+
lines.push("");
|
|
181
|
+
lines.push("Most repeated commands:");
|
|
182
|
+
report.repeatedCommands.forEach((r) => lines.push(`- ${r.value} (${r.count}x)`));
|
|
183
|
+
}
|
|
184
|
+
lines.push("");
|
|
185
|
+
lines.push(`Next: ${report.nextAction}`);
|
|
186
|
+
lines.push(report.note);
|
|
187
|
+
return lines.join("\n");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
buildSessionsView,
|
|
192
|
+
renderSessionsTerminal,
|
|
193
|
+
buildLocalReport,
|
|
194
|
+
renderLocalReportTerminal,
|
|
195
|
+
};
|
|
196
|
+
};
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -355,6 +355,18 @@ const {
|
|
|
355
355
|
runFirewall,
|
|
356
356
|
});
|
|
357
357
|
|
|
358
|
+
const {
|
|
359
|
+
buildSessionsView,
|
|
360
|
+
renderSessionsTerminal,
|
|
361
|
+
buildLocalReport,
|
|
362
|
+
renderLocalReportTerminal,
|
|
363
|
+
} = require("./prismo-dev/sessions-report")({
|
|
364
|
+
path,
|
|
365
|
+
getUsageSummary,
|
|
366
|
+
estimateWaste,
|
|
367
|
+
formatTokenCount,
|
|
368
|
+
});
|
|
369
|
+
|
|
358
370
|
const repairPlanner = require("./prismo-dev/repair-planner")({
|
|
359
371
|
fs,
|
|
360
372
|
path,
|
|
@@ -510,6 +522,10 @@ const { runCli } = require("./prismo-dev/cli")({
|
|
|
510
522
|
runDisconnect,
|
|
511
523
|
runStatus,
|
|
512
524
|
runSync,
|
|
525
|
+
buildSessionsView,
|
|
526
|
+
renderSessionsTerminal,
|
|
527
|
+
buildLocalReport,
|
|
528
|
+
renderLocalReportTerminal,
|
|
513
529
|
renderGuardTerminal,
|
|
514
530
|
runGuard,
|
|
515
531
|
REPAIR_CAUSES,
|
package/package.json
CHANGED