getprismo 0.1.54 → 0.1.55

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.
@@ -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,
@@ -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");
@@ -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
+ };
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/shanirsh/prismodev#readme",