aiopt 0.2.9 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/cli.js +672 -110
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -230,6 +230,169 @@ var init_solutions = __esm({
|
|
|
230
230
|
}
|
|
231
231
|
});
|
|
232
232
|
|
|
233
|
+
// src/code-scan.ts
|
|
234
|
+
function isTextLike(p) {
|
|
235
|
+
const ext = import_path3.default.extname(p).toLowerCase();
|
|
236
|
+
return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".go", ".java", ".kt", ".rb", ".php"].includes(ext);
|
|
237
|
+
}
|
|
238
|
+
function walk(root, out) {
|
|
239
|
+
const items = import_fs3.default.readdirSync(root, { withFileTypes: true });
|
|
240
|
+
for (const it of items) {
|
|
241
|
+
if (DEFAULT_EXCLUDES.has(it.name)) continue;
|
|
242
|
+
const full = import_path3.default.join(root, it.name);
|
|
243
|
+
if (it.isDirectory()) walk(full, out);
|
|
244
|
+
else out.push(full);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function findAllLines(content, re) {
|
|
248
|
+
const lines = [];
|
|
249
|
+
const parts = content.split(/\r?\n/);
|
|
250
|
+
for (let i = 0; i < parts.length; i++) {
|
|
251
|
+
const line = parts[i];
|
|
252
|
+
if (re.test(line)) lines.push(i + 1);
|
|
253
|
+
}
|
|
254
|
+
return lines;
|
|
255
|
+
}
|
|
256
|
+
function runCodeScan(rootDir) {
|
|
257
|
+
const files = [];
|
|
258
|
+
walk(rootDir, files);
|
|
259
|
+
const findings = [];
|
|
260
|
+
for (const file of files) {
|
|
261
|
+
if (!isTextLike(file)) continue;
|
|
262
|
+
let st;
|
|
263
|
+
try {
|
|
264
|
+
st = import_fs3.default.statSync(file);
|
|
265
|
+
} catch {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (!st.isFile()) continue;
|
|
269
|
+
if (st.size > 1024 * 1024) continue;
|
|
270
|
+
let content = "";
|
|
271
|
+
try {
|
|
272
|
+
content = import_fs3.default.readFileSync(file, "utf8");
|
|
273
|
+
} catch {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
for (const ln of findAllLines(content, /\b(maxRetries|maximumRetries|retries|max_attempts|attempts)\s*[:=]\s*(\d{2,}|[6-9])\b/i)) {
|
|
277
|
+
findings.push({
|
|
278
|
+
ruleId: "AIOPT.RETRY.EXPLOSION_RISK",
|
|
279
|
+
level: "warning",
|
|
280
|
+
message: "High retry/attempt count detected. Consider capping retries to prevent cost explosions.",
|
|
281
|
+
file,
|
|
282
|
+
line: ln,
|
|
283
|
+
help: "Cap retries (e.g. 2-3), add backoff, and fail fast on non-retriable errors."
|
|
284
|
+
});
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
for (const ln of findAllLines(content, /(gpt-5\.?2|gpt-4\.?|o1-|o3-|claude-3|sonnet|opus)/i)) {
|
|
288
|
+
findings.push({
|
|
289
|
+
ruleId: "AIOPT.MODEL.ROUTING.EXPENSIVE_DEFAULT",
|
|
290
|
+
level: "note",
|
|
291
|
+
message: "Possible expensive model hard-coded. Consider cheap default + explicit override for critical paths.",
|
|
292
|
+
file,
|
|
293
|
+
line: ln,
|
|
294
|
+
help: "Route cheap by default; allow overrides via env/config for high-impact tasks."
|
|
295
|
+
});
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
const hasOpenAI = /\bopenai\b|\bOpenAI\b|responses\.create|chat\.completions\.create/i.test(content);
|
|
299
|
+
const hasTimeout = /\btimeout\b|\brequestTimeout\b|\bsignal\b/i.test(content);
|
|
300
|
+
if (hasOpenAI && !hasTimeout) {
|
|
301
|
+
findings.push({
|
|
302
|
+
ruleId: "AIOPT.TIMEOUT.MISSING",
|
|
303
|
+
level: "note",
|
|
304
|
+
message: "OpenAI/LLM call detected without obvious timeout. Add a timeout to reduce hanging retries and cost waste.",
|
|
305
|
+
file,
|
|
306
|
+
line: 1,
|
|
307
|
+
help: "Add request timeout / AbortSignal and handle retryable errors explicitly."
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return findings.slice(0, 200);
|
|
312
|
+
}
|
|
313
|
+
var import_fs3, import_path3, DEFAULT_EXCLUDES;
|
|
314
|
+
var init_code_scan = __esm({
|
|
315
|
+
"src/code-scan.ts"() {
|
|
316
|
+
"use strict";
|
|
317
|
+
import_fs3 = __toESM(require("fs"));
|
|
318
|
+
import_path3 = __toESM(require("path"));
|
|
319
|
+
DEFAULT_EXCLUDES = /* @__PURE__ */ new Set([
|
|
320
|
+
".git",
|
|
321
|
+
"node_modules",
|
|
322
|
+
"dist",
|
|
323
|
+
"aiopt-output",
|
|
324
|
+
".next",
|
|
325
|
+
"build",
|
|
326
|
+
"coverage"
|
|
327
|
+
]);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// src/sarif.ts
|
|
332
|
+
function toUri(p) {
|
|
333
|
+
const abs = import_path4.default.resolve(p);
|
|
334
|
+
const u = abs.replace(/\\/g, "/");
|
|
335
|
+
return u.match(/^[A-Za-z]:\//) ? `file:///${u}` : `file://${u}`;
|
|
336
|
+
}
|
|
337
|
+
function buildSarif(toolName, toolVersion, findings) {
|
|
338
|
+
const rulesMap = /* @__PURE__ */ new Map();
|
|
339
|
+
for (const f of findings) {
|
|
340
|
+
if (!rulesMap.has(f.ruleId)) {
|
|
341
|
+
rulesMap.set(f.ruleId, {
|
|
342
|
+
id: f.ruleId,
|
|
343
|
+
shortDescription: f.ruleId,
|
|
344
|
+
help: f.help
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const rules = [...rulesMap.values()].map((r) => ({
|
|
349
|
+
id: r.id,
|
|
350
|
+
shortDescription: { text: r.shortDescription },
|
|
351
|
+
help: r.help ? { text: r.help } : void 0
|
|
352
|
+
})).map((x) => {
|
|
353
|
+
const y = { ...x };
|
|
354
|
+
if (!y.help) delete y.help;
|
|
355
|
+
return y;
|
|
356
|
+
});
|
|
357
|
+
const results = findings.map((f) => ({
|
|
358
|
+
ruleId: f.ruleId,
|
|
359
|
+
level: f.level,
|
|
360
|
+
message: { text: f.message },
|
|
361
|
+
locations: [
|
|
362
|
+
{
|
|
363
|
+
physicalLocation: {
|
|
364
|
+
artifactLocation: { uri: toUri(f.file) },
|
|
365
|
+
region: { startLine: Math.max(1, Math.floor(f.line || 1)) }
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
]
|
|
369
|
+
}));
|
|
370
|
+
return {
|
|
371
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
372
|
+
version: "2.1.0",
|
|
373
|
+
runs: [
|
|
374
|
+
{
|
|
375
|
+
tool: {
|
|
376
|
+
driver: {
|
|
377
|
+
name: toolName,
|
|
378
|
+
version: toolVersion,
|
|
379
|
+
informationUri: "https://www.npmjs.com/package/aiopt",
|
|
380
|
+
rules
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
results
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
var import_path4;
|
|
389
|
+
var init_sarif = __esm({
|
|
390
|
+
"src/sarif.ts"() {
|
|
391
|
+
"use strict";
|
|
392
|
+
import_path4 = __toESM(require("path"));
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
|
|
233
396
|
// src/scan.ts
|
|
234
397
|
function topN(map, n) {
|
|
235
398
|
return [...map.entries()].map(([key, v]) => ({ key, cost: v.cost, events: v.events })).sort((a, b) => b.cost - a.cost).slice(0, n);
|
|
@@ -387,8 +550,8 @@ function round22(n) {
|
|
|
387
550
|
}
|
|
388
551
|
function writeOutputs(outDir, analysis, savings, policy, meta) {
|
|
389
552
|
const mode = meta?.mode || "legacy";
|
|
390
|
-
|
|
391
|
-
|
|
553
|
+
import_fs4.default.mkdirSync(outDir, { recursive: true });
|
|
554
|
+
import_fs4.default.writeFileSync(import_path5.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
|
|
392
555
|
const unknownCount = analysis.unknown_models?.length || 0;
|
|
393
556
|
const confidence = unknownCount === 0 ? "HIGH" : unknownCount <= 3 ? "MED" : "LOW";
|
|
394
557
|
const ratio = analysis.total_cost > 0 ? savings.estimated_savings_total / analysis.total_cost : 0;
|
|
@@ -420,7 +583,7 @@ function writeOutputs(outDir, analysis, savings, policy, meta) {
|
|
|
420
583
|
unknown_models: analysis.unknown_models,
|
|
421
584
|
notes: savings.notes
|
|
422
585
|
};
|
|
423
|
-
|
|
586
|
+
import_fs4.default.writeFileSync(import_path5.default.join(outDir, "report.json"), JSON.stringify(reportJson, null, 2));
|
|
424
587
|
const ratioMd = analysis.total_cost > 0 ? savings.estimated_savings_total / analysis.total_cost : 0;
|
|
425
588
|
const warningsMd = [];
|
|
426
589
|
if (ratioMd >= 0.9) warningsMd.push("WARNING: estimated savings >= 90% \u2014 check overlap/missing rate table");
|
|
@@ -449,7 +612,7 @@ function writeOutputs(outDir, analysis, savings, policy, meta) {
|
|
|
449
612
|
"- `aiopt-output/patches/*`",
|
|
450
613
|
""
|
|
451
614
|
].join("\n");
|
|
452
|
-
|
|
615
|
+
import_fs4.default.writeFileSync(import_path5.default.join(outDir, "report.md"), reportMd);
|
|
453
616
|
const reportTxt = [
|
|
454
617
|
`\uCD1D\uBE44\uC6A9: $${analysis.total_cost}`,
|
|
455
618
|
`\uC808\uAC10 \uAC00\uB2A5 \uAE08\uC561(Estimated): $${savings.estimated_savings_total}`,
|
|
@@ -459,19 +622,31 @@ function writeOutputs(outDir, analysis, savings, policy, meta) {
|
|
|
459
622
|
savings.notes[2],
|
|
460
623
|
""
|
|
461
624
|
].join("\n");
|
|
462
|
-
|
|
463
|
-
|
|
625
|
+
import_fs4.default.writeFileSync(import_path5.default.join(outDir, "report.txt"), reportTxt);
|
|
626
|
+
import_fs4.default.writeFileSync(import_path5.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
464
627
|
const fixes = buildTopFixes(analysis, savings);
|
|
465
628
|
writePatches(outDir, fixes);
|
|
629
|
+
try {
|
|
630
|
+
const cwd = meta?.cwd;
|
|
631
|
+
const cliVersion = meta?.cliVersion || "unknown";
|
|
632
|
+
if (cwd && import_fs4.default.existsSync(cwd)) {
|
|
633
|
+
const findings = runCodeScan(cwd);
|
|
634
|
+
const sarif = buildSarif("aiopt", cliVersion, findings);
|
|
635
|
+
import_fs4.default.writeFileSync(import_path5.default.join(outDir, "aiopt.sarif"), JSON.stringify(sarif, null, 2));
|
|
636
|
+
}
|
|
637
|
+
} catch {
|
|
638
|
+
}
|
|
466
639
|
}
|
|
467
|
-
var
|
|
640
|
+
var import_fs4, import_path5, ROUTE_TO_CHEAP_FEATURES;
|
|
468
641
|
var init_scan = __esm({
|
|
469
642
|
"src/scan.ts"() {
|
|
470
643
|
"use strict";
|
|
471
|
-
|
|
472
|
-
|
|
644
|
+
import_fs4 = __toESM(require("fs"));
|
|
645
|
+
import_path5 = __toESM(require("path"));
|
|
473
646
|
init_cost();
|
|
474
647
|
init_solutions();
|
|
648
|
+
init_code_scan();
|
|
649
|
+
init_sarif();
|
|
475
650
|
ROUTE_TO_CHEAP_FEATURES = /* @__PURE__ */ new Set(["summarize", "classify", "translate"]);
|
|
476
651
|
}
|
|
477
652
|
});
|
|
@@ -481,7 +656,7 @@ var require_package = __commonJS({
|
|
|
481
656
|
"package.json"(exports2, module2) {
|
|
482
657
|
module2.exports = {
|
|
483
658
|
name: "aiopt",
|
|
484
|
-
version: "0.
|
|
659
|
+
version: "0.3.1",
|
|
485
660
|
description: "Pre-deploy LLM cost accident guardrail (serverless local CLI)",
|
|
486
661
|
bin: {
|
|
487
662
|
aiopt: "dist/cli.js"
|
|
@@ -522,19 +697,19 @@ __export(install_exports, {
|
|
|
522
697
|
runInstall: () => runInstall
|
|
523
698
|
});
|
|
524
699
|
function ensureDir2(p) {
|
|
525
|
-
|
|
700
|
+
import_fs5.default.mkdirSync(p, { recursive: true });
|
|
526
701
|
}
|
|
527
702
|
function writeFile(filePath, content, force) {
|
|
528
|
-
if (!force &&
|
|
529
|
-
ensureDir2(
|
|
530
|
-
|
|
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);
|
|
531
706
|
return { wrote: true };
|
|
532
707
|
}
|
|
533
708
|
function runInstall(cwd, opts) {
|
|
534
709
|
const force = Boolean(opts.force);
|
|
535
|
-
const aioptDir =
|
|
536
|
-
const policiesDir =
|
|
537
|
-
const outDir =
|
|
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");
|
|
538
713
|
ensureDir2(aioptDir);
|
|
539
714
|
ensureDir2(policiesDir);
|
|
540
715
|
ensureDir2(outDir);
|
|
@@ -590,7 +765,7 @@ return {
|
|
|
590
765
|
};
|
|
591
766
|
\`\`\`
|
|
592
767
|
`;
|
|
593
|
-
const r1 = writeFile(
|
|
768
|
+
const r1 = writeFile(import_path6.default.join(aioptDir, "README.md"), readme, force);
|
|
594
769
|
created.push({ path: "aiopt/README.md", status: r1.wrote ? "created" : "skipped" });
|
|
595
770
|
const config = {
|
|
596
771
|
version: 1,
|
|
@@ -600,7 +775,7 @@ return {
|
|
|
600
775
|
policies_dir: "./aiopt/policies",
|
|
601
776
|
rate_table: { path: "./rates/rate_table.json" }
|
|
602
777
|
};
|
|
603
|
-
const r2 = writeFile(
|
|
778
|
+
const r2 = writeFile(import_path6.default.join(aioptDir, "aiopt.config.json"), JSON.stringify(config, null, 2) + "\n", force);
|
|
604
779
|
created.push({ path: "aiopt/aiopt.config.json", status: r2.wrote ? "created" : "skipped" });
|
|
605
780
|
const routing = {
|
|
606
781
|
version: 1,
|
|
@@ -631,15 +806,15 @@ return {
|
|
|
631
806
|
reduce_top_percentile: 0.2,
|
|
632
807
|
assumed_reduction_ratio: 0.25
|
|
633
808
|
};
|
|
634
|
-
const p1 = writeFile(
|
|
635
|
-
const p2 = writeFile(
|
|
636
|
-
const p3 = writeFile(
|
|
637
|
-
const p4 = writeFile(
|
|
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);
|
|
638
813
|
created.push({ path: "aiopt/policies/routing.json", status: p1.wrote ? "created" : "skipped" });
|
|
639
814
|
created.push({ path: "aiopt/policies/retry.json", status: p2.wrote ? "created" : "skipped" });
|
|
640
815
|
created.push({ path: "aiopt/policies/output.json", status: p3.wrote ? "created" : "skipped" });
|
|
641
816
|
created.push({ path: "aiopt/policies/context.json", status: p4.wrote ? "created" : "skipped" });
|
|
642
|
-
const wrapperPath =
|
|
817
|
+
const wrapperPath = import_path6.default.join(aioptDir, "aiopt-wrapper.js");
|
|
643
818
|
const wrapper = `// AIOpt Wrapper (guardrails) \u2014 local-only (CommonJS)
|
|
644
819
|
|
|
645
820
|
const fs = require('fs');
|
|
@@ -793,9 +968,9 @@ module.exports = { guardedCall };
|
|
|
793
968
|
;
|
|
794
969
|
const w = writeFile(wrapperPath, wrapper, force);
|
|
795
970
|
created.push({ path: "aiopt/aiopt-wrapper.js", status: w.wrote ? "created" : "skipped" });
|
|
796
|
-
const usagePath =
|
|
797
|
-
if (force || !
|
|
798
|
-
|
|
971
|
+
const usagePath = import_path6.default.join(outDir, "usage.jsonl");
|
|
972
|
+
if (force || !import_fs5.default.existsSync(usagePath)) {
|
|
973
|
+
import_fs5.default.writeFileSync(usagePath, "");
|
|
799
974
|
created.push({ path: "aiopt-output/usage.jsonl", status: "created" });
|
|
800
975
|
if (opts.seedSample) {
|
|
801
976
|
const sample = {
|
|
@@ -815,19 +990,19 @@ module.exports = { guardedCall };
|
|
|
815
990
|
latency_ms: 1,
|
|
816
991
|
meta: { feature_tag: "demo", routed_from: null, policy_hits: ["install-sample"] }
|
|
817
992
|
};
|
|
818
|
-
|
|
993
|
+
import_fs5.default.appendFileSync(usagePath, JSON.stringify(sample) + "\n");
|
|
819
994
|
}
|
|
820
995
|
} else {
|
|
821
996
|
created.push({ path: "aiopt-output/usage.jsonl", status: "skipped" });
|
|
822
997
|
}
|
|
823
998
|
return { created };
|
|
824
999
|
}
|
|
825
|
-
var
|
|
1000
|
+
var import_fs5, import_path6;
|
|
826
1001
|
var init_install = __esm({
|
|
827
1002
|
"src/install.ts"() {
|
|
828
1003
|
"use strict";
|
|
829
|
-
|
|
830
|
-
|
|
1004
|
+
import_fs5 = __toESM(require("fs"));
|
|
1005
|
+
import_path6 = __toESM(require("path"));
|
|
831
1006
|
}
|
|
832
1007
|
});
|
|
833
1008
|
|
|
@@ -838,10 +1013,10 @@ __export(doctor_exports, {
|
|
|
838
1013
|
});
|
|
839
1014
|
function canWrite(dir) {
|
|
840
1015
|
try {
|
|
841
|
-
|
|
842
|
-
const p =
|
|
843
|
-
|
|
844
|
-
|
|
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);
|
|
845
1020
|
return true;
|
|
846
1021
|
} catch {
|
|
847
1022
|
return false;
|
|
@@ -849,7 +1024,7 @@ function canWrite(dir) {
|
|
|
849
1024
|
}
|
|
850
1025
|
function tailLines(filePath, n) {
|
|
851
1026
|
try {
|
|
852
|
-
const raw =
|
|
1027
|
+
const raw = import_fs6.default.readFileSync(filePath, "utf8");
|
|
853
1028
|
const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
854
1029
|
return lines.slice(Math.max(0, lines.length - n));
|
|
855
1030
|
} catch {
|
|
@@ -857,15 +1032,15 @@ function tailLines(filePath, n) {
|
|
|
857
1032
|
}
|
|
858
1033
|
}
|
|
859
1034
|
function runDoctor(cwd) {
|
|
860
|
-
const aioptDir =
|
|
861
|
-
const policiesDir =
|
|
862
|
-
const outDir =
|
|
863
|
-
const usagePath =
|
|
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");
|
|
864
1039
|
const checks = [];
|
|
865
|
-
checks.push({ name: "aiopt/ exists", ok:
|
|
866
|
-
checks.push({ name: "aiopt/policies exists", ok:
|
|
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) });
|
|
867
1042
|
checks.push({ name: "aiopt-output/ writable", ok: canWrite(outDir) });
|
|
868
|
-
checks.push({ name: "usage.jsonl exists", ok:
|
|
1043
|
+
checks.push({ name: "usage.jsonl exists", ok: import_fs6.default.existsSync(usagePath), detail: usagePath });
|
|
869
1044
|
const last5raw = tailLines(usagePath, 5);
|
|
870
1045
|
const last5 = last5raw.length === 0 ? [{ status: "(empty usage.jsonl)" }] : last5raw.map((l) => {
|
|
871
1046
|
try {
|
|
@@ -903,12 +1078,12 @@ function runDoctor(cwd) {
|
|
|
903
1078
|
const ok = checks.every((c) => c.ok);
|
|
904
1079
|
return { ok, checks, last5 };
|
|
905
1080
|
}
|
|
906
|
-
var
|
|
1081
|
+
var import_fs6, import_path7;
|
|
907
1082
|
var init_doctor = __esm({
|
|
908
1083
|
"src/doctor.ts"() {
|
|
909
1084
|
"use strict";
|
|
910
|
-
|
|
911
|
-
|
|
1085
|
+
import_fs6 = __toESM(require("fs"));
|
|
1086
|
+
import_path7 = __toESM(require("path"));
|
|
912
1087
|
}
|
|
913
1088
|
});
|
|
914
1089
|
|
|
@@ -963,7 +1138,7 @@ function verifyLicenseKey(key, publicKeyPem) {
|
|
|
963
1138
|
}
|
|
964
1139
|
}
|
|
965
1140
|
function defaultLicensePath(cwd) {
|
|
966
|
-
return
|
|
1141
|
+
return import_path8.default.join(cwd, "aiopt", "license.json");
|
|
967
1142
|
}
|
|
968
1143
|
function writeLicenseFile(p, key, payload, verified) {
|
|
969
1144
|
const out = {
|
|
@@ -972,19 +1147,19 @@ function writeLicenseFile(p, key, payload, verified) {
|
|
|
972
1147
|
verified,
|
|
973
1148
|
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
974
1149
|
};
|
|
975
|
-
|
|
976
|
-
|
|
1150
|
+
import_fs7.default.mkdirSync(import_path8.default.dirname(p), { recursive: true });
|
|
1151
|
+
import_fs7.default.writeFileSync(p, JSON.stringify(out, null, 2));
|
|
977
1152
|
}
|
|
978
1153
|
function readLicenseFile(p) {
|
|
979
|
-
return JSON.parse(
|
|
1154
|
+
return JSON.parse(import_fs7.default.readFileSync(p, "utf8"));
|
|
980
1155
|
}
|
|
981
|
-
var import_crypto,
|
|
1156
|
+
var import_crypto, import_fs7, import_path8, DEFAULT_PUBLIC_KEY_PEM;
|
|
982
1157
|
var init_license = __esm({
|
|
983
1158
|
"src/license.ts"() {
|
|
984
1159
|
"use strict";
|
|
985
1160
|
import_crypto = __toESM(require("crypto"));
|
|
986
|
-
|
|
987
|
-
|
|
1161
|
+
import_fs7 = __toESM(require("fs"));
|
|
1162
|
+
import_path8 = __toESM(require("path"));
|
|
988
1163
|
DEFAULT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
989
1164
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz1LLE/pXIx5TloDa0LAf
|
|
990
1165
|
jg9NSIW6STWhsAFP2ZzXgpWoQ3cCmW6xcB/4QNEmPpGlfMWhyRfkxsdKuhnjUMTg
|
|
@@ -997,6 +1172,284 @@ DwIDAQAB
|
|
|
997
1172
|
}
|
|
998
1173
|
});
|
|
999
1174
|
|
|
1175
|
+
// src/gate.ts
|
|
1176
|
+
var gate_exports = {};
|
|
1177
|
+
__export(gate_exports, {
|
|
1178
|
+
formatGateStdout: () => formatGateStdout,
|
|
1179
|
+
runGate: () => runGate
|
|
1180
|
+
});
|
|
1181
|
+
function parseFileUri(uri) {
|
|
1182
|
+
try {
|
|
1183
|
+
if (!uri) return "";
|
|
1184
|
+
const u = uri.replace(/^file:\/\//, "");
|
|
1185
|
+
const norm = u.replace(/^\/+/, "").replace(/%20/g, " ");
|
|
1186
|
+
return norm;
|
|
1187
|
+
} catch {
|
|
1188
|
+
return uri;
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function runGate(outDir, cwd) {
|
|
1192
|
+
const sarifPath = import_path9.default.join(outDir, "aiopt.sarif");
|
|
1193
|
+
if (!import_fs8.default.existsSync(sarifPath)) {
|
|
1194
|
+
return { violations: 0, top3: [] };
|
|
1195
|
+
}
|
|
1196
|
+
let sarif;
|
|
1197
|
+
try {
|
|
1198
|
+
sarif = JSON.parse(import_fs8.default.readFileSync(sarifPath, "utf8"));
|
|
1199
|
+
} catch {
|
|
1200
|
+
return { violations: 0, top3: [] };
|
|
1201
|
+
}
|
|
1202
|
+
const results = sarif.runs?.[0]?.results || [];
|
|
1203
|
+
const viol = results.filter((r) => {
|
|
1204
|
+
const lvl = String(r.level || "").toLowerCase();
|
|
1205
|
+
return lvl === "warning" || lvl === "error";
|
|
1206
|
+
});
|
|
1207
|
+
const locs = [];
|
|
1208
|
+
for (const r of viol) {
|
|
1209
|
+
const loc = r.locations?.[0]?.physicalLocation;
|
|
1210
|
+
const uri = loc?.artifactLocation?.uri || "";
|
|
1211
|
+
const line = Number(loc?.region?.startLine || 1);
|
|
1212
|
+
let file = parseFileUri(uri);
|
|
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;
|
|
1216
|
+
} catch {
|
|
1217
|
+
}
|
|
1218
|
+
locs.push({ file, line: Number.isFinite(line) && line > 0 ? line : 1 });
|
|
1219
|
+
}
|
|
1220
|
+
const top3 = locs.slice(0, 3);
|
|
1221
|
+
return { violations: viol.length, top3 };
|
|
1222
|
+
}
|
|
1223
|
+
function formatGateStdout(r, outDir) {
|
|
1224
|
+
const lines = [];
|
|
1225
|
+
if (r.violations <= 0) {
|
|
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")}`);
|
|
1228
|
+
return { text: lines.join("\n"), exitCode: 0 };
|
|
1229
|
+
}
|
|
1230
|
+
lines.push(`FAIL: policy violations=${r.violations}`);
|
|
1231
|
+
lines.push("Top3:");
|
|
1232
|
+
for (const x of r.top3) lines.push(`- ${x.file}:${x.line}`);
|
|
1233
|
+
lines.push("See artifacts: aiopt-output/report.md | aiopt-output/aiopt.sarif | aiopt-output/patches/");
|
|
1234
|
+
const text = lines.slice(0, 10).join("\n");
|
|
1235
|
+
return { text, exitCode: 1 };
|
|
1236
|
+
}
|
|
1237
|
+
var import_fs8, import_path9;
|
|
1238
|
+
var init_gate = __esm({
|
|
1239
|
+
"src/gate.ts"() {
|
|
1240
|
+
"use strict";
|
|
1241
|
+
import_fs8 = __toESM(require("fs"));
|
|
1242
|
+
import_path9 = __toESM(require("path"));
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// src/fix.ts
|
|
1247
|
+
var fix_exports = {};
|
|
1248
|
+
__export(fix_exports, {
|
|
1249
|
+
runFix: () => runFix
|
|
1250
|
+
});
|
|
1251
|
+
function isTextLike2(p) {
|
|
1252
|
+
const ext = import_path10.default.extname(p).toLowerCase();
|
|
1253
|
+
return [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"].includes(ext);
|
|
1254
|
+
}
|
|
1255
|
+
function walk2(root, out) {
|
|
1256
|
+
const items = import_fs9.default.readdirSync(root, { withFileTypes: true });
|
|
1257
|
+
for (const it of items) {
|
|
1258
|
+
if (it.name.startsWith(".")) continue;
|
|
1259
|
+
if (DEFAULT_EXCLUDES2.has(it.name)) continue;
|
|
1260
|
+
const full = import_path10.default.join(root, it.name);
|
|
1261
|
+
if (it.isDirectory()) walk2(full, out);
|
|
1262
|
+
else out.push(full);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
function insertModelConst(content) {
|
|
1266
|
+
if (/\bAIOPT_MODEL\b/.test(content)) return { changed: false, next: content };
|
|
1267
|
+
if (!/\bprocess\.env\.AIOPT_MODEL\b/.test(content)) {
|
|
1268
|
+
}
|
|
1269
|
+
const line = "const AIOPT_MODEL = process.env.AIOPT_MODEL || 'gpt-5.2-mini';";
|
|
1270
|
+
const lines = content.split(/\r?\n/);
|
|
1271
|
+
let i = 0;
|
|
1272
|
+
if (lines[0] && lines[0].startsWith("#!")) i++;
|
|
1273
|
+
while (i < lines.length) {
|
|
1274
|
+
const t = lines[i].trim();
|
|
1275
|
+
if (t.startsWith("import ")) {
|
|
1276
|
+
i++;
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
if (t.startsWith("const ") && t.includes("require(")) {
|
|
1280
|
+
i++;
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
if (t === "" || t.startsWith("//") || t.startsWith("/*")) {
|
|
1284
|
+
i++;
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
lines.splice(i, 0, line, "");
|
|
1290
|
+
return { changed: true, next: lines.join("\n") };
|
|
1291
|
+
}
|
|
1292
|
+
function applyRetryCap(content) {
|
|
1293
|
+
let changed = false;
|
|
1294
|
+
let next = content;
|
|
1295
|
+
next = next.replace(/\b(maxRetries|maximumRetries|max_attempts|attempts)\s*:\s*(\d+)\b/gi, (m, key, num) => {
|
|
1296
|
+
const n = Number(num);
|
|
1297
|
+
if (!Number.isFinite(n) || n < 4) return m;
|
|
1298
|
+
changed = true;
|
|
1299
|
+
return `${key}: 3`;
|
|
1300
|
+
});
|
|
1301
|
+
next = next.replace(/\b(retries)\s*:\s*(\d+)\b/gi, (m, key, num) => {
|
|
1302
|
+
const n = Number(num);
|
|
1303
|
+
if (!Number.isFinite(n) || n < 4) return m;
|
|
1304
|
+
changed = true;
|
|
1305
|
+
return `${key}: 3`;
|
|
1306
|
+
});
|
|
1307
|
+
return { changed, next };
|
|
1308
|
+
}
|
|
1309
|
+
function applyModelRouting(content) {
|
|
1310
|
+
let changed = false;
|
|
1311
|
+
let next = content;
|
|
1312
|
+
const re = /(\bmodel\s*[:=]\s*)(['"])gpt-5\.2\2/g;
|
|
1313
|
+
if (re.test(next)) {
|
|
1314
|
+
const ins = insertModelConst(next);
|
|
1315
|
+
next = ins.next;
|
|
1316
|
+
next = next.replace(re, (_m, prefix) => {
|
|
1317
|
+
changed = true;
|
|
1318
|
+
return `${prefix}AIOPT_MODEL`;
|
|
1319
|
+
});
|
|
1320
|
+
const re2 = /(\bmodel\s*[:=]\s*)(['"])openai-codex\/gpt-5\.2\2/g;
|
|
1321
|
+
next = next.replace(re2, (_m, prefix) => {
|
|
1322
|
+
changed = true;
|
|
1323
|
+
return `${prefix}(process.env.AIOPT_MODEL_FULL || 'openai-codex/gpt-5.2-mini')`;
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
return { changed, next };
|
|
1327
|
+
}
|
|
1328
|
+
function tmpFilePath(original) {
|
|
1329
|
+
const base = import_path10.default.basename(original);
|
|
1330
|
+
const rand = Math.random().toString(16).slice(2);
|
|
1331
|
+
return import_path10.default.join(import_os.default.tmpdir(), `aiopt-fix-${base}-${rand}`);
|
|
1332
|
+
}
|
|
1333
|
+
function diffNoIndex(oldPath, newPath) {
|
|
1334
|
+
try {
|
|
1335
|
+
return (0, import_child_process.execSync)(`git diff --no-index -- ${JSON.stringify(oldPath)} ${JSON.stringify(newPath)}`, {
|
|
1336
|
+
encoding: "utf8",
|
|
1337
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1338
|
+
});
|
|
1339
|
+
} catch (e) {
|
|
1340
|
+
const out = String(e?.stdout || "");
|
|
1341
|
+
return out;
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
function normalizePatchPaths(diffText, rel) {
|
|
1345
|
+
const raw = diffText.split(/\r?\n/);
|
|
1346
|
+
const lines = [];
|
|
1347
|
+
for (let i = 0; i < raw.length; i++) {
|
|
1348
|
+
const line = raw[i];
|
|
1349
|
+
if (line.startsWith("old mode ") || line.startsWith("new mode ")) continue;
|
|
1350
|
+
if (line.startsWith("diff --git ")) {
|
|
1351
|
+
lines.push(`diff --git a/${rel} b/${rel}`);
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
if (line.startsWith("--- ")) {
|
|
1355
|
+
lines.push(`--- a/${rel}`);
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
if (line.startsWith("+++ ")) {
|
|
1359
|
+
lines.push(`+++ b/${rel}`);
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
lines.push(line);
|
|
1363
|
+
}
|
|
1364
|
+
return lines.join("\n");
|
|
1365
|
+
}
|
|
1366
|
+
function runFix(cwd, opts) {
|
|
1367
|
+
const files = [];
|
|
1368
|
+
walk2(cwd, files);
|
|
1369
|
+
const patches = [];
|
|
1370
|
+
const changedFiles = [];
|
|
1371
|
+
for (const file of files) {
|
|
1372
|
+
if (!isTextLike2(file)) continue;
|
|
1373
|
+
let st;
|
|
1374
|
+
try {
|
|
1375
|
+
st = import_fs9.default.statSync(file);
|
|
1376
|
+
} catch {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (!st.isFile()) continue;
|
|
1380
|
+
if (st.size > 1024 * 1024) continue;
|
|
1381
|
+
let content = "";
|
|
1382
|
+
try {
|
|
1383
|
+
content = import_fs9.default.readFileSync(file, "utf8");
|
|
1384
|
+
} catch {
|
|
1385
|
+
continue;
|
|
1386
|
+
}
|
|
1387
|
+
const r1 = applyRetryCap(content);
|
|
1388
|
+
const r2 = applyModelRouting(r1.next);
|
|
1389
|
+
const next = r2.next;
|
|
1390
|
+
if (next === content) continue;
|
|
1391
|
+
const tmp = tmpFilePath(file);
|
|
1392
|
+
import_fs9.default.writeFileSync(tmp, next);
|
|
1393
|
+
const rel = import_path10.default.relative(cwd, file).replace(/\\/g, "/");
|
|
1394
|
+
const d0 = diffNoIndex(file, tmp);
|
|
1395
|
+
const d = normalizePatchPaths(d0, rel);
|
|
1396
|
+
if (d && d.trim().length > 0) {
|
|
1397
|
+
patches.push(d);
|
|
1398
|
+
changedFiles.push(rel);
|
|
1399
|
+
}
|
|
1400
|
+
try {
|
|
1401
|
+
import_fs9.default.unlinkSync(tmp);
|
|
1402
|
+
} catch {
|
|
1403
|
+
}
|
|
1404
|
+
if (patches.join("\n").length > 5e5) break;
|
|
1405
|
+
}
|
|
1406
|
+
import_fs9.default.mkdirSync(opts.outDir, { recursive: true });
|
|
1407
|
+
const patchPath = import_path10.default.join(opts.outDir, "aiopt.patch");
|
|
1408
|
+
const header = [
|
|
1409
|
+
"# AIOpt patch (generated)",
|
|
1410
|
+
"# - retry cap: reduce high retry/attempt counts to 3",
|
|
1411
|
+
"# - model routing: cheap default via AIOPT_MODEL env override",
|
|
1412
|
+
""
|
|
1413
|
+
].join("\n");
|
|
1414
|
+
import_fs9.default.writeFileSync(patchPath, header + patches.join("\n"));
|
|
1415
|
+
if (opts.apply) {
|
|
1416
|
+
try {
|
|
1417
|
+
const inside = (0, import_child_process.execSync)("git rev-parse --is-inside-work-tree", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
1418
|
+
if (inside !== "true") {
|
|
1419
|
+
return { ok: false, applied: false, patchPath, changedFiles, hint: "not inside a git work tree (run from your repo root, or omit --apply and apply manually)" };
|
|
1420
|
+
}
|
|
1421
|
+
} catch {
|
|
1422
|
+
return { ok: false, applied: false, patchPath, changedFiles, hint: "git not available or not a git repo (omit --apply and apply manually)" };
|
|
1423
|
+
}
|
|
1424
|
+
try {
|
|
1425
|
+
(0, import_child_process.execSync)(`git apply ${JSON.stringify(patchPath)}`, { stdio: "inherit" });
|
|
1426
|
+
return { ok: true, applied: true, patchPath, changedFiles };
|
|
1427
|
+
} catch {
|
|
1428
|
+
return { ok: false, applied: false, patchPath, changedFiles, hint: "git apply failed. Ensure a clean working tree, then re-run; or open aiopt.patch and apply manually." };
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
return { ok: true, applied: false, patchPath, changedFiles };
|
|
1432
|
+
}
|
|
1433
|
+
var import_fs9, import_path10, import_os, import_child_process, DEFAULT_EXCLUDES2;
|
|
1434
|
+
var init_fix = __esm({
|
|
1435
|
+
"src/fix.ts"() {
|
|
1436
|
+
"use strict";
|
|
1437
|
+
import_fs9 = __toESM(require("fs"));
|
|
1438
|
+
import_path10 = __toESM(require("path"));
|
|
1439
|
+
import_os = __toESM(require("os"));
|
|
1440
|
+
import_child_process = require("child_process");
|
|
1441
|
+
DEFAULT_EXCLUDES2 = /* @__PURE__ */ new Set([
|
|
1442
|
+
".git",
|
|
1443
|
+
"node_modules",
|
|
1444
|
+
"dist",
|
|
1445
|
+
"aiopt-output",
|
|
1446
|
+
".next",
|
|
1447
|
+
"build",
|
|
1448
|
+
"coverage"
|
|
1449
|
+
]);
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1000
1453
|
// src/guard.ts
|
|
1001
1454
|
var guard_exports = {};
|
|
1002
1455
|
__export(guard_exports, {
|
|
@@ -1221,12 +1674,12 @@ __export(dashboard_exports, {
|
|
|
1221
1674
|
async function startDashboard(cwd, opts) {
|
|
1222
1675
|
const host = "127.0.0.1";
|
|
1223
1676
|
const port = opts.port || 3010;
|
|
1224
|
-
const outDir =
|
|
1225
|
-
const file = (name) =>
|
|
1677
|
+
const outDir = import_path11.default.join(cwd, "aiopt-output");
|
|
1678
|
+
const file = (name) => import_path11.default.join(outDir, name);
|
|
1226
1679
|
function readOrNull(p) {
|
|
1227
1680
|
try {
|
|
1228
|
-
if (!
|
|
1229
|
-
return
|
|
1681
|
+
if (!import_fs10.default.existsSync(p)) return null;
|
|
1682
|
+
return import_fs10.default.readFileSync(p, "utf8");
|
|
1230
1683
|
} catch {
|
|
1231
1684
|
return null;
|
|
1232
1685
|
}
|
|
@@ -1295,7 +1748,10 @@ async function startDashboard(cwd, opts) {
|
|
|
1295
1748
|
<body>
|
|
1296
1749
|
<div class="wrap">
|
|
1297
1750
|
<div class="top">
|
|
1298
|
-
<div
|
|
1751
|
+
<div>
|
|
1752
|
+
<div class="h1">AIOpt Local Dashboard</div>
|
|
1753
|
+
<div class="mini" id="baseDir">base: \u2014</div>
|
|
1754
|
+
</div>
|
|
1299
1755
|
<div class="pill"><span class="dot"></span> local-only \xB7 reads <span class="k">./aiopt-output</span></div>
|
|
1300
1756
|
</div>
|
|
1301
1757
|
|
|
@@ -1389,6 +1845,16 @@ function renderBars(el, items){
|
|
|
1389
1845
|
}
|
|
1390
1846
|
|
|
1391
1847
|
async function load(){
|
|
1848
|
+
const meta = await fetch('/api/_meta').then(r=>r.ok?r.json():null);
|
|
1849
|
+
if(meta && meta.baseDir){
|
|
1850
|
+
document.getElementById('baseDir').textContent = 'base: ' + meta.baseDir;
|
|
1851
|
+
}
|
|
1852
|
+
if(meta && meta.missing && meta.missing.length){
|
|
1853
|
+
// show missing files hint in the guard panel if nothing else yet
|
|
1854
|
+
// (prevents users from thinking it is stuck on loading)
|
|
1855
|
+
document.getElementById('guard').textContent = '(missing: ' + meta.missing.join(', ') + ')';
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1392
1858
|
const guardTxt = await fetch('/api/guard-last.txt').then(r=>r.ok?r.text():null);
|
|
1393
1859
|
const guardMeta = await fetch('/api/guard-last.json').then(r=>r.ok?r.json():null);
|
|
1394
1860
|
|
|
@@ -1508,6 +1974,13 @@ load();
|
|
|
1508
1974
|
}
|
|
1509
1975
|
if (url.startsWith("/api/")) {
|
|
1510
1976
|
const name = url.replace("/api/", "");
|
|
1977
|
+
if (name === "_meta") {
|
|
1978
|
+
const expected = ["guard-last.txt", "guard-last.json", "report.json", "report.md", "usage.jsonl", "guard-history.jsonl"];
|
|
1979
|
+
const missing = expected.filter((f) => !import_fs10.default.existsSync(file(f)));
|
|
1980
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1981
|
+
res.end(JSON.stringify({ baseDir: cwd, outDir, missing }, null, 2));
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1511
1984
|
const allow = /* @__PURE__ */ new Set(["guard-last.txt", "guard-last.json", "guard-history.jsonl", "report.md", "report.json", "usage.jsonl"]);
|
|
1512
1985
|
if (!allow.has(name)) {
|
|
1513
1986
|
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
@@ -1538,27 +2011,71 @@ load();
|
|
|
1538
2011
|
await new Promise(() => {
|
|
1539
2012
|
});
|
|
1540
2013
|
}
|
|
1541
|
-
var import_http,
|
|
2014
|
+
var import_http, import_fs10, import_path11;
|
|
1542
2015
|
var init_dashboard = __esm({
|
|
1543
2016
|
"src/dashboard.ts"() {
|
|
1544
2017
|
"use strict";
|
|
1545
2018
|
import_http = __toESM(require("http"));
|
|
1546
|
-
|
|
1547
|
-
|
|
2019
|
+
import_fs10 = __toESM(require("fs"));
|
|
2020
|
+
import_path11 = __toESM(require("path"));
|
|
2021
|
+
}
|
|
2022
|
+
});
|
|
2023
|
+
|
|
2024
|
+
// src/find-output.ts
|
|
2025
|
+
var find_output_exports = {};
|
|
2026
|
+
__export(find_output_exports, {
|
|
2027
|
+
findAioptOutputDir: () => findAioptOutputDir
|
|
2028
|
+
});
|
|
2029
|
+
function findAioptOutputDir(startCwd) {
|
|
2030
|
+
let cur = import_path12.default.resolve(startCwd);
|
|
2031
|
+
while (true) {
|
|
2032
|
+
const outDir = import_path12.default.join(cur, "aiopt-output");
|
|
2033
|
+
if (import_fs11.default.existsSync(outDir)) {
|
|
2034
|
+
try {
|
|
2035
|
+
if (import_fs11.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
|
|
2036
|
+
} catch {
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
const parent = import_path12.default.dirname(cur);
|
|
2040
|
+
if (parent === cur) break;
|
|
2041
|
+
cur = parent;
|
|
2042
|
+
}
|
|
2043
|
+
try {
|
|
2044
|
+
const base = import_path12.default.resolve(startCwd);
|
|
2045
|
+
const children = import_fs11.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path12.default.join(base, d.name));
|
|
2046
|
+
for (const child of children) {
|
|
2047
|
+
const outDir = import_path12.default.join(child, "aiopt-output");
|
|
2048
|
+
if (import_fs11.default.existsSync(outDir)) {
|
|
2049
|
+
try {
|
|
2050
|
+
if (import_fs11.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
|
|
2051
|
+
} catch {
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
} catch {
|
|
2056
|
+
}
|
|
2057
|
+
return { cwd: import_path12.default.resolve(startCwd), outDir: import_path12.default.join(import_path12.default.resolve(startCwd), "aiopt-output") };
|
|
2058
|
+
}
|
|
2059
|
+
var import_fs11, import_path12;
|
|
2060
|
+
var init_find_output = __esm({
|
|
2061
|
+
"src/find-output.ts"() {
|
|
2062
|
+
"use strict";
|
|
2063
|
+
import_fs11 = __toESM(require("fs"));
|
|
2064
|
+
import_path12 = __toESM(require("path"));
|
|
1548
2065
|
}
|
|
1549
2066
|
});
|
|
1550
2067
|
|
|
1551
2068
|
// src/rates-util.ts
|
|
1552
2069
|
function loadRateTableFromDistPath() {
|
|
1553
|
-
const p =
|
|
1554
|
-
return JSON.parse(
|
|
2070
|
+
const p = import_path13.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2071
|
+
return JSON.parse(import_fs12.default.readFileSync(p, "utf8"));
|
|
1555
2072
|
}
|
|
1556
|
-
var
|
|
2073
|
+
var import_fs12, import_path13;
|
|
1557
2074
|
var init_rates_util = __esm({
|
|
1558
2075
|
"src/rates-util.ts"() {
|
|
1559
2076
|
"use strict";
|
|
1560
|
-
|
|
1561
|
-
|
|
2077
|
+
import_fs12 = __toESM(require("fs"));
|
|
2078
|
+
import_path13 = __toESM(require("path"));
|
|
1562
2079
|
}
|
|
1563
2080
|
});
|
|
1564
2081
|
|
|
@@ -1569,8 +2086,8 @@ __export(quickstart_exports, {
|
|
|
1569
2086
|
seedDemoUsage: () => seedDemoUsage
|
|
1570
2087
|
});
|
|
1571
2088
|
function seedDemoUsage(outDir) {
|
|
1572
|
-
|
|
1573
|
-
const p =
|
|
2089
|
+
import_fs13.default.mkdirSync(outDir, { recursive: true });
|
|
2090
|
+
const p = import_path14.default.join(outDir, "usage.jsonl");
|
|
1574
2091
|
const now = Date.now();
|
|
1575
2092
|
const lines = [];
|
|
1576
2093
|
for (let i = 0; i < 60; i++) {
|
|
@@ -1587,18 +2104,18 @@ function seedDemoUsage(outDir) {
|
|
|
1587
2104
|
meta: { feature_tag: i % 2 ? "summarize" : "coding" }
|
|
1588
2105
|
});
|
|
1589
2106
|
}
|
|
1590
|
-
|
|
2107
|
+
import_fs13.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1591
2108
|
return p;
|
|
1592
2109
|
}
|
|
1593
2110
|
function runQuickstart(cwd, opts) {
|
|
1594
|
-
const outDir =
|
|
2111
|
+
const outDir = import_path14.default.join(cwd, "aiopt-output");
|
|
1595
2112
|
const usagePath = seedDemoUsage(outDir);
|
|
1596
2113
|
const rt = loadRateTableFromDistPath();
|
|
1597
2114
|
const { readJsonl: readJsonl2 } = (init_io(), __toCommonJS(io_exports));
|
|
1598
2115
|
const events = readJsonl2(usagePath);
|
|
1599
2116
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
1600
|
-
|
|
1601
|
-
|
|
2117
|
+
import_fs13.default.writeFileSync(import_path14.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
|
|
2118
|
+
import_fs13.default.writeFileSync(import_path14.default.join(outDir, "report.json"), JSON.stringify({
|
|
1602
2119
|
version: 3,
|
|
1603
2120
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1604
2121
|
confidence: analysis.unknown_models?.length ? "MEDIUM" : "HIGH",
|
|
@@ -1618,8 +2135,8 @@ function runQuickstart(cwd, opts) {
|
|
|
1618
2135
|
unknown_models: analysis.unknown_models || [],
|
|
1619
2136
|
notes: []
|
|
1620
2137
|
}, null, 2));
|
|
1621
|
-
|
|
1622
|
-
|
|
2138
|
+
import_fs13.default.writeFileSync(import_path14.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2139
|
+
import_fs13.default.writeFileSync(import_path14.default.join(outDir, "report.md"), "# AIOpt quickstart demo\n\nThis is a demo report generated by `aiopt quickstart --demo`.\n");
|
|
1623
2140
|
const r = runGuard(rt, {
|
|
1624
2141
|
baselineEvents: events,
|
|
1625
2142
|
candidate: {
|
|
@@ -1630,12 +2147,12 @@ function runQuickstart(cwd, opts) {
|
|
|
1630
2147
|
});
|
|
1631
2148
|
return { usagePath, outDir, guard: r, port: opts.port };
|
|
1632
2149
|
}
|
|
1633
|
-
var
|
|
2150
|
+
var import_fs13, import_path14;
|
|
1634
2151
|
var init_quickstart = __esm({
|
|
1635
2152
|
"src/quickstart.ts"() {
|
|
1636
2153
|
"use strict";
|
|
1637
|
-
|
|
1638
|
-
|
|
2154
|
+
import_fs13 = __toESM(require("fs"));
|
|
2155
|
+
import_path14 = __toESM(require("path"));
|
|
1639
2156
|
init_scan();
|
|
1640
2157
|
init_rates_util();
|
|
1641
2158
|
init_guard();
|
|
@@ -1643,8 +2160,8 @@ var init_quickstart = __esm({
|
|
|
1643
2160
|
});
|
|
1644
2161
|
|
|
1645
2162
|
// src/cli.ts
|
|
1646
|
-
var
|
|
1647
|
-
var
|
|
2163
|
+
var import_fs14 = __toESM(require("fs"));
|
|
2164
|
+
var import_path15 = __toESM(require("path"));
|
|
1648
2165
|
var import_commander = require("commander");
|
|
1649
2166
|
init_io();
|
|
1650
2167
|
init_scan();
|
|
@@ -1652,17 +2169,17 @@ var program = new import_commander.Command();
|
|
|
1652
2169
|
var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
|
|
1653
2170
|
var DEFAULT_OUTPUT_DIR = "./aiopt-output";
|
|
1654
2171
|
function loadRateTable() {
|
|
1655
|
-
const p =
|
|
1656
|
-
return JSON.parse(
|
|
2172
|
+
const p = import_path15.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2173
|
+
return JSON.parse(import_fs14.default.readFileSync(p, "utf8"));
|
|
1657
2174
|
}
|
|
1658
2175
|
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);
|
|
1659
2176
|
program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
|
|
1660
2177
|
ensureDir("./aiopt-input");
|
|
1661
2178
|
ensureDir("./aiopt-output");
|
|
1662
|
-
const sampleSrc =
|
|
1663
|
-
const dst =
|
|
1664
|
-
if (!
|
|
1665
|
-
|
|
2179
|
+
const sampleSrc = import_path15.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
|
|
2180
|
+
const dst = import_path15.default.join("./aiopt-input", "usage.jsonl");
|
|
2181
|
+
if (!import_fs14.default.existsSync(dst)) {
|
|
2182
|
+
import_fs14.default.copyFileSync(sampleSrc, dst);
|
|
1666
2183
|
console.log("Created ./aiopt-input/usage.jsonl (sample)");
|
|
1667
2184
|
} else {
|
|
1668
2185
|
console.log("Exists ./aiopt-input/usage.jsonl (skip)");
|
|
@@ -1672,7 +2189,7 @@ program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.json
|
|
|
1672
2189
|
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) => {
|
|
1673
2190
|
const inputPath = String(opts.input);
|
|
1674
2191
|
const outDir = String(opts.out);
|
|
1675
|
-
if (!
|
|
2192
|
+
if (!import_fs14.default.existsSync(inputPath)) {
|
|
1676
2193
|
console.error(`Input not found: ${inputPath}`);
|
|
1677
2194
|
process.exit(1);
|
|
1678
2195
|
}
|
|
@@ -1680,7 +2197,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
|
|
|
1680
2197
|
const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);
|
|
1681
2198
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
1682
2199
|
policy.generated_from.input = inputPath;
|
|
1683
|
-
writeOutputs(outDir, analysis, savings, policy, meta);
|
|
2200
|
+
writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
|
|
1684
2201
|
const { buildTopFixes: buildTopFixes2 } = await Promise.resolve().then(() => (init_solutions(), solutions_exports));
|
|
1685
2202
|
const fixes = buildTopFixes2(analysis, savings).slice(0, 3);
|
|
1686
2203
|
console.log("Top Fix 3:");
|
|
@@ -1688,7 +2205,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
|
|
|
1688
2205
|
const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
|
|
1689
2206
|
console.log(`${i + 1}) ${f.title} ${tag}`);
|
|
1690
2207
|
});
|
|
1691
|
-
console.log(`Report: ${
|
|
2208
|
+
console.log(`Report: ${import_path15.default.join(outDir, "report.md")}`);
|
|
1692
2209
|
});
|
|
1693
2210
|
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) => {
|
|
1694
2211
|
const inputPath = String(opts.input);
|
|
@@ -1698,7 +2215,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
|
|
|
1698
2215
|
const { policy } = analyze(rt, events);
|
|
1699
2216
|
policy.generated_from.input = inputPath;
|
|
1700
2217
|
ensureDir(outDir);
|
|
1701
|
-
|
|
2218
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
1702
2219
|
console.log(`OK: ${outDir}/cost-policy.json`);
|
|
1703
2220
|
});
|
|
1704
2221
|
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) => {
|
|
@@ -1738,7 +2255,7 @@ licenseCmd.command("verify").option("--path <path>", "license.json path (default
|
|
|
1738
2255
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
1739
2256
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
1740
2257
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
1741
|
-
if (!
|
|
2258
|
+
if (!import_fs14.default.existsSync(p)) {
|
|
1742
2259
|
console.error(`FAIL: license file not found: ${p}`);
|
|
1743
2260
|
process.exit(3);
|
|
1744
2261
|
}
|
|
@@ -1755,7 +2272,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
1755
2272
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
1756
2273
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
1757
2274
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
1758
|
-
if (!
|
|
2275
|
+
if (!import_fs14.default.existsSync(p)) {
|
|
1759
2276
|
console.log("NO_LICENSE");
|
|
1760
2277
|
process.exit(2);
|
|
1761
2278
|
}
|
|
@@ -1768,6 +2285,44 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
1768
2285
|
console.log(`INVALID: ${v.reason || "unknown"}`);
|
|
1769
2286
|
process.exit(3);
|
|
1770
2287
|
});
|
|
2288
|
+
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) => {
|
|
2289
|
+
const inputPath = String(opts.input);
|
|
2290
|
+
const outDir = String(opts.out);
|
|
2291
|
+
if (!import_fs14.default.existsSync(inputPath)) {
|
|
2292
|
+
console.error(`FAIL: input not found: ${inputPath}`);
|
|
2293
|
+
process.exit(1);
|
|
2294
|
+
}
|
|
2295
|
+
const rt = loadRateTable();
|
|
2296
|
+
const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);
|
|
2297
|
+
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
2298
|
+
policy.generated_from.input = inputPath;
|
|
2299
|
+
writeOutputs(outDir, analysis, savings, policy, { ...meta, cwd: process.cwd(), cliVersion: program.version() });
|
|
2300
|
+
const { runGate: runGate2, formatGateStdout: formatGateStdout2 } = await Promise.resolve().then(() => (init_gate(), gate_exports));
|
|
2301
|
+
const r = runGate2(outDir, process.cwd());
|
|
2302
|
+
const out = formatGateStdout2(r, outDir);
|
|
2303
|
+
console.log(out.text);
|
|
2304
|
+
process.exit(out.exitCode);
|
|
2305
|
+
});
|
|
2306
|
+
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) => {
|
|
2307
|
+
const outDir = String(opts.out);
|
|
2308
|
+
const { runFix: runFix2 } = await Promise.resolve().then(() => (init_fix(), fix_exports));
|
|
2309
|
+
const r = runFix2(process.cwd(), { outDir, apply: Boolean(opts.apply) });
|
|
2310
|
+
console.log(`Patch: ${r.patchPath}`);
|
|
2311
|
+
if (r.changedFiles.length) {
|
|
2312
|
+
console.log(`Files: ${r.changedFiles.slice(0, 10).join(", ")}${r.changedFiles.length > 10 ? " ..." : ""}`);
|
|
2313
|
+
} else {
|
|
2314
|
+
console.log("No changes suggested.");
|
|
2315
|
+
}
|
|
2316
|
+
if (r.applied) {
|
|
2317
|
+
console.log("OK: patch applied");
|
|
2318
|
+
process.exit(0);
|
|
2319
|
+
}
|
|
2320
|
+
if (!r.ok) {
|
|
2321
|
+
console.error(`FAIL: could not apply patch${r.hint ? ` (${r.hint})` : ""}`);
|
|
2322
|
+
process.exit(1);
|
|
2323
|
+
}
|
|
2324
|
+
process.exit(0);
|
|
2325
|
+
});
|
|
1771
2326
|
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) => {
|
|
1772
2327
|
const rt = loadRateTable();
|
|
1773
2328
|
const baselinePath = String(opts.baseline || opts.input);
|
|
@@ -1777,11 +2332,11 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
1777
2332
|
console.error("FAIL: diff mode requires both --baseline and --candidate");
|
|
1778
2333
|
process.exit(3);
|
|
1779
2334
|
}
|
|
1780
|
-
if (!
|
|
2335
|
+
if (!import_fs14.default.existsSync(baselinePath)) {
|
|
1781
2336
|
console.error(`FAIL: baseline not found: ${baselinePath}`);
|
|
1782
2337
|
process.exit(3);
|
|
1783
2338
|
}
|
|
1784
|
-
if (candidatePath && !
|
|
2339
|
+
if (candidatePath && !import_fs14.default.existsSync(candidatePath)) {
|
|
1785
2340
|
console.error(`FAIL: candidate not found: ${candidatePath}`);
|
|
1786
2341
|
process.exit(3);
|
|
1787
2342
|
}
|
|
@@ -1803,20 +2358,27 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
1803
2358
|
});
|
|
1804
2359
|
console.log(r.message);
|
|
1805
2360
|
try {
|
|
1806
|
-
const outDir =
|
|
1807
|
-
|
|
2361
|
+
const outDir = import_path15.default.resolve(DEFAULT_OUTPUT_DIR);
|
|
2362
|
+
import_fs14.default.mkdirSync(outDir, { recursive: true });
|
|
1808
2363
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1809
|
-
|
|
1810
|
-
|
|
2364
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "guard-last.txt"), r.message);
|
|
2365
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
|
|
1811
2366
|
const histLine = JSON.stringify({ ts, exitCode: r.exitCode, mode: candidateEvents ? "diff" : "transform", baseline: baselinePath, candidate: candidatePath }) + "\n";
|
|
1812
|
-
|
|
2367
|
+
import_fs14.default.appendFileSync(import_path15.default.join(outDir, "guard-history.jsonl"), histLine);
|
|
1813
2368
|
} catch {
|
|
1814
2369
|
}
|
|
1815
2370
|
process.exit(r.exitCode);
|
|
1816
2371
|
});
|
|
1817
|
-
program.command("dashboard").description("Local dashboard (localhost only): view last guard + last scan outputs").option("--port <n>", "port (default: 3010)", (v) => Number(v), 3010).action(async (opts) => {
|
|
2372
|
+
program.command("dashboard").description("Local dashboard (localhost only): view last guard + last scan outputs").option("--port <n>", "port (default: 3010)", (v) => Number(v), 3010).option("--dir <path>", "base directory containing ./aiopt-output (default: cwd)").option("--auto", "auto-detect by searching parents (and one-level children) for aiopt-output").action(async (opts) => {
|
|
1818
2373
|
const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
1819
|
-
|
|
2374
|
+
const base = opts.dir ? String(opts.dir) : process.cwd();
|
|
2375
|
+
if (opts.auto) {
|
|
2376
|
+
const { findAioptOutputDir: findAioptOutputDir2 } = await Promise.resolve().then(() => (init_find_output(), find_output_exports));
|
|
2377
|
+
const found = findAioptOutputDir2(base);
|
|
2378
|
+
await startDashboard2(found.cwd, { port: Number(opts.port || 3010) });
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
await startDashboard2(base, { port: Number(opts.port || 3010) });
|
|
1820
2382
|
});
|
|
1821
2383
|
program.command("quickstart").description("1-minute demo: generate sample usage, run scan+guard, and print dashboard URL").option("--demo", "run demo workflow (writes to ./aiopt-output)").option("--port <n>", "dashboard port (default: 3010)", (v) => Number(v), 3010).option("--budget-monthly <usd>", "optional budget gate for the demo guard", (v) => Number(v)).option("--serve", "start the local dashboard after generating demo outputs").option("--open", "best-effort open browser to the dashboard URL").action(async (opts) => {
|
|
1822
2384
|
if (!opts.demo) {
|
|
@@ -1830,14 +2392,14 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
|
|
|
1830
2392
|
console.log("--- guard ---");
|
|
1831
2393
|
console.log(r.guard.message);
|
|
1832
2394
|
try {
|
|
1833
|
-
const
|
|
1834
|
-
const
|
|
1835
|
-
|
|
2395
|
+
const fs15 = await import("fs");
|
|
2396
|
+
const path16 = await import("path");
|
|
2397
|
+
fs15.mkdirSync(r.outDir, { recursive: true });
|
|
1836
2398
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1837
|
-
|
|
1838
|
-
|
|
2399
|
+
fs15.writeFileSync(path16.join(r.outDir, "guard-last.txt"), r.guard.message);
|
|
2400
|
+
fs15.writeFileSync(path16.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
|
|
1839
2401
|
const histLine = JSON.stringify({ ts, exitCode: r.guard.exitCode, mode: "quickstart", baseline: r.usagePath, candidate: null }) + "\n";
|
|
1840
|
-
|
|
2402
|
+
fs15.appendFileSync(path16.join(r.outDir, "guard-history.jsonl"), histLine);
|
|
1841
2403
|
} catch {
|
|
1842
2404
|
}
|
|
1843
2405
|
console.log("--- next ---");
|
|
@@ -1846,11 +2408,11 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
|
|
|
1846
2408
|
const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
1847
2409
|
if (opts.open) {
|
|
1848
2410
|
try {
|
|
1849
|
-
const { execSync } = await import("child_process");
|
|
2411
|
+
const { execSync: execSync2 } = await import("child_process");
|
|
1850
2412
|
const url = `http://127.0.0.1:${port}/`;
|
|
1851
|
-
if (process.platform === "darwin")
|
|
1852
|
-
else if (process.platform === "win32")
|
|
1853
|
-
else
|
|
2413
|
+
if (process.platform === "darwin") execSync2(`open "${url}"`);
|
|
2414
|
+
else if (process.platform === "win32") execSync2(`cmd.exe /c start "" "${url}"`);
|
|
2415
|
+
else execSync2(`xdg-open "${url}"`);
|
|
1854
2416
|
} catch {
|
|
1855
2417
|
}
|
|
1856
2418
|
}
|