aiopt 0.3.7 → 0.3.9
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/dist/cli.js +291 -173
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -656,7 +656,7 @@ var require_package = __commonJS({
|
|
|
656
656
|
"package.json"(exports2, module2) {
|
|
657
657
|
module2.exports = {
|
|
658
658
|
name: "aiopt",
|
|
659
|
-
version: "0.3.
|
|
659
|
+
version: "0.3.9",
|
|
660
660
|
description: "Pre-deploy LLM cost accident guardrail (serverless local CLI)",
|
|
661
661
|
bin: {
|
|
662
662
|
aiopt: "dist/cli.js"
|
|
@@ -697,19 +697,19 @@ __export(install_exports, {
|
|
|
697
697
|
runInstall: () => runInstall
|
|
698
698
|
});
|
|
699
699
|
function ensureDir2(p) {
|
|
700
|
-
|
|
700
|
+
import_fs6.default.mkdirSync(p, { recursive: true });
|
|
701
701
|
}
|
|
702
702
|
function writeFile(filePath, content, force) {
|
|
703
|
-
if (!force &&
|
|
704
|
-
ensureDir2(
|
|
705
|
-
|
|
703
|
+
if (!force && import_fs6.default.existsSync(filePath)) return { wrote: false, reason: "exists" };
|
|
704
|
+
ensureDir2(import_path7.default.dirname(filePath));
|
|
705
|
+
import_fs6.default.writeFileSync(filePath, content);
|
|
706
706
|
return { wrote: true };
|
|
707
707
|
}
|
|
708
708
|
function runInstall(cwd, opts) {
|
|
709
709
|
const force = Boolean(opts.force);
|
|
710
|
-
const aioptDir =
|
|
711
|
-
const policiesDir =
|
|
712
|
-
const outDir =
|
|
710
|
+
const aioptDir = import_path7.default.join(cwd, "aiopt");
|
|
711
|
+
const policiesDir = import_path7.default.join(aioptDir, "policies");
|
|
712
|
+
const outDir = import_path7.default.join(cwd, "aiopt-output");
|
|
713
713
|
ensureDir2(aioptDir);
|
|
714
714
|
ensureDir2(policiesDir);
|
|
715
715
|
ensureDir2(outDir);
|
|
@@ -765,7 +765,7 @@ return {
|
|
|
765
765
|
};
|
|
766
766
|
\`\`\`
|
|
767
767
|
`;
|
|
768
|
-
const r1 = writeFile(
|
|
768
|
+
const r1 = writeFile(import_path7.default.join(aioptDir, "README.md"), readme, force);
|
|
769
769
|
created.push({ path: "aiopt/README.md", status: r1.wrote ? "created" : "skipped" });
|
|
770
770
|
const config = {
|
|
771
771
|
version: 1,
|
|
@@ -775,7 +775,7 @@ return {
|
|
|
775
775
|
policies_dir: "./aiopt/policies",
|
|
776
776
|
rate_table: { path: "./rates/rate_table.json" }
|
|
777
777
|
};
|
|
778
|
-
const r2 = writeFile(
|
|
778
|
+
const r2 = writeFile(import_path7.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
|
|
779
779
|
created.push({ path: "aiopt/aiopt.config.json", status: r2.wrote ? "created" : "skipped" });
|
|
780
780
|
const routing = {
|
|
781
781
|
version: 1,
|
|
@@ -806,15 +806,15 @@ return {
|
|
|
806
806
|
reduce_top_percentile: 0.2,
|
|
807
807
|
assumed_reduction_ratio: 0.25
|
|
808
808
|
};
|
|
809
|
-
const p1 = writeFile(
|
|
810
|
-
const p2 = writeFile(
|
|
811
|
-
const p3 = writeFile(
|
|
812
|
-
const p4 = writeFile(
|
|
809
|
+
const p1 = writeFile(import_path7.default.join(policiesDir, "routing.json"), JSON.stringify(routing, null, 2) + "\n", force);
|
|
810
|
+
const p2 = writeFile(import_path7.default.join(policiesDir, "retry.json"), JSON.stringify(retry, null, 2) + "\n", force);
|
|
811
|
+
const p3 = writeFile(import_path7.default.join(policiesDir, "output.json"), JSON.stringify(output, null, 2) + "\n", force);
|
|
812
|
+
const p4 = writeFile(import_path7.default.join(policiesDir, "context.json"), JSON.stringify(context, null, 2) + "\n", force);
|
|
813
813
|
created.push({ path: "aiopt/policies/routing.json", status: p1.wrote ? "created" : "skipped" });
|
|
814
814
|
created.push({ path: "aiopt/policies/retry.json", status: p2.wrote ? "created" : "skipped" });
|
|
815
815
|
created.push({ path: "aiopt/policies/output.json", status: p3.wrote ? "created" : "skipped" });
|
|
816
816
|
created.push({ path: "aiopt/policies/context.json", status: p4.wrote ? "created" : "skipped" });
|
|
817
|
-
const wrapperPath =
|
|
817
|
+
const wrapperPath = import_path7.default.join(aioptDir, "aiopt-wrapper.js");
|
|
818
818
|
const wrapper = `// AIOpt Wrapper (guardrails) \u2014 local-only (CommonJS)
|
|
819
819
|
|
|
820
820
|
const fs = require('fs');
|
|
@@ -968,9 +968,9 @@ module.exports = { guardedCall };
|
|
|
968
968
|
;
|
|
969
969
|
const w = writeFile(wrapperPath, wrapper, force);
|
|
970
970
|
created.push({ path: "aiopt/aiopt-wrapper.js", status: w.wrote ? "created" : "skipped" });
|
|
971
|
-
const usagePath =
|
|
972
|
-
if (force || !
|
|
973
|
-
|
|
971
|
+
const usagePath = import_path7.default.join(outDir, "usage.jsonl");
|
|
972
|
+
if (force || !import_fs6.default.existsSync(usagePath)) {
|
|
973
|
+
import_fs6.default.writeFileSync(usagePath, "");
|
|
974
974
|
created.push({ path: "aiopt-output/usage.jsonl", status: "created" });
|
|
975
975
|
if (opts.seedSample) {
|
|
976
976
|
const sample = {
|
|
@@ -990,19 +990,19 @@ module.exports = { guardedCall };
|
|
|
990
990
|
latency_ms: 1,
|
|
991
991
|
meta: { feature_tag: "demo", routed_from: null, policy_hits: ["install-sample"] }
|
|
992
992
|
};
|
|
993
|
-
|
|
993
|
+
import_fs6.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
|
|
994
994
|
}
|
|
995
995
|
} else {
|
|
996
996
|
created.push({ path: "aiopt-output/usage.jsonl", status: "skipped" });
|
|
997
997
|
}
|
|
998
998
|
return { created };
|
|
999
999
|
}
|
|
1000
|
-
var
|
|
1000
|
+
var import_fs6, import_path7;
|
|
1001
1001
|
var init_install = __esm({
|
|
1002
1002
|
"src/install.ts"() {
|
|
1003
1003
|
"use strict";
|
|
1004
|
-
|
|
1005
|
-
|
|
1004
|
+
import_fs6 = __toESM(require("fs"));
|
|
1005
|
+
import_path7 = __toESM(require("path"));
|
|
1006
1006
|
}
|
|
1007
1007
|
});
|
|
1008
1008
|
|
|
@@ -1013,10 +1013,10 @@ __export(doctor_exports, {
|
|
|
1013
1013
|
});
|
|
1014
1014
|
function canWrite(dir) {
|
|
1015
1015
|
try {
|
|
1016
|
-
|
|
1017
|
-
const p =
|
|
1018
|
-
|
|
1019
|
-
|
|
1016
|
+
import_fs7.default.mkdirSync(dir, { recursive: true });
|
|
1017
|
+
const p = import_path8.default.join(dir, `.aiopt-write-test-${Date.now()}`);
|
|
1018
|
+
import_fs7.default.writeFileSync(p, "ok");
|
|
1019
|
+
import_fs7.default.unlinkSync(p);
|
|
1020
1020
|
return true;
|
|
1021
1021
|
} catch {
|
|
1022
1022
|
return false;
|
|
@@ -1024,7 +1024,7 @@ function canWrite(dir) {
|
|
|
1024
1024
|
}
|
|
1025
1025
|
function tailLines(filePath, n) {
|
|
1026
1026
|
try {
|
|
1027
|
-
const raw =
|
|
1027
|
+
const raw = import_fs7.default.readFileSync(filePath, "utf8");
|
|
1028
1028
|
const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
1029
1029
|
return lines.slice(Math.max(0, lines.length - n));
|
|
1030
1030
|
} catch {
|
|
@@ -1032,15 +1032,15 @@ function tailLines(filePath, n) {
|
|
|
1032
1032
|
}
|
|
1033
1033
|
}
|
|
1034
1034
|
function runDoctor(cwd) {
|
|
1035
|
-
const aioptDir =
|
|
1036
|
-
const policiesDir =
|
|
1037
|
-
const outDir =
|
|
1038
|
-
const usagePath =
|
|
1035
|
+
const aioptDir = import_path8.default.join(cwd, "aiopt");
|
|
1036
|
+
const policiesDir = import_path8.default.join(aioptDir, "policies");
|
|
1037
|
+
const outDir = import_path8.default.join(cwd, "aiopt-output");
|
|
1038
|
+
const usagePath = import_path8.default.join(outDir, "usage.jsonl");
|
|
1039
1039
|
const checks = [];
|
|
1040
|
-
checks.push({ name: "aiopt/ exists", ok:
|
|
1041
|
-
checks.push({ name: "aiopt/policies exists", ok:
|
|
1040
|
+
checks.push({ name: "aiopt/ exists", ok: import_fs7.default.existsSync(aioptDir) });
|
|
1041
|
+
checks.push({ name: "aiopt/policies exists", ok: import_fs7.default.existsSync(policiesDir) });
|
|
1042
1042
|
checks.push({ name: "aiopt-output/ writable", ok: canWrite(outDir) });
|
|
1043
|
-
checks.push({ name: "usage.jsonl exists", ok:
|
|
1043
|
+
checks.push({ name: "usage.jsonl exists", ok: import_fs7.default.existsSync(usagePath), detail: usagePath });
|
|
1044
1044
|
const last5raw = tailLines(usagePath, 5);
|
|
1045
1045
|
const last5 = last5raw.length === 0 ? [{ status: "(empty usage.jsonl)" }] : last5raw.map((l) => {
|
|
1046
1046
|
try {
|
|
@@ -1078,12 +1078,12 @@ function runDoctor(cwd) {
|
|
|
1078
1078
|
const ok = checks.every((c) => c.ok);
|
|
1079
1079
|
return { ok, checks, last5 };
|
|
1080
1080
|
}
|
|
1081
|
-
var
|
|
1081
|
+
var import_fs7, import_path8;
|
|
1082
1082
|
var init_doctor = __esm({
|
|
1083
1083
|
"src/doctor.ts"() {
|
|
1084
1084
|
"use strict";
|
|
1085
|
-
|
|
1086
|
-
|
|
1085
|
+
import_fs7 = __toESM(require("fs"));
|
|
1086
|
+
import_path8 = __toESM(require("path"));
|
|
1087
1087
|
}
|
|
1088
1088
|
});
|
|
1089
1089
|
|
|
@@ -1138,7 +1138,7 @@ function verifyLicenseKey(key, publicKeyPem) {
|
|
|
1138
1138
|
}
|
|
1139
1139
|
}
|
|
1140
1140
|
function defaultLicensePath(cwd) {
|
|
1141
|
-
return
|
|
1141
|
+
return import_path9.default.join(cwd, "aiopt", "license.json");
|
|
1142
1142
|
}
|
|
1143
1143
|
function writeLicenseFile(p, key, payload, verified) {
|
|
1144
1144
|
const out = {
|
|
@@ -1147,19 +1147,19 @@ function writeLicenseFile(p, key, payload, verified) {
|
|
|
1147
1147
|
verified,
|
|
1148
1148
|
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1149
1149
|
};
|
|
1150
|
-
|
|
1151
|
-
|
|
1150
|
+
import_fs8.default.mkdirSync(import_path9.default.dirname(p), { recursive: true });
|
|
1151
|
+
import_fs8.default.writeFileSync(p, JSON.stringify(out, null, 2));
|
|
1152
1152
|
}
|
|
1153
1153
|
function readLicenseFile(p) {
|
|
1154
|
-
return JSON.parse(
|
|
1154
|
+
return JSON.parse(import_fs8.default.readFileSync(p, "utf8"));
|
|
1155
1155
|
}
|
|
1156
|
-
var import_crypto,
|
|
1156
|
+
var import_crypto, import_fs8, import_path9, DEFAULT_PUBLIC_KEY_PEM;
|
|
1157
1157
|
var init_license = __esm({
|
|
1158
1158
|
"src/license.ts"() {
|
|
1159
1159
|
"use strict";
|
|
1160
1160
|
import_crypto = __toESM(require("crypto"));
|
|
1161
|
-
|
|
1162
|
-
|
|
1161
|
+
import_fs8 = __toESM(require("fs"));
|
|
1162
|
+
import_path9 = __toESM(require("path"));
|
|
1163
1163
|
DEFAULT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
1164
1164
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz1LLE/pXIx5TloDa0LAf
|
|
1165
1165
|
jg9NSIW6STWhsAFP2ZzXgpWoQ3cCmW6xcB/4QNEmPpGlfMWhyRfkxsdKuhnjUMTg
|
|
@@ -1189,13 +1189,13 @@ function parseFileUri(uri) {
|
|
|
1189
1189
|
}
|
|
1190
1190
|
}
|
|
1191
1191
|
function runGate(outDir, cwd) {
|
|
1192
|
-
const sarifPath =
|
|
1193
|
-
if (!
|
|
1192
|
+
const sarifPath = import_path10.default.join(outDir, "aiopt.sarif");
|
|
1193
|
+
if (!import_fs9.default.existsSync(sarifPath)) {
|
|
1194
1194
|
return { violations: 0, top3: [] };
|
|
1195
1195
|
}
|
|
1196
1196
|
let sarif;
|
|
1197
1197
|
try {
|
|
1198
|
-
sarif = JSON.parse(
|
|
1198
|
+
sarif = JSON.parse(import_fs9.default.readFileSync(sarifPath, "utf8"));
|
|
1199
1199
|
} catch {
|
|
1200
1200
|
return { violations: 0, top3: [] };
|
|
1201
1201
|
}
|
|
@@ -1211,8 +1211,8 @@ function runGate(outDir, cwd) {
|
|
|
1211
1211
|
const line = Number(loc?.region?.startLine || 1);
|
|
1212
1212
|
let file = parseFileUri(uri);
|
|
1213
1213
|
try {
|
|
1214
|
-
const abs =
|
|
1215
|
-
file =
|
|
1214
|
+
const abs = import_path10.default.isAbsolute(file) ? file : import_path10.default.resolve(file);
|
|
1215
|
+
file = import_path10.default.relative(cwd, abs) || file;
|
|
1216
1216
|
} catch {
|
|
1217
1217
|
}
|
|
1218
1218
|
locs.push({ file, line: Number.isFinite(line) && line > 0 ? line : 1 });
|
|
@@ -1224,7 +1224,7 @@ function formatGateStdout(r, outDir) {
|
|
|
1224
1224
|
const lines = [];
|
|
1225
1225
|
if (r.violations <= 0) {
|
|
1226
1226
|
lines.push("OK: no policy violations");
|
|
1227
|
-
lines.push(`Artifacts: ${
|
|
1227
|
+
lines.push(`Artifacts: ${import_path10.default.join(outDir, "report.md")} | ${import_path10.default.join(outDir, "aiopt.sarif")}`);
|
|
1228
1228
|
return { text: lines.join("\n"), exitCode: 0 };
|
|
1229
1229
|
}
|
|
1230
1230
|
lines.push(`FAIL: policy violations=${r.violations}`);
|
|
@@ -1234,12 +1234,12 @@ function formatGateStdout(r, outDir) {
|
|
|
1234
1234
|
const text = lines.slice(0, 10).join("\n");
|
|
1235
1235
|
return { text, exitCode: 1 };
|
|
1236
1236
|
}
|
|
1237
|
-
var
|
|
1237
|
+
var import_fs9, import_path10;
|
|
1238
1238
|
var init_gate = __esm({
|
|
1239
1239
|
"src/gate.ts"() {
|
|
1240
1240
|
"use strict";
|
|
1241
|
-
|
|
1242
|
-
|
|
1241
|
+
import_fs9 = __toESM(require("fs"));
|
|
1242
|
+
import_path10 = __toESM(require("path"));
|
|
1243
1243
|
}
|
|
1244
1244
|
});
|
|
1245
1245
|
|
|
@@ -1249,15 +1249,15 @@ __export(fix_exports, {
|
|
|
1249
1249
|
runFix: () => runFix
|
|
1250
1250
|
});
|
|
1251
1251
|
function isTextLike2(p) {
|
|
1252
|
-
const ext =
|
|
1252
|
+
const ext = import_path11.default.extname(p).toLowerCase();
|
|
1253
1253
|
return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext);
|
|
1254
1254
|
}
|
|
1255
1255
|
function walk2(root, out) {
|
|
1256
|
-
const items =
|
|
1256
|
+
const items = import_fs10.default.readdirSync(root, { withFileTypes: true });
|
|
1257
1257
|
for (const it of items) {
|
|
1258
1258
|
if (it.name.startsWith(".")) continue;
|
|
1259
1259
|
if (DEFAULT_EXCLUDES2.has(it.name)) continue;
|
|
1260
|
-
const full =
|
|
1260
|
+
const full = import_path11.default.join(root, it.name);
|
|
1261
1261
|
if (it.isDirectory()) walk2(full, out);
|
|
1262
1262
|
else out.push(full);
|
|
1263
1263
|
}
|
|
@@ -1326,9 +1326,9 @@ function applyModelRouting(content) {
|
|
|
1326
1326
|
return { changed, next };
|
|
1327
1327
|
}
|
|
1328
1328
|
function tmpFilePath(original) {
|
|
1329
|
-
const base =
|
|
1329
|
+
const base = import_path11.default.basename(original);
|
|
1330
1330
|
const rand = Math.random().toString(16).slice(2);
|
|
1331
|
-
return
|
|
1331
|
+
return import_path11.default.join(import_os2.default.tmpdir(), `aiopt-fix-${base}-${rand}`);
|
|
1332
1332
|
}
|
|
1333
1333
|
function diffNoIndex(oldPath, newPath) {
|
|
1334
1334
|
try {
|
|
@@ -1372,7 +1372,7 @@ function runFix(cwd, opts) {
|
|
|
1372
1372
|
if (!isTextLike2(file)) continue;
|
|
1373
1373
|
let st;
|
|
1374
1374
|
try {
|
|
1375
|
-
st =
|
|
1375
|
+
st = import_fs10.default.statSync(file);
|
|
1376
1376
|
} catch {
|
|
1377
1377
|
continue;
|
|
1378
1378
|
}
|
|
@@ -1380,7 +1380,7 @@ function runFix(cwd, opts) {
|
|
|
1380
1380
|
if (st.size > 1024 * 1024) continue;
|
|
1381
1381
|
let content = "";
|
|
1382
1382
|
try {
|
|
1383
|
-
content =
|
|
1383
|
+
content = import_fs10.default.readFileSync(file, "utf8");
|
|
1384
1384
|
} catch {
|
|
1385
1385
|
continue;
|
|
1386
1386
|
}
|
|
@@ -1389,8 +1389,8 @@ function runFix(cwd, opts) {
|
|
|
1389
1389
|
const next = r2.next;
|
|
1390
1390
|
if (next === content) continue;
|
|
1391
1391
|
const tmp = tmpFilePath(file);
|
|
1392
|
-
|
|
1393
|
-
const rel =
|
|
1392
|
+
import_fs10.default.writeFileSync(tmp, next);
|
|
1393
|
+
const rel = import_path11.default.relative(cwd, file).replace(/\\/g, "/");
|
|
1394
1394
|
const d0 = diffNoIndex(file, tmp);
|
|
1395
1395
|
const d = normalizePatchPaths(d0, rel);
|
|
1396
1396
|
if (d && d.trim().length > 0) {
|
|
@@ -1398,20 +1398,20 @@ function runFix(cwd, opts) {
|
|
|
1398
1398
|
changedFiles.push(rel);
|
|
1399
1399
|
}
|
|
1400
1400
|
try {
|
|
1401
|
-
|
|
1401
|
+
import_fs10.default.unlinkSync(tmp);
|
|
1402
1402
|
} catch {
|
|
1403
1403
|
}
|
|
1404
1404
|
if (patches.join("\n").length > 5e5) break;
|
|
1405
1405
|
}
|
|
1406
|
-
|
|
1407
|
-
const patchPath =
|
|
1406
|
+
import_fs10.default.mkdirSync(opts.outDir, { recursive: true });
|
|
1407
|
+
const patchPath = import_path11.default.join(opts.outDir, "aiopt.patch");
|
|
1408
1408
|
const header = [
|
|
1409
1409
|
"# AIOpt patch (generated)",
|
|
1410
1410
|
"# - retry cap: reduce high retry/attempt counts to 3",
|
|
1411
1411
|
"# - model routing: cheap default via AIOPT_MODEL env override",
|
|
1412
1412
|
""
|
|
1413
1413
|
].join("\n");
|
|
1414
|
-
|
|
1414
|
+
import_fs10.default.writeFileSync(patchPath, header + patches.join("\n"));
|
|
1415
1415
|
if (opts.apply) {
|
|
1416
1416
|
try {
|
|
1417
1417
|
const inside = (0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
@@ -1430,13 +1430,13 @@ function runFix(cwd, opts) {
|
|
|
1430
1430
|
}
|
|
1431
1431
|
return { ok: true, applied: false, patchPath, changedFiles };
|
|
1432
1432
|
}
|
|
1433
|
-
var
|
|
1433
|
+
var import_fs10, import_path11, import_os2, import_child_process, DEFAULT_EXCLUDES2;
|
|
1434
1434
|
var init_fix = __esm({
|
|
1435
1435
|
"src/fix.ts"() {
|
|
1436
1436
|
"use strict";
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1437
|
+
import_fs10 = __toESM(require("fs"));
|
|
1438
|
+
import_path11 = __toESM(require("path"));
|
|
1439
|
+
import_os2 = __toESM(require("os"));
|
|
1440
1440
|
import_child_process = require("child_process");
|
|
1441
1441
|
DEFAULT_EXCLUDES2 = /* @__PURE__ */ new Set([
|
|
1442
1442
|
".git",
|
|
@@ -1669,7 +1669,7 @@ var init_guard = __esm({
|
|
|
1669
1669
|
// src/collect.ts
|
|
1670
1670
|
function exists(p) {
|
|
1671
1671
|
try {
|
|
1672
|
-
return
|
|
1672
|
+
return import_fs11.default.existsSync(p);
|
|
1673
1673
|
} catch {
|
|
1674
1674
|
return false;
|
|
1675
1675
|
}
|
|
@@ -1677,7 +1677,7 @@ function exists(p) {
|
|
|
1677
1677
|
function safeReadJsonl(p) {
|
|
1678
1678
|
const out = [];
|
|
1679
1679
|
try {
|
|
1680
|
-
const txt =
|
|
1680
|
+
const txt = import_fs11.default.readFileSync(p, "utf8");
|
|
1681
1681
|
for (const line of txt.split(/\r?\n/)) {
|
|
1682
1682
|
if (!line.trim()) continue;
|
|
1683
1683
|
try {
|
|
@@ -1692,11 +1692,11 @@ function safeReadJsonl(p) {
|
|
|
1692
1692
|
function listJsonlFiles(dir) {
|
|
1693
1693
|
const out = [];
|
|
1694
1694
|
try {
|
|
1695
|
-
for (const name of
|
|
1696
|
-
const full =
|
|
1695
|
+
for (const name of import_fs11.default.readdirSync(dir)) {
|
|
1696
|
+
const full = import_path12.default.join(dir, name);
|
|
1697
1697
|
let st;
|
|
1698
1698
|
try {
|
|
1699
|
-
st =
|
|
1699
|
+
st = import_fs11.default.statSync(full);
|
|
1700
1700
|
} catch {
|
|
1701
1701
|
continue;
|
|
1702
1702
|
}
|
|
@@ -1707,18 +1707,18 @@ function listJsonlFiles(dir) {
|
|
|
1707
1707
|
return out;
|
|
1708
1708
|
}
|
|
1709
1709
|
function findOpenClawSessionLogs() {
|
|
1710
|
-
const home =
|
|
1711
|
-
const root =
|
|
1710
|
+
const home = import_os3.default.homedir();
|
|
1711
|
+
const root = import_path12.default.join(home, ".openclaw", "agents");
|
|
1712
1712
|
if (!exists(root)) return [];
|
|
1713
1713
|
const found = [];
|
|
1714
1714
|
let agents = [];
|
|
1715
1715
|
try {
|
|
1716
|
-
agents =
|
|
1716
|
+
agents = import_fs11.default.readdirSync(root);
|
|
1717
1717
|
} catch {
|
|
1718
1718
|
agents = [];
|
|
1719
1719
|
}
|
|
1720
1720
|
for (const a of agents) {
|
|
1721
|
-
const sessDir =
|
|
1721
|
+
const sessDir = import_path12.default.join(root, a, "sessions");
|
|
1722
1722
|
if (!exists(sessDir)) continue;
|
|
1723
1723
|
for (const f of listJsonlFiles(sessDir)) found.push(f);
|
|
1724
1724
|
}
|
|
@@ -1790,18 +1790,18 @@ function collectToUsageJsonl(outPath) {
|
|
|
1790
1790
|
uniq.push(e);
|
|
1791
1791
|
}
|
|
1792
1792
|
uniq.sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
|
|
1793
|
-
|
|
1793
|
+
import_fs11.default.mkdirSync(import_path12.default.dirname(outPath), { recursive: true });
|
|
1794
1794
|
const lines = uniq.map((e) => JSON.stringify(e)).join("\n") + (uniq.length ? "\n" : "");
|
|
1795
|
-
|
|
1795
|
+
import_fs11.default.writeFileSync(outPath, lines);
|
|
1796
1796
|
return { outPath, sources, eventsWritten: uniq.length };
|
|
1797
1797
|
}
|
|
1798
|
-
var
|
|
1798
|
+
var import_fs11, import_path12, import_os3;
|
|
1799
1799
|
var init_collect = __esm({
|
|
1800
1800
|
"src/collect.ts"() {
|
|
1801
1801
|
"use strict";
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1802
|
+
import_fs11 = __toESM(require("fs"));
|
|
1803
|
+
import_path12 = __toESM(require("path"));
|
|
1804
|
+
import_os3 = __toESM(require("os"));
|
|
1805
1805
|
}
|
|
1806
1806
|
});
|
|
1807
1807
|
|
|
@@ -1813,8 +1813,8 @@ __export(dashboard_exports, {
|
|
|
1813
1813
|
async function startDashboard(cwd, opts) {
|
|
1814
1814
|
const host = "127.0.0.1";
|
|
1815
1815
|
const port = opts.port || 3010;
|
|
1816
|
-
const outDir =
|
|
1817
|
-
const file = (name) =>
|
|
1816
|
+
const outDir = import_path13.default.join(cwd, "aiopt-output");
|
|
1817
|
+
const file = (name) => import_path13.default.join(outDir, name);
|
|
1818
1818
|
let lastCollect = null;
|
|
1819
1819
|
let lastCollectError = null;
|
|
1820
1820
|
let lastAutoGen = null;
|
|
@@ -1822,7 +1822,7 @@ async function startDashboard(cwd, opts) {
|
|
|
1822
1822
|
function ensureUsageFile() {
|
|
1823
1823
|
try {
|
|
1824
1824
|
const usagePath = file("usage.jsonl");
|
|
1825
|
-
if (
|
|
1825
|
+
if (import_fs12.default.existsSync(usagePath)) return;
|
|
1826
1826
|
const r = collectToUsageJsonl(usagePath);
|
|
1827
1827
|
lastCollect = { ts: (/* @__PURE__ */ new Date()).toISOString(), outPath: r.outPath, sources: r.sources, eventsWritten: r.eventsWritten };
|
|
1828
1828
|
lastCollectError = null;
|
|
@@ -1831,11 +1831,11 @@ async function startDashboard(cwd, opts) {
|
|
|
1831
1831
|
}
|
|
1832
1832
|
}
|
|
1833
1833
|
function loadRateTable2() {
|
|
1834
|
-
const p =
|
|
1835
|
-
return JSON.parse(
|
|
1834
|
+
const p = import_path13.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
1835
|
+
return JSON.parse(import_fs12.default.readFileSync(p, "utf8"));
|
|
1836
1836
|
}
|
|
1837
1837
|
function readEvents(usagePath) {
|
|
1838
|
-
if (!
|
|
1838
|
+
if (!import_fs12.default.existsSync(usagePath)) return [];
|
|
1839
1839
|
return isCsvPath(usagePath) ? readCsv(usagePath) : readJsonl(usagePath);
|
|
1840
1840
|
}
|
|
1841
1841
|
function ensureScanAndGuard() {
|
|
@@ -1843,7 +1843,7 @@ async function startDashboard(cwd, opts) {
|
|
|
1843
1843
|
try {
|
|
1844
1844
|
const usagePath = file("usage.jsonl");
|
|
1845
1845
|
const rt = loadRateTable2();
|
|
1846
|
-
const needScan = !
|
|
1846
|
+
const needScan = !import_fs12.default.existsSync(file("report.json")) || !import_fs12.default.existsSync(file("report.md"));
|
|
1847
1847
|
if (needScan) {
|
|
1848
1848
|
const events = readEvents(usagePath);
|
|
1849
1849
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
@@ -1851,14 +1851,14 @@ async function startDashboard(cwd, opts) {
|
|
|
1851
1851
|
writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd, cliVersion: "dashboard" });
|
|
1852
1852
|
did.push("scan");
|
|
1853
1853
|
}
|
|
1854
|
-
const needGuard = !
|
|
1854
|
+
const needGuard = !import_fs12.default.existsSync(file("guard-last.txt")) || !import_fs12.default.existsSync(file("guard-last.json"));
|
|
1855
1855
|
if (needGuard) {
|
|
1856
1856
|
const events = readEvents(usagePath);
|
|
1857
1857
|
const r = runGuard(rt, { baselineEvents: events, candidate: {} });
|
|
1858
1858
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1859
|
+
import_fs12.default.writeFileSync(file("guard-last.txt"), r.message);
|
|
1860
|
+
import_fs12.default.writeFileSync(file("guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
|
|
1861
|
+
import_fs12.default.appendFileSync(file("guard-history.jsonl"), JSON.stringify({ ts, exitCode: r.exitCode, mode: "dashboard", baseline: usagePath, candidate: null }) + "\n");
|
|
1862
1862
|
did.push("guard");
|
|
1863
1863
|
}
|
|
1864
1864
|
if (did.length) lastAutoGen = { ts: (/* @__PURE__ */ new Date()).toISOString(), did };
|
|
@@ -1871,15 +1871,15 @@ async function startDashboard(cwd, opts) {
|
|
|
1871
1871
|
ensureScanAndGuard();
|
|
1872
1872
|
function readOrNull(p) {
|
|
1873
1873
|
try {
|
|
1874
|
-
if (!
|
|
1875
|
-
return
|
|
1874
|
+
if (!import_fs12.default.existsSync(p)) return null;
|
|
1875
|
+
return import_fs12.default.readFileSync(p, "utf8");
|
|
1876
1876
|
} catch {
|
|
1877
1877
|
return null;
|
|
1878
1878
|
}
|
|
1879
1879
|
}
|
|
1880
1880
|
function statOrNull(p) {
|
|
1881
1881
|
try {
|
|
1882
|
-
const st =
|
|
1882
|
+
const st = import_fs12.default.statSync(p);
|
|
1883
1883
|
return { size: st.size, mtimeMs: st.mtimeMs };
|
|
1884
1884
|
} catch {
|
|
1885
1885
|
return null;
|
|
@@ -1958,6 +1958,8 @@ async function startDashboard(cwd, opts) {
|
|
|
1958
1958
|
<span class="muted">\xB7</span>
|
|
1959
1959
|
<button id="btnRefresh" style="all:unset; cursor:pointer; padding:2px 8px; border:1px solid rgba(255,255,255,.16); border-radius:999px; background:rgba(255,255,255,.04); font-size:12px">Refresh</button>
|
|
1960
1960
|
<button id="btnLive" style="all:unset; cursor:pointer; padding:2px 8px; border:1px solid rgba(255,255,255,.16); border-radius:999px; background:rgba(255,255,255,.04); font-size:12px">Live: Off</button>
|
|
1961
|
+
<span class="muted">\xB7</span>
|
|
1962
|
+
<a href="https://github.com/tkddlr0716-collab/aiopt/issues/new?template=dashboard_feedback.yml" target="_blank">Feedback</a>
|
|
1961
1963
|
</div>
|
|
1962
1964
|
</div>
|
|
1963
1965
|
|
|
@@ -2272,7 +2274,7 @@ load();
|
|
|
2272
2274
|
ensureUsageFile();
|
|
2273
2275
|
ensureScanAndGuard();
|
|
2274
2276
|
const expected = ["guard-last.txt", "guard-last.json", "report.json", "report.md", "usage.jsonl", "guard-history.jsonl"];
|
|
2275
|
-
const missing = expected.filter((f) => !
|
|
2277
|
+
const missing = expected.filter((f) => !import_fs12.default.existsSync(file(f)));
|
|
2276
2278
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
2277
2279
|
res.end(JSON.stringify({ baseDir: cwd, outDir, missing, collect: lastCollect, collectError: lastCollectError, autoGen: lastAutoGen, autoGenError: lastAutoGenError }, null, 2));
|
|
2278
2280
|
return;
|
|
@@ -2305,7 +2307,7 @@ load();
|
|
|
2305
2307
|
globalThis.__aioptDashCache = globalThis.__aioptDashCache || {};
|
|
2306
2308
|
const cache = globalThis.__aioptDashCache;
|
|
2307
2309
|
if (!cache.usage || cache.usage.mtimeMs !== st.mtimeMs) {
|
|
2308
|
-
const txt2 =
|
|
2310
|
+
const txt2 = import_fs12.default.readFileSync(usagePath, "utf8");
|
|
2309
2311
|
const now = Date.now();
|
|
2310
2312
|
const liveBins = Array.from({ length: 60 }, () => ({ cost: 0, calls: 0 }));
|
|
2311
2313
|
const dayBins = Array.from({ length: 7 }, () => ({ cost: 0, calls: 0 }));
|
|
@@ -2370,13 +2372,13 @@ load();
|
|
|
2370
2372
|
await new Promise(() => {
|
|
2371
2373
|
});
|
|
2372
2374
|
}
|
|
2373
|
-
var import_http,
|
|
2375
|
+
var import_http, import_fs12, import_path13;
|
|
2374
2376
|
var init_dashboard = __esm({
|
|
2375
2377
|
"src/dashboard.ts"() {
|
|
2376
2378
|
"use strict";
|
|
2377
2379
|
import_http = __toESM(require("http"));
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
+
import_fs12 = __toESM(require("fs"));
|
|
2381
|
+
import_path13 = __toESM(require("path"));
|
|
2380
2382
|
init_collect();
|
|
2381
2383
|
init_scan();
|
|
2382
2384
|
init_io();
|
|
@@ -2390,55 +2392,55 @@ __export(find_output_exports, {
|
|
|
2390
2392
|
findAioptOutputDir: () => findAioptOutputDir
|
|
2391
2393
|
});
|
|
2392
2394
|
function findAioptOutputDir(startCwd) {
|
|
2393
|
-
let cur =
|
|
2395
|
+
let cur = import_path14.default.resolve(startCwd);
|
|
2394
2396
|
while (true) {
|
|
2395
|
-
const outDir =
|
|
2396
|
-
if (
|
|
2397
|
+
const outDir = import_path14.default.join(cur, "aiopt-output");
|
|
2398
|
+
if (import_fs13.default.existsSync(outDir)) {
|
|
2397
2399
|
try {
|
|
2398
|
-
if (
|
|
2400
|
+
if (import_fs13.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
|
|
2399
2401
|
} catch {
|
|
2400
2402
|
}
|
|
2401
2403
|
}
|
|
2402
|
-
const parent =
|
|
2404
|
+
const parent = import_path14.default.dirname(cur);
|
|
2403
2405
|
if (parent === cur) break;
|
|
2404
2406
|
cur = parent;
|
|
2405
2407
|
}
|
|
2406
2408
|
try {
|
|
2407
|
-
const base =
|
|
2408
|
-
const children =
|
|
2409
|
+
const base = import_path14.default.resolve(startCwd);
|
|
2410
|
+
const children = import_fs13.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path14.default.join(base, d.name));
|
|
2409
2411
|
for (const child of children) {
|
|
2410
|
-
const outDir =
|
|
2411
|
-
if (
|
|
2412
|
+
const outDir = import_path14.default.join(child, "aiopt-output");
|
|
2413
|
+
if (import_fs13.default.existsSync(outDir)) {
|
|
2412
2414
|
try {
|
|
2413
|
-
if (
|
|
2415
|
+
if (import_fs13.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
|
|
2414
2416
|
} catch {
|
|
2415
2417
|
}
|
|
2416
2418
|
}
|
|
2417
2419
|
}
|
|
2418
2420
|
} catch {
|
|
2419
2421
|
}
|
|
2420
|
-
return { cwd:
|
|
2422
|
+
return { cwd: import_path14.default.resolve(startCwd), outDir: import_path14.default.join(import_path14.default.resolve(startCwd), "aiopt-output") };
|
|
2421
2423
|
}
|
|
2422
|
-
var
|
|
2424
|
+
var import_fs13, import_path14;
|
|
2423
2425
|
var init_find_output = __esm({
|
|
2424
2426
|
"src/find-output.ts"() {
|
|
2425
2427
|
"use strict";
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
+
import_fs13 = __toESM(require("fs"));
|
|
2429
|
+
import_path14 = __toESM(require("path"));
|
|
2428
2430
|
}
|
|
2429
2431
|
});
|
|
2430
2432
|
|
|
2431
2433
|
// src/rates-util.ts
|
|
2432
2434
|
function loadRateTableFromDistPath() {
|
|
2433
|
-
const p =
|
|
2434
|
-
return JSON.parse(
|
|
2435
|
+
const p = import_path15.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2436
|
+
return JSON.parse(import_fs14.default.readFileSync(p, "utf8"));
|
|
2435
2437
|
}
|
|
2436
|
-
var
|
|
2438
|
+
var import_fs14, import_path15;
|
|
2437
2439
|
var init_rates_util = __esm({
|
|
2438
2440
|
"src/rates-util.ts"() {
|
|
2439
2441
|
"use strict";
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
+
import_fs14 = __toESM(require("fs"));
|
|
2443
|
+
import_path15 = __toESM(require("path"));
|
|
2442
2444
|
}
|
|
2443
2445
|
});
|
|
2444
2446
|
|
|
@@ -2449,8 +2451,8 @@ __export(quickstart_exports, {
|
|
|
2449
2451
|
seedDemoUsage: () => seedDemoUsage
|
|
2450
2452
|
});
|
|
2451
2453
|
function seedDemoUsage(outDir) {
|
|
2452
|
-
|
|
2453
|
-
const p =
|
|
2454
|
+
import_fs15.default.mkdirSync(outDir, { recursive: true });
|
|
2455
|
+
const p = import_path16.default.join(outDir, "usage.jsonl");
|
|
2454
2456
|
const now = Date.now();
|
|
2455
2457
|
const lines = [];
|
|
2456
2458
|
for (let i = 0; i < 60; i++) {
|
|
@@ -2467,18 +2469,18 @@ function seedDemoUsage(outDir) {
|
|
|
2467
2469
|
meta: { feature_tag: i % 2 ? "summarize" : "coding" }
|
|
2468
2470
|
});
|
|
2469
2471
|
}
|
|
2470
|
-
|
|
2472
|
+
import_fs15.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
2471
2473
|
return p;
|
|
2472
2474
|
}
|
|
2473
2475
|
function runQuickstart(cwd, opts) {
|
|
2474
|
-
const outDir =
|
|
2476
|
+
const outDir = import_path16.default.join(cwd, "aiopt-output");
|
|
2475
2477
|
const usagePath = seedDemoUsage(outDir);
|
|
2476
2478
|
const rt = loadRateTableFromDistPath();
|
|
2477
2479
|
const { readJsonl: readJsonl2 } = (init_io(), __toCommonJS(io_exports));
|
|
2478
2480
|
const events = readJsonl2(usagePath);
|
|
2479
2481
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
|
|
2483
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "report.json"), JSON.stringify({
|
|
2482
2484
|
version: 3,
|
|
2483
2485
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2484
2486
|
confidence: analysis.unknown_models?.length ? "MEDIUM" : "HIGH",
|
|
@@ -2498,8 +2500,8 @@ function runQuickstart(cwd, opts) {
|
|
|
2498
2500
|
unknown_models: analysis.unknown_models || [],
|
|
2499
2501
|
notes: []
|
|
2500
2502
|
}, null, 2));
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2504
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "report.md"), "# AIOpt quickstart demo\n\nThis is a demo report generated by `aiopt quickstart --demo`.\n");
|
|
2503
2505
|
const r = runGuard(rt, {
|
|
2504
2506
|
baselineEvents: events,
|
|
2505
2507
|
candidate: {
|
|
@@ -2510,12 +2512,12 @@ function runQuickstart(cwd, opts) {
|
|
|
2510
2512
|
});
|
|
2511
2513
|
return { usagePath, outDir, guard: r, port: opts.port };
|
|
2512
2514
|
}
|
|
2513
|
-
var
|
|
2515
|
+
var import_fs15, import_path16;
|
|
2514
2516
|
var init_quickstart = __esm({
|
|
2515
2517
|
"src/quickstart.ts"() {
|
|
2516
2518
|
"use strict";
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
+
import_fs15 = __toESM(require("fs"));
|
|
2520
|
+
import_path16 = __toESM(require("path"));
|
|
2519
2521
|
init_scan();
|
|
2520
2522
|
init_rates_util();
|
|
2521
2523
|
init_guard();
|
|
@@ -2523,36 +2525,64 @@ var init_quickstart = __esm({
|
|
|
2523
2525
|
});
|
|
2524
2526
|
|
|
2525
2527
|
// src/cli.ts
|
|
2526
|
-
var
|
|
2527
|
-
var
|
|
2528
|
+
var import_fs16 = __toESM(require("fs"));
|
|
2529
|
+
var import_path17 = __toESM(require("path"));
|
|
2528
2530
|
var import_commander = require("commander");
|
|
2529
2531
|
init_io();
|
|
2530
2532
|
init_scan();
|
|
2533
|
+
|
|
2534
|
+
// src/usage-path.ts
|
|
2535
|
+
var import_fs5 = __toESM(require("fs"));
|
|
2536
|
+
var import_path6 = __toESM(require("path"));
|
|
2537
|
+
var import_os = __toESM(require("os"));
|
|
2538
|
+
function homeAioptUsagePath() {
|
|
2539
|
+
return import_path6.default.join(import_os.default.homedir(), ".aiopt", "aiopt-output", "usage.jsonl");
|
|
2540
|
+
}
|
|
2541
|
+
function resolveUsagePath(preferred) {
|
|
2542
|
+
const tried = [];
|
|
2543
|
+
const push = (p) => {
|
|
2544
|
+
if (!p) return;
|
|
2545
|
+
if (tried.includes(p)) return;
|
|
2546
|
+
tried.push(p);
|
|
2547
|
+
};
|
|
2548
|
+
push(preferred);
|
|
2549
|
+
push(homeAioptUsagePath());
|
|
2550
|
+
push("./aiopt-output/usage.jsonl");
|
|
2551
|
+
for (const p of tried) {
|
|
2552
|
+
try {
|
|
2553
|
+
if (import_fs5.default.existsSync(p)) return { path: p, tried };
|
|
2554
|
+
} catch {
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
return { path: preferred, tried };
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2560
|
+
// src/cli.ts
|
|
2531
2561
|
var program = new import_commander.Command();
|
|
2532
2562
|
var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
|
|
2533
2563
|
var DEFAULT_OUTPUT_DIR = "./aiopt-output";
|
|
2534
2564
|
function loadRateTable() {
|
|
2535
|
-
const p =
|
|
2536
|
-
return JSON.parse(
|
|
2565
|
+
const p = import_path17.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2566
|
+
return JSON.parse(import_fs16.default.readFileSync(p, "utf8"));
|
|
2537
2567
|
}
|
|
2538
2568
|
program.name("aiopt").description("AI \uBE44\uC6A9 \uC790\uB3D9 \uC808\uAC10 \uC778\uD504\uB77C \u2014 \uC11C\uBC84 \uC5C6\uB294 \uB85C\uCEEC CLI MVP").version(require_package().version);
|
|
2539
2569
|
program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
|
|
2540
2570
|
ensureDir("./aiopt-input");
|
|
2541
2571
|
ensureDir("./aiopt-output");
|
|
2542
|
-
const sampleSrc =
|
|
2543
|
-
const dst =
|
|
2544
|
-
if (!
|
|
2545
|
-
|
|
2572
|
+
const sampleSrc = import_path17.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
|
|
2573
|
+
const dst = import_path17.default.join("./aiopt-input", "usage.jsonl");
|
|
2574
|
+
if (!import_fs16.default.existsSync(dst)) {
|
|
2575
|
+
import_fs16.default.copyFileSync(sampleSrc, dst);
|
|
2546
2576
|
console.log("Created ./aiopt-input/usage.jsonl (sample)");
|
|
2547
2577
|
} else {
|
|
2548
2578
|
console.log("Exists ./aiopt-input/usage.jsonl (skip)");
|
|
2549
2579
|
}
|
|
2550
2580
|
console.log("Ready: ./aiopt-output/");
|
|
2551
2581
|
});
|
|
2552
|
-
program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C \uBD84\uC11D\uD558\uACE0 report.md/report.json + patches\uAE4C\uC9C0 \uC0DD\uC131").option("--input <path>", "input file path (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).action(async (opts) => {
|
|
2582
|
+
program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C \uBD84\uC11D\uD558\uACE0 report.md/report.json + patches\uAE4C\uC9C0 \uC0DD\uC131").option("--input <path>", "input file path (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).option("--json", "print machine-readable JSON to stdout").action(async (opts) => {
|
|
2553
2583
|
const inputPath = String(opts.input);
|
|
2554
2584
|
const outDir = String(opts.out);
|
|
2555
|
-
if (!
|
|
2585
|
+
if (!import_fs16.default.existsSync(inputPath)) {
|
|
2556
2586
|
console.error(`Input not found: ${inputPath}`);
|
|
2557
2587
|
process.exit(1);
|
|
2558
2588
|
}
|
|
@@ -2563,12 +2593,32 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
|
|
|
2563
2593
|
writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
|
|
2564
2594
|
const { buildTopFixes: buildTopFixes2 } = await Promise.resolve().then(() => (init_solutions(), solutions_exports));
|
|
2565
2595
|
const fixes = buildTopFixes2(analysis, savings).slice(0, 3);
|
|
2596
|
+
if (opts.json) {
|
|
2597
|
+
const payload = {
|
|
2598
|
+
ok: true,
|
|
2599
|
+
outDir,
|
|
2600
|
+
input: inputPath,
|
|
2601
|
+
report: {
|
|
2602
|
+
report_md: import_path17.default.join(outDir, "report.md"),
|
|
2603
|
+
report_json: import_path17.default.join(outDir, "report.json"),
|
|
2604
|
+
cost_policy_json: import_path17.default.join(outDir, "cost-policy.json"),
|
|
2605
|
+
sarif: import_path17.default.join(outDir, "aiopt.sarif")
|
|
2606
|
+
},
|
|
2607
|
+
summary: {
|
|
2608
|
+
total_cost_usd: analysis.total_cost,
|
|
2609
|
+
estimated_savings_usd: savings.estimated_savings_total,
|
|
2610
|
+
confidence: meta?.mode || null
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2566
2616
|
console.log("Top Fix 3:");
|
|
2567
2617
|
fixes.forEach((f, i) => {
|
|
2568
2618
|
const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
|
|
2569
2619
|
console.log(`${i + 1}) ${f.title} ${tag}`);
|
|
2570
2620
|
});
|
|
2571
|
-
console.log(`Report: ${
|
|
2621
|
+
console.log(`Report: ${import_path17.default.join(outDir, "report.md")}`);
|
|
2572
2622
|
});
|
|
2573
2623
|
program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE30\uBC18\uC73C\uB85C cost-policy.json\uB9CC \uC7AC\uC0DD\uC131 (MVP: scan\uACFC \uB3D9\uC77C \uB85C\uC9C1)").option("--input <path>", "input file path (default: ./aiopt-input/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).action((opts) => {
|
|
2574
2624
|
const inputPath = String(opts.input);
|
|
@@ -2578,7 +2628,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
|
|
|
2578
2628
|
const { policy } = analyze(rt, events);
|
|
2579
2629
|
policy.generated_from.input = inputPath;
|
|
2580
2630
|
ensureDir(outDir);
|
|
2581
|
-
|
|
2631
|
+
import_fs16.default.writeFileSync(import_path17.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2582
2632
|
console.log(`OK: ${outDir}/cost-policy.json`);
|
|
2583
2633
|
});
|
|
2584
2634
|
program.command("install").description("Install AIOpt guardrails: create aiopt/ + policies + usage.jsonl").option("--force", "overwrite existing files").option("--seed-sample", "seed 1 sample line into aiopt-output/usage.jsonl").action(async (opts) => {
|
|
@@ -2618,7 +2668,7 @@ licenseCmd.command("verify").option("--path <path>", "license.json path (default
|
|
|
2618
2668
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
2619
2669
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
2620
2670
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
2621
|
-
if (!
|
|
2671
|
+
if (!import_fs16.default.existsSync(p)) {
|
|
2622
2672
|
console.error(`FAIL: license file not found: ${p}`);
|
|
2623
2673
|
process.exit(3);
|
|
2624
2674
|
}
|
|
@@ -2635,7 +2685,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
2635
2685
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
2636
2686
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
2637
2687
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
2638
|
-
if (!
|
|
2688
|
+
if (!import_fs16.default.existsSync(p)) {
|
|
2639
2689
|
console.log("NO_LICENSE");
|
|
2640
2690
|
process.exit(2);
|
|
2641
2691
|
}
|
|
@@ -2648,28 +2698,79 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
2648
2698
|
console.log(`INVALID: ${v.reason || "unknown"}`);
|
|
2649
2699
|
process.exit(3);
|
|
2650
2700
|
});
|
|
2651
|
-
program.command("gate").description("Merge gate (CI-friendly): fail (exit 1) when policy violations are detected; prints <=10 lines").option("--input <path>", "input usage jsonl/csv (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).action(async (opts) => {
|
|
2652
|
-
const
|
|
2701
|
+
program.command("gate").description("Merge gate (CI-friendly): fail (exit 1) when policy violations are detected; prints <=10 lines").option("--input <path>", "input usage jsonl/csv (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).option("--json", "print machine-readable JSON to stdout").action(async (opts) => {
|
|
2702
|
+
const preferredInput = String(opts.input);
|
|
2653
2703
|
const outDir = String(opts.out);
|
|
2654
|
-
|
|
2655
|
-
|
|
2704
|
+
const resolved = resolveUsagePath(preferredInput);
|
|
2705
|
+
const inputPath = resolved.path;
|
|
2706
|
+
const defaultOut = "./aiopt-output";
|
|
2707
|
+
let finalOutDir = outDir;
|
|
2708
|
+
if (outDir === defaultOut) {
|
|
2709
|
+
try {
|
|
2710
|
+
const os4 = require("os");
|
|
2711
|
+
finalOutDir = import_path17.default.join(os4.homedir(), ".aiopt", "aiopt-output");
|
|
2712
|
+
} catch {
|
|
2713
|
+
finalOutDir = outDir;
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
if (!import_fs16.default.existsSync(inputPath)) {
|
|
2717
|
+
if (opts.json) {
|
|
2718
|
+
console.log(JSON.stringify({
|
|
2719
|
+
ok: false,
|
|
2720
|
+
exitCode: 1,
|
|
2721
|
+
error: "input_not_found",
|
|
2722
|
+
message: `FAIL: input not found: ${preferredInput}`,
|
|
2723
|
+
tried: resolved.tried,
|
|
2724
|
+
hint: "Run: aiopt dashboard (auto-collects OpenClaw usage) or pass --input <usage.jsonl>"
|
|
2725
|
+
}, null, 2));
|
|
2726
|
+
} else {
|
|
2727
|
+
console.error(`FAIL: input not found: ${preferredInput}`);
|
|
2728
|
+
console.error(`Tried: ${resolved.tried.join(", ")}`);
|
|
2729
|
+
console.error("Hint: run `aiopt dashboard` first (auto-collects OpenClaw usage)");
|
|
2730
|
+
}
|
|
2656
2731
|
process.exit(1);
|
|
2657
2732
|
}
|
|
2658
2733
|
const rt = loadRateTable();
|
|
2659
2734
|
const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);
|
|
2660
2735
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
2661
2736
|
policy.generated_from.input = inputPath;
|
|
2662
|
-
writeOutputs(
|
|
2737
|
+
writeOutputs(finalOutDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
|
|
2663
2738
|
const { runGate: runGate2, formatGateStdout: formatGateStdout2 } = await Promise.resolve().then(() => (init_gate(), gate_exports));
|
|
2664
|
-
const r = runGate2(
|
|
2665
|
-
|
|
2739
|
+
const r = runGate2(finalOutDir, process.cwd());
|
|
2740
|
+
if (opts.json) {
|
|
2741
|
+
const payload = {
|
|
2742
|
+
ok: r.violations <= 0,
|
|
2743
|
+
exitCode: r.violations <= 0 ? 0 : 1,
|
|
2744
|
+
violations: r.violations,
|
|
2745
|
+
top3: r.top3,
|
|
2746
|
+
artifacts: {
|
|
2747
|
+
report_md: import_path17.default.join(finalOutDir, "report.md"),
|
|
2748
|
+
sarif: import_path17.default.join(finalOutDir, "aiopt.sarif"),
|
|
2749
|
+
patches_dir: import_path17.default.join(finalOutDir, "patches")
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2753
|
+
process.exit(payload.exitCode);
|
|
2754
|
+
}
|
|
2755
|
+
const out = formatGateStdout2(r, finalOutDir);
|
|
2666
2756
|
console.log(out.text);
|
|
2667
2757
|
process.exit(out.exitCode);
|
|
2668
2758
|
});
|
|
2669
|
-
program.command("fix").description("Auto-fix suggestions: generate aiopt.patch (and optionally apply it via git apply)").option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).option("--apply", "apply the generated patch via git apply").action(async (opts) => {
|
|
2759
|
+
program.command("fix").description("Auto-fix suggestions: generate aiopt.patch (and optionally apply it via git apply)").option("--out <dir>", "output dir (default: ./aiopt-output)", DEFAULT_OUTPUT_DIR).option("--apply", "apply the generated patch via git apply").option("--json", "print machine-readable JSON to stdout").action(async (opts) => {
|
|
2670
2760
|
const outDir = String(opts.out);
|
|
2671
2761
|
const { runFix: runFix2 } = await Promise.resolve().then(() => (init_fix(), fix_exports));
|
|
2672
2762
|
const r = runFix2(process.cwd(), { outDir, apply: Boolean(opts.apply) });
|
|
2763
|
+
if (opts.json) {
|
|
2764
|
+
const payload = {
|
|
2765
|
+
ok: r.ok,
|
|
2766
|
+
applied: r.applied,
|
|
2767
|
+
patchPath: r.patchPath,
|
|
2768
|
+
changedFiles: r.changedFiles,
|
|
2769
|
+
hint: r.hint || null
|
|
2770
|
+
};
|
|
2771
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2772
|
+
process.exit(r.ok ? 0 : 1);
|
|
2773
|
+
}
|
|
2673
2774
|
console.log(`Patch: ${r.patchPath}`);
|
|
2674
2775
|
if (r.changedFiles.length) {
|
|
2675
2776
|
console.log(`Files: ${r.changedFiles.slice(0, 10).join(", ")}${r.changedFiles.length > 10 ? " ..." : ""}`);
|
|
@@ -2686,7 +2787,7 @@ program.command("fix").description("Auto-fix suggestions: generate aiopt.patch (
|
|
|
2686
2787
|
}
|
|
2687
2788
|
process.exit(0);
|
|
2688
2789
|
});
|
|
2689
|
-
program.command("guard").description("Pre-deploy guardrail: compare baseline usage vs candidate change (or diff two log sets) and print warnings (exit codes 0/2/3)").option("--input <path>", "baseline usage jsonl/csv (legacy alias for --baseline; default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--baseline <path>", "baseline usage jsonl/csv (diff mode when used with --candidate)").option("--candidate <path>", "candidate usage jsonl/csv (diff mode: compare two real log sets)").option("--provider <provider>", "candidate provider override (transform mode only)").option("--model <model>", "candidate model override (transform mode only)").option("--context-mult <n>", "multiply input_tokens by n (transform mode only)", (v) => Number(v)).option("--output-mult <n>", "multiply output_tokens by n (transform mode only)", (v) => Number(v)).option("--retries-delta <n>", "add n to retries (transform mode only)", (v) => Number(v)).option("--call-mult <n>", "multiply call volume by n (traffic spike)", (v) => Number(v)).option("--budget-monthly <usd>", "fail if estimated candidate monthly cost exceeds this budget", (v) => Number(v)).action(async (opts) => {
|
|
2790
|
+
program.command("guard").description("Pre-deploy guardrail: compare baseline usage vs candidate change (or diff two log sets) and print warnings (exit codes 0/2/3)").option("--input <path>", "baseline usage jsonl/csv (legacy alias for --baseline; default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--baseline <path>", "baseline usage jsonl/csv (diff mode when used with --candidate)").option("--candidate <path>", "candidate usage jsonl/csv (diff mode: compare two real log sets)").option("--provider <provider>", "candidate provider override (transform mode only)").option("--model <model>", "candidate model override (transform mode only)").option("--context-mult <n>", "multiply input_tokens by n (transform mode only)", (v) => Number(v)).option("--output-mult <n>", "multiply output_tokens by n (transform mode only)", (v) => Number(v)).option("--retries-delta <n>", "add n to retries (transform mode only)", (v) => Number(v)).option("--call-mult <n>", "multiply call volume by n (traffic spike)", (v) => Number(v)).option("--budget-monthly <usd>", "fail if estimated candidate monthly cost exceeds this budget", (v) => Number(v)).option("--json", "print machine-readable JSON to stdout").action(async (opts) => {
|
|
2690
2791
|
const rt = loadRateTable();
|
|
2691
2792
|
const baselinePath = String(opts.baseline || opts.input);
|
|
2692
2793
|
const candidatePath = opts.candidate ? String(opts.candidate) : null;
|
|
@@ -2695,11 +2796,11 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
2695
2796
|
console.error("FAIL: diff mode requires both --baseline and --candidate");
|
|
2696
2797
|
process.exit(3);
|
|
2697
2798
|
}
|
|
2698
|
-
if (!
|
|
2799
|
+
if (!import_fs16.default.existsSync(baselinePath)) {
|
|
2699
2800
|
console.error(`FAIL: baseline not found: ${baselinePath}`);
|
|
2700
2801
|
process.exit(3);
|
|
2701
2802
|
}
|
|
2702
|
-
if (candidatePath && !
|
|
2803
|
+
if (candidatePath && !import_fs16.default.existsSync(candidatePath)) {
|
|
2703
2804
|
console.error(`FAIL: candidate not found: ${candidatePath}`);
|
|
2704
2805
|
process.exit(3);
|
|
2705
2806
|
}
|
|
@@ -2719,23 +2820,40 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
2719
2820
|
budgetMonthlyUsd: opts.budgetMonthly
|
|
2720
2821
|
}
|
|
2721
2822
|
});
|
|
2722
|
-
|
|
2823
|
+
if (opts.json) {
|
|
2824
|
+
const payload = {
|
|
2825
|
+
exitCode: r.exitCode,
|
|
2826
|
+
message: r.message,
|
|
2827
|
+
mode: candidateEvents ? "diff" : "transform",
|
|
2828
|
+
baseline: baselinePath,
|
|
2829
|
+
candidate: candidatePath,
|
|
2830
|
+
artifacts: {
|
|
2831
|
+
outDir: import_path17.default.resolve(DEFAULT_OUTPUT_DIR),
|
|
2832
|
+
guard_last_txt: import_path17.default.join(import_path17.default.resolve(DEFAULT_OUTPUT_DIR), "guard-last.txt"),
|
|
2833
|
+
guard_last_json: import_path17.default.join(import_path17.default.resolve(DEFAULT_OUTPUT_DIR), "guard-last.json"),
|
|
2834
|
+
guard_history_jsonl: import_path17.default.join(import_path17.default.resolve(DEFAULT_OUTPUT_DIR), "guard-history.jsonl")
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2838
|
+
} else {
|
|
2839
|
+
console.log(r.message);
|
|
2840
|
+
}
|
|
2723
2841
|
try {
|
|
2724
|
-
const outDir =
|
|
2725
|
-
|
|
2842
|
+
const outDir = import_path17.default.resolve(DEFAULT_OUTPUT_DIR);
|
|
2843
|
+
import_fs16.default.mkdirSync(outDir, { recursive: true });
|
|
2726
2844
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2727
|
-
|
|
2728
|
-
|
|
2845
|
+
import_fs16.default.writeFileSync(import_path17.default.join(outDir, "guard-last.txt"), r.message);
|
|
2846
|
+
import_fs16.default.writeFileSync(import_path17.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
|
|
2729
2847
|
const histLine = JSON.stringify({ ts, exitCode: r.exitCode, mode: candidateEvents ? "diff" : "transform", baseline: baselinePath, candidate: candidatePath }) + "\n";
|
|
2730
|
-
|
|
2848
|
+
import_fs16.default.appendFileSync(import_path17.default.join(outDir, "guard-history.jsonl"), histLine);
|
|
2731
2849
|
} catch {
|
|
2732
2850
|
}
|
|
2733
2851
|
process.exit(r.exitCode);
|
|
2734
2852
|
});
|
|
2735
2853
|
program.command("dashboard").description("Local dashboard (localhost only): view usage + scan + guard (auto-collects OpenClaw usage)").option("--port <n>", "port (default: 3010)", (v) => Number(v), 3010).option("--dir <path>", "base directory (default: ~/.aiopt). Use this to pin a consistent data source.").option("--auto", "auto-detect by searching parents (and one-level children) for aiopt-output (use only if you really want project-local)").action(async (opts) => {
|
|
2736
2854
|
const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
2737
|
-
const
|
|
2738
|
-
const defaultBase = require("path").join(
|
|
2855
|
+
const os4 = await import("os");
|
|
2856
|
+
const defaultBase = require("path").join(os4.homedir(), ".aiopt");
|
|
2739
2857
|
const base = opts.dir ? String(opts.dir) : defaultBase;
|
|
2740
2858
|
if (opts.auto && !opts.dir) {
|
|
2741
2859
|
const { findAioptOutputDir: findAioptOutputDir2 } = await Promise.resolve().then(() => (init_find_output(), find_output_exports));
|
|
@@ -2757,14 +2875,14 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
|
|
|
2757
2875
|
console.log("--- guard ---");
|
|
2758
2876
|
console.log(r.guard.message);
|
|
2759
2877
|
try {
|
|
2760
|
-
const
|
|
2761
|
-
const
|
|
2762
|
-
|
|
2878
|
+
const fs17 = await import("fs");
|
|
2879
|
+
const path18 = await import("path");
|
|
2880
|
+
fs17.mkdirSync(r.outDir, { recursive: true });
|
|
2763
2881
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2764
|
-
|
|
2765
|
-
|
|
2882
|
+
fs17.writeFileSync(path18.join(r.outDir, "guard-last.txt"), r.guard.message);
|
|
2883
|
+
fs17.writeFileSync(path18.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
|
|
2766
2884
|
const histLine = JSON.stringify({ ts, exitCode: r.guard.exitCode, mode: "quickstart", baseline: r.usagePath, candidate: null }) + "\n";
|
|
2767
|
-
|
|
2885
|
+
fs17.appendFileSync(path18.join(r.outDir, "guard-history.jsonl"), histLine);
|
|
2768
2886
|
} catch {
|
|
2769
2887
|
}
|
|
2770
2888
|
console.log("--- next ---");
|