getprismo 0.1.1 → 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 +43 -9
- package/lib/prismo-dev-scan.js +641 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
#
|
|
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
|
|
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
|
|
21
|
-
npx getprismo
|
|
22
|
+
npx getprismo scan --usage
|
|
23
|
+
npx getprismo setup
|
|
24
|
+
npx getprismo watch --once
|
|
22
25
|
```
|
|
23
26
|
|
|
24
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
```
|
package/lib/prismo-dev-scan.js
CHANGED
|
@@ -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" ?
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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 >=
|
|
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
|
|
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("
|
|
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("#
|
|
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("
|
|
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.
|
|
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);
|
|
@@ -1835,15 +2228,27 @@ function getCodexSessionFiles() {
|
|
|
1835
2228
|
|
|
1836
2229
|
function getClaudeSessionFiles(cwd = process.cwd()) {
|
|
1837
2230
|
const claudeHome = process.env.PRISMO_CLAUDE_HOME || path.join(os.homedir(), ".claude");
|
|
1838
|
-
const
|
|
1839
|
-
|
|
1840
|
-
|
|
2231
|
+
const candidates = [cwd];
|
|
2232
|
+
try {
|
|
2233
|
+
candidates.push(fs.realpathSync(cwd));
|
|
2234
|
+
} catch {
|
|
2235
|
+
// Keep the original cwd candidate when realpath is unavailable.
|
|
2236
|
+
}
|
|
2237
|
+
const files = [];
|
|
2238
|
+
for (const candidate of Array.from(new Set(candidates))) {
|
|
2239
|
+
const safeProject = candidate.replace(/[\/\\:]/g, "-").replace(/^-/, "-");
|
|
2240
|
+
const projectDir = path.join(claudeHome, "projects", safeProject);
|
|
2241
|
+
files.push(...listFilesRecursive(projectDir, (file) => file.endsWith(".jsonl"), 200));
|
|
2242
|
+
}
|
|
2243
|
+
return Array.from(new Set(files));
|
|
1841
2244
|
}
|
|
1842
2245
|
|
|
1843
2246
|
function sameResolvedPath(a, b) {
|
|
1844
2247
|
if (!a || !b) return false;
|
|
1845
2248
|
try {
|
|
1846
|
-
|
|
2249
|
+
const resolvedA = fs.existsSync(a) ? fs.realpathSync(a) : path.resolve(a);
|
|
2250
|
+
const resolvedB = fs.existsSync(b) ? fs.realpathSync(b) : path.resolve(b);
|
|
2251
|
+
return resolvedA === resolvedB;
|
|
1847
2252
|
} catch {
|
|
1848
2253
|
return false;
|
|
1849
2254
|
}
|
|
@@ -1871,20 +2276,23 @@ function getUsageSummary(options = {}) {
|
|
|
1871
2276
|
const totals = selected.reduce(
|
|
1872
2277
|
(acc, session) => {
|
|
1873
2278
|
acc.displayTokens += session.displayTokens || 0;
|
|
2279
|
+
acc.contextTokens += session.contextTokens || 0;
|
|
1874
2280
|
acc.estimatedTokens += session.estimatedTotalTokens || 0;
|
|
1875
2281
|
acc.exactTokens += session.exactAvailable ? session.exactTotalTokens : 0;
|
|
1876
2282
|
acc.toolTokens += session.estimatedToolTokens || 0;
|
|
1877
2283
|
acc.sessions += 1;
|
|
1878
2284
|
return acc;
|
|
1879
2285
|
},
|
|
1880
|
-
{ sessions: 0, displayTokens: 0, estimatedTokens: 0, exactTokens: 0, toolTokens: 0 }
|
|
2286
|
+
{ sessions: 0, displayTokens: 0, contextTokens: 0, estimatedTokens: 0, exactTokens: 0, toolTokens: 0 }
|
|
1881
2287
|
);
|
|
2288
|
+
const sources = Array.from(new Set(selected.map((session) => session.tool).filter(Boolean)));
|
|
1882
2289
|
return {
|
|
1883
2290
|
generatedAt: new Date().toISOString(),
|
|
1884
2291
|
scannedPath: cwd,
|
|
1885
2292
|
tool,
|
|
1886
2293
|
confidence: selected.every((session) => session.exactAvailable) && selected.length ? "exact-local-log" : "mixed-or-estimated",
|
|
1887
2294
|
totals,
|
|
2295
|
+
sources,
|
|
1888
2296
|
sessions: selected,
|
|
1889
2297
|
};
|
|
1890
2298
|
}
|
|
@@ -1914,6 +2322,91 @@ function formatTokenCount(value) {
|
|
|
1914
2322
|
return String(Math.round(n));
|
|
1915
2323
|
}
|
|
1916
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
|
+
|
|
1917
2410
|
function renderUsageTerminal(summary, title = "Prismo Usage") {
|
|
1918
2411
|
const lines = [];
|
|
1919
2412
|
lines.push("");
|
|
@@ -1942,16 +2435,67 @@ function renderUsageTerminal(summary, title = "Prismo Usage") {
|
|
|
1942
2435
|
return lines.join("\n");
|
|
1943
2436
|
}
|
|
1944
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
|
+
|
|
1945
2488
|
async function watchUsage(options = {}) {
|
|
1946
2489
|
const intervalMs = options.intervalMs || 3000;
|
|
1947
2490
|
const iterations = options.once ? 1 : Number.POSITIVE_INFINITY;
|
|
1948
2491
|
for (let i = 0; i < iterations; i += 1) {
|
|
1949
2492
|
const summary = getUsageSummary(options);
|
|
2493
|
+
summary.live = buildLiveSessionView(summary);
|
|
1950
2494
|
if (options.json) {
|
|
1951
2495
|
console.log(JSON.stringify(summary, null, 2));
|
|
1952
2496
|
} else {
|
|
1953
2497
|
console.clear();
|
|
1954
|
-
console.log(
|
|
2498
|
+
console.log(renderWatchTerminal(summary));
|
|
1955
2499
|
console.log(`\nRefreshing every ${Math.round(intervalMs / 1000)}s. Press Ctrl+C to stop.`);
|
|
1956
2500
|
}
|
|
1957
2501
|
if (i + 1 >= iterations) break;
|
|
@@ -2078,6 +2622,34 @@ function createDemoResult() {
|
|
|
2078
2622
|
sessions: [{}, {}, {}],
|
|
2079
2623
|
},
|
|
2080
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
|
+
},
|
|
2081
2653
|
topTokenLeaks: getTopTokenLeaks(issues),
|
|
2082
2654
|
hasClaudeIgnore: false,
|
|
2083
2655
|
repoDetected: true,
|
|
@@ -2101,10 +2673,11 @@ function runDevFlow(rootDir = process.cwd(), options = {}) {
|
|
|
2101
2673
|
const scanDone = printStep("Scanning repo and local usage", options.json);
|
|
2102
2674
|
const scan = scanRepo(root, { includeUsage: true, usageLimit: options.limit || 3 });
|
|
2103
2675
|
scanDone();
|
|
2676
|
+
const initialContext = createOptimizeContext(root);
|
|
2677
|
+
const scope = initialContext.frameworks.some((name) => ["Next.js", "React", "Vite"].includes(name)) ? "frontend" : null;
|
|
2104
2678
|
const optimizeDone = printStep("Generating compact context files", options.json);
|
|
2105
|
-
const optimize = runOptimize(root);
|
|
2679
|
+
const optimize = runOptimize(root, { scope });
|
|
2106
2680
|
optimizeDone();
|
|
2107
|
-
const scope = optimize.frameworks.some((name) => ["Next.js", "React", "Vite"].includes(name)) ? "frontend" : null;
|
|
2108
2681
|
const ctx = createOptimizeContext(root, scope);
|
|
2109
2682
|
const prompt = renderContextCommand(ctx, scope);
|
|
2110
2683
|
return {
|
|
@@ -2119,7 +2692,7 @@ function runDevFlow(rootDir = process.cwd(), options = {}) {
|
|
|
2119
2692
|
function renderDevTerminal(result) {
|
|
2120
2693
|
const lines = [];
|
|
2121
2694
|
lines.push("");
|
|
2122
|
-
lines.push(color("
|
|
2695
|
+
lines.push(color("PrismoDev", "bold"));
|
|
2123
2696
|
lines.push("");
|
|
2124
2697
|
lines.push(`Score: ${result.scan.score}/100 | Risk: ${result.scan.risk} | Token leaks: ${result.scan.issues.length}`);
|
|
2125
2698
|
if (result.scan.realUsage && result.scan.realUsage.sessions.length) {
|
|
@@ -2182,6 +2755,7 @@ function printHelp() {
|
|
|
2182
2755
|
|
|
2183
2756
|
Usage:
|
|
2184
2757
|
prismo dev [path]
|
|
2758
|
+
prismo setup [--json] [--proxy-url URL] [path]
|
|
2185
2759
|
prismo scan [--fix] [--json] [--usage] [--no-report] [path]
|
|
2186
2760
|
prismo optimize [scope] [--json] [path]
|
|
2187
2761
|
prismo context [scope] [--json] [path]
|
|
@@ -2191,12 +2765,13 @@ Usage:
|
|
|
2191
2765
|
|
|
2192
2766
|
Commands:
|
|
2193
2767
|
dev Guided flow: scan, optimize, and print a paste-ready context prompt.
|
|
2194
|
-
scan Run
|
|
2768
|
+
scan Run PrismoDev for Claude Code, Codex, Cursor, and AI coding workflows.
|
|
2195
2769
|
optimize Generate lightweight AI-readable project context files in .prismo/.
|
|
2196
2770
|
context Print a copy-pasteable compact context prompt for AI coding tools.
|
|
2197
2771
|
usage Read local Codex/Claude Code session logs and summarize token usage.
|
|
2198
2772
|
watch Refresh local session usage in the terminal.
|
|
2199
2773
|
demo Show sample output without needing a messy repo.
|
|
2774
|
+
setup Detect coding tools, tracking modes, local logs, and Prismo proxy readiness.
|
|
2200
2775
|
|
|
2201
2776
|
Options:
|
|
2202
2777
|
--fix Safely create .claudeignore if missing and generate the report.
|
|
@@ -2205,6 +2780,7 @@ Options:
|
|
|
2205
2780
|
--no-report Do not write prismo-dev-report.md.
|
|
2206
2781
|
--limit N Number of recent local sessions to show.
|
|
2207
2782
|
--interval N Refresh interval in seconds for watch mode.
|
|
2783
|
+
--proxy-url URL Prismo proxy URL to check during setup.
|
|
2208
2784
|
--once Run watch mode once, useful for tests and scripts.
|
|
2209
2785
|
--help Show this help.
|
|
2210
2786
|
`);
|
|
@@ -2212,7 +2788,7 @@ Options:
|
|
|
2212
2788
|
|
|
2213
2789
|
function printCommandHelp(command) {
|
|
2214
2790
|
const help = {
|
|
2215
|
-
scan: `
|
|
2791
|
+
scan: `PrismoDev
|
|
2216
2792
|
|
|
2217
2793
|
Usage:
|
|
2218
2794
|
prismo scan [--fix] [--json] [--usage] [--no-report] [--limit N] [path]
|
|
@@ -2265,7 +2841,7 @@ Usage:
|
|
|
2265
2841
|
Examples:
|
|
2266
2842
|
prismo watch codex
|
|
2267
2843
|
prismo watch claude --once --json`,
|
|
2268
|
-
dev: `
|
|
2844
|
+
dev: `PrismoDev
|
|
2269
2845
|
|
|
2270
2846
|
Usage:
|
|
2271
2847
|
prismo dev [--json] [--limit N] [path]
|
|
@@ -2274,12 +2850,25 @@ Flow:
|
|
|
2274
2850
|
1. Scan repo and local usage.
|
|
2275
2851
|
2. Generate .prismo/ optimized context files.
|
|
2276
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.`,
|
|
2277
2866
|
demo: `Prismo Demo
|
|
2278
2867
|
|
|
2279
2868
|
Usage:
|
|
2280
2869
|
prismo demo
|
|
2281
2870
|
|
|
2282
|
-
Shows sample
|
|
2871
|
+
Shows sample PrismoDev output without reading local files.`,
|
|
2283
2872
|
};
|
|
2284
2873
|
console.log(help[command] || "Unknown command. Try: prismo --help");
|
|
2285
2874
|
}
|
|
@@ -2294,8 +2883,8 @@ async function runCli(argv) {
|
|
|
2294
2883
|
printCommandHelp(command);
|
|
2295
2884
|
return;
|
|
2296
2885
|
}
|
|
2297
|
-
if (!["dev", "scan", "optimize", "context", "usage", "watch", "demo"].includes(command)) {
|
|
2298
|
-
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`);
|
|
2299
2888
|
}
|
|
2300
2889
|
|
|
2301
2890
|
if (command === "demo") {
|
|
@@ -2329,6 +2918,24 @@ async function runCli(argv) {
|
|
|
2329
2918
|
return;
|
|
2330
2919
|
}
|
|
2331
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
|
+
|
|
2332
2939
|
if (command === "usage" || command === "watch") {
|
|
2333
2940
|
const json = rest.includes("--json");
|
|
2334
2941
|
const knownTools = new Set(["codex", "claude", "all"]);
|
|
@@ -2455,7 +3062,9 @@ module.exports = {
|
|
|
2455
3062
|
estimateTokens,
|
|
2456
3063
|
renderMarkdownReport,
|
|
2457
3064
|
renderUsageTerminal,
|
|
3065
|
+
renderWatchTerminal,
|
|
2458
3066
|
renderTerminalReport,
|
|
3067
|
+
runSetup,
|
|
2459
3068
|
runOptimize,
|
|
2460
3069
|
runCli,
|
|
2461
3070
|
scanRepo,
|