aiopt 0.3.8 → 0.3.10

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
@@ -330,6 +330,11 @@ var init_code_scan = __esm({
330
330
 
331
331
  // src/sarif.ts
332
332
  function toUri(p) {
333
+ try {
334
+ const rel = import_path4.default.relative(process.cwd(), import_path4.default.resolve(p)).replace(/\\/g, "/");
335
+ if (rel && !rel.startsWith("..")) return rel;
336
+ } catch {
337
+ }
333
338
  const abs = import_path4.default.resolve(p);
334
339
  const u = abs.replace(/\\/g, "/");
335
340
  return u.match(/^[A-Za-z]:\//) ? `file:///${u}` : `file://${u}`;
@@ -656,7 +661,7 @@ var require_package = __commonJS({
656
661
  "package.json"(exports2, module2) {
657
662
  module2.exports = {
658
663
  name: "aiopt",
659
- version: "0.3.8",
664
+ version: "0.3.10",
660
665
  description: "Pre-deploy LLM cost accident guardrail (serverless local CLI)",
661
666
  bin: {
662
667
  aiopt: "dist/cli.js"
@@ -697,19 +702,19 @@ __export(install_exports, {
697
702
  runInstall: () => runInstall
698
703
  });
699
704
  function ensureDir2(p) {
700
- import_fs5.default.mkdirSync(p, { recursive: true });
705
+ import_fs6.default.mkdirSync(p, { recursive: true });
701
706
  }
702
707
  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);
708
+ if (!force && import_fs6.default.existsSync(filePath)) return { wrote: false, reason: "exists" };
709
+ ensureDir2(import_path7.default.dirname(filePath));
710
+ import_fs6.default.writeFileSync(filePath, content);
706
711
  return { wrote: true };
707
712
  }
708
713
  function runInstall(cwd, opts) {
709
714
  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");
715
+ const aioptDir = import_path7.default.join(cwd, "aiopt");
716
+ const policiesDir = import_path7.default.join(aioptDir, "policies");
717
+ const outDir = import_path7.default.join(cwd, "aiopt-output");
713
718
  ensureDir2(aioptDir);
714
719
  ensureDir2(policiesDir);
715
720
  ensureDir2(outDir);
@@ -765,7 +770,7 @@ return {
765
770
  };
766
771
  \`\`\`
767
772
  `;
768
- const r1 = writeFile(import_path6.default.join(aioptDir, "README.md"), readme, force);
773
+ const r1 = writeFile(import_path7.default.join(aioptDir, "README.md"), readme, force);
769
774
  created.push({ path: "aiopt/README.md", status: r1.wrote ? "created" : "skipped" });
770
775
  const config = {
771
776
  version: 1,
@@ -775,7 +780,7 @@ return {
775
780
  policies_dir: "./aiopt/policies",
776
781
  rate_table: { path: "./rates/rate_table.json" }
777
782
  };
778
- const r2 = writeFile(import_path6.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
783
+ const r2 = writeFile(import_path7.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
779
784
  created.push({ path: "aiopt/aiopt.config.json", status: r2.wrote ? "created" : "skipped" });
780
785
  const routing = {
781
786
  version: 1,
@@ -806,15 +811,15 @@ return {
806
811
  reduce_top_percentile: 0.2,
807
812
  assumed_reduction_ratio: 0.25
808
813
  };
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);
814
+ const p1 = writeFile(import_path7.default.join(policiesDir, "routing.json"), JSON.stringify(routing, null, 2) + "\n", force);
815
+ const p2 = writeFile(import_path7.default.join(policiesDir, "retry.json"), JSON.stringify(retry, null, 2) + "\n", force);
816
+ const p3 = writeFile(import_path7.default.join(policiesDir, "output.json"), JSON.stringify(output, null, 2) + "\n", force);
817
+ const p4 = writeFile(import_path7.default.join(policiesDir, "context.json"), JSON.stringify(context, null, 2) + "\n", force);
813
818
  created.push({ path: "aiopt/policies/routing.json", status: p1.wrote ? "created" : "skipped" });
814
819
  created.push({ path: "aiopt/policies/retry.json", status: p2.wrote ? "created" : "skipped" });
815
820
  created.push({ path: "aiopt/policies/output.json", status: p3.wrote ? "created" : "skipped" });
816
821
  created.push({ path: "aiopt/policies/context.json", status: p4.wrote ? "created" : "skipped" });
817
- const wrapperPath = import_path6.default.join(aioptDir, "aiopt-wrapper.js");
822
+ const wrapperPath = import_path7.default.join(aioptDir, "aiopt-wrapper.js");
818
823
  const wrapper = `// AIOpt Wrapper (guardrails) \u2014 local-only (CommonJS)
819
824
 
820
825
  const fs = require('fs');
@@ -968,9 +973,9 @@ module.exports = { guardedCall };
968
973
  ;
969
974
  const w = writeFile(wrapperPath, wrapper, force);
970
975
  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, "");
976
+ const usagePath = import_path7.default.join(outDir, "usage.jsonl");
977
+ if (force || !import_fs6.default.existsSync(usagePath)) {
978
+ import_fs6.default.writeFileSync(usagePath, "");
974
979
  created.push({ path: "aiopt-output/usage.jsonl", status: "created" });
975
980
  if (opts.seedSample) {
976
981
  const sample = {
@@ -990,19 +995,19 @@ module.exports = { guardedCall };
990
995
  latency_ms: 1,
991
996
  meta: { feature_tag: "demo", routed_from: null, policy_hits: ["install-sample"] }
992
997
  };
993
- import_fs5.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
998
+ import_fs6.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
994
999
  }
995
1000
  } else {
996
1001
  created.push({ path: "aiopt-output/usage.jsonl", status: "skipped" });
997
1002
  }
998
1003
  return { created };
999
1004
  }
1000
- var import_fs5, import_path6;
1005
+ var import_fs6, import_path7;
1001
1006
  var init_install = __esm({
1002
1007
  "src/install.ts"() {
1003
1008
  "use strict";
1004
- import_fs5 = __toESM(require("fs"));
1005
- import_path6 = __toESM(require("path"));
1009
+ import_fs6 = __toESM(require("fs"));
1010
+ import_path7 = __toESM(require("path"));
1006
1011
  }
1007
1012
  });
1008
1013
 
@@ -1013,10 +1018,10 @@ __export(doctor_exports, {
1013
1018
  });
1014
1019
  function canWrite(dir) {
1015
1020
  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);
1021
+ import_fs7.default.mkdirSync(dir, { recursive: true });
1022
+ const p = import_path8.default.join(dir, `.aiopt-write-test-${Date.now()}`);
1023
+ import_fs7.default.writeFileSync(p, "ok");
1024
+ import_fs7.default.unlinkSync(p);
1020
1025
  return true;
1021
1026
  } catch {
1022
1027
  return false;
@@ -1024,7 +1029,7 @@ function canWrite(dir) {
1024
1029
  }
1025
1030
  function tailLines(filePath, n) {
1026
1031
  try {
1027
- const raw = import_fs6.default.readFileSync(filePath, "utf8");
1032
+ const raw = import_fs7.default.readFileSync(filePath, "utf8");
1028
1033
  const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
1029
1034
  return lines.slice(Math.max(0, lines.length - n));
1030
1035
  } catch {
@@ -1032,15 +1037,15 @@ function tailLines(filePath, n) {
1032
1037
  }
1033
1038
  }
1034
1039
  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");
1040
+ const aioptDir = import_path8.default.join(cwd, "aiopt");
1041
+ const policiesDir = import_path8.default.join(aioptDir, "policies");
1042
+ const outDir = import_path8.default.join(cwd, "aiopt-output");
1043
+ const usagePath = import_path8.default.join(outDir, "usage.jsonl");
1039
1044
  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) });
1045
+ checks.push({ name: "aiopt/ exists", ok: import_fs7.default.existsSync(aioptDir) });
1046
+ checks.push({ name: "aiopt/policies exists", ok: import_fs7.default.existsSync(policiesDir) });
1042
1047
  checks.push({ name: "aiopt-output/ writable", ok: canWrite(outDir) });
1043
- checks.push({ name: "usage.jsonl exists", ok: import_fs6.default.existsSync(usagePath), detail: usagePath });
1048
+ checks.push({ name: "usage.jsonl exists", ok: import_fs7.default.existsSync(usagePath), detail: usagePath });
1044
1049
  const last5raw = tailLines(usagePath, 5);
1045
1050
  const last5 = last5raw.length === 0 ? [{ status: "(empty usage.jsonl)" }] : last5raw.map((l) => {
1046
1051
  try {
@@ -1078,12 +1083,12 @@ function runDoctor(cwd) {
1078
1083
  const ok = checks.every((c) => c.ok);
1079
1084
  return { ok, checks, last5 };
1080
1085
  }
1081
- var import_fs6, import_path7;
1086
+ var import_fs7, import_path8;
1082
1087
  var init_doctor = __esm({
1083
1088
  "src/doctor.ts"() {
1084
1089
  "use strict";
1085
- import_fs6 = __toESM(require("fs"));
1086
- import_path7 = __toESM(require("path"));
1090
+ import_fs7 = __toESM(require("fs"));
1091
+ import_path8 = __toESM(require("path"));
1087
1092
  }
1088
1093
  });
1089
1094
 
@@ -1138,7 +1143,7 @@ function verifyLicenseKey(key, publicKeyPem) {
1138
1143
  }
1139
1144
  }
1140
1145
  function defaultLicensePath(cwd) {
1141
- return import_path8.default.join(cwd, "aiopt", "license.json");
1146
+ return import_path9.default.join(cwd, "aiopt", "license.json");
1142
1147
  }
1143
1148
  function writeLicenseFile(p, key, payload, verified) {
1144
1149
  const out = {
@@ -1147,19 +1152,19 @@ function writeLicenseFile(p, key, payload, verified) {
1147
1152
  verified,
1148
1153
  verified_at: (/* @__PURE__ */ new Date()).toISOString()
1149
1154
  };
1150
- import_fs7.default.mkdirSync(import_path8.default.dirname(p), { recursive: true });
1151
- import_fs7.default.writeFileSync(p, JSON.stringify(out, null, 2));
1155
+ import_fs8.default.mkdirSync(import_path9.default.dirname(p), { recursive: true });
1156
+ import_fs8.default.writeFileSync(p, JSON.stringify(out, null, 2));
1152
1157
  }
1153
1158
  function readLicenseFile(p) {
1154
- return JSON.parse(import_fs7.default.readFileSync(p, "utf8"));
1159
+ return JSON.parse(import_fs8.default.readFileSync(p, "utf8"));
1155
1160
  }
1156
- var import_crypto, import_fs7, import_path8, DEFAULT_PUBLIC_KEY_PEM;
1161
+ var import_crypto, import_fs8, import_path9, DEFAULT_PUBLIC_KEY_PEM;
1157
1162
  var init_license = __esm({
1158
1163
  "src/license.ts"() {
1159
1164
  "use strict";
1160
1165
  import_crypto = __toESM(require("crypto"));
1161
- import_fs7 = __toESM(require("fs"));
1162
- import_path8 = __toESM(require("path"));
1166
+ import_fs8 = __toESM(require("fs"));
1167
+ import_path9 = __toESM(require("path"));
1163
1168
  DEFAULT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
1164
1169
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz1LLE/pXIx5TloDa0LAf
1165
1170
  jg9NSIW6STWhsAFP2ZzXgpWoQ3cCmW6xcB/4QNEmPpGlfMWhyRfkxsdKuhnjUMTg
@@ -1189,13 +1194,13 @@ function parseFileUri(uri) {
1189
1194
  }
1190
1195
  }
1191
1196
  function runGate(outDir, cwd) {
1192
- const sarifPath = import_path9.default.join(outDir, "aiopt.sarif");
1193
- if (!import_fs8.default.existsSync(sarifPath)) {
1197
+ const sarifPath = import_path10.default.join(outDir, "aiopt.sarif");
1198
+ if (!import_fs9.default.existsSync(sarifPath)) {
1194
1199
  return { violations: 0, top3: [] };
1195
1200
  }
1196
1201
  let sarif;
1197
1202
  try {
1198
- sarif = JSON.parse(import_fs8.default.readFileSync(sarifPath, "utf8"));
1203
+ sarif = JSON.parse(import_fs9.default.readFileSync(sarifPath, "utf8"));
1199
1204
  } catch {
1200
1205
  return { violations: 0, top3: [] };
1201
1206
  }
@@ -1211,8 +1216,8 @@ function runGate(outDir, cwd) {
1211
1216
  const line = Number(loc?.region?.startLine || 1);
1212
1217
  let file = parseFileUri(uri);
1213
1218
  try {
1214
- const abs = import_path9.default.isAbsolute(file) ? file : import_path9.default.resolve(file);
1215
- file = import_path9.default.relative(cwd, abs) || file;
1219
+ const abs = import_path10.default.isAbsolute(file) ? file : import_path10.default.resolve(file);
1220
+ file = import_path10.default.relative(cwd, abs) || file;
1216
1221
  } catch {
1217
1222
  }
1218
1223
  locs.push({ file, line: Number.isFinite(line) && line > 0 ? line : 1 });
@@ -1224,7 +1229,7 @@ function formatGateStdout(r, outDir) {
1224
1229
  const lines = [];
1225
1230
  if (r.violations <= 0) {
1226
1231
  lines.push("OK: no policy violations");
1227
- lines.push(`Artifacts: ${import_path9.default.join(outDir, "report.md")} | ${import_path9.default.join(outDir, "aiopt.sarif")}`);
1232
+ lines.push(`Artifacts: ${import_path10.default.join(outDir, "report.md")} | ${import_path10.default.join(outDir, "aiopt.sarif")}`);
1228
1233
  return { text: lines.join("\n"), exitCode: 0 };
1229
1234
  }
1230
1235
  lines.push(`FAIL: policy violations=${r.violations}`);
@@ -1234,12 +1239,12 @@ function formatGateStdout(r, outDir) {
1234
1239
  const text = lines.slice(0, 10).join("\n");
1235
1240
  return { text, exitCode: 1 };
1236
1241
  }
1237
- var import_fs8, import_path9;
1242
+ var import_fs9, import_path10;
1238
1243
  var init_gate = __esm({
1239
1244
  "src/gate.ts"() {
1240
1245
  "use strict";
1241
- import_fs8 = __toESM(require("fs"));
1242
- import_path9 = __toESM(require("path"));
1246
+ import_fs9 = __toESM(require("fs"));
1247
+ import_path10 = __toESM(require("path"));
1243
1248
  }
1244
1249
  });
1245
1250
 
@@ -1249,15 +1254,15 @@ __export(fix_exports, {
1249
1254
  runFix: () => runFix
1250
1255
  });
1251
1256
  function isTextLike2(p) {
1252
- const ext = import_path10.default.extname(p).toLowerCase();
1257
+ const ext = import_path11.default.extname(p).toLowerCase();
1253
1258
  return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext);
1254
1259
  }
1255
1260
  function walk2(root, out) {
1256
- const items = import_fs9.default.readdirSync(root, { withFileTypes: true });
1261
+ const items = import_fs10.default.readdirSync(root, { withFileTypes: true });
1257
1262
  for (const it of items) {
1258
1263
  if (it.name.startsWith(".")) continue;
1259
1264
  if (DEFAULT_EXCLUDES2.has(it.name)) continue;
1260
- const full = import_path10.default.join(root, it.name);
1265
+ const full = import_path11.default.join(root, it.name);
1261
1266
  if (it.isDirectory()) walk2(full, out);
1262
1267
  else out.push(full);
1263
1268
  }
@@ -1326,9 +1331,9 @@ function applyModelRouting(content) {
1326
1331
  return { changed, next };
1327
1332
  }
1328
1333
  function tmpFilePath(original) {
1329
- const base = import_path10.default.basename(original);
1334
+ const base = import_path11.default.basename(original);
1330
1335
  const rand = Math.random().toString(16).slice(2);
1331
- return import_path10.default.join(import_os.default.tmpdir(), `aiopt-fix-${base}-${rand}`);
1336
+ return import_path11.default.join(import_os2.default.tmpdir(), `aiopt-fix-${base}-${rand}`);
1332
1337
  }
1333
1338
  function diffNoIndex(oldPath, newPath) {
1334
1339
  try {
@@ -1372,7 +1377,7 @@ function runFix(cwd, opts) {
1372
1377
  if (!isTextLike2(file)) continue;
1373
1378
  let st;
1374
1379
  try {
1375
- st = import_fs9.default.statSync(file);
1380
+ st = import_fs10.default.statSync(file);
1376
1381
  } catch {
1377
1382
  continue;
1378
1383
  }
@@ -1380,7 +1385,7 @@ function runFix(cwd, opts) {
1380
1385
  if (st.size > 1024 * 1024) continue;
1381
1386
  let content = "";
1382
1387
  try {
1383
- content = import_fs9.default.readFileSync(file, "utf8");
1388
+ content = import_fs10.default.readFileSync(file, "utf8");
1384
1389
  } catch {
1385
1390
  continue;
1386
1391
  }
@@ -1389,8 +1394,8 @@ function runFix(cwd, opts) {
1389
1394
  const next = r2.next;
1390
1395
  if (next === content) continue;
1391
1396
  const tmp = tmpFilePath(file);
1392
- import_fs9.default.writeFileSync(tmp, next);
1393
- const rel = import_path10.default.relative(cwd, file).replace(/\\/g, "/");
1397
+ import_fs10.default.writeFileSync(tmp, next);
1398
+ const rel = import_path11.default.relative(cwd, file).replace(/\\/g, "/");
1394
1399
  const d0 = diffNoIndex(file, tmp);
1395
1400
  const d = normalizePatchPaths(d0, rel);
1396
1401
  if (d && d.trim().length > 0) {
@@ -1398,20 +1403,20 @@ function runFix(cwd, opts) {
1398
1403
  changedFiles.push(rel);
1399
1404
  }
1400
1405
  try {
1401
- import_fs9.default.unlinkSync(tmp);
1406
+ import_fs10.default.unlinkSync(tmp);
1402
1407
  } catch {
1403
1408
  }
1404
1409
  if (patches.join("\n").length > 5e5) break;
1405
1410
  }
1406
- import_fs9.default.mkdirSync(opts.outDir, { recursive: true });
1407
- const patchPath = import_path10.default.join(opts.outDir, "aiopt.patch");
1411
+ import_fs10.default.mkdirSync(opts.outDir, { recursive: true });
1412
+ const patchPath = import_path11.default.join(opts.outDir, "aiopt.patch");
1408
1413
  const header = [
1409
1414
  "# AIOpt patch (generated)",
1410
1415
  "# - retry cap: reduce high retry/attempt counts to 3",
1411
1416
  "# - model routing: cheap default via AIOPT_MODEL env override",
1412
1417
  ""
1413
1418
  ].join("\n");
1414
- import_fs9.default.writeFileSync(patchPath, header + patches.join("\n"));
1419
+ import_fs10.default.writeFileSync(patchPath, header + patches.join("\n"));
1415
1420
  if (opts.apply) {
1416
1421
  try {
1417
1422
  const inside = (0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
@@ -1430,13 +1435,13 @@ function runFix(cwd, opts) {
1430
1435
  }
1431
1436
  return { ok: true, applied: false, patchPath, changedFiles };
1432
1437
  }
1433
- var import_fs9, import_path10, import_os, import_child_process, DEFAULT_EXCLUDES2;
1438
+ var import_fs10, import_path11, import_os2, import_child_process, DEFAULT_EXCLUDES2;
1434
1439
  var init_fix = __esm({
1435
1440
  "src/fix.ts"() {
1436
1441
  "use strict";
1437
- import_fs9 = __toESM(require("fs"));
1438
- import_path10 = __toESM(require("path"));
1439
- import_os = __toESM(require("os"));
1442
+ import_fs10 = __toESM(require("fs"));
1443
+ import_path11 = __toESM(require("path"));
1444
+ import_os2 = __toESM(require("os"));
1440
1445
  import_child_process = require("child_process");
1441
1446
  DEFAULT_EXCLUDES2 = /* @__PURE__ */ new Set([
1442
1447
  ".git",
@@ -1669,7 +1674,7 @@ var init_guard = __esm({
1669
1674
  // src/collect.ts
1670
1675
  function exists(p) {
1671
1676
  try {
1672
- return import_fs10.default.existsSync(p);
1677
+ return import_fs11.default.existsSync(p);
1673
1678
  } catch {
1674
1679
  return false;
1675
1680
  }
@@ -1677,7 +1682,7 @@ function exists(p) {
1677
1682
  function safeReadJsonl(p) {
1678
1683
  const out = [];
1679
1684
  try {
1680
- const txt = import_fs10.default.readFileSync(p, "utf8");
1685
+ const txt = import_fs11.default.readFileSync(p, "utf8");
1681
1686
  for (const line of txt.split(/\r?\n/)) {
1682
1687
  if (!line.trim()) continue;
1683
1688
  try {
@@ -1692,11 +1697,11 @@ function safeReadJsonl(p) {
1692
1697
  function listJsonlFiles(dir) {
1693
1698
  const out = [];
1694
1699
  try {
1695
- for (const name of import_fs10.default.readdirSync(dir)) {
1696
- const full = import_path11.default.join(dir, name);
1700
+ for (const name of import_fs11.default.readdirSync(dir)) {
1701
+ const full = import_path12.default.join(dir, name);
1697
1702
  let st;
1698
1703
  try {
1699
- st = import_fs10.default.statSync(full);
1704
+ st = import_fs11.default.statSync(full);
1700
1705
  } catch {
1701
1706
  continue;
1702
1707
  }
@@ -1707,18 +1712,18 @@ function listJsonlFiles(dir) {
1707
1712
  return out;
1708
1713
  }
1709
1714
  function findOpenClawSessionLogs() {
1710
- const home = import_os2.default.homedir();
1711
- const root = import_path11.default.join(home, ".openclaw", "agents");
1715
+ const home = import_os3.default.homedir();
1716
+ const root = import_path12.default.join(home, ".openclaw", "agents");
1712
1717
  if (!exists(root)) return [];
1713
1718
  const found = [];
1714
1719
  let agents = [];
1715
1720
  try {
1716
- agents = import_fs10.default.readdirSync(root);
1721
+ agents = import_fs11.default.readdirSync(root);
1717
1722
  } catch {
1718
1723
  agents = [];
1719
1724
  }
1720
1725
  for (const a of agents) {
1721
- const sessDir = import_path11.default.join(root, a, "sessions");
1726
+ const sessDir = import_path12.default.join(root, a, "sessions");
1722
1727
  if (!exists(sessDir)) continue;
1723
1728
  for (const f of listJsonlFiles(sessDir)) found.push(f);
1724
1729
  }
@@ -1790,18 +1795,18 @@ function collectToUsageJsonl(outPath) {
1790
1795
  uniq.push(e);
1791
1796
  }
1792
1797
  uniq.sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
1793
- import_fs10.default.mkdirSync(import_path11.default.dirname(outPath), { recursive: true });
1798
+ import_fs11.default.mkdirSync(import_path12.default.dirname(outPath), { recursive: true });
1794
1799
  const lines = uniq.map((e) => JSON.stringify(e)).join("\n") + (uniq.length ? "\n" : "");
1795
- import_fs10.default.writeFileSync(outPath, lines);
1800
+ import_fs11.default.writeFileSync(outPath, lines);
1796
1801
  return { outPath, sources, eventsWritten: uniq.length };
1797
1802
  }
1798
- var import_fs10, import_path11, import_os2;
1803
+ var import_fs11, import_path12, import_os3;
1799
1804
  var init_collect = __esm({
1800
1805
  "src/collect.ts"() {
1801
1806
  "use strict";
1802
- import_fs10 = __toESM(require("fs"));
1803
- import_path11 = __toESM(require("path"));
1804
- import_os2 = __toESM(require("os"));
1807
+ import_fs11 = __toESM(require("fs"));
1808
+ import_path12 = __toESM(require("path"));
1809
+ import_os3 = __toESM(require("os"));
1805
1810
  }
1806
1811
  });
1807
1812
 
@@ -1813,8 +1818,8 @@ __export(dashboard_exports, {
1813
1818
  async function startDashboard(cwd, opts) {
1814
1819
  const host = "127.0.0.1";
1815
1820
  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);
1821
+ const outDir = import_path13.default.join(cwd, "aiopt-output");
1822
+ const file = (name) => import_path13.default.join(outDir, name);
1818
1823
  let lastCollect = null;
1819
1824
  let lastCollectError = null;
1820
1825
  let lastAutoGen = null;
@@ -1822,7 +1827,7 @@ async function startDashboard(cwd, opts) {
1822
1827
  function ensureUsageFile() {
1823
1828
  try {
1824
1829
  const usagePath = file("usage.jsonl");
1825
- if (import_fs11.default.existsSync(usagePath)) return;
1830
+ if (import_fs12.default.existsSync(usagePath)) return;
1826
1831
  const r = collectToUsageJsonl(usagePath);
1827
1832
  lastCollect = { ts: (/* @__PURE__ */ new Date()).toISOString(), outPath: r.outPath, sources: r.sources, eventsWritten: r.eventsWritten };
1828
1833
  lastCollectError = null;
@@ -1831,11 +1836,11 @@ async function startDashboard(cwd, opts) {
1831
1836
  }
1832
1837
  }
1833
1838
  function loadRateTable2() {
1834
- const p = import_path12.default.join(__dirname, "..", "rates", "rate_table.json");
1835
- return JSON.parse(import_fs11.default.readFileSync(p, "utf8"));
1839
+ const p = import_path13.default.join(__dirname, "..", "rates", "rate_table.json");
1840
+ return JSON.parse(import_fs12.default.readFileSync(p, "utf8"));
1836
1841
  }
1837
1842
  function readEvents(usagePath) {
1838
- if (!import_fs11.default.existsSync(usagePath)) return [];
1843
+ if (!import_fs12.default.existsSync(usagePath)) return [];
1839
1844
  return isCsvPath(usagePath) ? readCsv(usagePath) : readJsonl(usagePath);
1840
1845
  }
1841
1846
  function ensureScanAndGuard() {
@@ -1843,7 +1848,7 @@ async function startDashboard(cwd, opts) {
1843
1848
  try {
1844
1849
  const usagePath = file("usage.jsonl");
1845
1850
  const rt = loadRateTable2();
1846
- const needScan = !import_fs11.default.existsSync(file("report.json")) || !import_fs11.default.existsSync(file("report.md"));
1851
+ const needScan = !import_fs12.default.existsSync(file("report.json")) || !import_fs12.default.existsSync(file("report.md"));
1847
1852
  if (needScan) {
1848
1853
  const events = readEvents(usagePath);
1849
1854
  const { analysis, savings, policy, meta } = analyze(rt, events);
@@ -1851,14 +1856,14 @@ async function startDashboard(cwd, opts) {
1851
1856
  writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd, cliVersion: "dashboard" });
1852
1857
  did.push("scan");
1853
1858
  }
1854
- const needGuard = !import_fs11.default.existsSync(file("guard-last.txt")) || !import_fs11.default.existsSync(file("guard-last.json"));
1859
+ const needGuard = !import_fs12.default.existsSync(file("guard-last.txt")) || !import_fs12.default.existsSync(file("guard-last.json"));
1855
1860
  if (needGuard) {
1856
1861
  const events = readEvents(usagePath);
1857
1862
  const r = runGuard(rt, { baselineEvents: events, candidate: {} });
1858
1863
  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");
1864
+ import_fs12.default.writeFileSync(file("guard-last.txt"), r.message);
1865
+ import_fs12.default.writeFileSync(file("guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
1866
+ import_fs12.default.appendFileSync(file("guard-history.jsonl"), JSON.stringify({ ts, exitCode: r.exitCode, mode: "dashboard", baseline: usagePath, candidate: null }) + "\n");
1862
1867
  did.push("guard");
1863
1868
  }
1864
1869
  if (did.length) lastAutoGen = { ts: (/* @__PURE__ */ new Date()).toISOString(), did };
@@ -1871,15 +1876,15 @@ async function startDashboard(cwd, opts) {
1871
1876
  ensureScanAndGuard();
1872
1877
  function readOrNull(p) {
1873
1878
  try {
1874
- if (!import_fs11.default.existsSync(p)) return null;
1875
- return import_fs11.default.readFileSync(p, "utf8");
1879
+ if (!import_fs12.default.existsSync(p)) return null;
1880
+ return import_fs12.default.readFileSync(p, "utf8");
1876
1881
  } catch {
1877
1882
  return null;
1878
1883
  }
1879
1884
  }
1880
1885
  function statOrNull(p) {
1881
1886
  try {
1882
- const st = import_fs11.default.statSync(p);
1887
+ const st = import_fs12.default.statSync(p);
1883
1888
  return { size: st.size, mtimeMs: st.mtimeMs };
1884
1889
  } catch {
1885
1890
  return null;
@@ -1958,6 +1963,8 @@ async function startDashboard(cwd, opts) {
1958
1963
  <span class="muted">\xB7</span>
1959
1964
  <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
1965
  <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>
1966
+ <span class="muted">\xB7</span>
1967
+ <a href="https://github.com/tkddlr0716-collab/aiopt/issues/new?template=dashboard_feedback.yml" target="_blank">Feedback</a>
1961
1968
  </div>
1962
1969
  </div>
1963
1970
 
@@ -2272,7 +2279,7 @@ load();
2272
2279
  ensureUsageFile();
2273
2280
  ensureScanAndGuard();
2274
2281
  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)));
2282
+ const missing = expected.filter((f) => !import_fs12.default.existsSync(file(f)));
2276
2283
  res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
2277
2284
  res.end(JSON.stringify({ baseDir: cwd, outDir, missing, collect: lastCollect, collectError: lastCollectError, autoGen: lastAutoGen, autoGenError: lastAutoGenError }, null, 2));
2278
2285
  return;
@@ -2305,7 +2312,7 @@ load();
2305
2312
  globalThis.__aioptDashCache = globalThis.__aioptDashCache || {};
2306
2313
  const cache = globalThis.__aioptDashCache;
2307
2314
  if (!cache.usage || cache.usage.mtimeMs !== st.mtimeMs) {
2308
- const txt2 = import_fs11.default.readFileSync(usagePath, "utf8");
2315
+ const txt2 = import_fs12.default.readFileSync(usagePath, "utf8");
2309
2316
  const now = Date.now();
2310
2317
  const liveBins = Array.from({ length: 60 }, () => ({ cost: 0, calls: 0 }));
2311
2318
  const dayBins = Array.from({ length: 7 }, () => ({ cost: 0, calls: 0 }));
@@ -2370,13 +2377,13 @@ load();
2370
2377
  await new Promise(() => {
2371
2378
  });
2372
2379
  }
2373
- var import_http, import_fs11, import_path12;
2380
+ var import_http, import_fs12, import_path13;
2374
2381
  var init_dashboard = __esm({
2375
2382
  "src/dashboard.ts"() {
2376
2383
  "use strict";
2377
2384
  import_http = __toESM(require("http"));
2378
- import_fs11 = __toESM(require("fs"));
2379
- import_path12 = __toESM(require("path"));
2385
+ import_fs12 = __toESM(require("fs"));
2386
+ import_path13 = __toESM(require("path"));
2380
2387
  init_collect();
2381
2388
  init_scan();
2382
2389
  init_io();
@@ -2390,55 +2397,55 @@ __export(find_output_exports, {
2390
2397
  findAioptOutputDir: () => findAioptOutputDir
2391
2398
  });
2392
2399
  function findAioptOutputDir(startCwd) {
2393
- let cur = import_path13.default.resolve(startCwd);
2400
+ let cur = import_path14.default.resolve(startCwd);
2394
2401
  while (true) {
2395
- const outDir = import_path13.default.join(cur, "aiopt-output");
2396
- if (import_fs12.default.existsSync(outDir)) {
2402
+ const outDir = import_path14.default.join(cur, "aiopt-output");
2403
+ if (import_fs13.default.existsSync(outDir)) {
2397
2404
  try {
2398
- if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
2405
+ if (import_fs13.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
2399
2406
  } catch {
2400
2407
  }
2401
2408
  }
2402
- const parent = import_path13.default.dirname(cur);
2409
+ const parent = import_path14.default.dirname(cur);
2403
2410
  if (parent === cur) break;
2404
2411
  cur = parent;
2405
2412
  }
2406
2413
  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));
2414
+ const base = import_path14.default.resolve(startCwd);
2415
+ const children = import_fs13.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path14.default.join(base, d.name));
2409
2416
  for (const child of children) {
2410
- const outDir = import_path13.default.join(child, "aiopt-output");
2411
- if (import_fs12.default.existsSync(outDir)) {
2417
+ const outDir = import_path14.default.join(child, "aiopt-output");
2418
+ if (import_fs13.default.existsSync(outDir)) {
2412
2419
  try {
2413
- if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
2420
+ if (import_fs13.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
2414
2421
  } catch {
2415
2422
  }
2416
2423
  }
2417
2424
  }
2418
2425
  } catch {
2419
2426
  }
2420
- return { cwd: import_path13.default.resolve(startCwd), outDir: import_path13.default.join(import_path13.default.resolve(startCwd), "aiopt-output") };
2427
+ return { cwd: import_path14.default.resolve(startCwd), outDir: import_path14.default.join(import_path14.default.resolve(startCwd), "aiopt-output") };
2421
2428
  }
2422
- var import_fs12, import_path13;
2429
+ var import_fs13, import_path14;
2423
2430
  var init_find_output = __esm({
2424
2431
  "src/find-output.ts"() {
2425
2432
  "use strict";
2426
- import_fs12 = __toESM(require("fs"));
2427
- import_path13 = __toESM(require("path"));
2433
+ import_fs13 = __toESM(require("fs"));
2434
+ import_path14 = __toESM(require("path"));
2428
2435
  }
2429
2436
  });
2430
2437
 
2431
2438
  // src/rates-util.ts
2432
2439
  function loadRateTableFromDistPath() {
2433
- const p = import_path14.default.join(__dirname, "..", "rates", "rate_table.json");
2434
- return JSON.parse(import_fs13.default.readFileSync(p, "utf8"));
2440
+ const p = import_path15.default.join(__dirname, "..", "rates", "rate_table.json");
2441
+ return JSON.parse(import_fs14.default.readFileSync(p, "utf8"));
2435
2442
  }
2436
- var import_fs13, import_path14;
2443
+ var import_fs14, import_path15;
2437
2444
  var init_rates_util = __esm({
2438
2445
  "src/rates-util.ts"() {
2439
2446
  "use strict";
2440
- import_fs13 = __toESM(require("fs"));
2441
- import_path14 = __toESM(require("path"));
2447
+ import_fs14 = __toESM(require("fs"));
2448
+ import_path15 = __toESM(require("path"));
2442
2449
  }
2443
2450
  });
2444
2451
 
@@ -2449,8 +2456,8 @@ __export(quickstart_exports, {
2449
2456
  seedDemoUsage: () => seedDemoUsage
2450
2457
  });
2451
2458
  function seedDemoUsage(outDir) {
2452
- import_fs14.default.mkdirSync(outDir, { recursive: true });
2453
- const p = import_path15.default.join(outDir, "usage.jsonl");
2459
+ import_fs15.default.mkdirSync(outDir, { recursive: true });
2460
+ const p = import_path16.default.join(outDir, "usage.jsonl");
2454
2461
  const now = Date.now();
2455
2462
  const lines = [];
2456
2463
  for (let i = 0; i < 60; i++) {
@@ -2467,18 +2474,18 @@ function seedDemoUsage(outDir) {
2467
2474
  meta: { feature_tag: i % 2 ? "summarize" : "coding" }
2468
2475
  });
2469
2476
  }
2470
- import_fs14.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
2477
+ import_fs15.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
2471
2478
  return p;
2472
2479
  }
2473
2480
  function runQuickstart(cwd, opts) {
2474
- const outDir = import_path15.default.join(cwd, "aiopt-output");
2481
+ const outDir = import_path16.default.join(cwd, "aiopt-output");
2475
2482
  const usagePath = seedDemoUsage(outDir);
2476
2483
  const rt = loadRateTableFromDistPath();
2477
2484
  const { readJsonl: readJsonl2 } = (init_io(), __toCommonJS(io_exports));
2478
2485
  const events = readJsonl2(usagePath);
2479
2486
  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({
2487
+ import_fs15.default.writeFileSync(import_path16.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
2488
+ import_fs15.default.writeFileSync(import_path16.default.join(outDir, "report.json"), JSON.stringify({
2482
2489
  version: 3,
2483
2490
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
2484
2491
  confidence: analysis.unknown_models?.length ? "MEDIUM" : "HIGH",
@@ -2498,8 +2505,8 @@ function runQuickstart(cwd, opts) {
2498
2505
  unknown_models: analysis.unknown_models || [],
2499
2506
  notes: []
2500
2507
  }, 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");
2508
+ import_fs15.default.writeFileSync(import_path16.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
2509
+ 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
2510
  const r = runGuard(rt, {
2504
2511
  baselineEvents: events,
2505
2512
  candidate: {
@@ -2510,12 +2517,12 @@ function runQuickstart(cwd, opts) {
2510
2517
  });
2511
2518
  return { usagePath, outDir, guard: r, port: opts.port };
2512
2519
  }
2513
- var import_fs14, import_path15;
2520
+ var import_fs15, import_path16;
2514
2521
  var init_quickstart = __esm({
2515
2522
  "src/quickstart.ts"() {
2516
2523
  "use strict";
2517
- import_fs14 = __toESM(require("fs"));
2518
- import_path15 = __toESM(require("path"));
2524
+ import_fs15 = __toESM(require("fs"));
2525
+ import_path16 = __toESM(require("path"));
2519
2526
  init_scan();
2520
2527
  init_rates_util();
2521
2528
  init_guard();
@@ -2523,26 +2530,54 @@ var init_quickstart = __esm({
2523
2530
  });
2524
2531
 
2525
2532
  // src/cli.ts
2526
- var import_fs15 = __toESM(require("fs"));
2527
- var import_path16 = __toESM(require("path"));
2533
+ var import_fs16 = __toESM(require("fs"));
2534
+ var import_path17 = __toESM(require("path"));
2528
2535
  var import_commander = require("commander");
2529
2536
  init_io();
2530
2537
  init_scan();
2538
+
2539
+ // src/usage-path.ts
2540
+ var import_fs5 = __toESM(require("fs"));
2541
+ var import_path6 = __toESM(require("path"));
2542
+ var import_os = __toESM(require("os"));
2543
+ function homeAioptUsagePath() {
2544
+ return import_path6.default.join(import_os.default.homedir(), ".aiopt", "aiopt-output", "usage.jsonl");
2545
+ }
2546
+ function resolveUsagePath(preferred) {
2547
+ const tried = [];
2548
+ const push = (p) => {
2549
+ if (!p) return;
2550
+ if (tried.includes(p)) return;
2551
+ tried.push(p);
2552
+ };
2553
+ push(preferred);
2554
+ push(homeAioptUsagePath());
2555
+ push("./aiopt-output/usage.jsonl");
2556
+ for (const p of tried) {
2557
+ try {
2558
+ if (import_fs5.default.existsSync(p)) return { path: p, tried };
2559
+ } catch {
2560
+ }
2561
+ }
2562
+ return { path: preferred, tried };
2563
+ }
2564
+
2565
+ // src/cli.ts
2531
2566
  var program = new import_commander.Command();
2532
2567
  var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
2533
2568
  var DEFAULT_OUTPUT_DIR = "./aiopt-output";
2534
2569
  function loadRateTable() {
2535
- const p = import_path16.default.join(__dirname, "..", "rates", "rate_table.json");
2536
- return JSON.parse(import_fs15.default.readFileSync(p, "utf8"));
2570
+ const p = import_path17.default.join(__dirname, "..", "rates", "rate_table.json");
2571
+ return JSON.parse(import_fs16.default.readFileSync(p, "utf8"));
2537
2572
  }
2538
2573
  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
2574
  program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
2540
2575
  ensureDir("./aiopt-input");
2541
2576
  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);
2577
+ const sampleSrc = import_path17.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
2578
+ const dst = import_path17.default.join("./aiopt-input", "usage.jsonl");
2579
+ if (!import_fs16.default.existsSync(dst)) {
2580
+ import_fs16.default.copyFileSync(sampleSrc, dst);
2546
2581
  console.log("Created ./aiopt-input/usage.jsonl (sample)");
2547
2582
  } else {
2548
2583
  console.log("Exists ./aiopt-input/usage.jsonl (skip)");
@@ -2552,7 +2587,7 @@ program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.json
2552
2587
  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
2588
  const inputPath = String(opts.input);
2554
2589
  const outDir = String(opts.out);
2555
- if (!import_fs15.default.existsSync(inputPath)) {
2590
+ if (!import_fs16.default.existsSync(inputPath)) {
2556
2591
  console.error(`Input not found: ${inputPath}`);
2557
2592
  process.exit(1);
2558
2593
  }
@@ -2569,10 +2604,10 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
2569
2604
  outDir,
2570
2605
  input: inputPath,
2571
2606
  report: {
2572
- report_md: import_path16.default.join(outDir, "report.md"),
2573
- report_json: import_path16.default.join(outDir, "report.json"),
2574
- cost_policy_json: import_path16.default.join(outDir, "cost-policy.json"),
2575
- sarif: import_path16.default.join(outDir, "aiopt.sarif")
2607
+ report_md: import_path17.default.join(outDir, "report.md"),
2608
+ report_json: import_path17.default.join(outDir, "report.json"),
2609
+ cost_policy_json: import_path17.default.join(outDir, "cost-policy.json"),
2610
+ sarif: import_path17.default.join(outDir, "aiopt.sarif")
2576
2611
  },
2577
2612
  summary: {
2578
2613
  total_cost_usd: analysis.total_cost,
@@ -2588,7 +2623,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
2588
2623
  const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
2589
2624
  console.log(`${i + 1}) ${f.title} ${tag}`);
2590
2625
  });
2591
- console.log(`Report: ${import_path16.default.join(outDir, "report.md")}`);
2626
+ console.log(`Report: ${import_path17.default.join(outDir, "report.md")}`);
2592
2627
  });
2593
2628
  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) => {
2594
2629
  const inputPath = String(opts.input);
@@ -2598,7 +2633,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
2598
2633
  const { policy } = analyze(rt, events);
2599
2634
  policy.generated_from.input = inputPath;
2600
2635
  ensureDir(outDir);
2601
- import_fs15.default.writeFileSync(import_path16.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
2636
+ import_fs16.default.writeFileSync(import_path17.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
2602
2637
  console.log(`OK: ${outDir}/cost-policy.json`);
2603
2638
  });
2604
2639
  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) => {
@@ -2638,7 +2673,7 @@ licenseCmd.command("verify").option("--path <path>", "license.json path (default
2638
2673
  const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
2639
2674
  const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
2640
2675
  const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
2641
- if (!import_fs15.default.existsSync(p)) {
2676
+ if (!import_fs16.default.existsSync(p)) {
2642
2677
  console.error(`FAIL: license file not found: ${p}`);
2643
2678
  process.exit(3);
2644
2679
  }
@@ -2655,7 +2690,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
2655
2690
  const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
2656
2691
  const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
2657
2692
  const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
2658
- if (!import_fs15.default.existsSync(p)) {
2693
+ if (!import_fs16.default.existsSync(p)) {
2659
2694
  console.log("NO_LICENSE");
2660
2695
  process.exit(2);
2661
2696
  }
@@ -2669,19 +2704,44 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
2669
2704
  process.exit(3);
2670
2705
  });
2671
2706
  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) => {
2672
- const inputPath = String(opts.input);
2707
+ const preferredInput = String(opts.input);
2673
2708
  const outDir = String(opts.out);
2674
- if (!import_fs15.default.existsSync(inputPath)) {
2675
- console.error(`FAIL: input not found: ${inputPath}`);
2709
+ const resolved = resolveUsagePath(preferredInput);
2710
+ const inputPath = resolved.path;
2711
+ const defaultOut = "./aiopt-output";
2712
+ let finalOutDir = outDir;
2713
+ if (outDir === defaultOut) {
2714
+ try {
2715
+ const os4 = require("os");
2716
+ finalOutDir = import_path17.default.join(os4.homedir(), ".aiopt", "aiopt-output");
2717
+ } catch {
2718
+ finalOutDir = outDir;
2719
+ }
2720
+ }
2721
+ if (!import_fs16.default.existsSync(inputPath)) {
2722
+ if (opts.json) {
2723
+ console.log(JSON.stringify({
2724
+ ok: false,
2725
+ exitCode: 1,
2726
+ error: "input_not_found",
2727
+ message: `FAIL: input not found: ${preferredInput}`,
2728
+ tried: resolved.tried,
2729
+ hint: "Run: aiopt dashboard (auto-collects OpenClaw usage) or pass --input <usage.jsonl>"
2730
+ }, null, 2));
2731
+ } else {
2732
+ console.error(`FAIL: input not found: ${preferredInput}`);
2733
+ console.error(`Tried: ${resolved.tried.join(", ")}`);
2734
+ console.error("Hint: run `aiopt dashboard` first (auto-collects OpenClaw usage)");
2735
+ }
2676
2736
  process.exit(1);
2677
2737
  }
2678
2738
  const rt = loadRateTable();
2679
2739
  const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);
2680
2740
  const { analysis, savings, policy, meta } = analyze(rt, events);
2681
2741
  policy.generated_from.input = inputPath;
2682
- writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
2742
+ writeOutputs(finalOutDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
2683
2743
  const { runGate: runGate2, formatGateStdout: formatGateStdout2 } = await Promise.resolve().then(() => (init_gate(), gate_exports));
2684
- const r = runGate2(outDir, process.cwd());
2744
+ const r = runGate2(finalOutDir, process.cwd());
2685
2745
  if (opts.json) {
2686
2746
  const payload = {
2687
2747
  ok: r.violations <= 0,
@@ -2689,15 +2749,15 @@ program.command("gate").description("Merge gate (CI-friendly): fail (exit 1) whe
2689
2749
  violations: r.violations,
2690
2750
  top3: r.top3,
2691
2751
  artifacts: {
2692
- report_md: import_path16.default.join(outDir, "report.md"),
2693
- sarif: import_path16.default.join(outDir, "aiopt.sarif"),
2694
- patches_dir: import_path16.default.join(outDir, "patches")
2752
+ report_md: import_path17.default.join(finalOutDir, "report.md"),
2753
+ sarif: import_path17.default.join(finalOutDir, "aiopt.sarif"),
2754
+ patches_dir: import_path17.default.join(finalOutDir, "patches")
2695
2755
  }
2696
2756
  };
2697
2757
  console.log(JSON.stringify(payload, null, 2));
2698
2758
  process.exit(payload.exitCode);
2699
2759
  }
2700
- const out = formatGateStdout2(r, outDir);
2760
+ const out = formatGateStdout2(r, finalOutDir);
2701
2761
  console.log(out.text);
2702
2762
  process.exit(out.exitCode);
2703
2763
  });
@@ -2741,11 +2801,11 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
2741
2801
  console.error("FAIL: diff mode requires both --baseline and --candidate");
2742
2802
  process.exit(3);
2743
2803
  }
2744
- if (!import_fs15.default.existsSync(baselinePath)) {
2804
+ if (!import_fs16.default.existsSync(baselinePath)) {
2745
2805
  console.error(`FAIL: baseline not found: ${baselinePath}`);
2746
2806
  process.exit(3);
2747
2807
  }
2748
- if (candidatePath && !import_fs15.default.existsSync(candidatePath)) {
2808
+ if (candidatePath && !import_fs16.default.existsSync(candidatePath)) {
2749
2809
  console.error(`FAIL: candidate not found: ${candidatePath}`);
2750
2810
  process.exit(3);
2751
2811
  }
@@ -2773,10 +2833,10 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
2773
2833
  baseline: baselinePath,
2774
2834
  candidate: candidatePath,
2775
2835
  artifacts: {
2776
- outDir: import_path16.default.resolve(DEFAULT_OUTPUT_DIR),
2777
- guard_last_txt: import_path16.default.join(import_path16.default.resolve(DEFAULT_OUTPUT_DIR), "guard-last.txt"),
2778
- guard_last_json: import_path16.default.join(import_path16.default.resolve(DEFAULT_OUTPUT_DIR), "guard-last.json"),
2779
- guard_history_jsonl: import_path16.default.join(import_path16.default.resolve(DEFAULT_OUTPUT_DIR), "guard-history.jsonl")
2836
+ outDir: import_path17.default.resolve(DEFAULT_OUTPUT_DIR),
2837
+ guard_last_txt: import_path17.default.join(import_path17.default.resolve(DEFAULT_OUTPUT_DIR), "guard-last.txt"),
2838
+ guard_last_json: import_path17.default.join(import_path17.default.resolve(DEFAULT_OUTPUT_DIR), "guard-last.json"),
2839
+ guard_history_jsonl: import_path17.default.join(import_path17.default.resolve(DEFAULT_OUTPUT_DIR), "guard-history.jsonl")
2780
2840
  }
2781
2841
  };
2782
2842
  console.log(JSON.stringify(payload, null, 2));
@@ -2784,21 +2844,21 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
2784
2844
  console.log(r.message);
2785
2845
  }
2786
2846
  try {
2787
- const outDir = import_path16.default.resolve(DEFAULT_OUTPUT_DIR);
2788
- import_fs15.default.mkdirSync(outDir, { recursive: true });
2847
+ const outDir = import_path17.default.resolve(DEFAULT_OUTPUT_DIR);
2848
+ import_fs16.default.mkdirSync(outDir, { recursive: true });
2789
2849
  const ts = (/* @__PURE__ */ new Date()).toISOString();
2790
- import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.txt"), r.message);
2791
- import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
2850
+ import_fs16.default.writeFileSync(import_path17.default.join(outDir, "guard-last.txt"), r.message);
2851
+ import_fs16.default.writeFileSync(import_path17.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
2792
2852
  const histLine = JSON.stringify({ ts, exitCode: r.exitCode, mode: candidateEvents ? "diff" : "transform", baseline: baselinePath, candidate: candidatePath }) + "\n";
2793
- import_fs15.default.appendFileSync(import_path16.default.join(outDir, "guard-history.jsonl"), histLine);
2853
+ import_fs16.default.appendFileSync(import_path17.default.join(outDir, "guard-history.jsonl"), histLine);
2794
2854
  } catch {
2795
2855
  }
2796
2856
  process.exit(r.exitCode);
2797
2857
  });
2798
2858
  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) => {
2799
2859
  const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
2800
- const os3 = await import("os");
2801
- const defaultBase = require("path").join(os3.homedir(), ".aiopt");
2860
+ const os4 = await import("os");
2861
+ const defaultBase = require("path").join(os4.homedir(), ".aiopt");
2802
2862
  const base = opts.dir ? String(opts.dir) : defaultBase;
2803
2863
  if (opts.auto && !opts.dir) {
2804
2864
  const { findAioptOutputDir: findAioptOutputDir2 } = await Promise.resolve().then(() => (init_find_output(), find_output_exports));
@@ -2820,14 +2880,14 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
2820
2880
  console.log("--- guard ---");
2821
2881
  console.log(r.guard.message);
2822
2882
  try {
2823
- const fs16 = await import("fs");
2824
- const path17 = await import("path");
2825
- fs16.mkdirSync(r.outDir, { recursive: true });
2883
+ const fs17 = await import("fs");
2884
+ const path18 = await import("path");
2885
+ fs17.mkdirSync(r.outDir, { recursive: true });
2826
2886
  const ts = (/* @__PURE__ */ new Date()).toISOString();
2827
- fs16.writeFileSync(path17.join(r.outDir, "guard-last.txt"), r.guard.message);
2828
- fs16.writeFileSync(path17.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
2887
+ fs17.writeFileSync(path18.join(r.outDir, "guard-last.txt"), r.guard.message);
2888
+ fs17.writeFileSync(path18.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
2829
2889
  const histLine = JSON.stringify({ ts, exitCode: r.guard.exitCode, mode: "quickstart", baseline: r.usagePath, candidate: null }) + "\n";
2830
- fs16.appendFileSync(path17.join(r.outDir, "guard-history.jsonl"), histLine);
2890
+ fs17.appendFileSync(path18.join(r.outDir, "guard-history.jsonl"), histLine);
2831
2891
  } catch {
2832
2892
  }
2833
2893
  console.log("--- next ---");