aiopt 0.3.1 → 0.3.2
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 +365 -110
- 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.2",
|
|
660
660
|
description: "Pre-deploy LLM cost accident guardrail (serverless local CLI)",
|
|
661
661
|
bin: {
|
|
662
662
|
aiopt: "dist/cli.js"
|
|
@@ -1666,6 +1666,149 @@ var init_guard = __esm({
|
|
|
1666
1666
|
}
|
|
1667
1667
|
});
|
|
1668
1668
|
|
|
1669
|
+
// src/collect.ts
|
|
1670
|
+
var collect_exports = {};
|
|
1671
|
+
__export(collect_exports, {
|
|
1672
|
+
collectToUsageJsonl: () => collectToUsageJsonl
|
|
1673
|
+
});
|
|
1674
|
+
function exists(p) {
|
|
1675
|
+
try {
|
|
1676
|
+
return import_fs10.default.existsSync(p);
|
|
1677
|
+
} catch {
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
function safeReadJsonl(p) {
|
|
1682
|
+
const out = [];
|
|
1683
|
+
try {
|
|
1684
|
+
const txt = import_fs10.default.readFileSync(p, "utf8");
|
|
1685
|
+
for (const line of txt.split(/\r?\n/)) {
|
|
1686
|
+
if (!line.trim()) continue;
|
|
1687
|
+
try {
|
|
1688
|
+
out.push(JSON.parse(line));
|
|
1689
|
+
} catch {
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
} catch {
|
|
1693
|
+
}
|
|
1694
|
+
return out;
|
|
1695
|
+
}
|
|
1696
|
+
function listJsonlFiles(dir) {
|
|
1697
|
+
const out = [];
|
|
1698
|
+
try {
|
|
1699
|
+
for (const name of import_fs10.default.readdirSync(dir)) {
|
|
1700
|
+
const full = import_path11.default.join(dir, name);
|
|
1701
|
+
let st;
|
|
1702
|
+
try {
|
|
1703
|
+
st = import_fs10.default.statSync(full);
|
|
1704
|
+
} catch {
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1707
|
+
if (st.isFile() && name.endsWith(".jsonl")) out.push(full);
|
|
1708
|
+
}
|
|
1709
|
+
} catch {
|
|
1710
|
+
}
|
|
1711
|
+
return out;
|
|
1712
|
+
}
|
|
1713
|
+
function findOpenClawSessionLogs() {
|
|
1714
|
+
const home = import_os2.default.homedir();
|
|
1715
|
+
const root = import_path11.default.join(home, ".openclaw", "agents");
|
|
1716
|
+
if (!exists(root)) return [];
|
|
1717
|
+
const found = [];
|
|
1718
|
+
let agents = [];
|
|
1719
|
+
try {
|
|
1720
|
+
agents = import_fs10.default.readdirSync(root);
|
|
1721
|
+
} catch {
|
|
1722
|
+
agents = [];
|
|
1723
|
+
}
|
|
1724
|
+
for (const a of agents) {
|
|
1725
|
+
const sessDir = import_path11.default.join(root, a, "sessions");
|
|
1726
|
+
if (!exists(sessDir)) continue;
|
|
1727
|
+
for (const f of listJsonlFiles(sessDir)) found.push(f);
|
|
1728
|
+
}
|
|
1729
|
+
return found;
|
|
1730
|
+
}
|
|
1731
|
+
function parseOpenClawSessionFile(p) {
|
|
1732
|
+
const rows = safeReadJsonl(p);
|
|
1733
|
+
const events = [];
|
|
1734
|
+
for (const r of rows) {
|
|
1735
|
+
if (r && r.type === "message" && r.message && typeof r.message === "object") {
|
|
1736
|
+
const m = r.message;
|
|
1737
|
+
const u = m.usage;
|
|
1738
|
+
if (!u) continue;
|
|
1739
|
+
const input = Number(u.input ?? u.prompt ?? u.prompt_tokens ?? 0);
|
|
1740
|
+
const output = Number(u.output ?? u.completion ?? u.completion_tokens ?? 0);
|
|
1741
|
+
const costTotal = u.cost && typeof u.cost === "object" ? Number(u.cost.total ?? u.costTotal ?? u.cost_usd) : void 0;
|
|
1742
|
+
let tsRaw = m.timestamp || r.timestamp || (/* @__PURE__ */ new Date()).toISOString();
|
|
1743
|
+
let ts = String(tsRaw);
|
|
1744
|
+
if (String(tsRaw).match(/^\d{10,}$/)) {
|
|
1745
|
+
try {
|
|
1746
|
+
ts = new Date(Number(tsRaw)).toISOString();
|
|
1747
|
+
} catch {
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
const provider = String(m.provider || r.provider || "openclaw");
|
|
1751
|
+
const model = String(m.model || r.modelId || "unknown");
|
|
1752
|
+
if (!Number.isFinite(input) && !Number.isFinite(output) && !Number.isFinite(costTotal)) continue;
|
|
1753
|
+
events.push({
|
|
1754
|
+
ts,
|
|
1755
|
+
provider,
|
|
1756
|
+
model,
|
|
1757
|
+
input_tokens: Number.isFinite(input) ? input : 0,
|
|
1758
|
+
output_tokens: Number.isFinite(output) ? output : 0,
|
|
1759
|
+
retries: 0,
|
|
1760
|
+
status: "ok",
|
|
1761
|
+
cost_usd: Number.isFinite(costTotal) ? Number(costTotal) : void 0,
|
|
1762
|
+
meta: {
|
|
1763
|
+
source: "openclaw-session",
|
|
1764
|
+
session_file: p,
|
|
1765
|
+
cache_read_tokens: u.cacheRead,
|
|
1766
|
+
cache_write_tokens: u.cacheWrite,
|
|
1767
|
+
total_tokens: u.totalTokens
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return events;
|
|
1773
|
+
}
|
|
1774
|
+
function stableKey(e) {
|
|
1775
|
+
return `${e.ts}|${e.provider}|${e.model}|${e.input_tokens}|${e.output_tokens}|${e.cost_usd ?? ""}`;
|
|
1776
|
+
}
|
|
1777
|
+
function collectToUsageJsonl(outPath) {
|
|
1778
|
+
const all = [];
|
|
1779
|
+
const sources = [];
|
|
1780
|
+
const ocFiles = findOpenClawSessionLogs();
|
|
1781
|
+
let ocEvents = 0;
|
|
1782
|
+
for (const f of ocFiles) {
|
|
1783
|
+
const evs = parseOpenClawSessionFile(f);
|
|
1784
|
+
ocEvents += evs.length;
|
|
1785
|
+
all.push(...evs);
|
|
1786
|
+
}
|
|
1787
|
+
sources.push({ name: "openclaw", files: ocFiles.length, events: ocEvents });
|
|
1788
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1789
|
+
const uniq = [];
|
|
1790
|
+
for (const e of all) {
|
|
1791
|
+
const k = stableKey(e);
|
|
1792
|
+
if (seen.has(k)) continue;
|
|
1793
|
+
seen.add(k);
|
|
1794
|
+
uniq.push(e);
|
|
1795
|
+
}
|
|
1796
|
+
uniq.sort((a, b) => Date.parse(a.ts) - Date.parse(b.ts));
|
|
1797
|
+
import_fs10.default.mkdirSync(import_path11.default.dirname(outPath), { recursive: true });
|
|
1798
|
+
const lines = uniq.map((e) => JSON.stringify(e)).join("\n") + (uniq.length ? "\n" : "");
|
|
1799
|
+
import_fs10.default.writeFileSync(outPath, lines);
|
|
1800
|
+
return { outPath, sources, eventsWritten: uniq.length };
|
|
1801
|
+
}
|
|
1802
|
+
var import_fs10, import_path11, import_os2;
|
|
1803
|
+
var init_collect = __esm({
|
|
1804
|
+
"src/collect.ts"() {
|
|
1805
|
+
"use strict";
|
|
1806
|
+
import_fs10 = __toESM(require("fs"));
|
|
1807
|
+
import_path11 = __toESM(require("path"));
|
|
1808
|
+
import_os2 = __toESM(require("os"));
|
|
1809
|
+
}
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1669
1812
|
// src/dashboard.ts
|
|
1670
1813
|
var dashboard_exports = {};
|
|
1671
1814
|
__export(dashboard_exports, {
|
|
@@ -1674,12 +1817,22 @@ __export(dashboard_exports, {
|
|
|
1674
1817
|
async function startDashboard(cwd, opts) {
|
|
1675
1818
|
const host = "127.0.0.1";
|
|
1676
1819
|
const port = opts.port || 3010;
|
|
1677
|
-
const outDir =
|
|
1678
|
-
const file = (name) =>
|
|
1820
|
+
const outDir = import_path12.default.join(cwd, "aiopt-output");
|
|
1821
|
+
const file = (name) => import_path12.default.join(outDir, name);
|
|
1822
|
+
function ensureUsageFile() {
|
|
1823
|
+
try {
|
|
1824
|
+
const usagePath = file("usage.jsonl");
|
|
1825
|
+
if (import_fs11.default.existsSync(usagePath)) return;
|
|
1826
|
+
const { collectToUsageJsonl: collectToUsageJsonl2 } = (init_collect(), __toCommonJS(collect_exports));
|
|
1827
|
+
collectToUsageJsonl2(usagePath);
|
|
1828
|
+
} catch {
|
|
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
|
-
|
|
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('
|
|
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('
|
|
1955
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
2235
|
res.end(JSON.stringify({ baseDir: cwd, outDir, missing }, 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,13 @@ 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"));
|
|
2021
2276
|
}
|
|
2022
2277
|
});
|
|
2023
2278
|
|
|
@@ -2027,55 +2282,55 @@ __export(find_output_exports, {
|
|
|
2027
2282
|
findAioptOutputDir: () => findAioptOutputDir
|
|
2028
2283
|
});
|
|
2029
2284
|
function findAioptOutputDir(startCwd) {
|
|
2030
|
-
let cur =
|
|
2285
|
+
let cur = import_path13.default.resolve(startCwd);
|
|
2031
2286
|
while (true) {
|
|
2032
|
-
const outDir =
|
|
2033
|
-
if (
|
|
2287
|
+
const outDir = import_path13.default.join(cur, "aiopt-output");
|
|
2288
|
+
if (import_fs12.default.existsSync(outDir)) {
|
|
2034
2289
|
try {
|
|
2035
|
-
if (
|
|
2290
|
+
if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: cur, outDir };
|
|
2036
2291
|
} catch {
|
|
2037
2292
|
}
|
|
2038
2293
|
}
|
|
2039
|
-
const parent =
|
|
2294
|
+
const parent = import_path13.default.dirname(cur);
|
|
2040
2295
|
if (parent === cur) break;
|
|
2041
2296
|
cur = parent;
|
|
2042
2297
|
}
|
|
2043
2298
|
try {
|
|
2044
|
-
const base =
|
|
2045
|
-
const children =
|
|
2299
|
+
const base = import_path13.default.resolve(startCwd);
|
|
2300
|
+
const children = import_fs12.default.readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => import_path13.default.join(base, d.name));
|
|
2046
2301
|
for (const child of children) {
|
|
2047
|
-
const outDir =
|
|
2048
|
-
if (
|
|
2302
|
+
const outDir = import_path13.default.join(child, "aiopt-output");
|
|
2303
|
+
if (import_fs12.default.existsSync(outDir)) {
|
|
2049
2304
|
try {
|
|
2050
|
-
if (
|
|
2305
|
+
if (import_fs12.default.statSync(outDir).isDirectory()) return { cwd: child, outDir };
|
|
2051
2306
|
} catch {
|
|
2052
2307
|
}
|
|
2053
2308
|
}
|
|
2054
2309
|
}
|
|
2055
2310
|
} catch {
|
|
2056
2311
|
}
|
|
2057
|
-
return { cwd:
|
|
2312
|
+
return { cwd: import_path13.default.resolve(startCwd), outDir: import_path13.default.join(import_path13.default.resolve(startCwd), "aiopt-output") };
|
|
2058
2313
|
}
|
|
2059
|
-
var
|
|
2314
|
+
var import_fs12, import_path13;
|
|
2060
2315
|
var init_find_output = __esm({
|
|
2061
2316
|
"src/find-output.ts"() {
|
|
2062
2317
|
"use strict";
|
|
2063
|
-
|
|
2064
|
-
|
|
2318
|
+
import_fs12 = __toESM(require("fs"));
|
|
2319
|
+
import_path13 = __toESM(require("path"));
|
|
2065
2320
|
}
|
|
2066
2321
|
});
|
|
2067
2322
|
|
|
2068
2323
|
// src/rates-util.ts
|
|
2069
2324
|
function loadRateTableFromDistPath() {
|
|
2070
|
-
const p =
|
|
2071
|
-
return JSON.parse(
|
|
2325
|
+
const p = import_path14.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2326
|
+
return JSON.parse(import_fs13.default.readFileSync(p, "utf8"));
|
|
2072
2327
|
}
|
|
2073
|
-
var
|
|
2328
|
+
var import_fs13, import_path14;
|
|
2074
2329
|
var init_rates_util = __esm({
|
|
2075
2330
|
"src/rates-util.ts"() {
|
|
2076
2331
|
"use strict";
|
|
2077
|
-
|
|
2078
|
-
|
|
2332
|
+
import_fs13 = __toESM(require("fs"));
|
|
2333
|
+
import_path14 = __toESM(require("path"));
|
|
2079
2334
|
}
|
|
2080
2335
|
});
|
|
2081
2336
|
|
|
@@ -2086,8 +2341,8 @@ __export(quickstart_exports, {
|
|
|
2086
2341
|
seedDemoUsage: () => seedDemoUsage
|
|
2087
2342
|
});
|
|
2088
2343
|
function seedDemoUsage(outDir) {
|
|
2089
|
-
|
|
2090
|
-
const p =
|
|
2344
|
+
import_fs14.default.mkdirSync(outDir, { recursive: true });
|
|
2345
|
+
const p = import_path15.default.join(outDir, "usage.jsonl");
|
|
2091
2346
|
const now = Date.now();
|
|
2092
2347
|
const lines = [];
|
|
2093
2348
|
for (let i = 0; i < 60; i++) {
|
|
@@ -2104,18 +2359,18 @@ function seedDemoUsage(outDir) {
|
|
|
2104
2359
|
meta: { feature_tag: i % 2 ? "summarize" : "coding" }
|
|
2105
2360
|
});
|
|
2106
2361
|
}
|
|
2107
|
-
|
|
2362
|
+
import_fs14.default.writeFileSync(p, lines.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
2108
2363
|
return p;
|
|
2109
2364
|
}
|
|
2110
2365
|
function runQuickstart(cwd, opts) {
|
|
2111
|
-
const outDir =
|
|
2366
|
+
const outDir = import_path15.default.join(cwd, "aiopt-output");
|
|
2112
2367
|
const usagePath = seedDemoUsage(outDir);
|
|
2113
2368
|
const rt = loadRateTableFromDistPath();
|
|
2114
2369
|
const { readJsonl: readJsonl2 } = (init_io(), __toCommonJS(io_exports));
|
|
2115
2370
|
const events = readJsonl2(usagePath);
|
|
2116
2371
|
const { analysis, savings, policy, meta } = analyze(rt, events);
|
|
2117
|
-
|
|
2118
|
-
|
|
2372
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "analysis.json"), JSON.stringify(analysis, null, 2));
|
|
2373
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "report.json"), JSON.stringify({
|
|
2119
2374
|
version: 3,
|
|
2120
2375
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2121
2376
|
confidence: analysis.unknown_models?.length ? "MEDIUM" : "HIGH",
|
|
@@ -2135,8 +2390,8 @@ function runQuickstart(cwd, opts) {
|
|
|
2135
2390
|
unknown_models: analysis.unknown_models || [],
|
|
2136
2391
|
notes: []
|
|
2137
2392
|
}, null, 2));
|
|
2138
|
-
|
|
2139
|
-
|
|
2393
|
+
import_fs14.default.writeFileSync(import_path15.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2394
|
+
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
2395
|
const r = runGuard(rt, {
|
|
2141
2396
|
baselineEvents: events,
|
|
2142
2397
|
candidate: {
|
|
@@ -2147,12 +2402,12 @@ function runQuickstart(cwd, opts) {
|
|
|
2147
2402
|
});
|
|
2148
2403
|
return { usagePath, outDir, guard: r, port: opts.port };
|
|
2149
2404
|
}
|
|
2150
|
-
var
|
|
2405
|
+
var import_fs14, import_path15;
|
|
2151
2406
|
var init_quickstart = __esm({
|
|
2152
2407
|
"src/quickstart.ts"() {
|
|
2153
2408
|
"use strict";
|
|
2154
|
-
|
|
2155
|
-
|
|
2409
|
+
import_fs14 = __toESM(require("fs"));
|
|
2410
|
+
import_path15 = __toESM(require("path"));
|
|
2156
2411
|
init_scan();
|
|
2157
2412
|
init_rates_util();
|
|
2158
2413
|
init_guard();
|
|
@@ -2160,8 +2415,8 @@ var init_quickstart = __esm({
|
|
|
2160
2415
|
});
|
|
2161
2416
|
|
|
2162
2417
|
// src/cli.ts
|
|
2163
|
-
var
|
|
2164
|
-
var
|
|
2418
|
+
var import_fs15 = __toESM(require("fs"));
|
|
2419
|
+
var import_path16 = __toESM(require("path"));
|
|
2165
2420
|
var import_commander = require("commander");
|
|
2166
2421
|
init_io();
|
|
2167
2422
|
init_scan();
|
|
@@ -2169,17 +2424,17 @@ var program = new import_commander.Command();
|
|
|
2169
2424
|
var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
|
|
2170
2425
|
var DEFAULT_OUTPUT_DIR = "./aiopt-output";
|
|
2171
2426
|
function loadRateTable() {
|
|
2172
|
-
const p =
|
|
2173
|
-
return JSON.parse(
|
|
2427
|
+
const p = import_path16.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
2428
|
+
return JSON.parse(import_fs15.default.readFileSync(p, "utf8"));
|
|
2174
2429
|
}
|
|
2175
2430
|
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
2431
|
program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
|
|
2177
2432
|
ensureDir("./aiopt-input");
|
|
2178
2433
|
ensureDir("./aiopt-output");
|
|
2179
|
-
const sampleSrc =
|
|
2180
|
-
const dst =
|
|
2181
|
-
if (!
|
|
2182
|
-
|
|
2434
|
+
const sampleSrc = import_path16.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
|
|
2435
|
+
const dst = import_path16.default.join("./aiopt-input", "usage.jsonl");
|
|
2436
|
+
if (!import_fs15.default.existsSync(dst)) {
|
|
2437
|
+
import_fs15.default.copyFileSync(sampleSrc, dst);
|
|
2183
2438
|
console.log("Created ./aiopt-input/usage.jsonl (sample)");
|
|
2184
2439
|
} else {
|
|
2185
2440
|
console.log("Exists ./aiopt-input/usage.jsonl (skip)");
|
|
@@ -2189,7 +2444,7 @@ program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.json
|
|
|
2189
2444
|
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
2445
|
const inputPath = String(opts.input);
|
|
2191
2446
|
const outDir = String(opts.out);
|
|
2192
|
-
if (!
|
|
2447
|
+
if (!import_fs15.default.existsSync(inputPath)) {
|
|
2193
2448
|
console.error(`Input not found: ${inputPath}`);
|
|
2194
2449
|
process.exit(1);
|
|
2195
2450
|
}
|
|
@@ -2205,7 +2460,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
|
|
|
2205
2460
|
const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
|
|
2206
2461
|
console.log(`${i + 1}) ${f.title} ${tag}`);
|
|
2207
2462
|
});
|
|
2208
|
-
console.log(`Report: ${
|
|
2463
|
+
console.log(`Report: ${import_path16.default.join(outDir, "report.md")}`);
|
|
2209
2464
|
});
|
|
2210
2465
|
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
2466
|
const inputPath = String(opts.input);
|
|
@@ -2215,7 +2470,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
|
|
|
2215
2470
|
const { policy } = analyze(rt, events);
|
|
2216
2471
|
policy.generated_from.input = inputPath;
|
|
2217
2472
|
ensureDir(outDir);
|
|
2218
|
-
|
|
2473
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
2219
2474
|
console.log(`OK: ${outDir}/cost-policy.json`);
|
|
2220
2475
|
});
|
|
2221
2476
|
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 +2510,7 @@ licenseCmd.command("verify").option("--path <path>", "license.json path (default
|
|
|
2255
2510
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
2256
2511
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
2257
2512
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
2258
|
-
if (!
|
|
2513
|
+
if (!import_fs15.default.existsSync(p)) {
|
|
2259
2514
|
console.error(`FAIL: license file not found: ${p}`);
|
|
2260
2515
|
process.exit(3);
|
|
2261
2516
|
}
|
|
@@ -2272,7 +2527,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
2272
2527
|
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
2273
2528
|
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
2274
2529
|
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
2275
|
-
if (!
|
|
2530
|
+
if (!import_fs15.default.existsSync(p)) {
|
|
2276
2531
|
console.log("NO_LICENSE");
|
|
2277
2532
|
process.exit(2);
|
|
2278
2533
|
}
|
|
@@ -2288,7 +2543,7 @@ licenseCmd.command("status").option("--path <path>", "license.json path (default
|
|
|
2288
2543
|
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
2544
|
const inputPath = String(opts.input);
|
|
2290
2545
|
const outDir = String(opts.out);
|
|
2291
|
-
if (!
|
|
2546
|
+
if (!import_fs15.default.existsSync(inputPath)) {
|
|
2292
2547
|
console.error(`FAIL: input not found: ${inputPath}`);
|
|
2293
2548
|
process.exit(1);
|
|
2294
2549
|
}
|
|
@@ -2332,11 +2587,11 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
2332
2587
|
console.error("FAIL: diff mode requires both --baseline and --candidate");
|
|
2333
2588
|
process.exit(3);
|
|
2334
2589
|
}
|
|
2335
|
-
if (!
|
|
2590
|
+
if (!import_fs15.default.existsSync(baselinePath)) {
|
|
2336
2591
|
console.error(`FAIL: baseline not found: ${baselinePath}`);
|
|
2337
2592
|
process.exit(3);
|
|
2338
2593
|
}
|
|
2339
|
-
if (candidatePath && !
|
|
2594
|
+
if (candidatePath && !import_fs15.default.existsSync(candidatePath)) {
|
|
2340
2595
|
console.error(`FAIL: candidate not found: ${candidatePath}`);
|
|
2341
2596
|
process.exit(3);
|
|
2342
2597
|
}
|
|
@@ -2358,13 +2613,13 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
2358
2613
|
});
|
|
2359
2614
|
console.log(r.message);
|
|
2360
2615
|
try {
|
|
2361
|
-
const outDir =
|
|
2362
|
-
|
|
2616
|
+
const outDir = import_path16.default.resolve(DEFAULT_OUTPUT_DIR);
|
|
2617
|
+
import_fs15.default.mkdirSync(outDir, { recursive: true });
|
|
2363
2618
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2364
|
-
|
|
2365
|
-
|
|
2619
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.txt"), r.message);
|
|
2620
|
+
import_fs15.default.writeFileSync(import_path16.default.join(outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.exitCode }, null, 2));
|
|
2366
2621
|
const histLine = JSON.stringify({ ts, exitCode: r.exitCode, mode: candidateEvents ? "diff" : "transform", baseline: baselinePath, candidate: candidatePath }) + "\n";
|
|
2367
|
-
|
|
2622
|
+
import_fs15.default.appendFileSync(import_path16.default.join(outDir, "guard-history.jsonl"), histLine);
|
|
2368
2623
|
} catch {
|
|
2369
2624
|
}
|
|
2370
2625
|
process.exit(r.exitCode);
|
|
@@ -2392,14 +2647,14 @@ program.command("quickstart").description("1-minute demo: generate sample usage,
|
|
|
2392
2647
|
console.log("--- guard ---");
|
|
2393
2648
|
console.log(r.guard.message);
|
|
2394
2649
|
try {
|
|
2395
|
-
const
|
|
2396
|
-
const
|
|
2397
|
-
|
|
2650
|
+
const fs16 = await import("fs");
|
|
2651
|
+
const path17 = await import("path");
|
|
2652
|
+
fs16.mkdirSync(r.outDir, { recursive: true });
|
|
2398
2653
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2399
|
-
|
|
2400
|
-
|
|
2654
|
+
fs16.writeFileSync(path17.join(r.outDir, "guard-last.txt"), r.guard.message);
|
|
2655
|
+
fs16.writeFileSync(path17.join(r.outDir, "guard-last.json"), JSON.stringify({ ts, exitCode: r.guard.exitCode }, null, 2));
|
|
2401
2656
|
const histLine = JSON.stringify({ ts, exitCode: r.guard.exitCode, mode: "quickstart", baseline: r.usagePath, candidate: null }) + "\n";
|
|
2402
|
-
|
|
2657
|
+
fs16.appendFileSync(path17.join(r.outDir, "guard-history.jsonl"), histLine);
|
|
2403
2658
|
} catch {
|
|
2404
2659
|
}
|
|
2405
2660
|
console.log("--- next ---");
|