costlayers 0.8.16 → 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 +6 -6
- package/bin/agentspend.js +141 -35
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -8,15 +8,15 @@ Daily Codex use:
|
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
cd your-repo
|
|
11
|
-
npx -y costlayers@latest codex --email you@example.com
|
|
11
|
+
npx -y costlayers@latest codex --email you@example.com --chatgpt
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
If no API key is set, it uses ChatGPT-login usage-stretch mode.
|
|
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`.
|
|
16
15
|
|
|
17
16
|
Run a one-command API savings test:
|
|
18
17
|
|
|
19
18
|
```bash
|
|
19
|
+
export OPENAI_API_KEY=sk-proj-...
|
|
20
20
|
npx -y costlayers@latest test --email you@example.com
|
|
21
21
|
```
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ npx -y costlayers@latest test --email you@example.com
|
|
|
25
25
|
Inside a repo:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npx -y costlayers@latest codex --email you@example.com
|
|
28
|
+
npx -y costlayers@latest codex --email you@example.com --chatgpt
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
This gives Codex `.agentspend/repo-pack.md` and `.agentspend/runtime-plan.md`
|
|
@@ -45,7 +45,7 @@ API write permission:
|
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
47
|
export OPENAI_API_KEY=sk-proj-...
|
|
48
|
-
npx -y costlayers@latest codex --email you@example.com
|
|
48
|
+
npx -y costlayers@latest codex --email you@example.com --api
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
ChatGPT-login Codex can be metered, but it does not create per-request OpenAI
|
|
@@ -54,7 +54,7 @@ Platform invoice savings because it is not billed through your Platform API key.
|
|
|
54
54
|
## Which Mode Should I Use?
|
|
55
55
|
|
|
56
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` for invoice-backed savings.
|
|
57
|
+
- OpenAI Platform API billing: set `OPENAI_API_KEY`, then use `costlayers codex --email you@example.com --api` for invoice-backed savings.
|
|
58
58
|
- Savings proof: set `OPENAI_API_KEY`, then run `costlayers test --email you@example.com`.
|
|
59
59
|
- Other OpenAI-compatible clients: point the client at the CostLayers gateway URL and check `costlayers gateway report`.
|
|
60
60
|
|
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;
|
|
@@ -66,8 +66,8 @@ Usage:
|
|
|
66
66
|
costlayers doctor
|
|
67
67
|
|
|
68
68
|
Commands:
|
|
69
|
-
codex Start Codex with CostLayers.
|
|
70
|
-
test Run a safe read-only Codex task and print the CostLayers savings report.
|
|
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.
|
|
71
71
|
init Create .agentspend config and agent instructions.
|
|
72
72
|
scan Build repo context pack and savings report.
|
|
73
73
|
start One-command setup, signup, gateway start, and optional agent run.
|
|
@@ -107,6 +107,14 @@ function ensureDir(dir) {
|
|
|
107
107
|
fs.mkdirSync(dir, { recursive: true });
|
|
108
108
|
}
|
|
109
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
|
+
|
|
110
118
|
function writeIfMissing(file, content) {
|
|
111
119
|
if (!fs.existsSync(file)) fs.writeFileSync(file, content, "utf8");
|
|
112
120
|
}
|
|
@@ -127,7 +135,7 @@ function guardRepoRoot(repo, args) {
|
|
|
127
135
|
"",
|
|
128
136
|
"Run it inside a project folder instead:",
|
|
129
137
|
" cd path/to/your-repo",
|
|
130
|
-
` npx -y ${INSTALL_SPEC} codex --email you@example.com`,
|
|
138
|
+
` npx -y ${INSTALL_SPEC} codex --email you@example.com --chatgpt`,
|
|
131
139
|
"",
|
|
132
140
|
"Or pass --repo path/to/your-repo from anywhere.",
|
|
133
141
|
"If you really intend to scan your whole home directory, add --allow-home.",
|
|
@@ -153,6 +161,101 @@ function normalizedEmail(value) {
|
|
|
153
161
|
return String(value || "").trim().toLowerCase();
|
|
154
162
|
}
|
|
155
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
|
+
|
|
156
259
|
function estimateTokens(text) {
|
|
157
260
|
return Math.ceil(String(text || "").length / 4);
|
|
158
261
|
}
|
|
@@ -407,9 +510,10 @@ This public scanner estimates repeated context waste. Real savings should be val
|
|
|
407
510
|
`;
|
|
408
511
|
}
|
|
409
512
|
|
|
410
|
-
function init(repo) {
|
|
513
|
+
function init(repo, options = {}) {
|
|
411
514
|
const outDir = path.join(repo, ".agentspend");
|
|
412
515
|
ensureDir(outDir);
|
|
516
|
+
ensureAgentSpendGitignore(outDir);
|
|
413
517
|
writeIfMissing(path.join(outDir, "config.json"), JSON.stringify({
|
|
414
518
|
version: VERSION,
|
|
415
519
|
created_utc: new Date().toISOString(),
|
|
@@ -425,7 +529,7 @@ function init(repo) {
|
|
|
425
529
|
process.stdout.write(`AGENTS.md already exists; snippet saved to ${path.join(outDir, "AGENTS_SNIPPET.md")}\n`);
|
|
426
530
|
}
|
|
427
531
|
process.stdout.write(`CostLayers initialized in ${outDir}\n`);
|
|
428
|
-
process.stdout.write("Next: costlayers scan\n");
|
|
532
|
+
if (!options.suppressNext) process.stdout.write("Next: costlayers scan\n");
|
|
429
533
|
}
|
|
430
534
|
|
|
431
535
|
function scan(repo, args) {
|
|
@@ -451,21 +555,30 @@ function connectEngine(repo, args) {
|
|
|
451
555
|
api_key: args["api-key"] ? String(args["api-key"]) : null,
|
|
452
556
|
connected_utc: new Date().toISOString()
|
|
453
557
|
};
|
|
454
|
-
|
|
558
|
+
saveConnection(repo, config);
|
|
455
559
|
process.stdout.write(`Connected CostLayers engine: ${config.engine_url}\n`);
|
|
456
560
|
}
|
|
457
561
|
|
|
458
562
|
function loadConnection(repo, args) {
|
|
459
|
-
const
|
|
460
|
-
const saved = readJsonIfExists(path.join(outDir, "connection.json")) || {};
|
|
563
|
+
const saved = loadStoredConnection(repo) || {};
|
|
461
564
|
const engineUrl = String(args["engine-url"] || saved.engine_url || "").replace(/\/+$/, "");
|
|
462
565
|
if (!engineUrl) {
|
|
463
|
-
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"));
|
|
464
577
|
process.exit(2);
|
|
465
578
|
}
|
|
466
579
|
return {
|
|
467
580
|
engine_url: engineUrl,
|
|
468
|
-
api_key:
|
|
581
|
+
api_key: apiKey
|
|
469
582
|
};
|
|
470
583
|
}
|
|
471
584
|
|
|
@@ -557,23 +670,22 @@ function codexArgsAfterDash(argv) {
|
|
|
557
670
|
|
|
558
671
|
function withAutoCodexMode(args = {}, options = {}) {
|
|
559
672
|
const next = { ...args };
|
|
560
|
-
const
|
|
561
|
-
const hasApiKey = Boolean(process.env[keyEnv]);
|
|
562
|
-
const wantsInvoice = apiInvoiceModeRequested(next) || (options.preferInvoice && hasApiKey);
|
|
673
|
+
const wantsInvoice = apiInvoiceModeRequested(next) || Boolean(options.forceInvoice);
|
|
563
674
|
if (!chatgptModeRequested(next) && wantsInvoice) next["codex-proxy"] = true;
|
|
564
675
|
return next;
|
|
565
676
|
}
|
|
566
677
|
|
|
567
|
-
function assertCodexProxyApiKey(args = {}) {
|
|
678
|
+
function assertCodexProxyApiKey(args = {}, rerunCommand = "") {
|
|
568
679
|
if (!codexProxyEnabled(args)) return;
|
|
569
680
|
const keyEnv = codexProxyApiKeyEnv(args);
|
|
570
681
|
if (process.env[keyEnv]) return;
|
|
682
|
+
const command = rerunCommand || `npx -y ${INSTALL_SPEC} codex --email you@example.com --api`;
|
|
571
683
|
process.stderr.write([
|
|
572
684
|
`CostLayers invoice mode needs ${keyEnv} in this shell.`,
|
|
573
685
|
"",
|
|
574
686
|
"Set an OpenAI Platform API key with Responses API write permission, then rerun:",
|
|
575
687
|
` export ${keyEnv}=sk-proj-...`,
|
|
576
|
-
`
|
|
688
|
+
` ${command}`,
|
|
577
689
|
"",
|
|
578
690
|
"ChatGPT-login Codex can be metered, but it cannot produce provider invoice savings because there is no per-request Platform invoice to reduce.",
|
|
579
691
|
""
|
|
@@ -673,19 +785,11 @@ async function signupConnection(repo, args) {
|
|
|
673
785
|
label: payload.label,
|
|
674
786
|
connected_utc: new Date().toISOString()
|
|
675
787
|
};
|
|
676
|
-
|
|
677
|
-
return connection;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
function saveConnection(repo, connection) {
|
|
681
|
-
const outDir = path.join(repo, ".agentspend");
|
|
682
|
-
ensureDir(outDir);
|
|
683
|
-
fs.writeFileSync(path.join(outDir, "connection.json"), JSON.stringify(connection, null, 2) + "\n", "utf8");
|
|
788
|
+
return saveConnection(repo, connection);
|
|
684
789
|
}
|
|
685
790
|
|
|
686
791
|
async function ensureConnection(repo, args) {
|
|
687
|
-
const
|
|
688
|
-
const saved = readJsonIfExists(path.join(outDir, "connection.json"));
|
|
792
|
+
const saved = loadStoredConnection(repo);
|
|
689
793
|
const requestedEmail = normalizedEmail(args.email);
|
|
690
794
|
if (saved && saved.engine_url && saved.api_key) {
|
|
691
795
|
if (!requestedEmail) return saved;
|
|
@@ -837,7 +941,7 @@ async function runAgent(repo, args, argv, options = {}) {
|
|
|
837
941
|
}
|
|
838
942
|
if (!options.skipSetup) init(repo);
|
|
839
943
|
const { outDir, pack, report } = options.precomputed || scanToFiles(repo, args);
|
|
840
|
-
const connection =
|
|
944
|
+
const connection = loadStoredConnection(repo);
|
|
841
945
|
let plan = buildLocalPlan(report);
|
|
842
946
|
if (connection && connection.engine_url) {
|
|
843
947
|
try {
|
|
@@ -950,24 +1054,24 @@ async function codexShortcut(repo, args, argv) {
|
|
|
950
1054
|
const codexTail = codexArgsAfterDash(argv);
|
|
951
1055
|
const command = codexTail.length > 0 ? codexTail : ["codex"];
|
|
952
1056
|
const commandToRun = isCodexCommand(command) ? command : ["codex", ...command];
|
|
953
|
-
const nextArgs = withAutoCodexMode(args
|
|
1057
|
+
const nextArgs = withAutoCodexMode(args);
|
|
954
1058
|
if (codexProxyEnabled(nextArgs)) {
|
|
955
|
-
process.stdout.write(`CostLayers Codex: API invoice mode enabled from ${codexProxyApiKeyEnv(nextArgs)}.\n`);
|
|
1059
|
+
process.stdout.write(`CostLayers Codex: API invoice mode explicitly enabled from ${codexProxyApiKeyEnv(nextArgs)}.\n`);
|
|
956
1060
|
} else {
|
|
957
|
-
process.stdout.write(`CostLayers Codex: ChatGPT usage-stretch mode.
|
|
1061
|
+
process.stdout.write(`CostLayers Codex: ChatGPT usage-stretch mode. Pass --api to route API-billed provider calls for invoice savings.\n`);
|
|
958
1062
|
}
|
|
959
1063
|
return start(repo, nextArgs, ["start", "--", ...commandToRun]);
|
|
960
1064
|
}
|
|
961
1065
|
|
|
962
1066
|
async function savingsTest(repo, args) {
|
|
963
|
-
const nextArgs = withAutoCodexMode(args, {
|
|
1067
|
+
const nextArgs = withAutoCodexMode({ ...args, api: true }, { forceInvoice: true });
|
|
964
1068
|
if (!codexProxyEnabled(nextArgs)) {
|
|
965
1069
|
const keyEnv = codexProxyApiKeyEnv(nextArgs);
|
|
966
1070
|
process.stderr.write([
|
|
967
1071
|
`CostLayers test needs ${keyEnv} for real API invoice savings.`,
|
|
968
1072
|
"",
|
|
969
1073
|
"Set your OpenAI Platform API key, then rerun:",
|
|
970
|
-
`
|
|
1074
|
+
` export ${keyEnv}=sk-proj-...`,
|
|
971
1075
|
` npx -y ${INSTALL_SPEC} test --email you@example.com`,
|
|
972
1076
|
"",
|
|
973
1077
|
`For ChatGPT-login metering without invoice savings, run:`,
|
|
@@ -979,6 +1083,7 @@ async function savingsTest(repo, args) {
|
|
|
979
1083
|
const prompt = typeof args.prompt === "string"
|
|
980
1084
|
? args.prompt
|
|
981
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`);
|
|
982
1087
|
process.stdout.write("CostLayers savings test: running one safe read-only Codex task.\n");
|
|
983
1088
|
const status = await start(repo, nextArgs, ["start", "--", "codex", "exec", "--sandbox", "read-only", prompt], { returnStatus: true });
|
|
984
1089
|
process.stdout.write("\nCostLayers savings test report\n");
|
|
@@ -995,7 +1100,7 @@ async function start(repo, args, argv, options = {}) {
|
|
|
995
1100
|
const command = dash >= 0 ? argv.slice(dash + 1) : [];
|
|
996
1101
|
const codexTelemetryRun = command.length > 0 && isCodexCommand(command) && !codexProxyEnabled(args);
|
|
997
1102
|
if (command.length > 0 && isCodexCommand(command)) assertCodexProxyApiKey(args);
|
|
998
|
-
init(repo);
|
|
1103
|
+
init(repo, { suppressNext: true });
|
|
999
1104
|
process.stdout.write(`Scanning repo: ${repo}\n`);
|
|
1000
1105
|
const precomputed = scanToFiles(repo, args);
|
|
1001
1106
|
const { outDir, pack, report } = precomputed;
|
|
@@ -1047,8 +1152,9 @@ async function start(repo, args, argv, options = {}) {
|
|
|
1047
1152
|
return runAgent(repo, args, argv, { skipSetup: true, precomputed, returnStatus: options.returnStatus });
|
|
1048
1153
|
}
|
|
1049
1154
|
process.stdout.write(`\nNext options:\n`);
|
|
1050
|
-
process.stdout.write(`
|
|
1051
|
-
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`);
|
|
1052
1158
|
process.stdout.write(` Prove API savings: npx -y ${INSTALL_SPEC} test --email you@example.com\n`);
|
|
1053
1159
|
process.stdout.write(` Or run Codex directly: codex --profile costlayers\n`);
|
|
1054
1160
|
process.stdout.write(` View report: npx -y ${INSTALL_SPEC} gateway report\n`);
|
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"
|