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 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.7",
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
- import_fs5.default.mkdirSync(p, { recursive: true });
700
+ import_fs6.default.mkdirSync(p, { recursive: true });
701
701
  }
702
702
  function writeFile(filePath, content, force) {
703
- if (!force && import_fs5.default.existsSync(filePath)) return { wrote: false, reason: "exists" };
704
- ensureDir2(import_path6.default.dirname(filePath));
705
- import_fs5.default.writeFileSync(filePath, content);
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 = import_path6.default.join(cwd, "aiopt");
711
- const policiesDir = import_path6.default.join(aioptDir, "policies");
712
- const outDir = import_path6.default.join(cwd, "aiopt-output");
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(import_path6.default.join(aioptDir, "README.md"), readme, force);
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(import_path6.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
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(import_path6.default.join(policiesDir, "routing.json"), JSON.stringify(routing, null, 2) + "\n", force);
810
- const p2 = writeFile(import_path6.default.join(policiesDir, "retry.json"), JSON.stringify(retry, null, 2) + "\n", force);
811
- const p3 = writeFile(import_path6.default.join(policiesDir, "output.json"), JSON.stringify(output, null, 2) + "\n", force);
812
- const p4 = writeFile(import_path6.default.join(policiesDir, "context.json"), JSON.stringify(context, null, 2) + "\n", force);
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 = import_path6.default.join(aioptDir, "aiopt-wrapper.js");
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 = import_path6.default.join(outDir, "usage.jsonl");
972
- if (force || !import_fs5.default.existsSync(usagePath)) {
973
- import_fs5.default.writeFileSync(usagePath, "");
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
- import_fs5.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
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 import_fs5, import_path6;
1000
+ var import_fs6, import_path7;
1001
1001
  var init_install = __esm({
1002
1002
  "src/install.ts"() {
1003
1003
  "use strict";
1004
- import_fs5 = __toESM(require("fs"));
1005
- import_path6 = __toESM(require("path"));
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
- import_fs6.default.mkdirSync(dir, { recursive: true });
1017
- const p = import_path7.default.join(dir, `.aiopt-write-test-${Date.now()}`);
1018
- import_fs6.default.writeFileSync(p, "ok");
1019
- import_fs6.default.unlinkSync(p);
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 = import_fs6.default.readFileSync(filePath, "utf8");
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 = import_path7.default.join(cwd, "aiopt");
1036
- const policiesDir = import_path7.default.join(aioptDir, "policies");
1037
- const outDir = import_path7.default.join(cwd, "aiopt-output");
1038
- const usagePath = import_path7.default.join(outDir, "usage.jsonl");
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: import_fs6.default.existsSync(aioptDir) });
1041
- checks.push({ name: "aiopt/policies exists", ok: import_fs6.default.existsSync(policiesDir) });
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: import_fs6.default.existsSync(usagePath), detail: usagePath });
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 import_fs6, import_path7;
1081
+ var import_fs7, import_path8;
1082
1082
  var init_doctor = __esm({
1083
1083
  "src/doctor.ts"() {
1084
1084
  "use strict";
1085
- import_fs6 = __toESM(require("fs"));
1086
- import_path7 = __toESM(require("path"));
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 import_path8.default.join(cwd, "aiopt", "license.json");
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
- import_fs7.default.mkdirSync(import_path8.default.dirname(p), { recursive: true });
1151
- import_fs7.default.writeFileSync(p, JSON.stringify(out, null, 2));
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(import_fs7.default.readFileSync(p, "utf8"));
1154
+ return JSON.parse(import_fs8.default.readFileSync(p, "utf8"));
1155
1155
  }
1156
- var import_crypto, import_fs7, import_path8, DEFAULT_PUBLIC_KEY_PEM;
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
- import_fs7 = __toESM(require("fs"));
1162
- import_path8 = __toESM(require("path"));
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 = import_path9.default.join(outDir, "aiopt.sarif");
1193
- if (!import_fs8.default.existsSync(sarifPath)) {
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(import_fs8.default.readFileSync(sarifPath, "utf8"));
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 = import_path9.default.isAbsolute(file) ? file : import_path9.default.resolve(file);
1215
- file = import_path9.default.relative(cwd, abs) || 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: ${import_path9.default.join(outDir, "report.md")} | ${import_path9.default.join(outDir, "aiopt.sarif")}`);
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 import_fs8, import_path9;
1237
+ var import_fs9, import_path10;
1238
1238
  var init_gate = __esm({
1239
1239
  "src/gate.ts"() {
1240
1240
  "use strict";
1241
- import_fs8 = __toESM(require("fs"));
1242
- import_path9 = __toESM(require("path"));
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 = import_path10.default.extname(p).toLowerCase();
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 = import_fs9.default.readdirSync(root, { withFileTypes: true });
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 = import_path10.default.join(root, it.name);
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 = import_path10.default.basename(original);
1329
+ const base = import_path11.default.basename(original);
1330
1330
  const rand = Math.random().toString(16).slice(2);
1331
- return import_path10.default.join(import_os.default.tmpdir(), `aiopt-fix-${base}-${rand}`);
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 = import_fs9.default.statSync(file);
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 = import_fs9.default.readFileSync(file, "utf8");
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
- import_fs9.default.writeFileSync(tmp, next);
1393
- const rel = import_path10.default.relative(cwd, file).replace(/\\/g, "/");
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
- import_fs9.default.unlinkSync(tmp);
1401
+ import_fs10.default.unlinkSync(tmp);
1402
1402
  } catch {
1403
1403
  }
1404
1404
  if (patches.join("\n").length > 5e5) break;
1405
1405
  }
1406
- import_fs9.default.mkdirSync(opts.outDir, { recursive: true });
1407
- const patchPath = import_path10.default.join(opts.outDir, "aiopt.patch");
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
- import_fs9.default.writeFileSync(patchPath, header + patches.join("\n"));
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 import_fs9, import_path10, import_os, import_child_process, DEFAULT_EXCLUDES2;
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
- import_fs9 = __toESM(require("fs"));
1438
- import_path10 = __toESM(require("path"));
1439
- import_os = __toESM(require("os"));
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 import_fs10.default.existsSync(p);
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 = import_fs10.default.readFileSync(p, "utf8");
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 import_fs10.default.readdirSync(dir)) {
1696
- const full = import_path11.default.join(dir, name);
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 = import_fs10.default.statSync(full);
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 = import_os2.default.homedir();
1711
- const root = import_path11.default.join(home, ".openclaw", "agents");
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 = import_fs10.default.readdirSync(root);
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 = import_path11.default.join(root, a, "sessions");
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
- import_fs10.default.mkdirSync(import_path11.default.dirname(outPath), { recursive: true });
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
- import_fs10.default.writeFileSync(outPath, lines);
1795
+ import_fs11.default.writeFileSync(outPath, lines);
1796
1796
  return { outPath, sources, eventsWritten: uniq.length };
1797
1797
  }
1798
- var import_fs10, import_path11, import_os2;
1798
+ var import_fs11, import_path12, import_os3;
1799
1799
  var init_collect = __esm({
1800
1800
  "src/collect.ts"() {
1801
1801
  "use strict";
1802
- import_fs10 = __toESM(require("fs"));
1803
- import_path11 = __toESM(require("path"));
1804
- import_os2 = __toESM(require("os"));
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 = import_path12.default.join(cwd, "aiopt-output");
1817
- const file = (name) => import_path12.default.join(outDir, 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 (import_fs11.default.existsSync(usagePath)) return;
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 = import_path12.default.join(__dirname, "..", "rates", "rate_table.json");
1835
- return JSON.parse(import_fs11.default.readFileSync(p, "utf8"));
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 (!import_fs11.default.existsSync(usagePath)) return [];
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 = !import_fs11.default.existsSync(file("report.json")) || !import_fs11.default.existsSync(file("report.md"));
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 = !import_fs11.default.existsSync(file("guard-last.txt")) || !import_fs11.default.existsSync(file("guard-last.json"));
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
- import_fs11.default.writeFileSync(file("guard-last.txt"), r.message);
1860
- import_fs11.default.writeFileSync(file("guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
1861
- import_fs11.default.appendFileSync(file("guard-history.jsonl"), JSON.stringify({ ts, exitCode: r.exitCode, mode: "dashboard", baseline: usagePath, candidate: null }) + "\n");
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 (!import_fs11.default.existsSync(p)) return null;
1875
- return import_fs11.default.readFileSync(p, "utf8");
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 = import_fs11.default.statSync(p);
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) => !import_fs11.default.existsSync(file(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 = import_fs11.default.readFileSync(usagePath, "utf8");
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, import_fs11, import_path12;
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
- import_fs11 = __toESM(require("fs"));
2379
- import_path12 = __toESM(require("path"));
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 = import_path13.default.resolve(startCwd);
2395
+ let cur = import_path14.default.resolve(startCwd);
2394
2396
  while (true) {
2395
- const outDir = import_path13.default.join(cur, "aiopt-output");
2396
- if (import_fs12.default.existsSync(outDir)) {
2397
+ const outDir = import_path14.default.join(cur, "aiopt-output");
2398
+ if (import_fs13.default.existsSync(outDir)) {
2397
2399
  try {
2398
- if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
2400
+ if (import_fs13.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
2399
2401
  } catch {
2400
2402
  }
2401
2403
  }
2402
- const parent = import_path13.default.dirname(cur);
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 = import_path13.default.resolve(startCwd);
2408
- const children = import_fs12.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path13.default.join(base, d.name));
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 = import_path13.default.join(child, "aiopt-output");
2411
- if (import_fs12.default.existsSync(outDir)) {
2412
+ const outDir = import_path14.default.join(child, "aiopt-output");
2413
+ if (import_fs13.default.existsSync(outDir)) {
2412
2414
  try {
2413
- if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
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: import_path13.default.resolve(startCwd), outDir: import_path13.default.join(import_path13.default.resolve(startCwd), "aiopt-output") };
2422
+ return { cwd: import_path14.default.resolve(startCwd), outDir: import_path14.default.join(import_path14.default.resolve(startCwd), "aiopt-output") };
2421
2423
  }
2422
- var import_fs12, import_path13;
2424
+ var import_fs13, import_path14;
2423
2425
  var init_find_output = __esm({
2424
2426
  "src/find-output.ts"() {
2425
2427
  "use strict";
2426
- import_fs12 = __toESM(require("fs"));
2427
- import_path13 = __toESM(require("path"));
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 = import_path14.default.join(__dirname, "..", "rates", "rate_table.json");
2434
- return JSON.parse(import_fs13.default.readFileSync(p, "utf8"));
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 import_fs13, import_path14;
2438
+ var import_fs14, import_path15;
2437
2439
  var init_rates_util = __esm({
2438
2440
  "src/rates-util.ts"() {
2439
2441
  "use strict";
2440
- import_fs13 = __toESM(require("fs"));
2441
- import_path14 = __toESM(require("path"));
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
- import_fs14.default.mkdirSync(outDir, { recursive: true });
2453
- const p = import_path15.default.join(outDir, "usage.jsonl");
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
- import_fs14.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
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 = import_path15.default.join(cwd, "aiopt-output");
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
- import_fs14.default.writeFileSync(import_path15.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
2481
- import_fs14.default.writeFileSync(import_path15.default.join(outDir, "report.json"), JSON.stringify({
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
- import_fs14.default.writeFileSync(import_path15.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
2502
- import_fs14.default.writeFileSync(import_path15.default.join(outDir, "report.md"), "# AIOpt quickstart demo\n\nThis is a demo report generated by `aiopt quickstart --demo`.\n");
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 import_fs14, import_path15;
2515
+ var import_fs15, import_path16;
2514
2516
  var init_quickstart = __esm({
2515
2517
  "src/quickstart.ts"() {
2516
2518
  "use strict";
2517
- import_fs14 = __toESM(require("fs"));
2518
- import_path15 = __toESM(require("path"));
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 import_fs15 = __toESM(require("fs"));
2527
- var import_path16 = __toESM(require("path"));
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 = import_path16.default.join(__dirname, "..", "rates", "rate_table.json");
2536
- return JSON.parse(import_fs15.default.readFileSync(p, "utf8"));
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 = import_path16.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
2543
- const dst = import_path16.default.join("./aiopt-input", "usage.jsonl");
2544
- if (!import_fs15.default.existsSync(dst)) {
2545
- import_fs15.default.copyFileSync(sampleSrc, dst);
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 (!import_fs15.default.existsSync(inputPath)) {
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: ${import_path16.default.join(outDir, "report.md")}`);
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
- import_fs15.default.writeFileSync(import_path16.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
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 (!import_fs15.default.existsSync(p)) {
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 (!import_fs15.default.existsSync(p)) {
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 inputPath = String(opts.input);
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
- if (!import_fs15.default.existsSync(inputPath)) {
2655
- console.error(`FAIL: input not found: ${inputPath}`);
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(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
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(outDir, process.cwd());
2665
- const out = formatGateStdout2(r, outDir);
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 (!import_fs15.default.existsSync(baselinePath)) {
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 && !import_fs15.default.existsSync(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
- console.log(r.message);
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 = import_path16.default.resolve(DEFAULT_OUTPUT_DIR);
2725
- import_fs15.default.mkdirSync(outDir, { recursive: true });
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
- import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.txt"), r.message);
2728
- import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
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
- import_fs15.default.appendFileSync(import_path16.default.join(outDir, "guard-history.jsonl"), histLine);
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 os3 = await import("os");
2738
- const defaultBase = require("path").join(os3.homedir(), ".aiopt");
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 fs16 = await import("fs");
2761
- const path17 = await import("path");
2762
- fs16.mkdirSync(r.outDir, { recursive: true });
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
- fs16.writeFileSync(path17.join(r.outDir, "guard-last.txt"), r.guard.message);
2765
- fs16.writeFileSync(path17.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
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
- fs16.appendFileSync(path17.join(r.outDir, "guard-history.jsonl"), histLine);
2885
+ fs17.appendFileSync(path18.join(r.outDir, "guard-history.jsonl"), histLine);
2768
2886
  } catch {
2769
2887
  }
2770
2888
  console.log("--- next ---");