costlayers 0.8.15 → 0.8.17
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 +15 -13
- package/bin/agentspend.js +210 -30
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -2,29 +2,30 @@
|
|
|
2
2
|
|
|
3
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
|
+
## Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Daily Codex use:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
cd your-repo
|
|
11
|
-
npx -y costlayers@latest
|
|
11
|
+
npx -y costlayers@latest codex --email you@example.com --chatgpt
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
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`.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Run a one-command API savings test:
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
|
|
20
|
-
costlayers
|
|
19
|
+
export OPENAI_API_KEY=sk-proj-...
|
|
20
|
+
npx -y costlayers@latest test --email you@example.com
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Inside a repo:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
|
-
|
|
27
|
-
npx -y costlayers@latest start --email you@example.com -- codex
|
|
28
|
+
npx -y costlayers@latest codex --email you@example.com --chatgpt
|
|
28
29
|
```
|
|
29
30
|
|
|
30
31
|
This gives Codex `.agentspend/repo-pack.md` and `.agentspend/runtime-plan.md`
|
|
@@ -44,7 +45,7 @@ API write permission:
|
|
|
44
45
|
|
|
45
46
|
```bash
|
|
46
47
|
export OPENAI_API_KEY=sk-proj-...
|
|
47
|
-
npx -y costlayers@latest
|
|
48
|
+
npx -y costlayers@latest codex --email you@example.com --api
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
ChatGPT-login Codex can be metered, but it does not create per-request OpenAI
|
|
@@ -52,8 +53,9 @@ Platform invoice savings because it is not billed through your Platform API key.
|
|
|
52
53
|
|
|
53
54
|
## Which Mode Should I Use?
|
|
54
55
|
|
|
55
|
-
- ChatGPT-login Codex: use `
|
|
56
|
-
- OpenAI Platform API billing: use
|
|
56
|
+
- ChatGPT-login Codex: use `costlayers codex --email you@example.com --chatgpt` to reduce repeated repo context and stretch usage limits.
|
|
57
|
+
- OpenAI Platform API billing: set `OPENAI_API_KEY`, then use `costlayers codex --email you@example.com --api` for invoice-backed savings.
|
|
58
|
+
- Savings proof: set `OPENAI_API_KEY`, then run `costlayers test --email you@example.com`.
|
|
57
59
|
- Other OpenAI-compatible clients: point the client at the CostLayers gateway URL and check `costlayers gateway report`.
|
|
58
60
|
|
|
59
61
|
To install only the Codex profile after signup:
|
package/bin/agentspend.js
CHANGED
|
@@ -9,7 +9,7 @@ const https = require("https");
|
|
|
9
9
|
const os = require("os");
|
|
10
10
|
const { spawnSync } = require("child_process");
|
|
11
11
|
|
|
12
|
-
const VERSION = "0.8.
|
|
12
|
+
const VERSION = "0.8.17";
|
|
13
13
|
const INSTALL_SPEC = "costlayers@latest";
|
|
14
14
|
const DEFAULT_RUNS_PER_WEEK = 20;
|
|
15
15
|
const WEEKS_PER_MONTH = 4.33;
|
|
@@ -50,6 +50,8 @@ function usage(exitCode = 0) {
|
|
|
50
50
|
CostLayers ${VERSION}
|
|
51
51
|
|
|
52
52
|
Usage:
|
|
53
|
+
costlayers codex [--email <email>] [--chatgpt|--api] [-- <codex args>]
|
|
54
|
+
costlayers test [--email <email>] [--prompt <text>]
|
|
53
55
|
costlayers init [--repo <path>]
|
|
54
56
|
costlayers scan [--repo <path>] [--price-per-1m <usd>] [--tasks <n>] [--runs-per-week <n>]
|
|
55
57
|
costlayers start [--email <email>] [--provider-url <url>] [--mode measure|reduce] [--runs-per-week <n>] [--codex-proxy] [-- <agent command>]
|
|
@@ -64,6 +66,8 @@ Usage:
|
|
|
64
66
|
costlayers doctor
|
|
65
67
|
|
|
66
68
|
Commands:
|
|
69
|
+
codex Start Codex with CostLayers. Defaults to ChatGPT-login mode unless --api is passed.
|
|
70
|
+
test Run a safe read-only API invoice-mode Codex task and print the CostLayers savings report.
|
|
67
71
|
init Create .agentspend config and agent instructions.
|
|
68
72
|
scan Build repo context pack and savings report.
|
|
69
73
|
start One-command setup, signup, gateway start, and optional agent run.
|
|
@@ -103,6 +107,14 @@ function ensureDir(dir) {
|
|
|
103
107
|
fs.mkdirSync(dir, { recursive: true });
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
function chmodBestEffort(file, mode) {
|
|
111
|
+
try {
|
|
112
|
+
fs.chmodSync(file, mode);
|
|
113
|
+
} catch {
|
|
114
|
+
// Windows and some network filesystems may ignore POSIX modes.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
106
118
|
function writeIfMissing(file, content) {
|
|
107
119
|
if (!fs.existsSync(file)) fs.writeFileSync(file, content, "utf8");
|
|
108
120
|
}
|
|
@@ -123,7 +135,7 @@ function guardRepoRoot(repo, args) {
|
|
|
123
135
|
"",
|
|
124
136
|
"Run it inside a project folder instead:",
|
|
125
137
|
" cd path/to/your-repo",
|
|
126
|
-
` npx -y ${INSTALL_SPEC}
|
|
138
|
+
` npx -y ${INSTALL_SPEC} codex --email you@example.com --chatgpt`,
|
|
127
139
|
"",
|
|
128
140
|
"Or pass --repo path/to/your-repo from anywhere.",
|
|
129
141
|
"If you really intend to scan your whole home directory, add --allow-home.",
|
|
@@ -149,6 +161,101 @@ function normalizedEmail(value) {
|
|
|
149
161
|
return String(value || "").trim().toLowerCase();
|
|
150
162
|
}
|
|
151
163
|
|
|
164
|
+
function configRoot() {
|
|
165
|
+
const base = process.env.XDG_CONFIG_HOME
|
|
166
|
+
? path.join(process.env.XDG_CONFIG_HOME, "costlayers")
|
|
167
|
+
: path.join(os.homedir(), ".config", "costlayers");
|
|
168
|
+
ensureDir(base);
|
|
169
|
+
chmodBestEffort(base, 0o700);
|
|
170
|
+
return base;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function connectionsDir() {
|
|
174
|
+
const dir = path.join(configRoot(), "connections");
|
|
175
|
+
ensureDir(dir);
|
|
176
|
+
chmodBestEffort(dir, 0o700);
|
|
177
|
+
return dir;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function connectionSecretPath(connectionId) {
|
|
181
|
+
return path.join(connectionsDir(), `${connectionId}.json`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function repoConnectionPath(repo) {
|
|
185
|
+
return path.join(repo, ".agentspend", "connection.json");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function ensureAgentSpendGitignore(outDir) {
|
|
189
|
+
ensureDir(outDir);
|
|
190
|
+
const file = path.join(outDir, ".gitignore");
|
|
191
|
+
const required = ["connection.json", "*.secret.json", "gateway-key.txt"];
|
|
192
|
+
let current = fs.existsSync(file) ? fs.readFileSync(file, "utf8") : "";
|
|
193
|
+
const lines = new Set(current.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
194
|
+
let changed = false;
|
|
195
|
+
for (const item of required) {
|
|
196
|
+
if (!lines.has(item)) {
|
|
197
|
+
current += `${current.endsWith("\n") || current.length === 0 ? "" : "\n"}${item}\n`;
|
|
198
|
+
changed = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (changed || !fs.existsSync(file)) fs.writeFileSync(file, current, "utf8");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function connectionIdFor(repo, connection = {}) {
|
|
205
|
+
if (connection.connection_id) return String(connection.connection_id);
|
|
206
|
+
const seed = connection.api_key || `${path.resolve(repo)}\n${connection.engine_url || ""}\n${connection.email || ""}`;
|
|
207
|
+
return `cl_${sha256(seed).slice(0, 24)}`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function publicConnectionMetadata(connection) {
|
|
211
|
+
return {
|
|
212
|
+
version: VERSION,
|
|
213
|
+
connection_id: connection.connection_id,
|
|
214
|
+
engine_url: connection.engine_url,
|
|
215
|
+
email: normalizedEmail(connection.email),
|
|
216
|
+
label: connection.label || "",
|
|
217
|
+
connected_utc: connection.connected_utc || new Date().toISOString(),
|
|
218
|
+
secret_store: "~/.config/costlayers/connections",
|
|
219
|
+
note: "Live CostLayers keys are stored outside the repo. Run `costlayers dashboard` to print your private dashboard URL."
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function writePrivateJson(file, payload) {
|
|
224
|
+
ensureDir(path.dirname(file));
|
|
225
|
+
fs.writeFileSync(file, JSON.stringify(payload, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
226
|
+
chmodBestEffort(file, 0o600);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function saveConnection(repo, connection) {
|
|
230
|
+
const outDir = path.join(repo, ".agentspend");
|
|
231
|
+
ensureDir(outDir);
|
|
232
|
+
ensureAgentSpendGitignore(outDir);
|
|
233
|
+
const connectionId = connectionIdFor(repo, connection);
|
|
234
|
+
const full = {
|
|
235
|
+
...connection,
|
|
236
|
+
connection_id: connectionId,
|
|
237
|
+
email: normalizedEmail(connection.email),
|
|
238
|
+
connected_utc: connection.connected_utc || new Date().toISOString()
|
|
239
|
+
};
|
|
240
|
+
writePrivateJson(connectionSecretPath(connectionId), full);
|
|
241
|
+
fs.writeFileSync(repoConnectionPath(repo), JSON.stringify(publicConnectionMetadata(full), null, 2) + "\n", "utf8");
|
|
242
|
+
return full;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function loadStoredConnection(repo) {
|
|
246
|
+
const saved = readJsonIfExists(repoConnectionPath(repo));
|
|
247
|
+
if (!saved) return null;
|
|
248
|
+
if (saved.api_key || saved.gateway_url) {
|
|
249
|
+
const migrated = saveConnection(repo, saved);
|
|
250
|
+
process.stdout.write(`CostLayers moved the live key out of .agentspend into ~/.config/costlayers.\n`);
|
|
251
|
+
return migrated;
|
|
252
|
+
}
|
|
253
|
+
const connectionId = saved.connection_id || connectionIdFor(repo, saved);
|
|
254
|
+
const secret = readJsonIfExists(connectionSecretPath(connectionId));
|
|
255
|
+
if (secret && secret.api_key) return { ...saved, ...secret, connection_id: connectionId };
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
152
259
|
function estimateTokens(text) {
|
|
153
260
|
return Math.ceil(String(text || "").length / 4);
|
|
154
261
|
}
|
|
@@ -403,9 +510,10 @@ This public scanner estimates repeated context waste. Real savings should be val
|
|
|
403
510
|
`;
|
|
404
511
|
}
|
|
405
512
|
|
|
406
|
-
function init(repo) {
|
|
513
|
+
function init(repo, options = {}) {
|
|
407
514
|
const outDir = path.join(repo, ".agentspend");
|
|
408
515
|
ensureDir(outDir);
|
|
516
|
+
ensureAgentSpendGitignore(outDir);
|
|
409
517
|
writeIfMissing(path.join(outDir, "config.json"), JSON.stringify({
|
|
410
518
|
version: VERSION,
|
|
411
519
|
created_utc: new Date().toISOString(),
|
|
@@ -421,7 +529,7 @@ function init(repo) {
|
|
|
421
529
|
process.stdout.write(`AGENTS.md already exists; snippet saved to ${path.join(outDir, "AGENTS_SNIPPET.md")}\n`);
|
|
422
530
|
}
|
|
423
531
|
process.stdout.write(`CostLayers initialized in ${outDir}\n`);
|
|
424
|
-
process.stdout.write("Next: costlayers scan\n");
|
|
532
|
+
if (!options.suppressNext) process.stdout.write("Next: costlayers scan\n");
|
|
425
533
|
}
|
|
426
534
|
|
|
427
535
|
function scan(repo, args) {
|
|
@@ -447,21 +555,30 @@ function connectEngine(repo, args) {
|
|
|
447
555
|
api_key: args["api-key"] ? String(args["api-key"]) : null,
|
|
448
556
|
connected_utc: new Date().toISOString()
|
|
449
557
|
};
|
|
450
|
-
|
|
558
|
+
saveConnection(repo, config);
|
|
451
559
|
process.stdout.write(`Connected CostLayers engine: ${config.engine_url}\n`);
|
|
452
560
|
}
|
|
453
561
|
|
|
454
562
|
function loadConnection(repo, args) {
|
|
455
|
-
const
|
|
456
|
-
const saved = readJsonIfExists(path.join(outDir, "connection.json")) || {};
|
|
563
|
+
const saved = loadStoredConnection(repo) || {};
|
|
457
564
|
const engineUrl = String(args["engine-url"] || saved.engine_url || "").replace(/\/+$/, "");
|
|
458
565
|
if (!engineUrl) {
|
|
459
|
-
process.stderr.write("Missing engine connection. Run `
|
|
566
|
+
process.stderr.write("Missing engine connection. Run `costlayers signup --email you@example.com` first.\n");
|
|
567
|
+
process.exit(2);
|
|
568
|
+
}
|
|
569
|
+
const apiKey = args["api-key"] ? String(args["api-key"]) : saved.api_key || null;
|
|
570
|
+
if (!apiKey) {
|
|
571
|
+
process.stderr.write([
|
|
572
|
+
"CostLayers cannot find the live key for this repo.",
|
|
573
|
+
"Keys are stored outside the repo in ~/.config/costlayers/connections.",
|
|
574
|
+
"Run `costlayers signup --email you@example.com` again from this repo to create a fresh key.",
|
|
575
|
+
""
|
|
576
|
+
].join("\n"));
|
|
460
577
|
process.exit(2);
|
|
461
578
|
}
|
|
462
579
|
return {
|
|
463
580
|
engine_url: engineUrl,
|
|
464
|
-
api_key:
|
|
581
|
+
api_key: apiKey
|
|
465
582
|
};
|
|
466
583
|
}
|
|
467
584
|
|
|
@@ -538,16 +655,37 @@ function codexProxyApiKeyEnv(args = {}) {
|
|
|
538
655
|
return String(args["api-key-env"] || "OPENAI_API_KEY");
|
|
539
656
|
}
|
|
540
657
|
|
|
541
|
-
function
|
|
658
|
+
function apiInvoiceModeRequested(args = {}) {
|
|
659
|
+
return Boolean(args.api || args.invoice || args["api-mode"] || codexProxyEnabled(args));
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function chatgptModeRequested(args = {}) {
|
|
663
|
+
return Boolean(args.chatgpt || args["no-api"]);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function codexArgsAfterDash(argv) {
|
|
667
|
+
const dash = argv.indexOf("--");
|
|
668
|
+
return dash >= 0 ? argv.slice(dash + 1) : [];
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function withAutoCodexMode(args = {}, options = {}) {
|
|
672
|
+
const next = { ...args };
|
|
673
|
+
const wantsInvoice = apiInvoiceModeRequested(next) || Boolean(options.forceInvoice);
|
|
674
|
+
if (!chatgptModeRequested(next) && wantsInvoice) next["codex-proxy"] = true;
|
|
675
|
+
return next;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function assertCodexProxyApiKey(args = {}, rerunCommand = "") {
|
|
542
679
|
if (!codexProxyEnabled(args)) return;
|
|
543
680
|
const keyEnv = codexProxyApiKeyEnv(args);
|
|
544
681
|
if (process.env[keyEnv]) return;
|
|
682
|
+
const command = rerunCommand || `npx -y ${INSTALL_SPEC} codex --email you@example.com --api`;
|
|
545
683
|
process.stderr.write([
|
|
546
684
|
`CostLayers invoice mode needs ${keyEnv} in this shell.`,
|
|
547
685
|
"",
|
|
548
686
|
"Set an OpenAI Platform API key with Responses API write permission, then rerun:",
|
|
549
687
|
` export ${keyEnv}=sk-proj-...`,
|
|
550
|
-
`
|
|
688
|
+
` ${command}`,
|
|
551
689
|
"",
|
|
552
690
|
"ChatGPT-login Codex can be metered, but it cannot produce provider invoice savings because there is no per-request Platform invoice to reduce.",
|
|
553
691
|
""
|
|
@@ -647,19 +785,11 @@ async function signupConnection(repo, args) {
|
|
|
647
785
|
label: payload.label,
|
|
648
786
|
connected_utc: new Date().toISOString()
|
|
649
787
|
};
|
|
650
|
-
|
|
651
|
-
return connection;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
function saveConnection(repo, connection) {
|
|
655
|
-
const outDir = path.join(repo, ".agentspend");
|
|
656
|
-
ensureDir(outDir);
|
|
657
|
-
fs.writeFileSync(path.join(outDir, "connection.json"), JSON.stringify(connection, null, 2) + "\n", "utf8");
|
|
788
|
+
return saveConnection(repo, connection);
|
|
658
789
|
}
|
|
659
790
|
|
|
660
791
|
async function ensureConnection(repo, args) {
|
|
661
|
-
const
|
|
662
|
-
const saved = readJsonIfExists(path.join(outDir, "connection.json"));
|
|
792
|
+
const saved = loadStoredConnection(repo);
|
|
663
793
|
const requestedEmail = normalizedEmail(args.email);
|
|
664
794
|
if (saved && saved.engine_url && saved.api_key) {
|
|
665
795
|
if (!requestedEmail) return saved;
|
|
@@ -811,7 +941,7 @@ async function runAgent(repo, args, argv, options = {}) {
|
|
|
811
941
|
}
|
|
812
942
|
if (!options.skipSetup) init(repo);
|
|
813
943
|
const { outDir, pack, report } = options.precomputed || scanToFiles(repo, args);
|
|
814
|
-
const connection =
|
|
944
|
+
const connection = loadStoredConnection(repo);
|
|
815
945
|
let plan = buildLocalPlan(report);
|
|
816
946
|
if (connection && connection.engine_url) {
|
|
817
947
|
try {
|
|
@@ -845,7 +975,9 @@ async function runAgent(repo, args, argv, options = {}) {
|
|
|
845
975
|
stdio: "inherit",
|
|
846
976
|
shell: process.platform === "win32"
|
|
847
977
|
});
|
|
848
|
-
|
|
978
|
+
const status = typeof result.status === "number" ? result.status : 1;
|
|
979
|
+
if (options.returnStatus) return status;
|
|
980
|
+
process.exit(status);
|
|
849
981
|
}
|
|
850
982
|
|
|
851
983
|
async function gateway(repo, args) {
|
|
@@ -918,12 +1050,57 @@ async function dashboard(repo, args) {
|
|
|
918
1050
|
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`);
|
|
919
1051
|
}
|
|
920
1052
|
|
|
921
|
-
async function
|
|
1053
|
+
async function codexShortcut(repo, args, argv) {
|
|
1054
|
+
const codexTail = codexArgsAfterDash(argv);
|
|
1055
|
+
const command = codexTail.length > 0 ? codexTail : ["codex"];
|
|
1056
|
+
const commandToRun = isCodexCommand(command) ? command : ["codex", ...command];
|
|
1057
|
+
const nextArgs = withAutoCodexMode(args);
|
|
1058
|
+
if (codexProxyEnabled(nextArgs)) {
|
|
1059
|
+
process.stdout.write(`CostLayers Codex: API invoice mode explicitly enabled from ${codexProxyApiKeyEnv(nextArgs)}.\n`);
|
|
1060
|
+
} else {
|
|
1061
|
+
process.stdout.write(`CostLayers Codex: ChatGPT usage-stretch mode. Pass --api to route API-billed provider calls for invoice savings.\n`);
|
|
1062
|
+
}
|
|
1063
|
+
return start(repo, nextArgs, ["start", "--", ...commandToRun]);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
async function savingsTest(repo, args) {
|
|
1067
|
+
const nextArgs = withAutoCodexMode({ ...args, api: true }, { forceInvoice: true });
|
|
1068
|
+
if (!codexProxyEnabled(nextArgs)) {
|
|
1069
|
+
const keyEnv = codexProxyApiKeyEnv(nextArgs);
|
|
1070
|
+
process.stderr.write([
|
|
1071
|
+
`CostLayers test needs ${keyEnv} for real API invoice savings.`,
|
|
1072
|
+
"",
|
|
1073
|
+
"Set your OpenAI Platform API key, then rerun:",
|
|
1074
|
+
` export ${keyEnv}=sk-proj-...`,
|
|
1075
|
+
` npx -y ${INSTALL_SPEC} test --email you@example.com`,
|
|
1076
|
+
"",
|
|
1077
|
+
`For ChatGPT-login metering without invoice savings, run:`,
|
|
1078
|
+
` npx -y ${INSTALL_SPEC} codex --email you@example.com --chatgpt`,
|
|
1079
|
+
""
|
|
1080
|
+
].join("\n"));
|
|
1081
|
+
process.exit(2);
|
|
1082
|
+
}
|
|
1083
|
+
const prompt = typeof args.prompt === "string"
|
|
1084
|
+
? args.prompt
|
|
1085
|
+
: "Analyze this repository. Find the main entry points, data flow, and the 5 files most worth reading. Do not edit files.";
|
|
1086
|
+
assertCodexProxyApiKey(nextArgs, `npx -y ${INSTALL_SPEC} test --email you@example.com`);
|
|
1087
|
+
process.stdout.write("CostLayers savings test: running one safe read-only Codex task.\n");
|
|
1088
|
+
const status = await start(repo, nextArgs, ["start", "--", "codex", "exec", "--sandbox", "read-only", prompt], { returnStatus: true });
|
|
1089
|
+
process.stdout.write("\nCostLayers savings test report\n");
|
|
1090
|
+
try {
|
|
1091
|
+
await gateway(repo, { ...nextArgs, _: ["gateway", "report"] });
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
process.stderr.write(`Could not fetch savings report: ${err.message}\n`);
|
|
1094
|
+
}
|
|
1095
|
+
process.exit(status);
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
async function start(repo, args, argv, options = {}) {
|
|
922
1099
|
const dash = argv.indexOf("--");
|
|
923
1100
|
const command = dash >= 0 ? argv.slice(dash + 1) : [];
|
|
924
1101
|
const codexTelemetryRun = command.length > 0 && isCodexCommand(command) && !codexProxyEnabled(args);
|
|
925
1102
|
if (command.length > 0 && isCodexCommand(command)) assertCodexProxyApiKey(args);
|
|
926
|
-
init(repo);
|
|
1103
|
+
init(repo, { suppressNext: true });
|
|
927
1104
|
process.stdout.write(`Scanning repo: ${repo}\n`);
|
|
928
1105
|
const precomputed = scanToFiles(repo, args);
|
|
929
1106
|
const { outDir, pack, report } = precomputed;
|
|
@@ -972,12 +1149,13 @@ async function start(repo, args, argv) {
|
|
|
972
1149
|
process.stdout.write(`CostLayers Codex profile: ${profilePath}\n`);
|
|
973
1150
|
process.stdout.write(`Codex profile mode: ${codexProxyEnabled(args) ? "API invoice mode" : "ChatGPT usage-stretch mode; native Codex model path preserved"}\n`);
|
|
974
1151
|
if (command.length > 0) {
|
|
975
|
-
return runAgent(repo, args, argv, { skipSetup: true, precomputed });
|
|
1152
|
+
return runAgent(repo, args, argv, { skipSetup: true, precomputed, returnStatus: options.returnStatus });
|
|
976
1153
|
}
|
|
977
1154
|
process.stdout.write(`\nNext options:\n`);
|
|
978
|
-
process.stdout.write(`
|
|
979
|
-
process.stdout.write(`
|
|
980
|
-
process.stdout.write(`
|
|
1155
|
+
process.stdout.write(` ChatGPT-login Codex: npx -y ${INSTALL_SPEC} codex --email you@example.com --chatgpt\n`);
|
|
1156
|
+
process.stdout.write(` API invoice Codex: export OPENAI_API_KEY=sk-proj-... && npx -y ${INSTALL_SPEC} codex --email you@example.com --api\n`);
|
|
1157
|
+
process.stdout.write(` Other OpenAI-compatible client base URL: ${gatewayBaseUrl}\n`);
|
|
1158
|
+
process.stdout.write(` Prove API savings: npx -y ${INSTALL_SPEC} test --email you@example.com\n`);
|
|
981
1159
|
process.stdout.write(` Or run Codex directly: codex --profile costlayers\n`);
|
|
982
1160
|
process.stdout.write(` View report: npx -y ${INSTALL_SPEC} gateway report\n`);
|
|
983
1161
|
process.stdout.write(` Dashboard: npx -y ${INSTALL_SPEC} dashboard\n`);
|
|
@@ -995,8 +1173,10 @@ function main() {
|
|
|
995
1173
|
const cmd = args._[0];
|
|
996
1174
|
if (!cmd || args.help || args.h) usage(0);
|
|
997
1175
|
const repo = path.resolve(String(args.repo || process.cwd()));
|
|
998
|
-
if (["init", "scan", "start", "run", "codex-profile"].includes(cmd)) guardRepoRoot(repo, args);
|
|
1176
|
+
if (["init", "scan", "start", "run", "codex-profile", "codex", "test"].includes(cmd)) guardRepoRoot(repo, args);
|
|
999
1177
|
if (cmd === "doctor") return doctor();
|
|
1178
|
+
if (cmd === "codex") return codexShortcut(repo, args, rawArgv);
|
|
1179
|
+
if (cmd === "test") return savingsTest(repo, args);
|
|
1000
1180
|
if (cmd === "init") return init(repo);
|
|
1001
1181
|
if (cmd === "scan") return scan(repo, args);
|
|
1002
1182
|
if (cmd === "start") return start(repo, args, rawArgv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "costlayers",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.17",
|
|
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",
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
},
|
|
9
9
|
"type": "commonjs",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
|
+
"homepage": "https://costlayers.com",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "mailto:rishabh@costlayers.com"
|
|
14
|
+
},
|
|
11
15
|
"private": false,
|
|
12
16
|
"engines": {
|
|
13
17
|
"node": ">=18"
|