getprismo 0.1.2 → 0.1.3

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 CHANGED
@@ -1,9 +1,11 @@
1
- # Prismo Dev Tools
1
+ # PrismoDev
2
2
 
3
3
  Prismo helps developers find and reduce token waste in AI coding workflows. It runs locally, supports Codex and Claude Code, and does not require API keys.
4
4
 
5
5
  ```bash
6
- npx getprismo dev
6
+ npx getprismo scan --usage
7
+ npx getprismo setup
8
+ npx getprismo watch
7
9
  ```
8
10
 
9
11
  ## What It Does
@@ -17,20 +19,29 @@ npx getprismo dev
17
19
  ## Quick Start
18
20
 
19
21
  ```bash
20
- npx getprismo demo
21
- npx getprismo dev
22
+ npx getprismo scan --usage
23
+ npx getprismo setup
24
+ npx getprismo watch --once
22
25
  ```
23
26
 
24
- The guided `dev` command:
27
+ Use this flow to see value immediately:
28
+
29
+ 1. `scan --usage` finds repo/context risks and reads local Codex/Claude Code usage logs when available.
30
+ 2. `setup` shows which tracking modes are possible, including Prismo proxy readiness.
31
+ 3. `watch --once` shows the current live local session view with warnings and next action.
25
32
 
26
- 1. Scans the repo and local coding-agent usage.
27
- 2. Generates optimized context files in `.prismo/`.
28
- 3. Prints a paste-ready prompt for your next AI coding session.
33
+ For a guided scan + context generation flow, run:
34
+
35
+ ```bash
36
+ npx getprismo dev
37
+ ```
29
38
 
30
39
  ## Common Commands
31
40
 
32
41
  ```bash
33
42
  npx getprismo scan --usage
43
+ npx getprismo setup
44
+ npx getprismo watch
34
45
  npx getprismo scan --fix
35
46
  npx getprismo optimize
36
47
  npx getprismo context frontend
@@ -41,7 +52,7 @@ npx getprismo usage claude
41
52
  ## Example Output
42
53
 
43
54
  ```text
44
- Prismo Dev Scan
55
+ PrismoDev
45
56
 
46
57
  Score: 72/100 | Risk: Medium | Token leaks: 5
47
58
  Estimated avoidable waste: 20-40%
@@ -59,6 +70,27 @@ Then: npx getprismo optimize
59
70
  Then: npx getprismo context frontend
60
71
  ```
61
72
 
73
+ ## Setup And Live Watch
74
+
75
+ ```bash
76
+ npx getprismo setup
77
+ ```
78
+
79
+ Setup is read-only. It detects Claude Code, Codex, Cursor, local logs, MCP/tool config, and whether the Prismo proxy is reachable for exact API tracking.
80
+
81
+ ```bash
82
+ npx getprismo watch
83
+ ```
84
+
85
+ Watch is the local live session view. It shows active session tokens, context risk, tool/output token spikes, largest context sources, top tools, warnings, and the next recommended action.
86
+
87
+ For machine-readable output:
88
+
89
+ ```bash
90
+ npx getprismo setup --json
91
+ npx getprismo watch --once --json
92
+ ```
93
+
62
94
  ## Local Usage Tracking
63
95
 
64
96
  Prismo can show real token usage when local tool logs expose token fields.
@@ -105,6 +137,8 @@ Existing reports and suggestion files are backed up before replacement.
105
137
  ```bash
106
138
  npx getprismo --help
107
139
  npx getprismo scan --help
140
+ npx getprismo setup --help
141
+ npx getprismo watch --help
108
142
  npx getprismo optimize --help
109
143
  npx getprismo usage --help
110
144
  ```
@@ -1,4 +1,6 @@
1
1
  const fs = require("fs");
2
+ const http = require("http");
3
+ const https = require("https");
2
4
  const os = require("os");
3
5
  const path = require("path");
4
6
 
@@ -129,6 +131,7 @@ const DEFAULT_CLAUDEIGNORE = [
129
131
  ];
130
132
 
131
133
  const NPX_COMMAND = "npx getprismo";
134
+ const DEFAULT_PRISMO_PROXY_URL = process.env.PRISMO_PROXY_URL || "http://localhost:8000";
132
135
 
133
136
  function shouldUseColor() {
134
137
  return process.stdout.isTTY && !process.env.NO_COLOR;
@@ -391,6 +394,270 @@ function scanCodexConfig(root) {
391
394
  return { files: found, mcpServers };
392
395
  }
393
396
 
397
+ function commandExists(command) {
398
+ const pathEntries = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
399
+ const names = process.platform === "win32" ? [command, `${command}.cmd`, `${command}.exe`, `${command}.ps1`] : [command];
400
+ return pathEntries.some((entry) => names.some((name) => fs.existsSync(path.join(entry, name))));
401
+ }
402
+
403
+ function pathExistsAny(paths) {
404
+ return paths.some((candidate) => fs.existsSync(candidate));
405
+ }
406
+
407
+ function detectOptimizationStack(root, claudeConfig, codexConfig) {
408
+ const projectClaudePlugin = fs.existsSync(path.join(root, ".claude-plugin")) || fs.existsSync(path.join(root, ".claude", "settings.json"));
409
+ const projectMana = fs.existsSync(path.join(root, ".mana-mcp.json")) || fs.existsSync(path.join(os.homedir(), ".mana"));
410
+ const projectHeadroom = fs.existsSync(path.join(root, ".headroom")) || fs.existsSync(path.join(os.homedir(), ".headroom"));
411
+ const projectDistill = fs.existsSync(path.join(os.homedir(), ".config", "distill")) || commandExists("distill");
412
+ const projectRtk = fs.existsSync(path.join(root, ".rtk")) || commandExists("rtk");
413
+
414
+ const tools = {
415
+ rtk: { detected: projectRtk, source: projectRtk ? "binary-or-project-config" : "not-detected" },
416
+ headroom: { detected: projectHeadroom || commandExists("headroom"), source: projectHeadroom ? "local-config" : commandExists("headroom") ? "binary" : "not-detected" },
417
+ distill: { detected: projectDistill, source: projectDistill ? "binary-or-user-config" : "not-detected" },
418
+ mana: { detected: projectMana || commandExists("mana"), source: projectMana ? "local-config" : commandExists("mana") ? "binary" : "not-detected" },
419
+ };
420
+
421
+ return {
422
+ tools,
423
+ claudeHooks: claudeConfig.hooks,
424
+ claudeMcpServers: claudeConfig.mcpServers,
425
+ codexMcpServers: codexConfig.mcpServers,
426
+ claudePluginDetected: projectClaudePlugin,
427
+ mcpServerTotal: claudeConfig.mcpServers + codexConfig.mcpServers,
428
+ detectedTools: Object.entries(tools).filter(([, value]) => value.detected).map(([name]) => name),
429
+ };
430
+ }
431
+
432
+ function detectAgentReadiness(root, claudeConfig, codexConfig, realUsage) {
433
+ const claudeHome = process.env.PRISMO_CLAUDE_HOME || path.join(os.homedir(), ".claude");
434
+ const codexHome = process.env.PRISMO_CODEX_HOME || path.join(os.homedir(), ".codex");
435
+ const cursorPaths = [
436
+ path.join(root, ".cursor"),
437
+ path.join(root, ".cursorrules"),
438
+ path.join(os.homedir(), ".cursor"),
439
+ path.join(os.homedir(), ".config", "Cursor"),
440
+ ];
441
+ const usageSources = new Set(realUsage && realUsage.sources ? realUsage.sources : []);
442
+
443
+ const claudeSessionFiles = getClaudeSessionFiles(root);
444
+ const codexSessionFiles = getCodexSessionFiles();
445
+
446
+ return {
447
+ claudeCode: {
448
+ detected: claudeConfig.files.length > 0 || fs.existsSync(claudeHome) || claudeSessionFiles.length > 0,
449
+ configFiles: claudeConfig.files,
450
+ localLogsFound: claudeSessionFiles.length > 0 || usageSources.has("claude-code"),
451
+ mcpServers: claudeConfig.mcpServers,
452
+ hooks: claudeConfig.hooks,
453
+ exactProxyTracking: "limited-for-subscription-mode",
454
+ recommendedMode: "local-log-and-repo-scan",
455
+ },
456
+ codex: {
457
+ detected: codexConfig.files.length > 0 || fs.existsSync(codexHome) || codexSessionFiles.length > 0,
458
+ configFiles: codexConfig.files,
459
+ localLogsFound: codexSessionFiles.length > 0 || usageSources.has("codex"),
460
+ mcpServers: codexConfig.mcpServers,
461
+ exactProxyTracking: "available-when-using-api-key-base-url-mode",
462
+ recommendedMode: "prismo-proxy-for-api-mode-or-local-log-watch",
463
+ },
464
+ cursor: {
465
+ detected: pathExistsAny(cursorPaths),
466
+ configFiles: cursorPaths.filter((candidate) => fs.existsSync(candidate)),
467
+ localLogsFound: false,
468
+ exactProxyTracking: "available-only-if-configured-for-openai-compatible-base-url",
469
+ recommendedMode: "repo-scan-and-prismo-proxy-when-supported",
470
+ },
471
+ localUsageLogsAvailable: Boolean((realUsage && realUsage.sessions.length) || claudeSessionFiles.length || codexSessionFiles.length),
472
+ exactProxyTrackingAvailable: true,
473
+ notes: [
474
+ "Exact tracking is available when a tool sends OpenAI/Anthropic API traffic through Prismo.",
475
+ "Subscription coding-agent sessions usually require local-log visibility unless the tool supports a custom base URL.",
476
+ ],
477
+ };
478
+ }
479
+
480
+ function detectToolOutputRisk({ exposedLargeFiles, exposedHighRiskDirs, highRiskDirs }) {
481
+ const noisyDirs = highRiskDirs.filter((dir) => ["coverage", "test-results", "playwright-report", "logs", "dist", "build", ".next"].some((name) => dir.path.split("/").includes(name)));
482
+ const exposedNoisyDirs = exposedHighRiskDirs.filter((dir) => noisyDirs.some((candidate) => candidate.path === dir.path));
483
+ const noisyFiles = exposedLargeFiles.filter((file) => ["log", "json", "minified", "lock/generated"].includes(file.kind) || /\.(log|json|ndjson|out|trace|har)$/i.test(file.path));
484
+ const estimatedExposureTokens = estimateTokens(noisyFiles.reduce((sum, file) => sum + file.size, 0));
485
+ let level = "Low";
486
+ if (exposedNoisyDirs.length >= 3 || noisyFiles.length >= 3 || estimatedExposureTokens >= 250000) level = "High";
487
+ else if (exposedNoisyDirs.length || noisyFiles.length || estimatedExposureTokens >= 50000) level = "Medium";
488
+
489
+ return {
490
+ level,
491
+ exposedNoisyDirectories: exposedNoisyDirs.map((dir) => dir.path),
492
+ noisyDirectoriesDetected: noisyDirs.map((dir) => ({ path: dir.path, exposed: dir.exposed })),
493
+ exposedNoisyFiles: noisyFiles.map((file) => ({
494
+ path: file.path,
495
+ kind: file.kind,
496
+ sizeBytes: file.size,
497
+ estimatedTokensIfRead: estimateTokens(file.size),
498
+ })),
499
+ estimatedExposureTokens,
500
+ summary:
501
+ level === "High"
502
+ ? "Large logs, test reports, build output, or generated files are exposed to coding-agent reads."
503
+ : level === "Medium"
504
+ ? "Some noisy tool-output artifacts are present and may enter context during broad exploration."
505
+ : "No major exposed tool-output artifacts detected.",
506
+ };
507
+ }
508
+
509
+ function buildProxyTrackingReadiness({ codexConfig, claudeConfig, realUsage }) {
510
+ return {
511
+ exactApiTracking: {
512
+ available: true,
513
+ description: "Available for apps and coding tools that send OpenAI or Anthropic API traffic through the Prismo base URL.",
514
+ },
515
+ codingAgentBaseUrlMode: {
516
+ codex: codexConfig.files.length ? "possible-if-using-api-key-mode" : "not-detected",
517
+ claudeCode: "limited-for-subscription-sessions",
518
+ cursor: "possible-if-configured-for-openai-compatible-provider",
519
+ },
520
+ localEstimateTracking: {
521
+ available: true,
522
+ logsFound: Boolean(realUsage && realUsage.sessions.length),
523
+ description: "Available for subscription coding tools when local Codex/Claude Code logs exist; accuracy depends on token fields exposed by those tools.",
524
+ },
525
+ unsupported: [
526
+ "Exact billing for hidden subscription sessions without provider traffic, API keys, or local token fields.",
527
+ "Prompt interception or tool rewriting is not enabled by PrismoDev Scan.",
528
+ ],
529
+ };
530
+ }
531
+
532
+ function checkUrlReachable(url, timeoutMs = 650) {
533
+ return new Promise((resolve) => {
534
+ let parsed;
535
+ try {
536
+ parsed = new URL(url);
537
+ } catch {
538
+ resolve({ url, reachable: false, error: "invalid-url" });
539
+ return;
540
+ }
541
+ const client = parsed.protocol === "https:" ? https : http;
542
+ const request = client.request(
543
+ {
544
+ method: "GET",
545
+ hostname: parsed.hostname,
546
+ port: parsed.port,
547
+ path: parsed.pathname === "/" ? "/health" : parsed.pathname,
548
+ timeout: timeoutMs,
549
+ },
550
+ (response) => {
551
+ response.resume();
552
+ resolve({ url, reachable: response.statusCode >= 200 && response.statusCode < 500, statusCode: response.statusCode });
553
+ }
554
+ );
555
+ request.on("timeout", () => {
556
+ request.destroy();
557
+ resolve({ url, reachable: false, error: "timeout" });
558
+ });
559
+ request.on("error", (error) => {
560
+ resolve({ url, reachable: false, error: error.code || error.message });
561
+ });
562
+ request.end();
563
+ });
564
+ }
565
+
566
+ async function runSetup(rootDir = process.cwd(), options = {}) {
567
+ const scan = scanRepo(rootDir, { includeUsage: true, usageLimit: options.limit || 3 });
568
+ const proxyUrl = options.proxyUrl || DEFAULT_PRISMO_PROXY_URL;
569
+ const proxy = options.skipProxyCheck
570
+ ? { url: proxyUrl, reachable: false, skipped: true }
571
+ : await checkUrlReachable(proxyUrl, options.timeoutMs || 650);
572
+
573
+ const modes = [
574
+ {
575
+ id: "local-log-tracking",
576
+ label: "Local log tracking",
577
+ status: scan.agentReadiness.localUsageLogsAvailable ? "available" : "limited",
578
+ description: "Reads local Codex/Claude Code session logs when present. Good for subscription coding tools when exact proxy traffic is unavailable.",
579
+ },
580
+ {
581
+ id: "exact-api-proxy",
582
+ label: "Exact API proxy tracking",
583
+ status: proxy.reachable ? "available" : "proxy-not-running",
584
+ description: "Exact tokens, costs, routing, budgets, and analytics when app or coding-tool traffic uses the Prismo OpenAI/Anthropic base URL.",
585
+ },
586
+ {
587
+ id: "codex-base-url",
588
+ label: "Codex API/base-url mode",
589
+ status: scan.agentReadiness.codex.detected ? "possible" : "not-detected",
590
+ description: "Use Prismo for exact tracking when Codex is running with API-key/base-url compatible traffic.",
591
+ },
592
+ {
593
+ id: "claude-subscription",
594
+ label: "Claude subscription sessions",
595
+ status: scan.agentReadiness.claudeCode.detected ? "local-estimates-only" : "not-detected",
596
+ description: "Claude Code subscription traffic is not exact unless it can be routed through Prismo; use local logs and repo diagnostics otherwise.",
597
+ },
598
+ ];
599
+
600
+ const recommended = [];
601
+ recommended.push(`${NPX_COMMAND} watch`);
602
+ if (!scan.hasClaudeIgnore) recommended.push(`${NPX_COMMAND} scan --fix`);
603
+ recommended.push(`${NPX_COMMAND} optimize`);
604
+ if (proxy.reachable) {
605
+ recommended.push(`OPENAI_BASE_URL=${proxyUrl.replace(/\/$/, "")}/v1 codex`);
606
+ } else {
607
+ recommended.push("Start the Prismo proxy, then route API-mode tools through its OpenAI/Anthropic base URL.");
608
+ }
609
+
610
+ return {
611
+ scannedPath: scan.root,
612
+ generatedAt: new Date().toISOString(),
613
+ prismoProxy: proxy,
614
+ detected: {
615
+ claudeCode: scan.agentReadiness.claudeCode,
616
+ codex: scan.agentReadiness.codex,
617
+ cursor: scan.agentReadiness.cursor,
618
+ optimizationStack: scan.optimizationStack,
619
+ localUsageLogsAvailable: scan.agentReadiness.localUsageLogsAvailable,
620
+ },
621
+ trackingModes: modes,
622
+ recommendedCommands: Array.from(new Set(recommended)).slice(0, 5),
623
+ caveats: [
624
+ "Prismo can track exact usage only when traffic flows through the Prismo proxy.",
625
+ "Subscription coding tools may expose local logs, but those are local visibility signals rather than provider billing records.",
626
+ "Setup is read-only and does not modify Claude, Codex, Cursor, MCP, shell, or Prismo config.",
627
+ ],
628
+ };
629
+ }
630
+
631
+ function renderSetupTerminal(result) {
632
+ const lines = [];
633
+ lines.push("");
634
+ lines.push(color("PrismoDev Setup", "bold"));
635
+ lines.push("");
636
+ lines.push(`Repo: ${result.scannedPath}`);
637
+ lines.push(`Prismo proxy: ${result.prismoProxy.reachable ? "running" : "not reachable"} (${result.prismoProxy.url})`);
638
+ if (result.prismoProxy.statusCode) lines.push(`Proxy status: HTTP ${result.prismoProxy.statusCode}`);
639
+ lines.push("");
640
+ lines.push("Detected:");
641
+ lines.push(`- Claude Code: ${result.detected.claudeCode.detected ? "detected" : "not detected"}; logs: ${result.detected.claudeCode.localLogsFound ? "found" : "not found"}; MCP: ${result.detected.claudeCode.mcpServers}; hooks: ${result.detected.claudeCode.hooks}`);
642
+ lines.push(`- Codex: ${result.detected.codex.detected ? "detected" : "not detected"}; logs: ${result.detected.codex.localLogsFound ? "found" : "not found"}; MCP: ${result.detected.codex.mcpServers}`);
643
+ lines.push(`- Cursor: ${result.detected.cursor.detected ? "detected" : "not detected"}`);
644
+ const detectedTools = result.detected.optimizationStack.detectedTools;
645
+ lines.push(`- Optimization tools: ${detectedTools.length ? detectedTools.join(", ") : "none detected"}`);
646
+ lines.push("");
647
+ lines.push("Tracking Modes:");
648
+ result.trackingModes.forEach((mode, index) => {
649
+ lines.push(`${index + 1}. ${mode.label}: ${mode.status}`);
650
+ lines.push(` ${mode.description}`);
651
+ });
652
+ lines.push("");
653
+ lines.push("Recommended:");
654
+ result.recommendedCommands.forEach((command, index) => lines.push(`${index + 1}. ${command}`));
655
+ lines.push("");
656
+ lines.push("Notes:");
657
+ result.caveats.forEach((caveat) => lines.push(`- ${caveat}`));
658
+ return lines.join("\n");
659
+ }
660
+
394
661
  function classifyLargeFiles(files) {
395
662
  return files
396
663
  .filter((file) => file.kind !== "binary" && file.size >= 500 * 1024)
@@ -433,7 +700,7 @@ function estimateMcpImpact(count) {
433
700
  }
434
701
 
435
702
  function severityWeight(severity) {
436
- return severity === "critical" ? 22 : severity === "high" ? 14 : severity === "medium" ? 8 : 4;
703
+ return severity === "critical" ? 10 : severity === "high" ? 6 : severity === "medium" ? 4 : 2;
437
704
  }
438
705
 
439
706
  function severityRank(severity) {
@@ -451,7 +718,7 @@ function addIssue(issues, severity, category, title, description, recommendation
451
718
  });
452
719
  }
453
720
 
454
- function buildRecommendations({ hasClaudeIgnore, gitignorePatterns, exposedHighRiskDirs, largeFiles, instructionFiles, claudeConfig }) {
721
+ function buildRecommendations({ hasClaudeIgnore, gitignorePatterns, exposedHighRiskDirs, largeFiles, instructionFiles, claudeConfig, toolOutputRisk, agentReadiness }) {
455
722
  const recs = [];
456
723
  if (!hasClaudeIgnore) {
457
724
  recs.push("Create .claudeignore with generated/cache folders and large artifacts excluded.");
@@ -465,6 +732,9 @@ function buildRecommendations({ hasClaudeIgnore, gitignorePatterns, exposedHighR
465
732
  if (largeFiles.some((file) => !file.ignored)) {
466
733
  recs.push("Avoid loading large logs, JSON dumps, coverage reports, and minified assets into coding-agent context.");
467
734
  }
735
+ if (toolOutputRisk && toolOutputRisk.level !== "Low") {
736
+ recs.push("Use command-output filtering or narrower shell commands for noisy tests, logs, diffs, and generated reports.");
737
+ }
468
738
  if (instructionFiles.some((file) => file.isClaude && file.tokens > 500)) {
469
739
  recs.push("Trim CLAUDE.md to project rules only; move long implementation notes into docs referenced on demand.");
470
740
  }
@@ -473,17 +743,24 @@ function buildRecommendations({ hasClaudeIgnore, gitignorePatterns, exposedHighR
473
743
  }
474
744
  recs.push("Start fresh sessions for unrelated tasks and compact long sessions when context growth accelerates.");
475
745
  recs.push("Use cheaper/faster models for mechanical edits, formatting, and low-risk refactors.");
746
+ if (agentReadiness && (agentReadiness.codex.detected || agentReadiness.claudeCode.detected)) {
747
+ recs.push("Run `npx getprismo watch` for local coding-session visibility while working.");
748
+ }
749
+ recs.push("Route API-mode coding tools through Prismo when they support custom OpenAI/Anthropic base URLs for exact cost tracking.");
476
750
  return Array.from(new Set(recs));
477
751
  }
478
752
 
479
- function scoreScan(issues, stats) {
480
- let score = 100;
481
- for (const issue of issues) {
482
- score -= severityWeight(issue.severity);
483
- }
484
- if (stats.totalFiles > 2500) score -= 8;
485
- if (stats.exposedLargeFiles > 10) score -= 8;
486
- if (stats.exposedHighRiskDirs > 4) score -= 8;
753
+ function scoreScan(issues, stats, context = {}) {
754
+ const issuePenalty = issues.reduce((sum, issue) => sum + severityWeight(issue.severity), 0);
755
+ const repoPenalty =
756
+ (stats.totalFiles > 2500 ? 4 : 0) +
757
+ (stats.exposedLargeFiles > 10 ? 4 : 0) +
758
+ (stats.exposedHighRiskDirs > 4 ? 4 : 0);
759
+ const toolOutputPenalty = context.toolOutputRisk && context.toolOutputRisk.level === "High" ? 8 : context.toolOutputRisk && context.toolOutputRisk.level === "Medium" ? 4 : 0;
760
+ const readinessCredit = context.agentReadiness && context.agentReadiness.localUsageLogsAvailable ? 3 : 0;
761
+ const proxyCredit = context.proxyTrackingReadiness && context.proxyTrackingReadiness.exactApiTracking.available ? 2 : 0;
762
+
763
+ let score = 100 - issuePenalty - repoPenalty - toolOutputPenalty + readinessCredit + proxyCredit;
487
764
  score = Math.max(0, Math.min(100, score));
488
765
 
489
766
  const risk = score >= 80 ? "Low" : score >= 55 ? "Medium" : "High";
@@ -516,6 +793,8 @@ function toJsonPayload(result) {
516
793
  score: result.score,
517
794
  riskLevel: result.risk,
518
795
  estimatedAvoidableWasteRange: result.avoidableWaste,
796
+ detectedFrameworks: result.frameworks,
797
+ stats: result.stats,
519
798
  issues: result.issues,
520
799
  recommendations: result.recommendations,
521
800
  largeFiles: result.largeFiles.map((file) => ({
@@ -551,6 +830,10 @@ function toJsonPayload(result) {
551
830
  hasCodexDirectory: fs.existsSync(path.join(result.root, ".codex")),
552
831
  hasOpenAiDirectory: fs.existsSync(path.join(result.root, ".openai")),
553
832
  },
833
+ agentReadiness: result.agentReadiness,
834
+ optimizationStack: result.optimizationStack,
835
+ toolOutputRisk: result.toolOutputRisk,
836
+ proxyTrackingReadiness: result.proxyTrackingReadiness,
554
837
  suggestedClaudeIgnore: result.recommendedClaudeIgnore,
555
838
  nextCommands: getNextCommands(result),
556
839
  generatedAt: result.generatedAt,
@@ -570,7 +853,7 @@ function addRealUsageIssues(issues, usage) {
570
853
  if (total >= 1000000) {
571
854
  addIssue(
572
855
  issues,
573
- total >= 5000000 ? "critical" : "high",
856
+ total >= 10000000 ? "high" : "medium",
574
857
  "repo_size",
575
858
  `Recent local AI sessions used ${formatTokenCount(total)} tokens`,
576
859
  exact ? "Prismo found exact token counts in local Codex/Claude Code session logs." : "Prismo estimated usage from local session text because exact token fields were unavailable.",
@@ -644,6 +927,7 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
644
927
  const combinedIgnorePatterns = Array.from(new Set([...gitignorePatterns, ...claudeIgnorePatterns]));
645
928
 
646
929
  const { files, highRiskDirs } = walkRepo(root, combinedIgnorePatterns);
930
+ const frameworks = detectFrameworks(root, { files });
647
931
  const instructionFiles = scanInstructionFiles(root);
648
932
  const largeFiles = classifyLargeFiles(files);
649
933
  const exposedLargeFiles = largeFiles.filter((file) => !file.ignored);
@@ -704,7 +988,7 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
704
988
  if (!hasClaudeIgnore) {
705
989
  addIssue(
706
990
  issues,
707
- exposedHighRiskDirs.length || exposedLargeFiles.length ? "critical" : "high",
991
+ exposedHighRiskDirs.length > 5 && exposedLargeFiles.length > 3 ? "critical" : "high",
708
992
  "ignore_file",
709
993
  ".claudeignore not found",
710
994
  "Claude Code-style workflows may expose generated files, caches, and logs unless they are ignored.",
@@ -801,6 +1085,36 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
801
1085
 
802
1086
  const realUsage = options.includeUsage ? getUsageSummary({ tool: options.usageTool || "all", cwd: root, limit: options.usageLimit || 5 }) : null;
803
1087
  addRealUsageIssues(issues, realUsage);
1088
+ const optimizationStack = detectOptimizationStack(root, claudeConfig, codexConfig);
1089
+ const agentReadiness = detectAgentReadiness(root, claudeConfig, codexConfig, realUsage);
1090
+ const toolOutputRisk = detectToolOutputRisk({ exposedLargeFiles, exposedHighRiskDirs, highRiskDirs });
1091
+ const proxyTrackingReadiness = buildProxyTrackingReadiness({ codexConfig, claudeConfig, realUsage });
1092
+
1093
+ if (toolOutputRisk.level !== "Low") {
1094
+ addIssue(
1095
+ issues,
1096
+ toolOutputRisk.level === "High" ? "high" : "medium",
1097
+ "large_file",
1098
+ `Tool output risk is ${toolOutputRisk.level}`,
1099
+ toolOutputRisk.summary,
1100
+ "Use narrower commands, summarize logs, and ignore generated test/build output before loading it into coding agents.",
1101
+ toolOutputRisk.estimatedExposureTokens
1102
+ ? `Likely avoidable token exposure: up to ~${toolOutputRisk.estimatedExposureTokens.toLocaleString()} tokens from exposed noisy artifacts.`
1103
+ : "Potential savings estimate: prevents noisy command/file output from becoming recurring context."
1104
+ );
1105
+ }
1106
+
1107
+ if (optimizationStack.mcpServerTotal >= 8) {
1108
+ addIssue(
1109
+ issues,
1110
+ "medium",
1111
+ "mcp_tooling",
1112
+ `${optimizationStack.mcpServerTotal} total MCP/tool servers detected`,
1113
+ "Large tool surfaces can increase tool-choice overhead across Claude Code and Codex-style workflows.",
1114
+ "Disable MCP servers not needed for the current repo or current task.",
1115
+ estimateMcpImpact(optimizationStack.mcpServerTotal)
1116
+ );
1117
+ }
804
1118
 
805
1119
  const sourceFiles = files.filter((file) => file.kind === "source").length;
806
1120
  const stats = {
@@ -834,7 +1148,7 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
834
1148
  "Likely avoidable token exposure: large repos make repeated discovery more expensive."
835
1149
  );
836
1150
  }
837
- const score = scoreScan(issues, stats);
1151
+ const score = scoreScan(issues, stats, { toolOutputRisk, agentReadiness, proxyTrackingReadiness });
838
1152
  const largeFileSuggestions = exposedLargeFiles
839
1153
  .filter((file) => file.size >= 1024 * 1024 || ["log", "json", "minified", "lock/generated"].includes(file.kind))
840
1154
  .map((file) => file.path);
@@ -850,6 +1164,8 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
850
1164
  largeFiles,
851
1165
  instructionFiles,
852
1166
  claudeConfig,
1167
+ toolOutputRisk,
1168
+ agentReadiness,
853
1169
  });
854
1170
  buildRealUsageRecommendations(realUsage).forEach((rec) => recommendations.push(rec));
855
1171
 
@@ -861,6 +1177,11 @@ function scanRepo(rootDir = process.cwd(), options = {}) {
861
1177
  issues,
862
1178
  recommendations,
863
1179
  realUsage,
1180
+ agentReadiness,
1181
+ optimizationStack,
1182
+ toolOutputRisk,
1183
+ proxyTrackingReadiness,
1184
+ frameworks,
864
1185
  files,
865
1186
  instructionFiles,
866
1187
  largeFiles,
@@ -886,7 +1207,7 @@ function renderTerminalReport(result, options = {}) {
886
1207
  const riskTone = result.risk === "High" ? "red" : result.risk === "Medium" ? "yellow" : "green";
887
1208
  const lines = [];
888
1209
  lines.push("");
889
- lines.push(color("Prismo Dev Scan", "bold", useColor));
1210
+ lines.push(color("PrismoDev", "bold", useColor));
890
1211
  lines.push("");
891
1212
  lines.push(`Score: ${color(`${result.score}/100`, riskTone, useColor)} | Risk: ${color(result.risk, riskTone, useColor)} | Token leaks: ${result.issues.length}`);
892
1213
  lines.push(`Estimated avoidable waste: ${result.avoidableWaste}`);
@@ -917,6 +1238,31 @@ function renderTerminalReport(result, options = {}) {
917
1238
  lines.push("- Real local usage: no matching local Codex/Claude Code sessions found for this repo");
918
1239
  }
919
1240
  lines.push("");
1241
+ lines.push(color("Coding Agent Readiness", "bold", useColor));
1242
+ lines.push(`- Claude Code: ${result.agentReadiness.claudeCode.detected ? "detected" : "not detected"}; logs: ${result.agentReadiness.claudeCode.localLogsFound ? "found" : "not found"}; MCP: ${result.agentReadiness.claudeCode.mcpServers}; hooks: ${result.agentReadiness.claudeCode.hooks}`);
1243
+ lines.push(`- Codex: ${result.agentReadiness.codex.detected ? "detected" : "not detected"}; logs: ${result.agentReadiness.codex.localLogsFound ? "found" : "not found"}; MCP: ${result.agentReadiness.codex.mcpServers}`);
1244
+ lines.push(`- Cursor: ${result.agentReadiness.cursor.detected ? "detected" : "not detected"}`);
1245
+ lines.push("");
1246
+ lines.push(color("Optimization Stack", "bold", useColor));
1247
+ const stack = result.optimizationStack;
1248
+ lines.push(`- RTK: ${stack.tools.rtk.detected ? "detected" : "not detected"}`);
1249
+ lines.push(`- Headroom: ${stack.tools.headroom.detected ? "detected" : "not detected"}`);
1250
+ lines.push(`- Distill: ${stack.tools.distill.detected ? "detected" : "not detected"}`);
1251
+ lines.push(`- Mana: ${stack.tools.mana.detected ? "detected" : "not detected"}`);
1252
+ lines.push(`- Claude hooks: ${stack.claudeHooks}; MCP servers: ${stack.mcpServerTotal}`);
1253
+ lines.push("");
1254
+ lines.push(color("Tool Output Risk", "bold", useColor));
1255
+ lines.push(`- Level: ${result.toolOutputRisk.level}`);
1256
+ lines.push(`- ${result.toolOutputRisk.summary}`);
1257
+ if (result.toolOutputRisk.exposedNoisyDirectories.length) lines.push(`- Exposed noisy dirs: ${result.toolOutputRisk.exposedNoisyDirectories.slice(0, 6).join(", ")}`);
1258
+ if (result.toolOutputRisk.exposedNoisyFiles.length) lines.push(`- Exposed noisy files: ${result.toolOutputRisk.exposedNoisyFiles.slice(0, 4).map((file) => file.path).join(", ")}`);
1259
+ lines.push("");
1260
+ lines.push(color("Prismo Proxy Tracking", "bold", useColor));
1261
+ lines.push("- Exact API tracking: available when traffic uses the Prismo OpenAI/Anthropic base URL");
1262
+ lines.push(`- Codex API/base-url mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.codex}`);
1263
+ lines.push(`- Claude Code subscription mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.claudeCode}`);
1264
+ lines.push("- Subscription sessions: local-log visibility when available, not guaranteed billing accuracy");
1265
+ lines.push("");
920
1266
  lines.push(color("Issues", "bold", useColor));
921
1267
  if (!result.issues.length) {
922
1268
  lines.push("- [ok] No major token-waste risks detected.");
@@ -943,7 +1289,7 @@ function renderTerminalReport(result, options = {}) {
943
1289
 
944
1290
  function renderMarkdownReport(result) {
945
1291
  const lines = [];
946
- lines.push("# Prismo Dev Scan Report");
1292
+ lines.push("# PrismoDev Report");
947
1293
  lines.push("");
948
1294
  lines.push("## Executive Summary");
949
1295
  lines.push("");
@@ -977,6 +1323,49 @@ function renderMarkdownReport(result) {
977
1323
  lines.push(`- Token-bloat directories: ${result.stats.highRiskDirs}`);
978
1324
  lines.push(`- Exposed token-bloat directories: ${result.stats.exposedHighRiskDirs}`);
979
1325
  lines.push("");
1326
+ lines.push("## Coding Agent Readiness");
1327
+ lines.push("");
1328
+ lines.push(`- Claude Code detected: ${result.agentReadiness.claudeCode.detected ? "yes" : "no"}`);
1329
+ lines.push(` - Local logs found: ${result.agentReadiness.claudeCode.localLogsFound ? "yes" : "no"}`);
1330
+ lines.push(` - MCP servers: ${result.agentReadiness.claudeCode.mcpServers}; hooks: ${result.agentReadiness.claudeCode.hooks}`);
1331
+ lines.push(` - Exact proxy tracking: ${result.agentReadiness.claudeCode.exactProxyTracking}`);
1332
+ lines.push(`- Codex detected: ${result.agentReadiness.codex.detected ? "yes" : "no"}`);
1333
+ lines.push(` - Local logs found: ${result.agentReadiness.codex.localLogsFound ? "yes" : "no"}`);
1334
+ lines.push(` - MCP servers: ${result.agentReadiness.codex.mcpServers}`);
1335
+ lines.push(` - Exact proxy tracking: ${result.agentReadiness.codex.exactProxyTracking}`);
1336
+ lines.push(`- Cursor detected: ${result.agentReadiness.cursor.detected ? "yes" : "no"}`);
1337
+ lines.push(` - Exact proxy tracking: ${result.agentReadiness.cursor.exactProxyTracking}`);
1338
+ lines.push("");
1339
+ lines.push("## Optimization Stack");
1340
+ lines.push("");
1341
+ lines.push(`- RTK: ${result.optimizationStack.tools.rtk.detected ? "detected" : "not detected"}`);
1342
+ lines.push(`- Headroom: ${result.optimizationStack.tools.headroom.detected ? "detected" : "not detected"}`);
1343
+ lines.push(`- Distill: ${result.optimizationStack.tools.distill.detected ? "detected" : "not detected"}`);
1344
+ lines.push(`- Mana: ${result.optimizationStack.tools.mana.detected ? "detected" : "not detected"}`);
1345
+ lines.push(`- Claude hooks: ${result.optimizationStack.claudeHooks}`);
1346
+ lines.push(`- Total MCP/tool servers: ${result.optimizationStack.mcpServerTotal}`);
1347
+ lines.push("");
1348
+ lines.push("## Tool Output Risk");
1349
+ lines.push("");
1350
+ lines.push(`- Level: ${result.toolOutputRisk.level}`);
1351
+ lines.push(`- ${result.toolOutputRisk.summary}`);
1352
+ if (result.toolOutputRisk.exposedNoisyDirectories.length) {
1353
+ lines.push(`- Exposed noisy directories: ${result.toolOutputRisk.exposedNoisyDirectories.map((dir) => `\`${dir}/\``).join(", ")}`);
1354
+ }
1355
+ if (result.toolOutputRisk.exposedNoisyFiles.length) {
1356
+ result.toolOutputRisk.exposedNoisyFiles.slice(0, 20).forEach((file) => {
1357
+ lines.push(`- \`${file.path}\` - ${formatBytes(file.sizeBytes)} - ~${file.estimatedTokensIfRead.toLocaleString()} tokens if read`);
1358
+ });
1359
+ }
1360
+ lines.push("");
1361
+ lines.push("## Prismo Proxy Tracking Readiness");
1362
+ lines.push("");
1363
+ lines.push("- Exact API tracking: available when app/tool traffic uses the Prismo OpenAI/Anthropic base URL.");
1364
+ lines.push(`- Codex API/base-url mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.codex}.`);
1365
+ lines.push(`- Claude Code subscription mode: ${result.proxyTrackingReadiness.codingAgentBaseUrlMode.claudeCode}.`);
1366
+ lines.push("- Local estimate tracking: available from local Codex/Claude Code logs when those logs exist.");
1367
+ lines.push("- Unsupported: exact billing for hidden subscription sessions without provider traffic, API keys, or local token fields.");
1368
+ lines.push("");
980
1369
  if (result.realUsage) {
981
1370
  lines.push("## Real Local Usage");
982
1371
  lines.push("");
@@ -1058,7 +1447,7 @@ function renderMarkdownReport(result) {
1058
1447
  lines.push("");
1059
1448
  lines.push("## Disclaimer");
1060
1449
  lines.push("");
1061
- lines.push("Prismo Dev Scan is a fast local scanner. It does not connect to Anthropic, OpenAI, Claude Code, Codex, Cursor, or billing accounts. Token and savings estimates are heuristic and should be treated as directional diagnostics only.");
1450
+ lines.push("PrismoDev is a fast local scanner. It does not connect to Anthropic, OpenAI, Claude Code, Codex, Cursor, or billing accounts. Token and savings estimates are heuristic and should be treated as directional diagnostics only.");
1062
1451
  lines.push("");
1063
1452
  return lines.join("\n");
1064
1453
  }
@@ -1821,7 +2210,11 @@ function analyzeSessionFile(filePath, tool) {
1821
2210
 
1822
2211
  session.turns = Math.max(session.userMessages, session.assistantMessages);
1823
2212
  session.estimatedTotalTokens = session.estimatedInputTokens + session.estimatedOutputTokens + session.estimatedToolTokens;
1824
- session.displayTokens = session.exactAvailable ? session.exactTotalTokens : session.estimatedTotalTokens;
2213
+ session.exactActiveTokens = session.exactAvailable
2214
+ ? Math.max(session.exactInputTokens - session.exactCacheReadTokens, 0) + session.exactOutputTokens + (session.exactCacheCreationTokens || 0)
2215
+ : 0;
2216
+ session.contextTokens = session.exactAvailable ? session.exactTotalTokens : session.estimatedTotalTokens;
2217
+ session.displayTokens = session.exactAvailable ? session.exactActiveTokens : session.estimatedTotalTokens;
1825
2218
  session.confidence = session.exactAvailable ? "exact-local-log" : "estimated-local-log";
1826
2219
  session.contextRisk = getSessionRisk(session.displayTokens, session.estimatedToolTokens);
1827
2220
  session.largestTextBlobs = session.largestTextBlobs.sort((a, b) => b.tokens - a.tokens).slice(0, 5);
@@ -1883,20 +2276,23 @@ function getUsageSummary(options = {}) {
1883
2276
  const totals = selected.reduce(
1884
2277
  (acc, session) => {
1885
2278
  acc.displayTokens += session.displayTokens || 0;
2279
+ acc.contextTokens += session.contextTokens || 0;
1886
2280
  acc.estimatedTokens += session.estimatedTotalTokens || 0;
1887
2281
  acc.exactTokens += session.exactAvailable ? session.exactTotalTokens : 0;
1888
2282
  acc.toolTokens += session.estimatedToolTokens || 0;
1889
2283
  acc.sessions += 1;
1890
2284
  return acc;
1891
2285
  },
1892
- { sessions: 0, displayTokens: 0, estimatedTokens: 0, exactTokens: 0, toolTokens: 0 }
2286
+ { sessions: 0, displayTokens: 0, contextTokens: 0, estimatedTokens: 0, exactTokens: 0, toolTokens: 0 }
1893
2287
  );
2288
+ const sources = Array.from(new Set(selected.map((session) => session.tool).filter(Boolean)));
1894
2289
  return {
1895
2290
  generatedAt: new Date().toISOString(),
1896
2291
  scannedPath: cwd,
1897
2292
  tool,
1898
2293
  confidence: selected.every((session) => session.exactAvailable) && selected.length ? "exact-local-log" : "mixed-or-estimated",
1899
2294
  totals,
2295
+ sources,
1900
2296
  sessions: selected,
1901
2297
  };
1902
2298
  }
@@ -1926,6 +2322,91 @@ function formatTokenCount(value) {
1926
2322
  return String(Math.round(n));
1927
2323
  }
1928
2324
 
2325
+ function getRiskRank(risk) {
2326
+ return { High: 3, Medium: 2, Low: 1 }[risk] || 0;
2327
+ }
2328
+
2329
+ function getTopToolNames(session, limit = 4) {
2330
+ return Object.entries(session && session.toolNames ? session.toolNames : {})
2331
+ .sort((a, b) => b[1] - a[1])
2332
+ .slice(0, limit)
2333
+ .map(([name, count]) => ({ name, count }));
2334
+ }
2335
+
2336
+ function buildLiveWarnings(activeSession, summary) {
2337
+ const warnings = [];
2338
+ if (!activeSession) {
2339
+ warnings.push("No active local session detected for this repo yet.");
2340
+ return warnings;
2341
+ }
2342
+ if (activeSession.contextRisk === "High") {
2343
+ warnings.push("Context risk is high; consider starting a fresh session at the next task boundary.");
2344
+ } else if (activeSession.contextRisk === "Medium") {
2345
+ warnings.push("Context risk is rising; keep reads and shell output narrow.");
2346
+ }
2347
+ if (activeSession.estimatedToolTokens >= 150000) {
2348
+ warnings.push("Tool/output tokens are dominating this session; summarize logs and test output before loading more.");
2349
+ } else if (activeSession.estimatedToolTokens >= 50000) {
2350
+ warnings.push("Tool/output tokens are elevated; prefer targeted commands and smaller file reads.");
2351
+ }
2352
+ if (activeSession.turns >= 30) {
2353
+ warnings.push("Long session detected; split unrelated follow-up work into a new session.");
2354
+ }
2355
+ if (!activeSession.exactAvailable) {
2356
+ warnings.push("Exact token fields were not found; usage is estimated from local session text.");
2357
+ }
2358
+ if (summary.totals.displayTokens >= 1000000) {
2359
+ warnings.push("Recent local usage is above 1M tokens; prioritize the largest session for cleanup.");
2360
+ }
2361
+ return Array.from(new Set(warnings)).slice(0, 5);
2362
+ }
2363
+
2364
+ function getRecommendedWatchAction(activeSession, warnings) {
2365
+ if (!activeSession) return `${NPX_COMMAND} setup`;
2366
+ if (warnings.some((warning) => warning.includes("Tool/output"))) return `${NPX_COMMAND} context`;
2367
+ if (activeSession.contextRisk === "High") return "Start a fresh coding-agent session before the next unrelated task.";
2368
+ if (activeSession.turns >= 20) return `${NPX_COMMAND} optimize`;
2369
+ return `${NPX_COMMAND} scan --usage`;
2370
+ }
2371
+
2372
+ function buildLiveSessionView(summary) {
2373
+ const activeSession = summary.sessions[0] || null;
2374
+ const warnings = buildLiveWarnings(activeSession, summary);
2375
+ const topTools = getTopToolNames(activeSession);
2376
+ const largestTextBlobs = activeSession ? activeSession.largestTextBlobs || [] : [];
2377
+ return {
2378
+ activeSession: activeSession
2379
+ ? {
2380
+ tool: activeSession.tool,
2381
+ sessionId: activeSession.sessionId,
2382
+ title: activeSession.title,
2383
+ model: activeSession.model,
2384
+ cwd: activeSession.cwd,
2385
+ updatedAt: activeSession.updatedAt,
2386
+ tokens: activeSession.displayTokens,
2387
+ contextTokens: activeSession.contextTokens,
2388
+ exactAvailable: activeSession.exactAvailable,
2389
+ confidence: activeSession.confidence,
2390
+ contextRisk: activeSession.contextRisk,
2391
+ turns: activeSession.turns,
2392
+ toolCalls: activeSession.toolCalls,
2393
+ toolResults: activeSession.toolResults,
2394
+ estimatedToolTokens: activeSession.estimatedToolTokens,
2395
+ topTools,
2396
+ largestTextBlobs,
2397
+ }
2398
+ : null,
2399
+ highestRisk: summary.sessions.reduce((risk, session) => (getRiskRank(session.contextRisk) > getRiskRank(risk) ? session.contextRisk : risk), "Low"),
2400
+ warnings,
2401
+ recommendedAction: getRecommendedWatchAction(activeSession, warnings),
2402
+ nextCommands: Array.from(new Set([
2403
+ `${NPX_COMMAND} context`,
2404
+ `${NPX_COMMAND} optimize`,
2405
+ `${NPX_COMMAND} scan --usage`,
2406
+ ])).slice(0, 3),
2407
+ };
2408
+ }
2409
+
1929
2410
  function renderUsageTerminal(summary, title = "Prismo Usage") {
1930
2411
  const lines = [];
1931
2412
  lines.push("");
@@ -1954,16 +2435,67 @@ function renderUsageTerminal(summary, title = "Prismo Usage") {
1954
2435
  return lines.join("\n");
1955
2436
  }
1956
2437
 
2438
+ function renderWatchTerminal(summary) {
2439
+ const live = summary.live || buildLiveSessionView(summary);
2440
+ const active = live.activeSession;
2441
+ const lines = [];
2442
+ lines.push("");
2443
+ lines.push(color("Prismo Watch", "bold"));
2444
+ lines.push("");
2445
+ if (!active) {
2446
+ lines.push("Active Session");
2447
+ lines.push("- No local Codex/Claude Code session detected for this repo yet.");
2448
+ lines.push("");
2449
+ lines.push("Next Action");
2450
+ lines.push(`Run: ${live.recommendedAction}`);
2451
+ lines.push("");
2452
+ lines.push("Tip: start Codex or Claude Code in this repo, then keep this watch open.");
2453
+ return lines.join("\n");
2454
+ }
2455
+ lines.push("Active Session");
2456
+ lines.push(`Source: ${active.tool}`);
2457
+ lines.push(`Tokens: ${formatTokenCount(active.tokens)} (${active.confidence})`);
2458
+ if (active.contextTokens && active.contextTokens !== active.tokens) lines.push(`Context tokens observed: ${formatTokenCount(active.contextTokens)}`);
2459
+ lines.push(`Context Risk: ${active.contextRisk}`);
2460
+ lines.push(`Tool/output tokens: ${formatTokenCount(active.estimatedToolTokens)}`);
2461
+ lines.push(`Turns: ${active.turns} | Tool calls: ${active.toolCalls}`);
2462
+ if (active.model) lines.push(`Model: ${active.model}`);
2463
+ if (active.updatedAt) lines.push(`Updated: ${active.updatedAt}`);
2464
+ lines.push("");
2465
+ lines.push("Live Warnings");
2466
+ live.warnings.forEach((warning) => lines.push(`- ${warning}`));
2467
+ lines.push("");
2468
+ if (active.largestTextBlobs.length) {
2469
+ lines.push("Largest Context Sources");
2470
+ active.largestTextBlobs.slice(0, 4).forEach((blob) => lines.push(`- ${blob.label}: ~${blob.tokens.toLocaleString()} tokens`));
2471
+ lines.push("");
2472
+ }
2473
+ if (active.topTools.length) {
2474
+ lines.push("Top Tools");
2475
+ active.topTools.forEach((tool) => lines.push(`- ${tool.name}: ${tool.count}`));
2476
+ lines.push("");
2477
+ }
2478
+ lines.push("Next Action");
2479
+ lines.push(`Run: ${live.recommendedAction}`);
2480
+ lines.push("");
2481
+ lines.push("Useful Commands");
2482
+ live.nextCommands.forEach((command) => lines.push(`- ${command}`));
2483
+ lines.push("");
2484
+ lines.push("Local estimates come from available coding-agent session logs; exact billing requires traffic routed through Prismo.");
2485
+ return lines.join("\n");
2486
+ }
2487
+
1957
2488
  async function watchUsage(options = {}) {
1958
2489
  const intervalMs = options.intervalMs || 3000;
1959
2490
  const iterations = options.once ? 1 : Number.POSITIVE_INFINITY;
1960
2491
  for (let i = 0; i < iterations; i += 1) {
1961
2492
  const summary = getUsageSummary(options);
2493
+ summary.live = buildLiveSessionView(summary);
1962
2494
  if (options.json) {
1963
2495
  console.log(JSON.stringify(summary, null, 2));
1964
2496
  } else {
1965
2497
  console.clear();
1966
- console.log(renderUsageTerminal(summary, "Prismo Watch"));
2498
+ console.log(renderWatchTerminal(summary));
1967
2499
  console.log(`\nRefreshing every ${Math.round(intervalMs / 1000)}s. Press Ctrl+C to stop.`);
1968
2500
  }
1969
2501
  if (i + 1 >= iterations) break;
@@ -2090,6 +2622,34 @@ function createDemoResult() {
2090
2622
  sessions: [{}, {}, {}],
2091
2623
  },
2092
2624
  stats: { totalFiles: 842, sourceFiles: 318, largeFiles: 4, exposedLargeFiles: 2, highRiskDirs: 7, exposedHighRiskDirs: 3 },
2625
+ agentReadiness: {
2626
+ claudeCode: { detected: true, localLogsFound: true, mcpServers: 4, hooks: 2, exactProxyTracking: "limited-for-subscription-mode" },
2627
+ codex: { detected: true, localLogsFound: true, mcpServers: 1, exactProxyTracking: "available-when-using-api-key-base-url-mode" },
2628
+ cursor: { detected: false, exactProxyTracking: "available-only-if-configured-for-openai-compatible-base-url" },
2629
+ localUsageLogsAvailable: true,
2630
+ },
2631
+ optimizationStack: {
2632
+ tools: {
2633
+ rtk: { detected: false },
2634
+ headroom: { detected: false },
2635
+ distill: { detected: false },
2636
+ mana: { detected: false },
2637
+ },
2638
+ claudeHooks: 2,
2639
+ mcpServerTotal: 5,
2640
+ },
2641
+ toolOutputRisk: {
2642
+ level: "High",
2643
+ summary: "Large logs, test reports, build output, or generated files are exposed to coding-agent reads.",
2644
+ exposedNoisyDirectories: ["logs", "coverage"],
2645
+ exposedNoisyFiles: [{ path: "logs/debug-output.json" }],
2646
+ },
2647
+ proxyTrackingReadiness: {
2648
+ codingAgentBaseUrlMode: {
2649
+ codex: "possible-if-using-api-key-mode",
2650
+ claudeCode: "limited-for-subscription-sessions",
2651
+ },
2652
+ },
2093
2653
  topTokenLeaks: getTopTokenLeaks(issues),
2094
2654
  hasClaudeIgnore: false,
2095
2655
  repoDetected: true,
@@ -2132,7 +2692,7 @@ function runDevFlow(rootDir = process.cwd(), options = {}) {
2132
2692
  function renderDevTerminal(result) {
2133
2693
  const lines = [];
2134
2694
  lines.push("");
2135
- lines.push(color("Prismo Dev", "bold"));
2695
+ lines.push(color("PrismoDev", "bold"));
2136
2696
  lines.push("");
2137
2697
  lines.push(`Score: ${result.scan.score}/100 | Risk: ${result.scan.risk} | Token leaks: ${result.scan.issues.length}`);
2138
2698
  if (result.scan.realUsage && result.scan.realUsage.sessions.length) {
@@ -2195,6 +2755,7 @@ function printHelp() {
2195
2755
 
2196
2756
  Usage:
2197
2757
  prismo dev [path]
2758
+ prismo setup [--json] [--proxy-url URL] [path]
2198
2759
  prismo scan [--fix] [--json] [--usage] [--no-report] [path]
2199
2760
  prismo optimize [scope] [--json] [path]
2200
2761
  prismo context [scope] [--json] [path]
@@ -2204,12 +2765,13 @@ Usage:
2204
2765
 
2205
2766
  Commands:
2206
2767
  dev Guided flow: scan, optimize, and print a paste-ready context prompt.
2207
- scan Run Prismo Dev Scan for Claude Code, Codex, Cursor, and AI coding workflows.
2768
+ scan Run PrismoDev for Claude Code, Codex, Cursor, and AI coding workflows.
2208
2769
  optimize Generate lightweight AI-readable project context files in .prismo/.
2209
2770
  context Print a copy-pasteable compact context prompt for AI coding tools.
2210
2771
  usage Read local Codex/Claude Code session logs and summarize token usage.
2211
2772
  watch Refresh local session usage in the terminal.
2212
2773
  demo Show sample output without needing a messy repo.
2774
+ setup Detect coding tools, tracking modes, local logs, and Prismo proxy readiness.
2213
2775
 
2214
2776
  Options:
2215
2777
  --fix Safely create .claudeignore if missing and generate the report.
@@ -2218,6 +2780,7 @@ Options:
2218
2780
  --no-report Do not write prismo-dev-report.md.
2219
2781
  --limit N Number of recent local sessions to show.
2220
2782
  --interval N Refresh interval in seconds for watch mode.
2783
+ --proxy-url URL Prismo proxy URL to check during setup.
2221
2784
  --once Run watch mode once, useful for tests and scripts.
2222
2785
  --help Show this help.
2223
2786
  `);
@@ -2225,7 +2788,7 @@ Options:
2225
2788
 
2226
2789
  function printCommandHelp(command) {
2227
2790
  const help = {
2228
- scan: `Prismo Scan
2791
+ scan: `PrismoDev
2229
2792
 
2230
2793
  Usage:
2231
2794
  prismo scan [--fix] [--json] [--usage] [--no-report] [--limit N] [path]
@@ -2278,7 +2841,7 @@ Usage:
2278
2841
  Examples:
2279
2842
  prismo watch codex
2280
2843
  prismo watch claude --once --json`,
2281
- dev: `Prismo Dev
2844
+ dev: `PrismoDev
2282
2845
 
2283
2846
  Usage:
2284
2847
  prismo dev [--json] [--limit N] [path]
@@ -2287,12 +2850,25 @@ Flow:
2287
2850
  1. Scan repo and local usage.
2288
2851
  2. Generate .prismo/ optimized context files.
2289
2852
  3. Print a paste-ready prompt.`,
2853
+ setup: `PrismoDev Setup
2854
+
2855
+ Usage:
2856
+ prismo setup [--json] [--proxy-url URL] [--limit N] [path]
2857
+
2858
+ Examples:
2859
+ prismo setup
2860
+ prismo setup --json
2861
+ prismo setup --proxy-url http://localhost:8000
2862
+
2863
+ Output:
2864
+ Detects Claude Code, Codex, Cursor, local logs, optimization stack, and Prismo proxy readiness.
2865
+ This command is read-only and does not modify coding-tool config.`,
2290
2866
  demo: `Prismo Demo
2291
2867
 
2292
2868
  Usage:
2293
2869
  prismo demo
2294
2870
 
2295
- Shows sample Prismo Dev Scan output without reading local files.`,
2871
+ Shows sample PrismoDev output without reading local files.`,
2296
2872
  };
2297
2873
  console.log(help[command] || "Unknown command. Try: prismo --help");
2298
2874
  }
@@ -2307,8 +2883,8 @@ async function runCli(argv) {
2307
2883
  printCommandHelp(command);
2308
2884
  return;
2309
2885
  }
2310
- if (!["dev", "scan", "optimize", "context", "usage", "watch", "demo"].includes(command)) {
2311
- throw new Error(`Unknown command: ${command}. Try: prismo dev, prismo scan, prismo optimize, prismo context, or prismo usage`);
2886
+ if (!["dev", "setup", "scan", "optimize", "context", "usage", "watch", "demo"].includes(command)) {
2887
+ throw new Error(`Unknown command: ${command}. Try: prismo dev, prismo setup, prismo scan, prismo optimize, prismo context, or prismo usage`);
2312
2888
  }
2313
2889
 
2314
2890
  if (command === "demo") {
@@ -2342,6 +2918,24 @@ async function runCli(argv) {
2342
2918
  return;
2343
2919
  }
2344
2920
 
2921
+ if (command === "setup") {
2922
+ const json = rest.includes("--json");
2923
+ const limitIndex = rest.indexOf("--limit");
2924
+ const proxyIndex = rest.indexOf("--proxy-url");
2925
+ const target = getPositionals(rest, new Set(["--limit", "--proxy-url"]))[0] || process.cwd();
2926
+ const result = await runSetup(target, {
2927
+ limit: parsePositiveInt(limitIndex >= 0 ? rest[limitIndex + 1] : null, 3),
2928
+ proxyUrl: proxyIndex >= 0 ? rest[proxyIndex + 1] : DEFAULT_PRISMO_PROXY_URL,
2929
+ skipProxyCheck: rest.includes("--skip-proxy-check"),
2930
+ });
2931
+ if (json) {
2932
+ console.log(JSON.stringify(result, null, 2));
2933
+ return;
2934
+ }
2935
+ console.log(renderSetupTerminal(result));
2936
+ return;
2937
+ }
2938
+
2345
2939
  if (command === "usage" || command === "watch") {
2346
2940
  const json = rest.includes("--json");
2347
2941
  const knownTools = new Set(["codex", "claude", "all"]);
@@ -2468,7 +3062,9 @@ module.exports = {
2468
3062
  estimateTokens,
2469
3063
  renderMarkdownReport,
2470
3064
  renderUsageTerminal,
3065
+ renderWatchTerminal,
2471
3066
  renderTerminalReport,
3067
+ runSetup,
2472
3068
  runOptimize,
2473
3069
  runCli,
2474
3070
  scanRepo,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "getprismo",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Local AI coding workflow scanner for Codex, Claude Code, Cursor, and token-waste diagnostics.",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {