costlayers 0.8.20 → 0.8.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,23 @@
1
1
  # CostLayers CLI
2
2
 
3
- CostLayers is a cost-optimization layer for AI coding agents. It reduces repeated token waste from repo exploration, gives API users invoice-mode savings, and gives ChatGPT-login Codex users a usage-stretch meter that shows how much repeated context was avoided.
3
+ CostLayers is an AI spend control layer for coding-agent teams. It starts with a free audit, finds repeated context waste, gives API users invoice-mode savings, and gives ChatGPT-login Codex users a usage-stretch meter that shows how much repeated context was avoided.
4
4
 
5
5
  ## Quick Start
6
6
 
7
- Daily Codex use:
7
+ Run a free AI spend audit:
8
8
 
9
9
  ```bash
10
10
  cd your-repo
11
- npx -y https://costlayers.com/costlayers-0.8.20.tgz codex --email you@example.com --chatgpt
11
+ npx -y https://costlayers.com/costlayers-0.8.27.tgz audit --monthly-spend 10000
12
12
  ```
13
13
 
14
- This default path preserves Codex's native ChatGPT-login/provider flow and shows a usage-stretch meter. CostLayers only routes billable API traffic when you explicitly pass `--api`.
14
+ Use your real monthly AI spend if you have it. Omit `--monthly-spend` to get a repo-only audit. The audit writes `.agentspend/ai-spend-audit.md` and labels scan estimates separately from verified invoice savings. After the audit, `costlayers dashboard` shows the local result even before signup.
15
15
 
16
16
  Run a one-command API savings test:
17
17
 
18
18
  ```bash
19
19
  export OPENAI_API_KEY=sk-proj-...
20
- npx -y https://costlayers.com/costlayers-0.8.20.tgz test --email you@example.com
20
+ npx -y https://costlayers.com/costlayers-0.8.27.tgz test --email you@example.com
21
21
  ```
22
22
 
23
23
  ## Usage
@@ -25,7 +25,13 @@ npx -y https://costlayers.com/costlayers-0.8.20.tgz test --email you@example.com
25
25
  Inside a repo:
26
26
 
27
27
  ```bash
28
- npx -y https://costlayers.com/costlayers-0.8.20.tgz codex --email you@example.com --chatgpt
28
+ npx -y https://costlayers.com/costlayers-0.8.27.tgz audit --monthly-spend 10000
29
+ ```
30
+
31
+ Then run Codex with CostLayers:
32
+
33
+ ```bash
34
+ npx -y https://costlayers.com/costlayers-0.8.27.tgz codex --email you@example.com --chatgpt
29
35
  ```
30
36
 
31
37
  This gives Codex `.agentspend/repo-pack.md` and `.agentspend/runtime-plan.md`
@@ -45,7 +51,7 @@ API write permission:
45
51
 
46
52
  ```bash
47
53
  export OPENAI_API_KEY=sk-proj-...
48
- npx -y https://costlayers.com/costlayers-0.8.20.tgz codex --email you@example.com --api
54
+ npx -y https://costlayers.com/costlayers-0.8.27.tgz codex --email you@example.com --api
49
55
  ```
50
56
 
51
57
  ChatGPT-login Codex can be metered, but it does not create per-request OpenAI
@@ -56,12 +62,13 @@ Platform invoice savings because it is not billed through your Platform API key.
56
62
  - ChatGPT-login Codex: use `costlayers codex --email you@example.com --chatgpt` to reduce repeated repo context and stretch usage limits.
57
63
  - OpenAI Platform API billing: set `OPENAI_API_KEY`, then use `costlayers codex --email you@example.com --api` for invoice-backed savings.
58
64
  - Savings proof: set `OPENAI_API_KEY`, then run `costlayers test --email you@example.com`.
65
+ - Free audit: run `costlayers audit --monthly-spend <usd>` before changing a workflow.
59
66
  - Other OpenAI-compatible clients: point the client at the CostLayers gateway URL and check `costlayers gateway report`.
60
67
 
61
68
  To install only the Codex profile after signup:
62
69
 
63
70
  ```bash
64
- npx -y https://costlayers.com/costlayers-0.8.20.tgz codex-profile
71
+ npx -y https://costlayers.com/costlayers-0.8.27.tgz codex-profile
65
72
  codex --profile costlayers
66
73
  ```
67
74
 
@@ -93,6 +100,8 @@ The hosted reducer defaults to quality-safe reduction:
93
100
 
94
101
  Output:
95
102
 
103
+ - `.agentspend/ai-spend-audit.md`
104
+ - `.agentspend/ai-spend-audit.json`
96
105
  - `.agentspend/repo-pack.md`
97
106
  - `.agentspend/savings-report.md`
98
107
  - `.agentspend/savings-report.json`
package/bin/agentspend.js CHANGED
@@ -11,14 +11,14 @@ const { spawnSync } = require("child_process");
11
11
 
12
12
  function packageVersion() {
13
13
  try {
14
- return require(path.join(__dirname, "..", "package.json")).version || "0.8.20";
14
+ return require(path.join(__dirname, "..", "package.json")).version || "0.8.27";
15
15
  } catch (_err) {
16
- return "0.8.20";
16
+ return "0.8.27";
17
17
  }
18
18
  }
19
19
 
20
20
  const VERSION = packageVersion();
21
- const INSTALL_SPEC = "https://costlayers.com/costlayers-0.8.20.tgz";
21
+ const INSTALL_SPEC = process.env.COSTLAYERS_INSTALL_SPEC || `https://costlayers.com/costlayers-${VERSION}.tgz`;
22
22
  const DEFAULT_RUNS_PER_WEEK = 20;
23
23
  const WEEKS_PER_MONTH = 4.33;
24
24
  const DEFAULT_EXCLUDES = new Set([
@@ -60,6 +60,7 @@ function usage(exitCode = 0) {
60
60
  CostLayers ${VERSION}
61
61
 
62
62
  Usage:
63
+ costlayers audit [--email <email>] [--monthly-spend <usd>] [--usage-file <path>]
63
64
  costlayers codex [--email <email>] [--chatgpt|--api] [-- <codex args>]
64
65
  costlayers test [--email <email>] [--prompt <text>]
65
66
  costlayers init [--repo <path>]
@@ -76,6 +77,7 @@ Usage:
76
77
  costlayers doctor
77
78
 
78
79
  Commands:
80
+ audit Run a free AI spend audit: repo context waste plus optional monthly spend/imported usage.
79
81
  codex Start Codex with CostLayers. Defaults to ChatGPT-login mode unless --api is passed.
80
82
  test Run a safe read-only API invoice-mode Codex task and print the CostLayers savings report.
81
83
  init Create .agentspend config and agent instructions.
@@ -195,7 +197,7 @@ function repoConnectionPath(repo) {
195
197
  return path.join(repo, ".agentspend", "connection.json");
196
198
  }
197
199
 
198
- function ensureAgentSpendGitignore(outDir) {
200
+ function ensureCostLayersGitignore(outDir) {
199
201
  ensureDir(outDir);
200
202
  const file = path.join(outDir, ".gitignore");
201
203
  const required = ["connection.json", "*.secret.json", "gateway-key.txt", "local-cache.json"];
@@ -239,7 +241,7 @@ function writePrivateJson(file, payload) {
239
241
  function saveConnection(repo, connection) {
240
242
  const outDir = path.join(repo, ".agentspend");
241
243
  ensureDir(outDir);
242
- ensureAgentSpendGitignore(outDir);
244
+ ensureCostLayersGitignore(outDir);
243
245
  const connectionId = connectionIdFor(repo, connection);
244
246
  const full = {
245
247
  ...connection,
@@ -677,7 +679,7 @@ function writeLocalCache(outDir, payload) {
677
679
  function scanToFiles(repo, args) {
678
680
  const outDir = path.join(repo, ".agentspend");
679
681
  ensureDir(outDir);
680
- ensureAgentSpendGitignore(outDir);
682
+ ensureCostLayersGitignore(outDir);
681
683
  const tasks = Number(args.tasks || 100);
682
684
  const runsPerWeek = Number(args["runs-per-week"] || DEFAULT_RUNS_PER_WEEK);
683
685
  const pricePer1m = Number(args["price-per-1m"] || 2.0);
@@ -786,7 +788,7 @@ This public scanner estimates repeated context waste. Real savings should be val
786
788
  function init(repo, options = {}) {
787
789
  const outDir = path.join(repo, ".agentspend");
788
790
  ensureDir(outDir);
789
- ensureAgentSpendGitignore(outDir);
791
+ ensureCostLayersGitignore(outDir);
790
792
  writeIfMissing(path.join(outDir, "config.json"), JSON.stringify({
791
793
  version: VERSION,
792
794
  created_utc: new Date().toISOString(),
@@ -862,6 +864,58 @@ function loadConnection(repo, args) {
862
864
  };
863
865
  }
864
866
 
867
+ function localAudit(repo) {
868
+ const auditPath = path.join(repo, ".agentspend", "ai-spend-audit.json");
869
+ const savingsPath = path.join(repo, ".agentspend", "savings-report.json");
870
+ const auditReportPath = path.join(repo, ".agentspend", "ai-spend-audit.md");
871
+ const savingsReportPath = path.join(repo, ".agentspend", "savings-report.md");
872
+ const audit = readJsonIfExists(auditPath);
873
+ const savings = readJsonIfExists(savingsPath);
874
+ if (!audit && !savings) return null;
875
+ return { audit, savings, auditReportPath, savingsReportPath };
876
+ }
877
+
878
+ function printLocalDashboard(repo) {
879
+ const local = localAudit(repo);
880
+ if (!local) {
881
+ process.stderr.write([
882
+ "No CostLayers dashboard is available for this repo yet.",
883
+ "",
884
+ "Run a free local audit first:",
885
+ ` npx -y ${INSTALL_SPEC} audit --monthly-spend 10000`,
886
+ "",
887
+ "Or create a hosted dashboard:",
888
+ ` npx -y ${INSTALL_SPEC} signup --email you@example.com`,
889
+ ""
890
+ ].join("\n"));
891
+ return false;
892
+ }
893
+ const audit = local.audit || {};
894
+ const savings = local.savings || {};
895
+ const contextTokens = audit.tokens_avoided_per_run ?? savings.tokens_avoided_per_repeated_task ?? 0;
896
+ const contextReduction = audit.context_reduction_percent ?? savings.estimated_reduction_percent ?? 0;
897
+ const contextPackTokens = audit.context_pack_tokens ?? savings.context_pack_tokens ?? 0;
898
+ const sourceTokens = audit.source_tokens_indexed ?? savings.source_tokens_indexed ?? 0;
899
+ process.stdout.write(`CostLayers Local Dashboard\n`);
900
+ process.stdout.write(`status: local audit only; no hosted account connected\n`);
901
+ process.stdout.write(`repo: ${path.basename(repo)}\n`);
902
+ if (audit.spend_analyzed_display) process.stdout.write(`ai_spend_analyzed: ${audit.spend_analyzed_display}\n`);
903
+ if (audit.waste_found_display) process.stdout.write(`candidate_waste_found: ${audit.waste_found_display}\n`);
904
+ if (audit.safe_savings_display) process.stdout.write(`quality_safe_savings_now: ${audit.safe_savings_display}\n`);
905
+ process.stdout.write(`context_tokens_avoided_per_agent_run: ${formatInt(contextTokens)}\n`);
906
+ process.stdout.write(`context_reduction: ${Number(contextReduction || 0).toFixed(2)}%\n`);
907
+ process.stdout.write(`before_after_context: ${formatInt(sourceTokens)} -> ${formatInt(contextPackTokens)} tokens\n`);
908
+ process.stdout.write(`local_report: ${local.audit ? local.auditReportPath : local.savingsReportPath}\n`);
909
+ process.stdout.write(`\nNext:\n`);
910
+ process.stdout.write(` Open the local report above, or create the hosted savings meter:\n`);
911
+ process.stdout.write(` npx -y ${INSTALL_SPEC} signup --email you@example.com\n`);
912
+ process.stdout.write(`\nEvidence labels:\n`);
913
+ process.stdout.write(` Context estimate: local scan, visible immediately.\n`);
914
+ process.stdout.write(` Usage stretch: appears after Codex profile token events.\n`);
915
+ process.stdout.write(` API invoice savings: appears only after API-billed traffic is routed through CostLayers.\n`);
916
+ return true;
917
+ }
918
+
865
919
  function defaultPublicGatewayUrl(engineUrl, apiKey) {
866
920
  try {
867
921
  const url = new URL(engineUrl);
@@ -920,10 +974,20 @@ function dashboardUrlFromConnection(connection) {
920
974
  return (connection.gateway_url || defaultPublicGatewayUrl(connection.engine_url, connection.api_key)).replace("/gateway/", "/engine/dashboard/");
921
975
  }
922
976
 
923
- function printSavingsSummary(report) {
977
+ function printSavingsSummary(report, options = {}) {
924
978
  const verdict = savingsVerdict(report);
925
979
  const projection = verdict.projection;
926
980
  const avoided = verdict.avoided;
981
+ if (options.compact) {
982
+ if (!verdict.meaningful) {
983
+ process.stdout.write(`CostLayers: repo scanned; no meaningful savings claim for this small/simple repo.\n`);
984
+ } else {
985
+ process.stdout.write(`CostLayers found context waste: ${formatInt(report.tokens_avoided_per_repeated_task)} tokens/run avoided (${report.estimated_reduction_percent}% less).\n`);
986
+ process.stdout.write(`Usage-stretch estimate: ${formatUsd(projection.monthlyUsd)}/month at ${formatInt(projection.runsPerWeek)} agent runs/week.\n`);
987
+ }
988
+ process.stdout.write(`Invoice savings require API mode; ChatGPT-login mode keeps native Codex auth.\n`);
989
+ return;
990
+ }
927
991
  if (!verdict.meaningful) {
928
992
  process.stdout.write(`\nCostLayers installed. This repo is too small for a meaningful savings claim.\n`);
929
993
  } else {
@@ -951,6 +1015,177 @@ function printSavingsSummary(report) {
951
1015
  }
952
1016
  }
953
1017
 
1018
+ function moneyNumber(value) {
1019
+ const text = String(value || "").replace(/[$,_\s]/g, "");
1020
+ const number = Number(text);
1021
+ return Number.isFinite(number) && number > 0 ? number : 0;
1022
+ }
1023
+
1024
+ function usageSpendFromObject(value) {
1025
+ if (!value) return 0;
1026
+ if (Array.isArray(value)) return value.reduce((sum, item) => sum + usageSpendFromObject(item), 0);
1027
+ if (typeof value !== "object") return 0;
1028
+ const directKeys = [
1029
+ "cost_usd",
1030
+ "total_cost_usd",
1031
+ "billed_usd",
1032
+ "spend_usd",
1033
+ "amount_usd",
1034
+ "total_usd",
1035
+ "cost",
1036
+ "total_cost",
1037
+ "amount",
1038
+ "usd"
1039
+ ];
1040
+ for (const key of directKeys) {
1041
+ if (Object.prototype.hasOwnProperty.call(value, key)) {
1042
+ const amount = moneyNumber(value[key]);
1043
+ if (amount > 0) return amount;
1044
+ }
1045
+ }
1046
+ const nestedKeys = ["data", "rows", "items", "usage", "results", "records"];
1047
+ let total = 0;
1048
+ for (const key of nestedKeys) {
1049
+ if (Object.prototype.hasOwnProperty.call(value, key)) total += usageSpendFromObject(value[key]);
1050
+ }
1051
+ return total;
1052
+ }
1053
+
1054
+ function parseCsvSpend(text) {
1055
+ const lines = String(text || "").split(/\r?\n/).filter(Boolean);
1056
+ if (lines.length < 2) return 0;
1057
+ const headers = lines[0].split(",").map((item) => item.trim().replace(/^"|"$/g, "").toLowerCase());
1058
+ const spendColumns = headers
1059
+ .map((name, index) => ({ name, index }))
1060
+ .filter(({ name }) => /^(cost_usd|total_cost_usd|billed_usd|spend_usd|amount_usd|total_usd|cost|amount|usd)$/.test(name));
1061
+ if (!spendColumns.length) return 0;
1062
+ let total = 0;
1063
+ for (const line of lines.slice(1)) {
1064
+ const cells = line.split(",");
1065
+ const column = spendColumns.find(({ index }) => moneyNumber(cells[index]) > 0);
1066
+ if (column) total += moneyNumber(cells[column.index]);
1067
+ }
1068
+ return total;
1069
+ }
1070
+
1071
+ function loadSpendInput(args) {
1072
+ const supplied = moneyNumber(args["monthly-spend"] || args.spend);
1073
+ if (supplied > 0) return { monthlySpendUsd: supplied, source: "user-supplied monthly spend" };
1074
+ const usageFile = args["usage-file"] ? path.resolve(String(args["usage-file"])) : "";
1075
+ if (!usageFile) return { monthlySpendUsd: 0, source: "not supplied" };
1076
+ if (!fs.existsSync(usageFile)) {
1077
+ process.stderr.write(`Usage file not found: ${usageFile}\n`);
1078
+ process.exit(2);
1079
+ }
1080
+ const text = fs.readFileSync(usageFile, "utf8");
1081
+ let total = 0;
1082
+ if (usageFile.toLowerCase().endsWith(".json")) {
1083
+ try {
1084
+ total = usageSpendFromObject(JSON.parse(text));
1085
+ } catch (err) {
1086
+ process.stderr.write(`Could not parse usage JSON: ${err.message}\n`);
1087
+ process.exit(2);
1088
+ }
1089
+ } else {
1090
+ total = parseCsvSpend(text);
1091
+ }
1092
+ return { monthlySpendUsd: total, source: `usage import: ${usageFile}` };
1093
+ }
1094
+
1095
+ function buildAuditMarkdown(audit) {
1096
+ return `# CostLayers AI Spend Audit
1097
+
1098
+ Generated: ${audit.created_utc}
1099
+ Repo: ${audit.repo}
1100
+
1101
+ ## Executive Summary
1102
+
1103
+ - AI spend analyzed: ${audit.spend_analyzed_display}
1104
+ - Candidate waste found: ${audit.waste_found_display}
1105
+ - Quality-safe savings available now: ${audit.safe_savings_display}
1106
+ - First-scan context reduction: ${audit.context_reduction_percent}%
1107
+ - Repeated-context tokens avoided/run: ${audit.tokens_avoided_per_run.toLocaleString()}
1108
+
1109
+ ## Evidence Labels
1110
+
1111
+ - Safe estimate: local repo scan found repeated context that agents can avoid before broad exploration.
1112
+ - Verified invoice savings: requires API-mode traffic through CostLayers and provider cost rows.
1113
+ - Risky savings: not counted here. CostLayers should forward unchanged when a reduction boundary is unsafe.
1114
+
1115
+ ## Recommended Next Step
1116
+
1117
+ ${audit.next_step}
1118
+
1119
+ ## Largest Repeated Context Sources
1120
+
1121
+ ${audit.largest_files.map((row) => `- ${row.path}: ${row.tokens.toLocaleString()} tokens`).join("\n")}
1122
+ `;
1123
+ }
1124
+
1125
+ function buildAuditReport(repo, report, args) {
1126
+ const projection = savingsProjection(report);
1127
+ const spendInput = loadSpendInput(args);
1128
+ const spendAnalyzed = spendInput.monthlySpendUsd;
1129
+ const contextSafeSavings = projection.monthlyUsd;
1130
+ const contextRatio = Math.max(0, Math.min(1, Number(report.estimated_reduction_percent || 0) / 100));
1131
+ const budgetWasteEstimate = spendAnalyzed > 0 ? spendAnalyzed * Math.min(0.25, contextRatio * 0.2) : 0;
1132
+ const wasteFound = Math.max(contextSafeSavings, budgetWasteEstimate);
1133
+ const safeSavings = contextSafeSavings;
1134
+ return {
1135
+ created_utc: new Date().toISOString(),
1136
+ repo: path.basename(repo),
1137
+ spend_source: spendInput.source,
1138
+ monthly_spend_analyzed_usd: Number(spendAnalyzed.toFixed(6)),
1139
+ waste_found_usd_per_month: Number(wasteFound.toFixed(6)),
1140
+ quality_safe_savings_usd_per_month: Number(safeSavings.toFixed(6)),
1141
+ context_reduction_percent: Number(report.estimated_reduction_percent || 0),
1142
+ tokens_avoided_per_run: Number(report.tokens_avoided_per_repeated_task || 0),
1143
+ source_tokens_indexed: Number(report.source_tokens_indexed || 0),
1144
+ context_pack_tokens: Number(report.context_pack_tokens || 0),
1145
+ largest_files: report.largest_files || [],
1146
+ spend_analyzed_display: spendAnalyzed > 0 ? `${formatUsd(spendAnalyzed)}/month (${spendInput.source})` : "not supplied; using repo-scan savings only",
1147
+ waste_found_display: `${formatUsd(wasteFound)}/month candidate estimate`,
1148
+ safe_savings_display: `${formatUsd(safeSavings)}/month from local context scan`,
1149
+ next_step: spendAnalyzed > 0
1150
+ ? "Enable API invoice mode on one controlled coding-agent workflow so CostLayers can turn the audit estimate into verified provider-dollar savings."
1151
+ : "Rerun with --monthly-spend <usd> or --usage-file <path>, then enable API invoice mode on one controlled workflow."
1152
+ };
1153
+ }
1154
+
1155
+ function writeAuditReport(outDir, repo, report, args) {
1156
+ const auditReport = buildAuditReport(repo, report, args);
1157
+ fs.writeFileSync(path.join(outDir, "ai-spend-audit.json"), JSON.stringify(auditReport, null, 2) + "\n", "utf8");
1158
+ fs.writeFileSync(path.join(outDir, "ai-spend-audit.md"), buildAuditMarkdown(auditReport), "utf8");
1159
+ return auditReport;
1160
+ }
1161
+
1162
+ async function audit(repo, args) {
1163
+ process.stdout.write(`Running CostLayers AI spend audit for: ${repo}\n`);
1164
+ const precomputed = scanToFiles(repo, args);
1165
+ const { outDir, report } = precomputed;
1166
+ const auditReport = writeAuditReport(outDir, repo, report, args);
1167
+ process.stdout.write(`\nCostLayers AI Spend Audit\n`);
1168
+ process.stdout.write(`\nYou found repeated context waste\n`);
1169
+ process.stdout.write(` AI spend analyzed: ${auditReport.spend_analyzed_display}\n`);
1170
+ process.stdout.write(` Candidate waste found: ${auditReport.waste_found_display}\n`);
1171
+ process.stdout.write(` Quality-safe savings available now: ${auditReport.safe_savings_display}\n`);
1172
+ process.stdout.write(` Context reduction: ${auditReport.context_reduction_percent}%\n`);
1173
+ process.stdout.write(` Tokens avoided per agent run: ${formatInt(auditReport.tokens_avoided_per_run)}\n`);
1174
+ process.stdout.write(` Audit report: ${path.join(outDir, "ai-spend-audit.md")}\n`);
1175
+ process.stdout.write(`\nView this result any time:\n`);
1176
+ process.stdout.write(` npx -y ${INSTALL_SPEC} dashboard\n`);
1177
+ process.stdout.write(`\nNext best step:\n`);
1178
+ process.stdout.write(` ${auditReport.next_step}\n`);
1179
+ process.stdout.write(`\nHosted dashboard, optional:\n`);
1180
+ process.stdout.write(` npx -y ${INSTALL_SPEC} signup --email you@example.com\n`);
1181
+ await trackEvent(repo, args, "ai_spend_audit", {
1182
+ monthly_spend_analyzed_usd: auditReport.monthly_spend_analyzed_usd,
1183
+ waste_found_usd_per_month: auditReport.waste_found_usd_per_month,
1184
+ quality_safe_savings_usd_per_month: auditReport.quality_safe_savings_usd_per_month,
1185
+ context_tokens_avoided_per_task: auditReport.tokens_avoided_per_run
1186
+ });
1187
+ }
1188
+
954
1189
  function codexHomeDir() {
955
1190
  return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
956
1191
  }
@@ -1154,7 +1389,10 @@ async function signup(repo, args) {
1154
1389
  process.stdout.write(`Dashboard: ${dashboardUrlFromConnection(connection)}\n`);
1155
1390
  process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
1156
1391
  process.stdout.write(`Plan: free beta\n`);
1157
- process.stdout.write(`Next: costlayers gateway start --mode reduce --provider-url https://api.openai.com\n`);
1392
+ process.stdout.write(`\nNext:\n`);
1393
+ process.stdout.write(` View dashboard: npx -y ${INSTALL_SPEC} dashboard\n`);
1394
+ process.stdout.write(` Run Codex with usage-stretch metering: npx -y ${INSTALL_SPEC} codex --email ${connection.email || "you@example.com"} --chatgpt\n`);
1395
+ process.stdout.write(` Prove API invoice savings: export OPENAI_API_KEY=sk-proj-... && npx -y ${INSTALL_SPEC} test --email ${connection.email || "you@example.com"}\n`);
1158
1396
  }
1159
1397
 
1160
1398
  async function codexProfile(repo, args) {
@@ -1327,24 +1565,28 @@ async function runAgent(repo, args, argv, options = {}) {
1327
1565
  if (connection && connection.engine_url) {
1328
1566
  try {
1329
1567
  plan = await fetchEnginePlan(connection, repo, pack, report);
1330
- process.stdout.write(`Fetched CostLayers engine plan\n`);
1568
+ if (!options.compactOutput) process.stdout.write(`Fetched CostLayers engine plan\n`);
1331
1569
  } catch (err) {
1332
1570
  process.stderr.write(`Engine unavailable; falling back to local plan: ${err.message}\n`);
1333
1571
  }
1334
1572
  }
1335
1573
  writeRuntimePrompt(outDir, plan);
1336
- process.stdout.write(`Runtime plan: ${path.join(outDir, "runtime-plan.md")}\n`);
1574
+ if (!options.compactOutput) process.stdout.write(`Runtime plan: ${path.join(outDir, "runtime-plan.md")}\n`);
1337
1575
  let commandToRun = command;
1338
1576
  if (connection && connection.engine_url && isCodexCommand(command)) {
1339
1577
  assertCodexProxyApiKey(args);
1340
1578
  const profilePath = writeCodexProfile(connection, args);
1341
1579
  commandToRun = withCostLayersCodexProfile(command);
1342
1580
  await trackEvent(repo, args, "codex_run", { profile_mode: codexProxyEnabled(args) ? "api" : "chatgpt" }, connection);
1343
- process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
1344
- process.stdout.write(`Codex metering enabled: ${commandToRun.join(" ")}\n`);
1345
- process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
1346
- process.stdout.write(`Savings dashboard: ${dashboardUrlFromConnection(connection)}\n`);
1347
- process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
1581
+ if (options.compactOutput) {
1582
+ process.stdout.write(`Launching Codex with CostLayers profile: ${commandToRun.join(" ")}\n`);
1583
+ } else {
1584
+ process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
1585
+ process.stdout.write(`Codex metering enabled: ${commandToRun.join(" ")}\n`);
1586
+ process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
1587
+ process.stdout.write(`Savings dashboard: ${dashboardUrlFromConnection(connection)}\n`);
1588
+ process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
1589
+ }
1348
1590
  }
1349
1591
  const env = {
1350
1592
  ...process.env,
@@ -1417,8 +1659,19 @@ async function gateway(repo, args) {
1417
1659
  }
1418
1660
 
1419
1661
  async function dashboard(repo, args) {
1420
- const connection = loadConnection(repo, args);
1421
- const status = await postJson(`${connection.engine_url}/v1/me`, {}, connection.api_key);
1662
+ const connection = loadStoredConnection(repo);
1663
+ if (!connection || !connection.engine_url || !connection.api_key) {
1664
+ if (printLocalDashboard(repo)) return;
1665
+ process.exit(2);
1666
+ }
1667
+ let status;
1668
+ try {
1669
+ status = await postJson(`${connection.engine_url}/v1/me`, {}, connection.api_key);
1670
+ } catch (err) {
1671
+ process.stderr.write(`Hosted dashboard unavailable: ${err.message}\n\n`);
1672
+ if (printLocalDashboard(repo)) return;
1673
+ process.exit(1);
1674
+ }
1422
1675
  await trackEvent(repo, args, "dashboard_open", {}, connection);
1423
1676
  const dashboardUrl = (connection.gateway_url || defaultPublicGatewayUrl(connection.engine_url, connection.api_key)).replace("/gateway/", "/engine/dashboard/");
1424
1677
  process.stdout.write(`CostLayers Dashboard\n`);
@@ -1440,11 +1693,11 @@ async function codexShortcut(repo, args, argv) {
1440
1693
  const codexTail = codexArgsAfterDash(argv);
1441
1694
  const command = codexTail.length > 0 ? codexTail : ["codex"];
1442
1695
  const commandToRun = isCodexCommand(command) ? command : ["codex", ...command];
1443
- const nextArgs = withAutoCodexMode(args);
1696
+ const nextArgs = withAutoCodexMode({ ...args, "ux-compact": true });
1444
1697
  if (codexProxyEnabled(nextArgs)) {
1445
1698
  process.stdout.write(`CostLayers Codex: API invoice mode explicitly enabled from ${codexProxyApiKeyEnv(nextArgs)}.\n`);
1446
1699
  } else {
1447
- process.stdout.write(`CostLayers Codex: ChatGPT usage-stretch mode. Pass --api to route API-billed provider calls for invoice savings.\n`);
1700
+ process.stdout.write(`CostLayers Codex: ChatGPT usage-stretch mode. Native Codex auth stays unchanged.\n`);
1448
1701
  }
1449
1702
  return start(repo, nextArgs, ["start", "--", ...commandToRun]);
1450
1703
  }
@@ -1485,16 +1738,18 @@ async function savingsTest(repo, args) {
1485
1738
  async function start(repo, args, argv, options = {}) {
1486
1739
  const dash = argv.indexOf("--");
1487
1740
  const command = dash >= 0 ? argv.slice(dash + 1) : [];
1741
+ const compactOutput = Boolean(args["ux-compact"]);
1488
1742
  const codexTelemetryRun = command.length > 0 && isCodexCommand(command) && !codexProxyEnabled(args);
1489
1743
  if (command.length > 0 && isCodexCommand(command)) assertCodexProxyApiKey(args);
1490
1744
  init(repo, { suppressNext: true });
1491
1745
  process.stdout.write(`Scanning repo: ${repo}\n`);
1492
1746
  const precomputed = scanToFiles(repo, args);
1493
1747
  const { outDir, pack, report } = precomputed;
1748
+ writeAuditReport(outDir, repo, report, args);
1494
1749
  process.stdout.write(`CostLayers scan complete\n`);
1495
1750
  if (precomputed.localCacheHit) process.stdout.write(`Local exact cache hit: source hashes unchanged, reused CostLayers artifacts\n`);
1496
- process.stdout.write(`Report: ${path.join(outDir, "savings-report.md")}\n`);
1497
- printSavingsSummary(report);
1751
+ if (!compactOutput) process.stdout.write(`Report: ${path.join(outDir, "savings-report.md")}\n`);
1752
+ printSavingsSummary(report, { compact: compactOutput });
1498
1753
  const connection = await ensureConnection(repo, args);
1499
1754
  await trackEvent(repo, args, "cli_start", {
1500
1755
  files_indexed: report.files_indexed,
@@ -1507,11 +1762,15 @@ async function start(repo, args, argv, options = {}) {
1507
1762
  } catch (err) {
1508
1763
  process.stderr.write(`Dashboard sync delayed; local report is still available: ${err.message}\n`);
1509
1764
  }
1510
- process.stdout.write(`CostLayers connection ready\n`);
1765
+ if (!compactOutput) process.stdout.write(`CostLayers connection ready\n`);
1511
1766
  let gatewayBaseUrl = connection.gateway_url || defaultPublicGatewayUrl(connection.engine_url, connection.api_key);
1512
1767
  if (codexTelemetryRun) {
1513
- process.stdout.write(`ChatGPT-login Codex mode: native Codex provider preserved; model calls are not routed through CostLayers.\n`);
1514
- process.stdout.write(`What users get: repo context discipline and a usage-stretch meter. This does not reduce a flat ChatGPT subscription invoice.\n`);
1768
+ if (compactOutput) {
1769
+ process.stdout.write(`Mode: ChatGPT-login Codex. Model calls are not routed; CostLayers adds repo context discipline and usage-stretch metering.\n`);
1770
+ } else {
1771
+ process.stdout.write(`ChatGPT-login Codex mode: native Codex provider preserved; model calls are not routed through CostLayers.\n`);
1772
+ process.stdout.write(`What users get: repo context discipline and a usage-stretch meter. This does not reduce a flat ChatGPT subscription invoice.\n`);
1773
+ }
1515
1774
  } else {
1516
1775
  const providerUrl = typeof args["provider-url"] === "string" ? args["provider-url"] : "https://api.openai.com";
1517
1776
  const payload = {
@@ -1537,13 +1796,15 @@ async function start(repo, args, argv, options = {}) {
1537
1796
  }
1538
1797
  }
1539
1798
  process.stdout.write(`Dashboard: ${dashboardUrlFromConnection(connection)}\n`);
1540
- process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
1799
+ if (!compactOutput) process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
1541
1800
  process.stdout.write(`Plan: free beta\n`);
1542
1801
  const profilePath = writeCodexProfile(connection, args);
1543
- process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
1544
- process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
1802
+ if (!compactOutput) {
1803
+ process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
1804
+ process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
1805
+ }
1545
1806
  if (command.length > 0) {
1546
- return runAgent(repo, args, argv, { skipSetup: true, precomputed, returnStatus: options.returnStatus });
1807
+ return runAgent(repo, args, argv, { skipSetup: true, precomputed, returnStatus: options.returnStatus, compactOutput });
1547
1808
  }
1548
1809
  process.stdout.write(`\nReady.\n`);
1549
1810
  process.stdout.write(` Run Codex: codex --profile costlayers\n`);
@@ -1581,8 +1842,9 @@ function main() {
1581
1842
  const cmd = args._[0];
1582
1843
  if (!cmd || args.help || args.h) usage(0);
1583
1844
  const repo = path.resolve(String(args.repo || process.cwd()));
1584
- if (["init", "scan", "start", "run", "codex-profile", "codex", "test"].includes(cmd)) guardRepoRoot(repo, args);
1845
+ if (["init", "scan", "audit", "start", "run", "codex-profile", "codex", "test"].includes(cmd)) guardRepoRoot(repo, args);
1585
1846
  if (cmd === "doctor") return doctor();
1847
+ if (cmd === "audit") return audit(repo, args);
1586
1848
  if (cmd === "codex") return codexShortcut(repo, args, rawArgv);
1587
1849
  if (cmd === "test") return savingsTest(repo, args);
1588
1850
  if (cmd === "init") return init(repo);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "costlayers",
3
- "version": "0.8.20",
3
+ "version": "0.8.27",
4
4
  "description": "CostLayers cost control for AI coding agents. Build compact repo context packs, gateway reports, and savings dashboards.",
5
5
  "bin": {
6
6
  "agentspend": "bin/agentspend.js",