costlayers 0.8.10 → 0.8.13

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,6 +1,6 @@
1
1
  # CostLayers CLI
2
2
 
3
- CostLayers helps teams reduce wasted AI coding-agent spend by creating a compact repo context pack, an OpenAI-compatible gateway, and a savings dashboard.
3
+ CostLayers helps coding-agent users stop paying for repeated repo context. API users can route model calls through the gateway for invoice savings. ChatGPT-login Codex users get a usage-stretch meter that shows how much repeated context was avoided.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,7 +8,7 @@ One-command setup:
8
8
 
9
9
  ```bash
10
10
  cd your-repo
11
- npx -y costlayers@latest start --email you@example.com
11
+ npx -y https://costlayers.com/costlayers-0.8.13.tgz start --email you@example.com
12
12
  ```
13
13
 
14
14
  ## Usage
@@ -24,19 +24,42 @@ Launch Codex with CostLayers context:
24
24
 
25
25
  ```bash
26
26
  cd your-repo
27
- npx -y costlayers@latest start --email you@example.com -- codex
27
+ npx -y https://costlayers.com/costlayers-0.8.13.tgz start --email you@example.com -- codex
28
28
  ```
29
29
 
30
30
  This gives Codex `.agentspend/repo-pack.md` and `.agentspend/runtime-plan.md`
31
31
  so it can avoid repeated broad repo exploration. It also writes
32
32
  `~/.codex/costlayers.config.toml` and runs Codex as
33
- `codex --profile costlayers`, enabling gateway metering and redacted Codex token
34
- telemetry for ChatGPT-login sessions.
33
+ `codex --profile costlayers`, enabling redacted Codex telemetry for
34
+ ChatGPT-login sessions while preserving Codex's native model provider.
35
+
36
+ This mode is for getting more useful work out of the same ChatGPT/Codex plan.
37
+ It does not reduce a flat ChatGPT subscription invoice.
38
+
39
+ ## Invoice Savings Mode
40
+
41
+ To reduce an OpenAI Platform invoice, route API-key billed model calls through
42
+ the CostLayers gateway. This requires an OpenAI Platform API key with Responses
43
+ API write permission:
44
+
45
+ ```bash
46
+ export OPENAI_API_KEY=sk-proj-...
47
+ npx -y https://costlayers.com/costlayers-0.8.13.tgz start --email you@example.com --codex-proxy -- codex
48
+ ```
49
+
50
+ ChatGPT-login Codex can be metered, but it does not create per-request OpenAI
51
+ Platform invoice savings because it is not billed through your Platform API key.
52
+
53
+ ## Which Mode Should I Use?
54
+
55
+ - ChatGPT-login Codex: use `start -- codex` to reduce repeated repo context and stretch usage limits.
56
+ - OpenAI Platform API billing: use `--codex-proxy` with `OPENAI_API_KEY` for invoice-backed savings.
57
+ - Other OpenAI-compatible clients: point the client at the CostLayers gateway URL and check `costlayers gateway report`.
35
58
 
36
59
  To install only the Codex profile after signup:
37
60
 
38
61
  ```bash
39
- npx -y costlayers@latest codex-profile
62
+ npx -y https://costlayers.com/costlayers-0.8.13.tgz codex-profile
40
63
  codex --profile costlayers
41
64
  ```
42
65
 
@@ -50,9 +73,10 @@ costlayers gateway report
50
73
  costlayers dashboard
51
74
  ```
52
75
 
53
- Metered dashboard savings only increase when model API traffic is routed through
54
- the CostLayers gateway URL. Codex-metered dashboard savings increase when Codex
55
- uses the CostLayers profile or emits token telemetry to the CostLayers meter.
76
+ Metered gateway savings increase when model API traffic is routed through the
77
+ CostLayers gateway URL with a provider API key. Codex-metered dashboard savings
78
+ increase when Codex uses the CostLayers profile or emits token telemetry to the
79
+ CostLayers meter.
56
80
  For ChatGPT-login Codex, dollar savings are modeled from observed Codex token
57
81
  events because the ChatGPT plan invoice is not an OpenAI Platform API invoice.
58
82
 
package/bin/agentspend.js CHANGED
@@ -9,7 +9,8 @@ const https = require("https");
9
9
  const os = require("os");
10
10
  const { spawnSync } = require("child_process");
11
11
 
12
- const VERSION = "0.8.10";
12
+ const VERSION = "0.8.13";
13
+ const INSTALL_SPEC = "https://costlayers.com/costlayers-0.8.13.tgz";
13
14
  const DEFAULT_RUNS_PER_WEEK = 20;
14
15
  const WEEKS_PER_MONTH = 4.33;
15
16
  const DEFAULT_EXCLUDES = new Set([
@@ -51,9 +52,9 @@ CostLayers ${VERSION}
51
52
  Usage:
52
53
  costlayers init [--repo <path>]
53
54
  costlayers scan [--repo <path>] [--price-per-1m <usd>] [--tasks <n>] [--runs-per-week <n>]
54
- costlayers start [--email <email>] [--provider-url <url>] [--mode measure|reduce] [--runs-per-week <n>] [-- <agent command>]
55
+ costlayers start [--email <email>] [--provider-url <url>] [--mode measure|reduce] [--runs-per-week <n>] [--codex-proxy] [-- <agent command>]
55
56
  costlayers signup [--email <email>] [--engine-url <url>]
56
- costlayers codex-profile [--repo <path>]
57
+ costlayers codex-profile [--repo <path>] [--codex-proxy]
57
58
  costlayers connect --engine-url <url> [--api-key <key>]
58
59
  costlayers gateway start [--provider-url <url>] [--api-key-env <name>] [--mode measure|reduce]
59
60
  costlayers gateway report
@@ -122,7 +123,7 @@ function guardRepoRoot(repo, args) {
122
123
  "",
123
124
  "Run it inside a project folder instead:",
124
125
  " cd path/to/your-repo",
125
- " npx -y costlayers@latest start --email you@example.com",
126
+ ` npx -y ${INSTALL_SPEC} start --email you@example.com`,
126
127
  "",
127
128
  "Or pass --repo path/to/your-repo from anywhere.",
128
129
  "If you really intend to scan your whole home directory, add --allow-home.",
@@ -506,6 +507,7 @@ function printSavingsSummary(report) {
506
507
  const projection = savingsProjection(report);
507
508
  const avoided = Number(report.tokens_avoided_per_repeated_task || 0);
508
509
  process.stdout.write(`\n${avoided > 0 ? "CostLayers found repeated context waste" : "CostLayers built your repo context pack"}\n`);
510
+ process.stdout.write(` Evidence: context estimate from local repo scan\n`);
509
511
  process.stdout.write(` Tokens avoided per repeated task: ${formatInt(report.tokens_avoided_per_repeated_task)}\n`);
510
512
  if (avoided > 0) {
511
513
  process.stdout.write(` Before vs after context: ${formatInt(report.baseline_broad_read_tokens)} -> ${formatInt(report.context_pack_tokens)} tokens (${report.estimated_reduction_percent}% less)\n`);
@@ -513,8 +515,9 @@ function printSavingsSummary(report) {
513
515
  process.stdout.write(` No repeated-context reduction found yet for this small/simple repo.\n`);
514
516
  process.stdout.write(` Context pack size: ${formatInt(report.context_pack_tokens)} tokens from ${formatInt(report.source_tokens_indexed)} indexed source tokens\n`);
515
517
  }
516
- process.stdout.write(` Modeled savings per ${formatInt(report.repeated_tasks_modeled)} repeated tasks: ${formatUsd(report.estimated_usd_saved)}\n`);
517
- process.stdout.write(` Projected at ${formatInt(projection.runsPerWeek)} agent runs/week: ${formatUsd(projection.weeklyUsd)}/week, ${formatUsd(projection.monthlyUsd)}/month\n`);
518
+ process.stdout.write(` Estimated waste value per ${formatInt(report.repeated_tasks_modeled)} repeated tasks: ${formatUsd(report.estimated_usd_saved)}\n`);
519
+ process.stdout.write(` Usage-stretch estimate at ${formatInt(projection.runsPerWeek)} agent runs/week: ${formatUsd(projection.weeklyUsd)}/week, ${formatUsd(projection.monthlyUsd)}/month\n`);
520
+ process.stdout.write(` Invoice savings require API traffic through CostLayers invoice mode.\n`);
518
521
  process.stdout.write(` Source tokens indexed: ${formatInt(report.source_tokens_indexed)}\n`);
519
522
  process.stdout.write(` Compact repo pack: ${formatInt(report.context_pack_tokens)} tokens\n`);
520
523
  }
@@ -523,25 +526,60 @@ function codexHomeDir() {
523
526
  return process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
524
527
  }
525
528
 
526
- function profileTomlString(connection) {
529
+ function codexProxyEnabled(args = {}) {
530
+ return Boolean(args["codex-proxy"] || args["proxy-codex"]);
531
+ }
532
+
533
+ function codexProxyApiKeyEnv(args = {}) {
534
+ return String(args["api-key-env"] || "OPENAI_API_KEY");
535
+ }
536
+
537
+ function assertCodexProxyApiKey(args = {}) {
538
+ if (!codexProxyEnabled(args)) return;
539
+ const keyEnv = codexProxyApiKeyEnv(args);
540
+ if (process.env[keyEnv]) return;
541
+ process.stderr.write([
542
+ `CostLayers invoice mode needs ${keyEnv} in this shell.`,
543
+ "",
544
+ "Set an OpenAI Platform API key with Responses API write permission, then rerun:",
545
+ ` export ${keyEnv}=sk-proj-...`,
546
+ ` npx -y ${INSTALL_SPEC} start --email you@example.com --codex-proxy -- codex`,
547
+ "",
548
+ "ChatGPT-login Codex can be metered, but it cannot produce provider invoice savings because there is no per-request Platform invoice to reduce.",
549
+ ""
550
+ ].join("\n"));
551
+ process.exit(2);
552
+ }
553
+
554
+ function profileTomlString(connection, args = {}) {
527
555
  const gateway = String(connection.gateway_url || defaultPublicGatewayUrl(connection.engine_url, connection.api_key)).replace(/\/+$/, "");
528
556
  const baseUrl = `${gateway}/v1`;
529
557
  const engineUrl = String(connection.engine_url || "https://costlayers.com/engine").replace(/\/+$/, "");
530
- return [
531
- "# Generated by CostLayers. This profile routes Codex through the CostLayers meter.",
558
+ const apiKeyEnv = codexProxyApiKeyEnv(args);
559
+ const lines = [
560
+ "# Generated by CostLayers. This profile sends Codex telemetry to the CostLayers meter.",
532
561
  "# Keep this file private because it contains your keyed CostLayers endpoint.",
533
- "",
562
+ "# Default mode preserves Codex's native model provider/auth, which works with ChatGPT-login Codex.",
563
+ ""
564
+ ];
565
+ if (codexProxyEnabled(args)) {
566
+ lines.push(
567
+ "# Proxy mode routes Codex model calls through the CostLayers gateway.",
568
+ `# It uses ${apiKeyEnv} from your shell and requires OpenAI Platform Responses API write scope.`,
569
+ "",
534
570
  'model_provider = "costlayers"',
535
571
  "",
536
572
  "[model_providers.costlayers]",
537
573
  'name = "CostLayers"',
538
574
  `base_url = ${JSON.stringify(baseUrl)}`,
539
575
  'wire_api = "responses"',
540
- "requires_openai_auth = true",
541
- "",
576
+ `env_key = ${JSON.stringify(apiKeyEnv)}`,
577
+ ""
578
+ );
579
+ }
580
+ lines.push(
542
581
  "[otel]",
543
582
  'environment = "costlayers"',
544
- 'exporter = "otlp-http"',
545
583
  "log_user_prompt = false",
546
584
  "",
547
585
  '[otel.exporter."otlp-http"]',
@@ -551,14 +589,15 @@ function profileTomlString(connection) {
551
589
  '[otel.exporter."otlp-http".headers]',
552
590
  `"x-costlayers-key" = ${JSON.stringify(connection.api_key || "")}`,
553
591
  ""
554
- ].join("\n");
592
+ );
593
+ return lines.join("\n");
555
594
  }
556
595
 
557
- function writeCodexProfile(connection) {
596
+ function writeCodexProfile(connection, args = {}) {
558
597
  const dir = codexHomeDir();
559
598
  ensureDir(dir);
560
599
  const file = path.join(dir, "costlayers.config.toml");
561
- fs.writeFileSync(file, profileTomlString(connection), "utf8");
600
+ fs.writeFileSync(file, profileTomlString(connection, args), "utf8");
562
601
  return file;
563
602
  }
564
603
 
@@ -626,9 +665,10 @@ async function signup(repo, args) {
626
665
 
627
666
  async function codexProfile(repo, args) {
628
667
  const connection = await ensureConnection(repo, args);
629
- const profilePath = writeCodexProfile(connection);
668
+ const profilePath = writeCodexProfile(connection, args);
630
669
  process.stdout.write(`CostLayers Codex profile installed\n`);
631
670
  process.stdout.write(`Profile: ${profilePath}\n`);
671
+ process.stdout.write(`Mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode, native Codex provider preserved"}\n`);
632
672
  process.stdout.write(`Dashboard: ${dashboardUrlFromConnection(connection)}\n`);
633
673
  process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
634
674
  process.stdout.write(`Run: codex --profile costlayers\n`);
@@ -748,10 +788,12 @@ async function runAgent(repo, args, argv, options = {}) {
748
788
  process.stdout.write(`Runtime plan: ${path.join(outDir, "runtime-plan.md")}\n`);
749
789
  let commandToRun = command;
750
790
  if (connection && connection.engine_url && isCodexCommand(command)) {
751
- const profilePath = writeCodexProfile(connection);
791
+ assertCodexProxyApiKey(args);
792
+ const profilePath = writeCodexProfile(connection, args);
752
793
  commandToRun = withCostLayersCodexProfile(command);
753
794
  process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
754
795
  process.stdout.write(`Codex metering enabled: ${commandToRun.join(" ")}\n`);
796
+ process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
755
797
  process.stdout.write(`Savings dashboard: ${dashboardUrlFromConnection(connection)}\n`);
756
798
  process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
757
799
  }
@@ -828,19 +870,22 @@ async function dashboard(repo, args) {
828
870
  process.stdout.write(`URL: ${dashboardUrl}\n`);
829
871
  process.stdout.write(`status: ${status.status || "unknown"}\n`);
830
872
  process.stdout.write(`plan: ${status.free_beta ? "free beta" : "metered"}\n`);
831
- process.stdout.write(`metered_savings_usd: ${status.metered_savings_usd ?? ""}\n`);
832
- process.stdout.write(`codex_metered_savings_usd: ${status.codex_metered_savings_usd ?? ""}\n`);
873
+ process.stdout.write(`api_invoice_savings_usd: ${status.metered_savings_usd ?? ""}\n`);
874
+ process.stdout.write(`chatgpt_codex_usage_stretch_usd: ${status.codex_metered_savings_usd ?? ""}\n`);
833
875
  process.stdout.write(`codex_meter_total_tokens: ${status.codex_meter_total_tokens ?? 0}\n`);
834
- process.stdout.write(`codex_context_savings_usd: ${status.context_savings_usd ?? ""}\n`);
876
+ process.stdout.write(`context_waste_estimate_usd: ${status.context_savings_usd ?? ""}\n`);
835
877
  process.stdout.write(`context_tokens_avoided_per_task: ${status.context_tokens_avoided_per_task ?? 0}\n`);
836
878
  process.stdout.write(`gateway_requests: ${status.gateway_request_count ?? 0}\n`);
837
879
  process.stdout.write(`blocked_requests: ${status.blocked_request_count ?? 0}\n`);
838
880
  process.stdout.write(`rate_limit_per_minute: ${status.rate_limit_per_minute ?? ""}\n`);
881
+ process.stdout.write(`claim: API invoice savings only count when provider API traffic is routed through CostLayers. ChatGPT-login Codex shows usage stretch, not invoice reduction.\n`);
839
882
  }
840
883
 
841
884
  async function start(repo, args, argv) {
842
885
  const dash = argv.indexOf("--");
843
886
  const command = dash >= 0 ? argv.slice(dash + 1) : [];
887
+ const codexTelemetryRun = command.length > 0 && isCodexCommand(command) && !codexProxyEnabled(args);
888
+ if (command.length > 0 && isCodexCommand(command)) assertCodexProxyApiKey(args);
844
889
  init(repo);
845
890
  process.stdout.write(`Scanning repo: ${repo}\n`);
846
891
  const precomputed = scanToFiles(repo, args);
@@ -856,37 +901,49 @@ async function start(repo, args, argv) {
856
901
  process.stderr.write(`Dashboard sync delayed; local report is still available: ${err.message}\n`);
857
902
  }
858
903
  process.stdout.write(`CostLayers connection ready\n`);
859
- const providerUrl = typeof args["provider-url"] === "string" ? args["provider-url"] : "https://api.openai.com";
860
- const payload = {
861
- host: args.host || "127.0.0.1",
862
- port: Number(args.port || 8788),
863
- provider_url: providerUrl,
864
- api_key_env: args["api-key-env"] || "OPENAI_API_KEY",
865
- mode: args.mode || "reduce",
866
- dry_run: Boolean(args["dry-run"]),
867
- public_gateway_url: args["public-gateway-url"] || connection.gateway_url || defaultPublicGatewayUrl(connection.engine_url, connection.api_key)
868
- };
869
- const result = await postJson(`${connection.engine_url}/v1/gateway/start`, payload, connection.api_key);
870
- if (!result.ok) {
871
- process.stderr.write(`${JSON.stringify(result, null, 2)}\n`);
872
- process.exit(1);
904
+ let gatewayBaseUrl = connection.gateway_url || defaultPublicGatewayUrl(connection.engine_url, connection.api_key);
905
+ if (codexTelemetryRun) {
906
+ process.stdout.write(`ChatGPT-login Codex mode: native Codex provider preserved; model calls are not routed through CostLayers.\n`);
907
+ process.stdout.write(`What users get: less repeated repo context and a usage-stretch meter. This does not reduce a flat ChatGPT subscription invoice.\n`);
908
+ } else {
909
+ const providerUrl = typeof args["provider-url"] === "string" ? args["provider-url"] : "https://api.openai.com";
910
+ const payload = {
911
+ host: args.host || "127.0.0.1",
912
+ port: Number(args.port || 8788),
913
+ provider_url: providerUrl,
914
+ api_key_env: args["api-key-env"] || "OPENAI_API_KEY",
915
+ mode: args.mode || "reduce",
916
+ dry_run: Boolean(args["dry-run"]),
917
+ public_gateway_url: args["public-gateway-url"] || gatewayBaseUrl
918
+ };
919
+ const result = await postJson(`${connection.engine_url}/v1/gateway/start`, payload, connection.api_key);
920
+ if (!result.ok) {
921
+ process.stderr.write(`${JSON.stringify(result, null, 2)}\n`);
922
+ process.exit(1);
923
+ }
924
+ gatewayBaseUrl = result.base_url || gatewayBaseUrl;
925
+ process.stdout.write(`CostLayers gateway ready: ${gatewayBaseUrl}\n`);
926
+ process.stdout.write(`OpenAI-compatible base URL: ${gatewayBaseUrl}\n`);
927
+ if (codexProxyEnabled(args)) {
928
+ process.stdout.write(`API invoice mode: Codex will use ${codexProxyApiKeyEnv(args)} through the CostLayers gateway.\n`);
929
+ }
873
930
  }
874
- process.stdout.write(`CostLayers gateway ready: ${result.base_url}\n`);
875
- process.stdout.write(`OpenAI-compatible base URL: ${result.base_url}\n`);
876
931
  process.stdout.write(`Dashboard: ${dashboardUrlFromConnection(connection)}\n`);
877
932
  process.stdout.write(`Keep this dashboard URL private; it contains your keyed CostLayers path.\n`);
878
933
  process.stdout.write(`Plan: free beta\n`);
879
- const profilePath = writeCodexProfile(connection);
934
+ const profilePath = writeCodexProfile(connection, args);
880
935
  process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
936
+ process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
881
937
  if (command.length > 0) {
882
938
  return runAgent(repo, args, argv, { skipSetup: true, precomputed });
883
939
  }
884
940
  process.stdout.write(`\nNext options:\n`);
885
- process.stdout.write(` Use gateway URL in your model client: ${result.base_url}\n`);
886
- process.stdout.write(` Run Codex metered: npx -y costlayers@latest start --email you@example.com -- codex\n`);
941
+ process.stdout.write(` Use gateway URL in your model client: ${gatewayBaseUrl}\n`);
942
+ process.stdout.write(` Run Codex metered: npx -y ${INSTALL_SPEC} start --email you@example.com -- codex\n`);
943
+ process.stdout.write(` Reduce an OpenAI API invoice: export OPENAI_API_KEY=sk-proj-... && npx -y ${INSTALL_SPEC} start --email you@example.com --codex-proxy -- codex\n`);
887
944
  process.stdout.write(` Or run Codex directly: codex --profile costlayers\n`);
888
- process.stdout.write(` View savings: npx -y costlayers@latest gateway report\n`);
889
- process.stdout.write(` Dashboard: npx -y costlayers@latest dashboard\n`);
945
+ process.stdout.write(` View report: npx -y ${INSTALL_SPEC} gateway report\n`);
946
+ process.stdout.write(` Dashboard: npx -y ${INSTALL_SPEC} dashboard\n`);
890
947
  }
891
948
 
892
949
  function doctor() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "costlayers",
3
- "version": "0.8.10",
3
+ "version": "0.8.13",
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",