aiopt 0.3.1 → 0.3.3
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 +367 -111
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -656,7 +656,7 @@ var require_package = __commonJS({
|
|
|
656
656
|
"package.json"(exports2, module2) {
|
|
657
657
|
module2.exports = {
|
|
658
658
|
name: "aiopt",
|
|
659
|
-
version: "0.3.
|
|
659
|
+
version: "0.3.3",
|
|
660
660
|
description: "Pre-deploy LLM cost accident guardrail (serverless local CLI)",
|
|
661
661
|
bin: {
|
|
662
662
|
aiopt: "dist/cli.js"
|
|
@@ -1666,6 +1666,145 @@ var init_guard = __esm({
|
|
|
1666
1666
|
}
|
|
1667
1667
|
});
|
|
1668
1668
|
|
|
1669
|
+
// src/collect.ts
|
|
1670
|
+
function exists(p) {
|
|
1671
|
+
try {
|
|
1672
|
+
return import_fs10.default.existsSync(p);
|
|
1673
|
+
} catch {
|
|
1674
|
+
return false;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
function safeReadJsonl(p) {
|
|
1678
|
+
const out = [];
|
|
1679
|
+
try {
|
|
1680
|
+
const txt = import_fs10.default.readFileSync(p, "utf8");
|
|
1681
|
+
for (const line of txt.split(/\r?\n/)) {
|
|
1682
|
+
if (!line.trim()) continue;
|
|
1683
|
+
try {
|
|
1684
|
+
out.push(JSON.parse(line));
|
|
1685
|
+
} catch {
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
return out;
|
|
1691
|
+
}
|
|
1692
|
+
function listJsonlFiles(dir) {
|
|
1693
|
+
const out = [];
|
|
1694
|
+
try {
|
|
1695
|
+
for (const name of import_fs10.default.readdirSync(dir)) {
|
|
1696
|
+
const full = import_path11.default.join(dir, name);
|
|
1697
|
+
let st;
|
|
1698
|
+
try {
|
|
1699
|
+
st = import_fs10.default.statSync(full);
|
|
1700
|
+
} catch {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
|
|
1704
|
+
}
|
|
1705
|
+
} catch {
|
|
1706
|
+
}
|
|
1707
|
+
return out;
|
|
1708
|
+
}
|
|
1709
|
+
function findOpenClawSessionLogs() {
|
|
1710
|
+
const home = import_os2.default.homedir();
|
|
1711
|
+
const root = import_path11.default.join(home, ".openclaw", "agents");
|
|
1712
|
+
if (!exists(root)) return [];
|
|
1713
|
+
const found = [];
|
|
1714
|
+
let agents = [];
|
|
1715
|
+
try {
|
|
1716
|
+
agents = import_fs10.default.readdirSync(root);
|
|
1717
|
+
} catch {
|
|
1718
|
+
agents = [];
|
|
1719
|
+
}
|
|
1720
|
+
for (const a of agents) {
|
|
1721
|
+
const sessDir = import_path11.default.join(root, a, "sessions");
|
|
1722
|
+
if (!exists(sessDir)) continue;
|
|
1723
|
+
for (const f of listJsonlFiles(sessDir)) found.push(f);
|
|
1724
|
+
}
|
|
1725
|
+
return found;
|
|
1726
|
+
}
|
|
1727
|
+
function parseOpenClawSessionFile(p) {
|
|
1728
|
+
const rows = safeReadJsonl(p);
|
|
1729
|
+
const events = [];
|
|
1730
|
+
for (const r of rows) {
|
|
1731
|
+
if (r && r.type === "message" && r.message && typeof r.message === "object") {
|
|
1732
|
+
const m = r.message;
|
|
1733
|
+
const u = m.usage;
|
|
1734
|
+
if (!u) continue;
|
|
1735
|
+
const input = Number(u.input ?? u.prompt ?? u.prompt_tokens ?? 0);
|
|
1736
|
+
const output = Number(u.output ?? u.completion ?? u.completion_tokens ?? 0);
|
|
1737
|
+
const costTotal = u.cost && typeof u.cost === "object" ? Number(u.cost.total ?? u.costTotal ?? u.cost_usd) : void 0;
|
|
1738
|
+
let tsRaw = m.timestamp || r.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1739
|
+
let ts = String(tsRaw);
|
|
1740
|
+
if (String(tsRaw).match(/^\d{10,}$/)) {
|
|
1741
|
+
try {
|
|
1742
|
+
ts = new Date(Number(tsRaw)).toISOString();
|
|
1743
|
+
} catch {
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
const provider = String(m.provider || r.provider || "openclaw");
|
|
1747
|
+
const model = String(m.model || r.modelId || "unknown");
|
|
1748
|
+
if (!Number.isFinite(input) && !Number.isFinite(output) && !Number.isFinite(costTotal)) continue;
|
|
1749
|
+
events.push({
|
|
1750
|
+
ts,
|
|
1751
|
+
provider,
|
|
1752
|
+
model,
|
|
1753
|
+
input_tokens: Number.isFinite(input) ? input : 0,
|
|
1754
|
+
output_tokens: Number.isFinite(output) ? output : 0,
|
|
1755
|
+
retries: 0,
|
|
1756
|
+
status: "ok",
|
|
1757
|
+
cost_usd: Number.isFinite(costTotal) ? Number(costTotal) : void 0,
|
|
1758
|
+
meta: {
|
|
1759
|
+
source: "openclaw-session",
|
|
1760
|
+
session_file: p,
|
|
1761
|
+
cache_read_tokens: u.cacheRead,
|
|
1762
|
+
cache_write_tokens: u.cacheWrite,
|
|
1763
|
+
total_tokens: u.totalTokens
|
|
1764
|
+
}
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return events;
|
|
1769
|
+
}
|
|
1770
|
+
function stableKey(e) {
|
|
1771
|
+
return `${e.ts}|${e.provider}|${e.model}|${e.input_tokens}|${e.output_tokens}|${e.cost_usd ?? ""}`;
|
|
1772
|
+
}
|
|
1773
|
+
function collectToUsageJsonl(outPath) {
|
|
1774
|
+
const all = [];
|
|
1775
|
+
const sources = [];
|
|
1776
|
+
const ocFiles = findOpenClawSessionLogs();
|
|
1777
|
+
let ocEvents = 0;
|
|
1778
|
+
for (const f of ocFiles) {
|
|
1779
|
+
const evs = parseOpenClawSessionFile(f);
|
|
1780
|
+
ocEvents += evs.length;
|
|
1781
|
+
all.push(...evs);
|
|
1782
|
+
}
|
|
1783
|
+
sources.push({ name: "openclaw", files: ocFiles.length, events: ocEvents });
|
|
1784
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1785
|
+
const uniq = [];
|
|
1786
|
+
for (const e of all) {
|
|
1787
|
+
const k = stableKey(e);
|
|
1788
|
+
if (seen.has(k)) continue;
|
|
1789
|
+
seen.add(k);
|
|
1790
|
+
uniq.push(e);
|
|
1791
|
+
}
|
|
1792
|
+
uniq.sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
|
|
1793
|
+
import_fs10.default.mkdirSync(import_path11.default.dirname(outPath), { recursive: true });
|
|
1794
|
+
const lines = uniq.map((e) => JSON.stringify(e)).join("\n") + (uniq.length ? "\n" : "");
|
|
1795
|
+
import_fs10.default.writeFileSync(outPath, lines);
|
|
1796
|
+
return { outPath, sources, eventsWritten: uniq.length };
|
|
1797
|
+
}
|
|
1798
|
+
var import_fs10, import_path11, import_os2;
|
|
1799
|
+
var init_collect = __esm({
|
|
1800
|
+
"src/collect.ts"() {
|
|
1801
|
+
"use strict";
|
|
1802
|
+
import_fs10 = __toESM(require("fs"));
|
|
1803
|
+
import_path11 = __toESM(require("path"));
|
|
1804
|
+
import_os2 = __toESM(require("os"));
|
|
1805
|
+
}
|
|
1806
|
+
});
|
|
1807
|
+
|
|
1669
1808
|
// src/dashboard.ts
|
|
1670
1809
|
var dashboard_exports = {};
|
|
1671
1810
|
__export(dashboard_exports, {
|
|
@@ -1674,12 +1813,26 @@ __export(dashboard_exports, {
|
|
|
1674
1813
|
async function startDashboard(cwd, opts) {
|
|
1675
1814
|
const host = "127.0.0.1";
|
|
1676
1815
|
const port = opts.port || 3010;
|
|
1677
|
-
const outDir =
|
|
1678
|
-
const file = (name) =>
|
|
1816
|
+
const outDir = import_path12.default.join(cwd, "aiopt-output");
|
|
1817
|
+
const file = (name) => import_path12.default.join(outDir, name);
|
|
1818
|
+
let lastCollect = null;
|
|
1819
|
+
let lastCollectError = null;
|
|
1820
|
+
function ensureUsageFile() {
|
|
1821
|
+
try {
|
|
1822
|
+
const usagePath = file("usage.jsonl");
|
|
1823
|
+
if (import_fs11.default.existsSync(usagePath)) return;
|
|
1824
|
+
const r = collectToUsageJsonl(usagePath);
|
|
1825
|
+
lastCollect = { ts: (/* @__PURE__ */ new Date()).toISOString(), outPath: r.outPath, sources: r.sources, eventsWritten: r.eventsWritten };
|
|
1826
|
+
lastCollectError = null;
|
|
1827
|
+
} catch (e) {
|
|
1828
|
+
lastCollectError = String(e?.message || e || "collect failed");
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
ensureUsageFile();
|
|
1679
1832
|
function readOrNull(p) {
|
|
1680
1833
|
try {
|
|
1681
|
-
if (!
|
|
1682
|
-
return
|
|
1834
|
+
if (!import_fs11.default.existsSync(p)) return null;
|
|
1835
|
+
return import_fs11.default.readFileSync(p, "utf8");
|
|
1683
1836
|
} catch {
|
|
1684
1837
|
return null;
|
|
1685
1838
|
}
|
|
@@ -1751,8 +1904,9 @@ async function startDashboard(cwd, opts) {
|
|
|
1751
1904
|
<div>
|
|
1752
1905
|
<div class="h1">AIOpt Local Dashboard</div>
|
|
1753
1906
|
<div class="mini" id="baseDir">base: \u2014</div>
|
|
1907
|
+
<div class="mini" id="missingHint" style="margin-top:4px">checking files\u2026</div>
|
|
1754
1908
|
</div>
|
|
1755
|
-
<div class="pill"><span class="dot"></span> local-only \xB7 reads <span class="k">./aiopt-output</span></div>
|
|
1909
|
+
<div class="pill"><span class="dot"></span> local-only \xB7 reads <span class="k">./aiopt-output</span> \xB7 <span id="live" class="muted">live: off</span></div>
|
|
1756
1910
|
</div>
|
|
1757
1911
|
|
|
1758
1912
|
<div class="grid">
|
|
@@ -1764,7 +1918,7 @@ async function startDashboard(cwd, opts) {
|
|
|
1764
1918
|
<div style="height:8px"></div>
|
|
1765
1919
|
<div id="guardBadge" class="badge"><span class="b"></span><span id="guardBadgeText">loading\u2026</span></div>
|
|
1766
1920
|
<div style="height:10px"></div>
|
|
1767
|
-
<pre id="guard">loading\u2026</pre>
|
|
1921
|
+
<pre id="guard">loading\u2026 (if this stays, it usually means required files are missing \u2014 see the top \u201Cmissing\u201D line)</pre>
|
|
1768
1922
|
<div style="height:10px"></div>
|
|
1769
1923
|
<div class="row">
|
|
1770
1924
|
<a href="/api/guard-last.txt" target="_blank">raw txt</a>
|
|
@@ -1777,6 +1931,14 @@ async function startDashboard(cwd, opts) {
|
|
|
1777
1931
|
<div style="height:12px"></div>
|
|
1778
1932
|
<div style="font-weight:900">Recent guard runs</div>
|
|
1779
1933
|
<pre id="guardHist" style="max-height:220px; overflow:auto">loading\u2026</pre>
|
|
1934
|
+
<div class="mini" style="margin-top:8px">
|
|
1935
|
+
Quick actions (copy/paste):
|
|
1936
|
+
<div style="margin-top:6px; display:flex; flex-wrap:wrap; gap:8px">
|
|
1937
|
+
<span class="k">aiopt quickstart --demo</span>
|
|
1938
|
+
<span class="k">aiopt guard --help</span>
|
|
1939
|
+
<span class="k">aiopt scan</span>
|
|
1940
|
+
</div>
|
|
1941
|
+
</div>
|
|
1780
1942
|
</div>
|
|
1781
1943
|
|
|
1782
1944
|
<div class="card c6">
|
|
@@ -1792,9 +1954,17 @@ async function startDashboard(cwd, opts) {
|
|
|
1792
1954
|
|
|
1793
1955
|
<div style="height:12px"></div>
|
|
1794
1956
|
<div class="row" style="justify-content:space-between">
|
|
1795
|
-
<div style="font-weight:900">
|
|
1957
|
+
<div style="font-weight:900">Live usage (last 60m)</div>
|
|
1796
1958
|
<div class="mini"><a href="/api/usage.jsonl" target="_blank">usage.jsonl</a></div>
|
|
1797
1959
|
</div>
|
|
1960
|
+
<div id="liveSvg" style="margin-top:8px"></div>
|
|
1961
|
+
<pre id="liveText" style="margin-top:8px">loading\u2026</pre>
|
|
1962
|
+
|
|
1963
|
+
<div style="height:12px"></div>
|
|
1964
|
+
<div class="row" style="justify-content:space-between">
|
|
1965
|
+
<div style="font-weight:900">Cost trend (last 7d)</div>
|
|
1966
|
+
<div class="mini">(from usage.jsonl)</div>
|
|
1967
|
+
</div>
|
|
1798
1968
|
<div id="trendSvg" style="margin-top:8px"></div>
|
|
1799
1969
|
<pre id="trend" style="margin-top:8px">loading\u2026</pre>
|
|
1800
1970
|
|
|
@@ -1844,19 +2014,41 @@ function renderBars(el, items){
|
|
|
1844
2014
|
}
|
|
1845
2015
|
}
|
|
1846
2016
|
|
|
2017
|
+
let __live = false;
|
|
2018
|
+
let __tick = 0;
|
|
2019
|
+
|
|
1847
2020
|
async function load(){
|
|
1848
|
-
|
|
2021
|
+
__tick++;
|
|
2022
|
+
// If fetch hangs / fails, do not leave \u201Cloading\u2026\u201D forever.
|
|
2023
|
+
const timer = setTimeout(()=>{
|
|
2024
|
+
const el = document.getElementById('missingHint');
|
|
2025
|
+
if(el && el.textContent && el.textContent.includes('checking')){
|
|
2026
|
+
el.textContent = 'still loading\u2026 (if this doesn't change, refresh. If it persists: run aiopt quickstart --demo or aiopt scan)';
|
|
2027
|
+
}
|
|
2028
|
+
}, 1500);
|
|
2029
|
+
|
|
2030
|
+
let meta = null;
|
|
2031
|
+
try{
|
|
2032
|
+
meta = await fetch('/api/_meta', { cache: 'no-store' }).then(r=>r.ok?r.json():null);
|
|
2033
|
+
}catch{}
|
|
2034
|
+
clearTimeout(timer);
|
|
2035
|
+
|
|
1849
2036
|
if(meta && meta.baseDir){
|
|
1850
2037
|
document.getElementById('baseDir').textContent = 'base: ' + meta.baseDir;
|
|
1851
2038
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
2039
|
+
|
|
2040
|
+
const miss = (meta && meta.missing) ? meta.missing : null;
|
|
2041
|
+
const hint = document.getElementById('missingHint');
|
|
2042
|
+
if(miss && miss.length){
|
|
2043
|
+
hint.textContent = 'missing: ' + miss.join(', ') + ' \u2192 not broken. Run: aiopt quickstart --demo (or aiopt scan)';
|
|
2044
|
+
} else if(miss && miss.length===0){
|
|
2045
|
+
hint.textContent = 'missing: (none)';
|
|
2046
|
+
} else {
|
|
2047
|
+
hint.textContent = 'missing: (unknown \u2014 failed to load /api/_meta)';
|
|
1856
2048
|
}
|
|
1857
2049
|
|
|
1858
|
-
const guardTxt = await fetch('/api/guard-last.txt').then(r=>r.ok?r.text():null);
|
|
1859
|
-
const guardMeta = await fetch('/api/guard-last.json').then(r=>r.ok?r.json():null);
|
|
2050
|
+
const guardTxt = await fetch('/api/guard-last.txt', { cache: 'no-store' }).then(r=>r.ok?r.text():null).catch(()=>null);
|
|
2051
|
+
const guardMeta = await fetch('/api/guard-last.json', { cache: 'no-store' }).then(r=>r.ok?r.json():null).catch(()=>null);
|
|
1860
2052
|
|
|
1861
2053
|
document.getElementById('guard').textContent = guardTxt || '(no guard-last.txt yet \u2014 run: aiopt guard)';
|
|
1862
2054
|
if(guardMeta){
|
|
@@ -1870,7 +2062,7 @@ async function load(){
|
|
|
1870
2062
|
else {badge.classList.add('fail'); t.textContent='FAIL (3)';}
|
|
1871
2063
|
}
|
|
1872
2064
|
|
|
1873
|
-
const histTxt = await fetch('/api/guard-history.jsonl').then(r=>r.ok?r.text():null);
|
|
2065
|
+
const histTxt = await fetch('/api/guard-history.jsonl', { cache: 'no-store' }).then(r=>r.ok?r.text():null).catch(()=>null);
|
|
1874
2066
|
if(histTxt){
|
|
1875
2067
|
const lines = histTxt.trim().split('
|
|
1876
2068
|
').filter(Boolean).slice(-15).reverse();
|
|
@@ -1891,7 +2083,7 @@ async function load(){
|
|
|
1891
2083
|
document.getElementById('guardHist').textContent = '(no guard-history.jsonl yet \u2014 run: aiopt guard)';
|
|
1892
2084
|
}
|
|
1893
2085
|
|
|
1894
|
-
const reportJson = await fetch('/api/report.json').then(r=>r.ok?r.json():null);
|
|
2086
|
+
const reportJson = await fetch('/api/report.json', { cache: 'no-store' }).then(r=>r.ok?r.json():null).catch(()=>null);
|
|
1895
2087
|
if(reportJson){
|
|
1896
2088
|
const total = reportJson.summary && reportJson.summary.total_cost_usd;
|
|
1897
2089
|
const sav = reportJson.summary && reportJson.summary.estimated_savings_usd;
|
|
@@ -1904,11 +2096,16 @@ async function load(){
|
|
|
1904
2096
|
document.getElementById('scanMeta').textContent = '(no report.json yet \u2014 run: aiopt scan)';
|
|
1905
2097
|
}
|
|
1906
2098
|
|
|
1907
|
-
const usageTxt = await fetch('/api/usage.jsonl').then(r=>r.ok?r.text():null);
|
|
2099
|
+
const usageTxt = await fetch('/api/usage.jsonl', { cache: 'no-store' }).then(r=>r.ok?r.text():null).catch(()=>null);
|
|
1908
2100
|
if(usageTxt){
|
|
1909
|
-
// 7d cost trend: sum(cost_usd) per day from usage.jsonl (ev.ts).
|
|
1910
2101
|
const now = Date.now();
|
|
2102
|
+
|
|
2103
|
+
// Live: last 60m (per-minute cost + calls)
|
|
2104
|
+
const liveBins = Array.from({length:60}, (_,i)=>({ min:i, cost:0, calls:0 }));
|
|
2105
|
+
|
|
2106
|
+
// 7d cost trend: sum(cost_usd) per day from usage.jsonl (ev.ts).
|
|
1911
2107
|
const bins = Array.from({length:7}, (_,i)=>({ day:i, cost:0, calls:0 }));
|
|
2108
|
+
|
|
1912
2109
|
for(const line of usageTxt.trim().split('
|
|
1913
2110
|
')){
|
|
1914
2111
|
if(!line) continue;
|
|
@@ -1916,52 +2113,108 @@ async function load(){
|
|
|
1916
2113
|
const ev = JSON.parse(line);
|
|
1917
2114
|
const t = Date.parse(ev.ts);
|
|
1918
2115
|
if(!Number.isFinite(t)) continue;
|
|
2116
|
+
|
|
2117
|
+
const cost = Number(ev.cost_usd);
|
|
2118
|
+
const c = Number.isFinite(cost) ? cost : 0;
|
|
2119
|
+
|
|
2120
|
+
// live minute bin
|
|
2121
|
+
const dm = Math.floor((now - t) / 60000);
|
|
2122
|
+
if(dm>=0 && dm<60){
|
|
2123
|
+
liveBins[dm].calls++;
|
|
2124
|
+
liveBins[dm].cost += c;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
// 7d day bin
|
|
1919
2128
|
const d = Math.floor((now - t) / 86400000);
|
|
1920
2129
|
if(d>=0 && d<7){
|
|
1921
2130
|
bins[d].calls++;
|
|
1922
|
-
|
|
1923
|
-
if(Number.isFinite(c)) bins[d].cost += c;
|
|
2131
|
+
bins[d].cost += c;
|
|
1924
2132
|
}
|
|
1925
2133
|
}catch{}
|
|
1926
2134
|
}
|
|
1927
2135
|
|
|
1928
|
-
//
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
});
|
|
1954
|
-
document.getElementById('trend').textContent = rows.join('
|
|
2136
|
+
// Live sparkline (last 60m)
|
|
2137
|
+
{
|
|
2138
|
+
const W=520, H=120, P=12;
|
|
2139
|
+
const pts = liveBins.slice().reverse();
|
|
2140
|
+
const max = Math.max(...pts.map(b=>b.cost), 0.000001);
|
|
2141
|
+
const xs = pts.map((_,i)=> P + (i*(W-2*P))/59);
|
|
2142
|
+
const ys = pts.map(b=> H-P - ((b.cost/max)*(H-2*P)) );
|
|
2143
|
+
let d = '';
|
|
2144
|
+
for(let i=0;i<xs.length;i++) d += (i===0?'M':'L') + xs[i].toFixed(1)+','+ys[i].toFixed(1)+' ';
|
|
2145
|
+
const area = 'M'+xs[0].toFixed(1)+','+(H-P).toFixed(1)+' ' + d + 'L'+xs[xs.length-1].toFixed(1)+','+(H-P).toFixed(1)+' Z';
|
|
2146
|
+
const svg =
|
|
2147
|
+
'<svg viewBox="0 0 '+W+' '+H+'" width="100%" height="'+H+'" xmlns="http://www.w3.org/2000/svg" style="background:rgba(255,255,255,.02); border:1px solid rgba(255,255,255,.10); border-radius:14px">'+
|
|
2148
|
+
'<path d="'+area+'" fill="rgba(167,139,250,.10)" />'+
|
|
2149
|
+
'<path d="'+d+'" fill="none" stroke="rgba(167,139,250,.95)" stroke-width="2" />'+
|
|
2150
|
+
'<text x="'+P+'" y="'+(P+10)+'" fill="rgba(229,231,235,.75)" font-size="11">max/min '+money(max)+'</text>'+
|
|
2151
|
+
'</svg>';
|
|
2152
|
+
document.getElementById('liveSvg').innerHTML = svg;
|
|
2153
|
+
|
|
2154
|
+
const rows = pts.slice(-10).map((b,idx)=>{
|
|
2155
|
+
const mAgo = 9-idx;
|
|
2156
|
+
const label = (mAgo===0 ? 'now' : (mAgo+'m'));
|
|
2157
|
+
const dollars = ('$' + (Math.round(b.cost*100)/100).toFixed(2));
|
|
2158
|
+
return String(label).padEnd(5) + ' ' + String(dollars).padStart(9) + ' (' + b.calls + ' calls)';
|
|
2159
|
+
});
|
|
2160
|
+
document.getElementById('liveText').textContent = rows.join('
|
|
1955
2161
|
');
|
|
2162
|
+
|
|
2163
|
+
const totalLive = pts.reduce((a,b)=>a+b.cost,0);
|
|
2164
|
+
const liveEl = document.getElementById('live');
|
|
2165
|
+
if(liveEl){
|
|
2166
|
+
liveEl.textContent = 'live: on \xB7 last60m ' + money(totalLive);
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// 7d sparkline
|
|
2171
|
+
{
|
|
2172
|
+
const W=520, H=120, P=12;
|
|
2173
|
+
const pts = bins.slice().reverse();
|
|
2174
|
+
const max = Math.max(...pts.map(b=>b.cost), 0.000001);
|
|
2175
|
+
const xs = pts.map((_,i)=> P + (i*(W-2*P))/6);
|
|
2176
|
+
const ys = pts.map(b=> H-P - ((b.cost/max)*(H-2*P)) );
|
|
2177
|
+
let d = '';
|
|
2178
|
+
for(let i=0;i<xs.length;i++) d += (i===0?'M':'L') + xs[i].toFixed(1)+','+ys[i].toFixed(1)+' ';
|
|
2179
|
+
const area = 'M'+xs[0].toFixed(1)+','+(H-P).toFixed(1)+' ' + d + 'L'+xs[xs.length-1].toFixed(1)+','+(H-P).toFixed(1)+' Z';
|
|
2180
|
+
|
|
2181
|
+
const circles = xs.map((x,i)=>'<circle cx="'+x.toFixed(1)+'" cy="'+ys[i].toFixed(1)+'" r="2.6" fill="rgba(52,211,153,.9)"/>').join('');
|
|
2182
|
+
const svg =
|
|
2183
|
+
'<svg viewBox="0 0 '+W+' '+H+'" width="100%" height="'+H+'" xmlns="http://www.w3.org/2000/svg" style="background:rgba(255,255,255,.02); border:1px solid rgba(255,255,255,.10); border-radius:14px">'+
|
|
2184
|
+
'<path d="'+area+'" fill="rgba(96,165,250,.12)" />'+
|
|
2185
|
+
'<path d="'+d+'" fill="none" stroke="rgba(96,165,250,.95)" stroke-width="2" />'+
|
|
2186
|
+
circles+
|
|
2187
|
+
'<text x="'+P+'" y="'+(P+10)+'" fill="rgba(229,231,235,.75)" font-size="11">max '+money(max)+'</text>'+
|
|
2188
|
+
'</svg>';
|
|
2189
|
+
document.getElementById('trendSvg').innerHTML = svg;
|
|
2190
|
+
|
|
2191
|
+
const rows = pts.map((b,idx)=>{
|
|
2192
|
+
const label = (idx===pts.length-1 ? 'd-6' : (idx===0 ? 'today' : ('d-'+idx)));
|
|
2193
|
+
const dollars = ('$' + (Math.round(b.cost*100)/100).toFixed(2));
|
|
2194
|
+
return String(label).padEnd(7) + ' ' + String(dollars).padStart(9) + ' (' + b.calls + ' calls)';
|
|
2195
|
+
});
|
|
2196
|
+
document.getElementById('trend').textContent = rows.join('
|
|
2197
|
+
');
|
|
2198
|
+
}
|
|
2199
|
+
|
|
1956
2200
|
} else {
|
|
2201
|
+
document.getElementById('liveText').textContent = '(no usage.jsonl yet)';
|
|
2202
|
+
document.getElementById('liveSvg').innerHTML = '';
|
|
1957
2203
|
document.getElementById('trend').textContent = '(no usage.jsonl yet)';
|
|
1958
2204
|
document.getElementById('trendSvg').innerHTML = '';
|
|
2205
|
+
const liveEl = document.getElementById('live');
|
|
2206
|
+
if(liveEl) liveEl.textContent = 'live: off';
|
|
1959
2207
|
}
|
|
1960
2208
|
|
|
1961
|
-
const reportMd = await fetch('/api/report.md').then(r=>r.ok?r.text():null);
|
|
2209
|
+
const reportMd = await fetch('/api/report.md').then(r=>r.ok?r.text():null).catch(()=>null);
|
|
1962
2210
|
document.getElementById('scan').textContent = reportMd || '(no report.md yet \u2014 run: aiopt scan)';
|
|
1963
2211
|
}
|
|
2212
|
+
|
|
2213
|
+
// Auto-refresh (simple polling): updates the dashboard as files change.
|
|
1964
2214
|
load();
|
|
2215
|
+
setInterval(()=>{ load(); }, 2000);
|
|
2216
|
+
const liveEl = document.getElementById('live');
|
|
2217
|
+
if(liveEl) liveEl.textContent = 'live: on (polling)';
|
|
1965
2218
|
</script>
|
|
1966
2219
|
</body>
|
|
1967
2220
|
</html>`;
|
|
@@ -1975,13 +2228,15 @@ load();
|
|
|
1975
2228
|
if (url.startsWith("/api/")) {
|
|
1976
2229
|
const name = url.replace("/api/", "");
|
|
1977
2230
|
if (name === "_meta") {
|
|
2231
|
+
ensureUsageFile();
|
|
1978
2232
|
const expected = ["guard-last.txt", "guard-last.json", "report.json", "report.md", "usage.jsonl", "guard-history.jsonl"];
|
|
1979
|
-
const missing = expected.filter((f) => !
|
|
2233
|
+
const missing = expected.filter((f) => !import_fs11.default.existsSync(file(f)));
|
|
1980
2234
|
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1981
|
-
res.end(JSON.stringify({ baseDir: cwd, outDir, missing }, null, 2));
|
|
2235
|
+
res.end(JSON.stringify({ baseDir: cwd, outDir, missing, collect: lastCollect, collectError: lastCollectError }, null, 2));
|
|
1982
2236
|
return;
|
|
1983
2237
|
}
|
|
1984
2238
|
const allow = /* @__PURE__ */ new Set(["guard-last.txt", "guard-last.json", "guard-history.jsonl", "report.md", "report.json", "usage.jsonl"]);
|
|
2239
|
+
if (name === "usage.jsonl") ensureUsageFile();
|
|
1985
2240
|
if (!allow.has(name)) {
|
|
1986
2241
|
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
1987
2242
|
res.end("not found");
|
|
@@ -2011,13 +2266,14 @@ load();
|
|
|
2011
2266
|
await new Promise(() => {
|
|
2012
2267
|
});
|
|
2013
2268
|
}
|
|
2014
|
-
var import_http,
|
|
2269
|
+
var import_http, import_fs11, import_path12;
|
|
2015
2270
|
var init_dashboard = __esm({
|
|
2016
2271
|
"src/dashboard.ts"() {
|
|
2017
2272
|
"use strict";
|
|
2018
2273
|
import_http = __toESM(require("http"));
|
|
2019
|
-
|
|
2020
|
-
|
|
2274
|
+
import_fs11 = __toESM(require("fs"));
|
|
2275
|
+
import_path12 = __toESM(require("path"));
|
|
2276
|
+
init_collect();
|
|
2021
2277
|
}
|
|
2022
2278
|
});
|
|
2023
2279
|
|
|
@@ -2027,55 +2283,55 @@ __export(find_output_exports, {
|
|
|
2027
2283
|
findAioptOutputDir: () => findAioptOutputDir
|
|
2028
2284
|
});
|
|
2029
2285
|
function findAioptOutputDir(startCwd) {
|
|
2030
|
-
let cur =
|
|
2286
|
+
let cur = import_path13.default.resolve(startCwd);
|
|
2031
2287
|
while (true) {
|
|
2032
|
-
const outDir =
|
|
2033
|
-
if (
|
|
2288
|
+
const outDir = import_path13.default.join(cur, "aiopt-output");
|
|
2289
|
+
if (import_fs12.default.existsSync(outDir)) {
|
|
2034
2290
|
try {
|
|
2035
|
-
if (
|
|
2291
|
+
if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
|
|
2036
2292
|
} catch {
|
|
2037
2293
|
}
|
|
2038
2294
|
}
|
|
2039
|
-
const parent =
|
|
2295
|
+
const parent = import_path13.default.dirname(cur);
|
|
2040
2296
|
if (parent === cur) break;
|
|
2041
2297
|
cur = parent;
|
|
2042
2298
|
}
|
|
2043
2299
|
try {
|
|
2044
|
-
const base =
|
|
2045
|
-
const children =
|
|
2300
|
+
const base = import_path13.default.resolve(startCwd);
|
|
2301
|
+
const children = import_fs12.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path13.default.join(base, d.name));
|
|
2046
2302
|
for (const child of children) {
|
|
2047
|
-
const outDir =
|
|
2048
|
-
if (
|
|
2303
|
+
const outDir = import_path13.default.join(child, "aiopt-output");
|
|
2304
|
+
if (import_fs12.default.existsSync(outDir)) {
|
|
2049
2305
|
try {
|
|
2050
|
-
if (
|
|
2306
|
+
if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
|
|
2051
2307
|
} catch {
|
|
2052
2308
|
}
|
|
2053
2309
|
}
|
|
2054
2310
|
}
|
|
2055
2311
|
} catch {
|
|
2056
2312
|
}
|
|
2057
|
-
return { cwd:
|
|
2313
|
+
return { cwd: import_path13.default.resolve(startCwd), outDir: import_path13.default.join(import_path13.default.resolve(startCwd), "aiopt-output") };
|
|
2058
2314
|
}
|
|
2059
|
-
var
|
|
2315
|
+
var import_fs12, import_path13;
|
|
2060
2316
|
var init_find_output = __esm({
|
|
2061
2317
|
"src/find-output.ts"() {
|
|
2062
2318
|
"use strict";
|
|
2063
|
-
|
|
2064
|
-
|
|
2319
|
+
import_fs12 = __toESM(require("fs"));
|
|
2320
|
+
import_path13 = __toESM(require("path"));
|
|
2065
2321
|
}
|
|
2066
2322
|
});
|
|
2067
2323
|
|
|
2068
2324
|
// src/rates-util.ts
|
|
2069
2325
|
function loadRateTableFromDistPath() {
|
|
2070
|
-
const p =
|
|
2071
|
-
return JSON.parse(
|
|
2326
|
+
const p = import_path14.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2327
|
+
return JSON.parse(import_fs13.default.readFileSync(p, "utf8"));
|
|
2072
2328
|
}
|
|
2073
|
-
var
|
|
2329
|
+
var import_fs13, import_path14;
|
|
2074
2330
|
var init_rates_util = __esm({
|
|
2075
2331
|
"src/rates-util.ts"() {
|
|
2076
2332
|
"use strict";
|
|
2077
|
-
|
|
2078
|
-
|
|
2333
|
+
import_fs13 = __toESM(require("fs"));
|
|
2334
|
+
import_path14 = __toESM(require("path"));
|
|
2079
2335
|
}
|
|
2080
2336
|
});
|
|
2081
2337
|
|
|
@@ -2086,8 +2342,8 @@ __export(quickstart_exports, {
|
|
|
2086
2342
|
seedDemoUsage: () => seedDemoUsage
|
|
2087
2343
|
});
|
|
2088
2344
|
function seedDemoUsage(outDir) {
|
|
2089
|
-
|
|
2090
|
-
const p =
|
|
2345
|
+
import_fs14.default.mkdirSync(outDir, { recursive: true });
|
|
2346
|
+
const p = import_path15.default.join(outDir, "usage.jsonl");
|
|
2091
2347
|
const now = Date.now();
|
|
2092
2348
|
const lines = [];
|
|
2093
2349
|
for (let i = 0; i < 60; i++) {
|
|
@@ -2104,18 +2360,18 @@ function seedDemoUsage(outDir) {
|
|
|
2104
2360
|
meta: { feature_tag: i % 2 ? "summarize" : "coding" }
|
|
2105
2361
|
});
|
|
2106
2362
|
}
|
|
2107
|
-
|
|
2363
|
+
import_fs14.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
2108
2364
|
return p;
|
|
2109
2365
|
}
|
|
2110
2366
|
function runQuickstart(cwd, opts) {
|
|
2111
|
-
const outDir =
|
|
2367
|
+
const outDir = import_path15.default.join(cwd, "aiopt-output");
|
|
2112
2368
|
const usagePath = seedDemoUsage(outDir);
|
|
2113
2369
|
const rt = loadRateTableFromDistPath();
|
|
2114
2370
|
const { readJsonl: readJsonl2 } = (init_io(), __toCommonJS(io_exports));
|
|
2115
2371
|
const events = readJsonl2(usagePath);
|
|
2116
2372
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
2117
|
-
|
|
2118
|
-
|
|
2373
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
|
|
2374
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "report.json"), JSON.stringify({
|
|
2119
2375
|
version: 3,
|
|
2120
2376
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2121
2377
|
confidence: analysis.unknown_models?.length ? "MEDIUM" : "HIGH",
|
|
@@ -2135,8 +2391,8 @@ function runQuickstart(cwd, opts) {
|
|
|
2135
2391
|
unknown_models: analysis.unknown_models || [],
|
|
2136
2392
|
notes: []
|
|
2137
2393
|
}, null, 2));
|
|
2138
|
-
|
|
2139
|
-
|
|
2394
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2395
|
+
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");
|
|
2140
2396
|
const r = runGuard(rt, {
|
|
2141
2397
|
baselineEvents: events,
|
|
2142
2398
|
candidate: {
|
|
@@ -2147,12 +2403,12 @@ function runQuickstart(cwd, opts) {
|
|
|
2147
2403
|
});
|
|
2148
2404
|
return { usagePath, outDir, guard: r, port: opts.port };
|
|
2149
2405
|
}
|
|
2150
|
-
var
|
|
2406
|
+
var import_fs14, import_path15;
|
|
2151
2407
|
var init_quickstart = __esm({
|
|
2152
2408
|
"src/quickstart.ts"() {
|
|
2153
2409
|
"use strict";
|
|
2154
|
-
|
|
2155
|
-
|
|
2410
|
+
import_fs14 = __toESM(require("fs"));
|
|
2411
|
+
import_path15 = __toESM(require("path"));
|
|
2156
2412
|
init_scan();
|
|
2157
2413
|
init_rates_util();
|
|
2158
2414
|
init_guard();
|
|
@@ -2160,8 +2416,8 @@ var init_quickstart = __esm({
|
|
|
2160
2416
|
});
|
|
2161
2417
|
|
|
2162
2418
|
// src/cli.ts
|
|
2163
|
-
var
|
|
2164
|
-
var
|
|
2419
|
+
var import_fs15 = __toESM(require("fs"));
|
|
2420
|
+
var import_path16 = __toESM(require("path"));
|
|
2165
2421
|
var import_commander = require("commander");
|
|
2166
2422
|
init_io();
|
|
2167
2423
|
init_scan();
|
|
@@ -2169,17 +2425,17 @@ var program = new import_commander.Command();
|
|
|
2169
2425
|
var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
|
|
2170
2426
|
var DEFAULT_OUTPUT_DIR = "./aiopt-output";
|
|
2171
2427
|
function loadRateTable() {
|
|
2172
|
-
const p =
|
|
2173
|
-
return JSON.parse(
|
|
2428
|
+
const p = import_path16.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2429
|
+
return JSON.parse(import_fs15.default.readFileSync(p, "utf8"));
|
|
2174
2430
|
}
|
|
2175
2431
|
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);
|
|
2176
2432
|
program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
|
|
2177
2433
|
ensureDir("./aiopt-input");
|
|
2178
2434
|
ensureDir("./aiopt-output");
|
|
2179
|
-
const sampleSrc =
|
|
2180
|
-
const dst =
|
|
2181
|
-
if (!
|
|
2182
|
-
|
|
2435
|
+
const sampleSrc = import_path16.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
|
|
2436
|
+
const dst = import_path16.default.join("./aiopt-input", "usage.jsonl");
|
|
2437
|
+
if (!import_fs15.default.existsSync(dst)) {
|
|
2438
|
+
import_fs15.default.copyFileSync(sampleSrc, dst);
|
|
2183
2439
|
console.log("Created ./aiopt-input/usage.jsonl (sample)");
|
|
2184
2440
|
} else {
|
|
2185
2441
|
console.log("Exists ./aiopt-input/usage.jsonl (skip)");
|
|
@@ -2189,7 +2445,7 @@ program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.json
|
|
|
2189
2445
|
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) => {
|
|
2190
2446
|
const inputPath = String(opts.input);
|
|
2191
2447
|
const outDir = String(opts.out);
|
|
2192
|
-
if (!
|
|
2448
|
+
if (!import_fs15.default.existsSync(inputPath)) {
|
|
2193
2449
|
console.error(`Input not found: ${inputPath}`);
|
|
2194
2450
|
process.exit(1);
|
|
2195
2451
|
}
|
|
@@ -2205,7 +2461,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
|
|
|
2205
2461
|
const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
|
|
2206
2462
|
console.log(`${i + 1}) ${f.title} ${tag}`);
|
|
2207
2463
|
});
|
|
2208
|
-
console.log(`Report: ${
|
|
2464
|
+
console.log(`Report: ${import_path16.default.join(outDir, "report.md")}`);
|
|
2209
2465
|
});
|
|
2210
2466
|
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) => {
|
|
2211
2467
|
const inputPath = String(opts.input);
|
|
@@ -2215,7 +2471,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
|
|
|
2215
2471
|
const { policy } = analyze(rt, events);
|
|
2216
2472
|
policy.generated_from.input = inputPath;
|
|
2217
2473
|
ensureDir(outDir);
|
|
2218
|
-
|
|
2474
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2219
2475
|
console.log(`OK: ${outDir}/cost-policy.json`);
|
|
2220
2476
|
});
|
|
2221
2477
|
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) => {
|
|
@@ -2255,7 +2511,7 @@ licenseCmd.command("verify").option("--path <path>", "license.json path (default
|
|
|
2255
2511
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
2256
2512
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
2257
2513
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
2258
|
-
if (!
|
|
2514
|
+
if (!import_fs15.default.existsSync(p)) {
|
|
2259
2515
|
console.error(`FAIL: license file not found: ${p}`);
|
|
2260
2516
|
process.exit(3);
|
|
2261
2517
|
}
|
|
@@ -2272,7 +2528,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
2272
2528
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
2273
2529
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
2274
2530
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
2275
|
-
if (!
|
|
2531
|
+
if (!import_fs15.default.existsSync(p)) {
|
|
2276
2532
|
console.log("NO_LICENSE");
|
|
2277
2533
|
process.exit(2);
|
|
2278
2534
|
}
|
|
@@ -2288,7 +2544,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
2288
2544
|
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
2545
|
const inputPath = String(opts.input);
|
|
2290
2546
|
const outDir = String(opts.out);
|
|
2291
|
-
if (!
|
|
2547
|
+
if (!import_fs15.default.existsSync(inputPath)) {
|
|
2292
2548
|
console.error(`FAIL: input not found: ${inputPath}`);
|
|
2293
2549
|
process.exit(1);
|
|
2294
2550
|
}
|
|
@@ -2332,11 +2588,11 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
2332
2588
|
console.error("FAIL: diff mode requires both --baseline and --candidate");
|
|
2333
2589
|
process.exit(3);
|
|
2334
2590
|
}
|
|
2335
|
-
if (!
|
|
2591
|
+
if (!import_fs15.default.existsSync(baselinePath)) {
|
|
2336
2592
|
console.error(`FAIL: baseline not found: ${baselinePath}`);
|
|
2337
2593
|
process.exit(3);
|
|
2338
2594
|
}
|
|
2339
|
-
if (candidatePath && !
|
|
2595
|
+
if (candidatePath && !import_fs15.default.existsSync(candidatePath)) {
|
|
2340
2596
|
console.error(`FAIL: candidate not found: ${candidatePath}`);
|
|
2341
2597
|
process.exit(3);
|
|
2342
2598
|
}
|
|
@@ -2358,13 +2614,13 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
2358
2614
|
});
|
|
2359
2615
|
console.log(r.message);
|
|
2360
2616
|
try {
|
|
2361
|
-
const outDir =
|
|
2362
|
-
|
|
2617
|
+
const outDir = import_path16.default.resolve(DEFAULT_OUTPUT_DIR);
|
|
2618
|
+
import_fs15.default.mkdirSync(outDir, { recursive: true });
|
|
2363
2619
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2364
|
-
|
|
2365
|
-
|
|
2620
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.txt"), r.message);
|
|
2621
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
|
|
2366
2622
|
const histLine = JSON.stringify({ ts, exitCode: r.exitCode, mode: candidateEvents ? "diff" : "transform", baseline: baselinePath, candidate: candidatePath }) + "\n";
|
|
2367
|
-
|
|
2623
|
+
import_fs15.default.appendFileSync(import_path16.default.join(outDir, "guard-history.jsonl"), histLine);
|
|
2368
2624
|
} catch {
|
|
2369
2625
|
}
|
|
2370
2626
|
process.exit(r.exitCode);
|
|
@@ -2392,14 +2648,14 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
|
|
|
2392
2648
|
console.log("--- guard ---");
|
|
2393
2649
|
console.log(r.guard.message);
|
|
2394
2650
|
try {
|
|
2395
|
-
const
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2651
|
+
const fs16 = await import("fs");
|
|
2652
|
+
const path17 = await import("path");
|
|
2653
|
+
fs16.mkdirSync(r.outDir, { recursive: true });
|
|
2398
2654
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2399
|
-
|
|
2400
|
-
|
|
2655
|
+
fs16.writeFileSync(path17.join(r.outDir, "guard-last.txt"), r.guard.message);
|
|
2656
|
+
fs16.writeFileSync(path17.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
|
|
2401
2657
|
const histLine = JSON.stringify({ ts, exitCode: r.guard.exitCode, mode: "quickstart", baseline: r.usagePath, candidate: null }) + "\n";
|
|
2402
|
-
|
|
2658
|
+
fs16.appendFileSync(path17.join(r.outDir, "guard-history.jsonl"), histLine);
|
|
2403
2659
|
} catch {
|
|
2404
2660
|
}
|
|
2405
2661
|
console.log("--- next ---");
|