aiopt 0.2.2 → 0.2.4
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 +43 -5
- package/dist/cli.js +368 -27
- package/dist/cli.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,17 +1,55 @@
|
|
|
1
|
-
# AIOpt —
|
|
1
|
+
# AIOpt — pre-deploy LLM cost accident guardrail (local-only)
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/aiopt)
|
|
4
4
|
[](https://github.com/tkddlr0716-collab/aiopt/actions/workflows/ci.yml)
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
|
|
7
|
-
**3-line quickstart (
|
|
7
|
+
**3-line quickstart (Guardrail mode)**
|
|
8
8
|
```bash
|
|
9
9
|
npx aiopt install --force
|
|
10
|
-
|
|
11
|
-
npx aiopt
|
|
10
|
+
# baseline: your existing usage log
|
|
11
|
+
npx aiopt guard --context-mult 1.2
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Exit codes:
|
|
15
|
+
- `0` OK
|
|
16
|
+
- `2` WARN (cost accident possible)
|
|
17
|
+
- `3` FAIL (merge-blocking)
|
|
18
|
+
|
|
19
|
+
CI tip: print guard output into the GitHub Actions **Step Summary** so you don’t need to scroll logs.
|
|
20
|
+
|
|
21
|
+
## Product definition (Guardrail)
|
|
22
|
+
AIOpt is a **pre-deploy cost accident guardrail** for LLM changes.
|
|
23
|
+
|
|
24
|
+
- **Baseline** = your observed usage log (`usage.jsonl` / `usage.csv`)
|
|
25
|
+
- **Candidate** = an estimated change (model/provider/context/output/retry deltas)
|
|
26
|
+
- Output = a deterministic verdict + monthly impact estimate + confidence
|
|
27
|
+
- Designed for CI: fast, local, no network calls (beyond downloading the npm package)
|
|
28
|
+
|
|
29
|
+
## CI integration (GitHub Actions)
|
|
30
|
+
You can run `aiopt guard` in CI to catch accidental cost blow-ups before merge.
|
|
31
|
+
|
|
32
|
+
### 1) Non-blocking (report only)
|
|
33
|
+
|
|
34
|
+
```yaml
|
|
35
|
+
- name: AI cost guard (non-blocking)
|
|
36
|
+
run: |
|
|
37
|
+
npx aiopt guard --input ./aiopt-output/usage.jsonl --context-mult 1.2 || true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2) Merge-blocking (fail on high risk)
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
- name: AI cost guard (blocking)
|
|
44
|
+
run: |
|
|
45
|
+
npx aiopt guard --input ./aiopt-output/usage.jsonl --context-mult 1.2
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Tips:
|
|
49
|
+
- Keep a small representative baseline log in the repo (or in CI artifacts) for deterministic checks.
|
|
50
|
+
- Use the wrapper (`npx aiopt install --force`) to record baseline locally during dev.
|
|
51
|
+
|
|
52
|
+
AIOpt is a **serverless local Guardrail CLI**.
|
|
15
53
|
- No signup, no upload, no dashboard, no server deployment.
|
|
16
54
|
- Reads local JSONL/CSV → writes local outputs.
|
|
17
55
|
- **No LLM calls** (math + deterministic rules only).
|
package/dist/cli.js
CHANGED
|
@@ -415,7 +415,7 @@ var require_package = __commonJS({
|
|
|
415
415
|
"package.json"(exports2, module2) {
|
|
416
416
|
module2.exports = {
|
|
417
417
|
name: "aiopt",
|
|
418
|
-
version: "0.2.
|
|
418
|
+
version: "0.2.4",
|
|
419
419
|
description: "Serverless local CLI MVP for AI API cost analysis & cost-policy generation",
|
|
420
420
|
bin: {
|
|
421
421
|
aiopt: "dist/cli.js"
|
|
@@ -432,7 +432,9 @@ var require_package = __commonJS({
|
|
|
432
432
|
build: "tsup",
|
|
433
433
|
dev: "node --enable-source-maps dist/cli.js",
|
|
434
434
|
prepack: "npm run build",
|
|
435
|
-
"test:npx": `npm pack --silent && node -e "const fs=require('fs');const p=fs.readdirSync('.').find(f=>/^aiopt-.*\\.tgz$/.test(f)); if(!p) throw new Error('tgz not found'); console.log('tgz',p);" && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1)
|
|
435
|
+
"test:npx": `npm pack --silent && node -e "const fs=require('fs');const p=fs.readdirSync('.').find(f=>/^aiopt-.*\\.tgz$/.test(f)); if(!p) throw new Error('tgz not found'); console.log('tgz',p);" && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) install --force && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) doctor && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) scan && test -f ./aiopt-output/report.md && echo OK`,
|
|
436
|
+
"test:guard": "npm run build --silent && node scripts/test-guard.js",
|
|
437
|
+
"test:license": "npm run build --silent && node scripts/test-license.js"
|
|
436
438
|
},
|
|
437
439
|
dependencies: {
|
|
438
440
|
commander: "^14.0.0",
|
|
@@ -843,16 +845,118 @@ var init_doctor = __esm({
|
|
|
843
845
|
}
|
|
844
846
|
});
|
|
845
847
|
|
|
848
|
+
// src/license.ts
|
|
849
|
+
var license_exports = {};
|
|
850
|
+
__export(license_exports, {
|
|
851
|
+
DEFAULT_PUBLIC_KEY_PEM: () => DEFAULT_PUBLIC_KEY_PEM,
|
|
852
|
+
defaultLicensePath: () => defaultLicensePath,
|
|
853
|
+
parseLicenseKey: () => parseLicenseKey,
|
|
854
|
+
readLicenseFile: () => readLicenseFile,
|
|
855
|
+
verifyLicenseKey: () => verifyLicenseKey,
|
|
856
|
+
writeLicenseFile: () => writeLicenseFile
|
|
857
|
+
});
|
|
858
|
+
function b64urlDecodeToBuffer(s) {
|
|
859
|
+
const padLen = (4 - s.length % 4) % 4;
|
|
860
|
+
const padded = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat(padLen);
|
|
861
|
+
return Buffer.from(padded, "base64");
|
|
862
|
+
}
|
|
863
|
+
function safeJsonParse(buf) {
|
|
864
|
+
const txt = buf.toString("utf8");
|
|
865
|
+
return JSON.parse(txt);
|
|
866
|
+
}
|
|
867
|
+
function parseLicenseKey(key) {
|
|
868
|
+
const parts = String(key).trim().split(".");
|
|
869
|
+
if (parts.length !== 2) throw new Error("invalid license key format: expected <payloadB64Url>.<sigB64Url>");
|
|
870
|
+
const [payloadB64Url, sigB64Url] = parts;
|
|
871
|
+
const payloadBuf = b64urlDecodeToBuffer(payloadB64Url);
|
|
872
|
+
const sigBuf = b64urlDecodeToBuffer(sigB64Url);
|
|
873
|
+
const payload = safeJsonParse(payloadBuf);
|
|
874
|
+
return { payload, signature: sigBuf, payloadB64Url };
|
|
875
|
+
}
|
|
876
|
+
function verifyLicenseKey(key, publicKeyPem) {
|
|
877
|
+
let parsed;
|
|
878
|
+
try {
|
|
879
|
+
parsed = parseLicenseKey(key);
|
|
880
|
+
} catch (e) {
|
|
881
|
+
return { ok: false, reason: e?.message || "parse error" };
|
|
882
|
+
}
|
|
883
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
884
|
+
if (typeof parsed.payload?.exp !== "number" || parsed.payload.exp < now) {
|
|
885
|
+
return { ok: false, reason: "expired", payload: parsed.payload };
|
|
886
|
+
}
|
|
887
|
+
try {
|
|
888
|
+
const verifier = import_crypto.default.createVerify("RSA-SHA256");
|
|
889
|
+
verifier.update(parsed.payloadB64Url);
|
|
890
|
+
verifier.end();
|
|
891
|
+
const ok = verifier.verify(publicKeyPem, parsed.signature);
|
|
892
|
+
if (!ok) return { ok: false, reason: "bad signature", payload: parsed.payload };
|
|
893
|
+
return { ok: true, payload: parsed.payload };
|
|
894
|
+
} catch (e) {
|
|
895
|
+
return { ok: false, reason: e?.message || "verify error", payload: parsed.payload };
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
function defaultLicensePath(cwd) {
|
|
899
|
+
return import_path6.default.join(cwd, "aiopt", "license.json");
|
|
900
|
+
}
|
|
901
|
+
function writeLicenseFile(p, key, payload, verified) {
|
|
902
|
+
const out = {
|
|
903
|
+
key,
|
|
904
|
+
payload,
|
|
905
|
+
verified,
|
|
906
|
+
verified_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
907
|
+
};
|
|
908
|
+
import_fs6.default.mkdirSync(import_path6.default.dirname(p), { recursive: true });
|
|
909
|
+
import_fs6.default.writeFileSync(p, JSON.stringify(out, null, 2));
|
|
910
|
+
}
|
|
911
|
+
function readLicenseFile(p) {
|
|
912
|
+
return JSON.parse(import_fs6.default.readFileSync(p, "utf8"));
|
|
913
|
+
}
|
|
914
|
+
var import_crypto, import_fs6, import_path6, DEFAULT_PUBLIC_KEY_PEM;
|
|
915
|
+
var init_license = __esm({
|
|
916
|
+
"src/license.ts"() {
|
|
917
|
+
"use strict";
|
|
918
|
+
import_crypto = __toESM(require("crypto"));
|
|
919
|
+
import_fs6 = __toESM(require("fs"));
|
|
920
|
+
import_path6 = __toESM(require("path"));
|
|
921
|
+
DEFAULT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
922
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz1LLE/pXIx5TloDa0LAf
|
|
923
|
+
jg9NSIW6STWhsAFP2ZzXgpWoQ3cCmW6xcB/4QNEmPpGlfMWhyRfkxsdKuhnjUMTg
|
|
924
|
+
8MpMAcbjjF8JrGS9iLnW4yrLm7jzsOcndjkGO7pH+32GopZk98dVzmIRPok2Je76
|
|
925
|
+
3MQRaxLi0jWytaCmacEB4R7HyuquOQlHPg0vD9NOEwrC/+br2GdQbD1lKPyLeLv3
|
|
926
|
+
RidwAs8Iw2xx5g8G+BsVSM/HRC3jQT5GynfnuDsvMHCvGLRct/76ajiR71/NFZEP
|
|
927
|
+
Z7liILNnZzCTlKGGZfZmG70t+zkg8HKdpRuWy8rZ0DPWyQg5MKm6TZOMV6dC0Rpg
|
|
928
|
+
DwIDAQAB
|
|
929
|
+
-----END PUBLIC KEY-----`;
|
|
930
|
+
}
|
|
931
|
+
});
|
|
932
|
+
|
|
846
933
|
// src/guard.ts
|
|
847
934
|
var guard_exports = {};
|
|
848
935
|
__export(guard_exports, {
|
|
849
936
|
runGuard: () => runGuard
|
|
850
937
|
});
|
|
938
|
+
function accidentRiskFromMonthly(monthly) {
|
|
939
|
+
if (!Number.isFinite(monthly)) return "Medium";
|
|
940
|
+
if (monthly >= 100) return "High";
|
|
941
|
+
if (monthly >= 10) return "Medium";
|
|
942
|
+
return "Low";
|
|
943
|
+
}
|
|
851
944
|
function round23(n) {
|
|
852
945
|
return Math.round(n * 100) / 100;
|
|
853
946
|
}
|
|
854
|
-
function monthEstimate(delta) {
|
|
855
|
-
|
|
947
|
+
function monthEstimate(delta, events) {
|
|
948
|
+
let days = 1;
|
|
949
|
+
try {
|
|
950
|
+
const times = events.map((e) => Date.parse(e.ts)).filter((t) => Number.isFinite(t)).sort((a, b) => a - b);
|
|
951
|
+
if (times.length >= 2) {
|
|
952
|
+
const spanMs = Math.max(0, times[times.length - 1] - times[0]);
|
|
953
|
+
const spanDays = spanMs / (1e3 * 60 * 60 * 24);
|
|
954
|
+
days = Math.max(1, spanDays);
|
|
955
|
+
}
|
|
956
|
+
} catch {
|
|
957
|
+
days = 1;
|
|
958
|
+
}
|
|
959
|
+
return delta * 30 / days;
|
|
856
960
|
}
|
|
857
961
|
function applyCandidate(events, cand) {
|
|
858
962
|
const ctxM = cand.contextMultiplier ?? 1;
|
|
@@ -875,23 +979,81 @@ function confidenceFromChange(cand) {
|
|
|
875
979
|
if (cand.model) reasons.push("model change");
|
|
876
980
|
if (cand.provider) reasons.push("provider change");
|
|
877
981
|
if (cand.contextMultiplier && cand.contextMultiplier !== 1) reasons.push("context length change");
|
|
982
|
+
if (cand.outputMultiplier && cand.outputMultiplier !== 1) reasons.push("output length change");
|
|
878
983
|
if (cand.retriesDelta && cand.retriesDelta !== 0) return { level: "High", reasons };
|
|
879
984
|
if (cand.model || cand.provider) return { level: "Medium", reasons };
|
|
880
|
-
if (cand.contextMultiplier && cand.contextMultiplier !== 1
|
|
985
|
+
if (cand.contextMultiplier && cand.contextMultiplier !== 1 || cand.outputMultiplier && cand.outputMultiplier !== 1) {
|
|
986
|
+
return { level: "Low", reasons };
|
|
987
|
+
}
|
|
881
988
|
return { level: "Medium", reasons: reasons.length ? reasons : ["unknown change"] };
|
|
882
989
|
}
|
|
990
|
+
function assessDataQuality(baselineEvents, base) {
|
|
991
|
+
const reasons = [];
|
|
992
|
+
const times = baselineEvents.map((e) => Date.parse(e.ts)).filter((t) => Number.isFinite(t)).sort((a, b) => a - b);
|
|
993
|
+
if (times.length < 2) reasons.push("ts span unknown");
|
|
994
|
+
else {
|
|
995
|
+
const spanDays = Math.max(0, (times[times.length - 1] - times[0]) / (1e3 * 60 * 60 * 24));
|
|
996
|
+
if (spanDays < 0.25) reasons.push("ts span too short");
|
|
997
|
+
}
|
|
998
|
+
const missingFt = baselineEvents.filter((e) => !e.feature_tag && !(e.meta && e.meta.feature_tag)).length;
|
|
999
|
+
if (missingFt / Math.max(1, baselineEvents.length) > 0.2) reasons.push("feature_tag missing >20%");
|
|
1000
|
+
const unknown = (base.analysis.unknown_models || []).length;
|
|
1001
|
+
if (unknown / Math.max(1, baselineEvents.length) > 0.2) reasons.push("unknown model/provider >20%");
|
|
1002
|
+
let penalty = "none";
|
|
1003
|
+
if (reasons.length >= 2) penalty = "medium";
|
|
1004
|
+
else if (reasons.length === 1) penalty = "low";
|
|
1005
|
+
return { reasons, penalty };
|
|
1006
|
+
}
|
|
1007
|
+
function degrade(level, penalty) {
|
|
1008
|
+
if (penalty === "none") return level;
|
|
1009
|
+
if (penalty === "low") return level === "High" ? "Medium" : "Low";
|
|
1010
|
+
return "Low";
|
|
1011
|
+
}
|
|
883
1012
|
function runGuard(rt, input) {
|
|
884
1013
|
if (!input.baselineEvents || input.baselineEvents.length === 0) {
|
|
885
|
-
|
|
1014
|
+
const conf2 = { level: "Low", reasons: ["baseline empty"] };
|
|
1015
|
+
const msg2 = [
|
|
1016
|
+
"FAIL: baseline usage is empty (need aiopt-output/usage.jsonl)",
|
|
1017
|
+
"Impact (monthly est): +$0 (insufficient baseline)",
|
|
1018
|
+
`Accident risk: ${accidentRiskFromMonthly(100)}`,
|
|
1019
|
+
`Confidence: ${conf2.level} (${conf2.reasons.length ? conf2.reasons.join(", ") : "baseline empty"})`,
|
|
1020
|
+
"Recommendation: run the wrapper to collect baseline usage before using guard."
|
|
1021
|
+
].join("\n");
|
|
1022
|
+
return { exitCode: 3, message: msg2 };
|
|
886
1023
|
}
|
|
887
|
-
const
|
|
888
|
-
const
|
|
1024
|
+
const baselineEvents = input.baselineEvents.map((e) => ({ ...e, billed_cost: void 0 }));
|
|
1025
|
+
const base = analyze(rt, baselineEvents);
|
|
1026
|
+
const candidateEvents = applyCandidate(baselineEvents, input.candidate);
|
|
889
1027
|
const cand = analyze(rt, candidateEvents);
|
|
890
1028
|
const baseCost = base.analysis.total_cost;
|
|
891
|
-
|
|
1029
|
+
let candCost = cand.analysis.total_cost;
|
|
1030
|
+
const callMult = input.candidate.callMultiplier && input.candidate.callMultiplier > 0 ? input.candidate.callMultiplier : 1;
|
|
1031
|
+
if (callMult !== 1) {
|
|
1032
|
+
candCost = candCost * callMult;
|
|
1033
|
+
}
|
|
1034
|
+
const attemptLog = baselineEvents.some((e) => e.trace_id && String(e.trace_id).length > 0 || e.attempt !== void 0 && Number(e.attempt) > 0);
|
|
1035
|
+
if (attemptLog && input.candidate.retriesDelta && input.candidate.retriesDelta > 0) {
|
|
1036
|
+
let retryUnit = 0;
|
|
1037
|
+
let retryCount = 0;
|
|
1038
|
+
for (const ev of baselineEvents) {
|
|
1039
|
+
const attempt = Number(ev.attempt || 1);
|
|
1040
|
+
if (attempt >= 2) {
|
|
1041
|
+
retryUnit += costOfEvent(rt, ev).cost;
|
|
1042
|
+
retryCount += 1;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (retryCount > 0) {
|
|
1046
|
+
retryUnit = retryUnit / retryCount;
|
|
1047
|
+
} else {
|
|
1048
|
+
retryUnit = baseCost / Math.max(1, baselineEvents.length);
|
|
1049
|
+
}
|
|
1050
|
+
candCost += retryUnit * input.candidate.retriesDelta;
|
|
1051
|
+
}
|
|
892
1052
|
const delta = candCost - baseCost;
|
|
893
|
-
const
|
|
894
|
-
const
|
|
1053
|
+
const changeConf = confidenceFromChange(input.candidate);
|
|
1054
|
+
const dq = assessDataQuality(baselineEvents, base);
|
|
1055
|
+
const conf = { level: degrade(changeConf.level, dq.penalty), reasons: [...changeConf.reasons, ...dq.reasons.map((r) => `data: ${r}`)] };
|
|
1056
|
+
const monthly = monthEstimate(Math.max(0, delta), baselineEvents);
|
|
895
1057
|
const monthlyRounded = round23(monthly);
|
|
896
1058
|
let exitCode = 0;
|
|
897
1059
|
let headline = "OK: no cost accident risk detected";
|
|
@@ -905,23 +1067,142 @@ function runGuard(rt, input) {
|
|
|
905
1067
|
const reasons = conf.reasons.length ? conf.reasons.join(", ") : "n/a";
|
|
906
1068
|
const msg = [
|
|
907
1069
|
headline,
|
|
908
|
-
`Summary: baseline=$${baseCost} \u2192 candidate=$${candCost} (\u0394=$${round23(delta)})`,
|
|
1070
|
+
`Summary: baseline=$${round23(baseCost)} \u2192 candidate=$${round23(candCost)} (\u0394=$${round23(delta)})`,
|
|
1071
|
+
callMult !== 1 ? `Call volume multiplier: x${callMult}` : null,
|
|
909
1072
|
`Impact (monthly est): +$${monthlyRounded}`,
|
|
1073
|
+
`Accident risk: ${accidentRiskFromMonthly(monthly)}`,
|
|
910
1074
|
`Confidence: ${conf.level} (${reasons})`,
|
|
911
1075
|
"Recommendation: review model/provider/retry/context changes before deploy."
|
|
912
|
-
].join("\n");
|
|
1076
|
+
].filter(Boolean).join("\n");
|
|
913
1077
|
return { exitCode, message: msg };
|
|
914
1078
|
}
|
|
915
1079
|
var init_guard = __esm({
|
|
916
1080
|
"src/guard.ts"() {
|
|
917
1081
|
"use strict";
|
|
918
1082
|
init_scan();
|
|
1083
|
+
init_cost();
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// src/dashboard.ts
|
|
1088
|
+
var dashboard_exports = {};
|
|
1089
|
+
__export(dashboard_exports, {
|
|
1090
|
+
startDashboard: () => startDashboard
|
|
1091
|
+
});
|
|
1092
|
+
async function startDashboard(cwd, opts) {
|
|
1093
|
+
const host = "127.0.0.1";
|
|
1094
|
+
const port = opts.port || 3010;
|
|
1095
|
+
const outDir = import_path7.default.join(cwd, "aiopt-output");
|
|
1096
|
+
const file = (name) => import_path7.default.join(outDir, name);
|
|
1097
|
+
function readOrNull(p) {
|
|
1098
|
+
try {
|
|
1099
|
+
if (!import_fs7.default.existsSync(p)) return null;
|
|
1100
|
+
return import_fs7.default.readFileSync(p, "utf8");
|
|
1101
|
+
} catch {
|
|
1102
|
+
return null;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const indexHtml = `<!doctype html>
|
|
1106
|
+
<html>
|
|
1107
|
+
<head>
|
|
1108
|
+
<meta charset="utf-8"/>
|
|
1109
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
1110
|
+
<title>AIOpt Local Dashboard</title>
|
|
1111
|
+
<style>
|
|
1112
|
+
body{font-family:ui-sans-serif,system-ui,Arial; margin:16px; max-width:980px}
|
|
1113
|
+
.row{display:flex; gap:12px; flex-wrap:wrap}
|
|
1114
|
+
.card{border:1px solid #ddd; border-radius:10px; padding:12px; flex:1; min-width:320px}
|
|
1115
|
+
pre{white-space:pre-wrap; word-break:break-word; background:#0b1020; color:#e6e6e6; padding:12px; border-radius:10px; overflow:auto}
|
|
1116
|
+
a{color:#2d6cdf}
|
|
1117
|
+
</style>
|
|
1118
|
+
</head>
|
|
1119
|
+
<body>
|
|
1120
|
+
<h1>AIOpt Local Dashboard</h1>
|
|
1121
|
+
<p>Local-only (bind: 127.0.0.1). Reads files from <code>./aiopt-output/</code>.</p>
|
|
1122
|
+
|
|
1123
|
+
<div class="row">
|
|
1124
|
+
<div class="card">
|
|
1125
|
+
<h2>Last Guard</h2>
|
|
1126
|
+
<div id="guardMeta"></div>
|
|
1127
|
+
<pre id="guard">loading...</pre>
|
|
1128
|
+
<p><a href="/api/guard-last.txt" target="_blank">raw</a></p>
|
|
1129
|
+
</div>
|
|
1130
|
+
<div class="card">
|
|
1131
|
+
<h2>Last Scan</h2>
|
|
1132
|
+
<pre id="scan">loading...</pre>
|
|
1133
|
+
<p>
|
|
1134
|
+
<a href="/api/report.md" target="_blank">report.md</a> \xB7
|
|
1135
|
+
<a href="/api/report.json" target="_blank">report.json</a>
|
|
1136
|
+
</p>
|
|
1137
|
+
</div>
|
|
1138
|
+
</div>
|
|
1139
|
+
|
|
1140
|
+
<script>
|
|
1141
|
+
async function load() {
|
|
1142
|
+
const guardTxt = await fetch('/api/guard-last.txt').then(r=>r.ok?r.text():null);
|
|
1143
|
+
const guardMeta = await fetch('/api/guard-last.json').then(r=>r.ok?r.json():null);
|
|
1144
|
+
document.getElementById('guard').textContent = guardTxt || '(no guard-last.txt yet)';
|
|
1145
|
+
document.getElementById('guardMeta').textContent = guardMeta ? ('exit=' + guardMeta.exitCode + ' @ ' + guardMeta.ts) : '';
|
|
1146
|
+
|
|
1147
|
+
const reportMd = await fetch('/api/report.md').then(r=>r.ok?r.text():null);
|
|
1148
|
+
document.getElementById('scan').textContent = reportMd || '(no report.md yet \u2014 run: aiopt scan)';
|
|
1149
|
+
}
|
|
1150
|
+
load();
|
|
1151
|
+
</script>
|
|
1152
|
+
</body>
|
|
1153
|
+
</html>`;
|
|
1154
|
+
const server = import_http.default.createServer((req, res) => {
|
|
1155
|
+
const url = req.url || "/";
|
|
1156
|
+
if (url === "/" || url === "/index.html") {
|
|
1157
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
1158
|
+
res.end(indexHtml);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (url.startsWith("/api/")) {
|
|
1162
|
+
const name = url.replace("/api/", "");
|
|
1163
|
+
const allow = /* @__PURE__ */ new Set(["guard-last.txt", "guard-last.json", "report.md", "report.json"]);
|
|
1164
|
+
if (!allow.has(name)) {
|
|
1165
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
1166
|
+
res.end("not found");
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const p = file(name);
|
|
1170
|
+
const txt = readOrNull(p);
|
|
1171
|
+
if (txt === null) {
|
|
1172
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
1173
|
+
res.end("missing");
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const ct = name.endsWith(".json") ? "application/json; charset=utf-8" : "text/plain; charset=utf-8";
|
|
1177
|
+
res.writeHead(200, { "content-type": ct });
|
|
1178
|
+
res.end(txt);
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
1182
|
+
res.end("not found");
|
|
1183
|
+
});
|
|
1184
|
+
await new Promise((resolve, reject) => {
|
|
1185
|
+
server.once("error", reject);
|
|
1186
|
+
server.listen(port, host, () => resolve());
|
|
1187
|
+
});
|
|
1188
|
+
console.log(`OK: dashboard http://${host}:${port}/`);
|
|
1189
|
+
console.log("Tip: run `aiopt guard ...` and `aiopt scan` to populate aiopt-output files.");
|
|
1190
|
+
await new Promise(() => {
|
|
1191
|
+
});
|
|
1192
|
+
}
|
|
1193
|
+
var import_http, import_fs7, import_path7;
|
|
1194
|
+
var init_dashboard = __esm({
|
|
1195
|
+
"src/dashboard.ts"() {
|
|
1196
|
+
"use strict";
|
|
1197
|
+
import_http = __toESM(require("http"));
|
|
1198
|
+
import_fs7 = __toESM(require("fs"));
|
|
1199
|
+
import_path7 = __toESM(require("path"));
|
|
919
1200
|
}
|
|
920
1201
|
});
|
|
921
1202
|
|
|
922
1203
|
// src/cli.ts
|
|
923
|
-
var
|
|
924
|
-
var
|
|
1204
|
+
var import_fs8 = __toESM(require("fs"));
|
|
1205
|
+
var import_path8 = __toESM(require("path"));
|
|
925
1206
|
var import_commander = require("commander");
|
|
926
1207
|
|
|
927
1208
|
// src/io.ts
|
|
@@ -982,17 +1263,17 @@ var program = new import_commander.Command();
|
|
|
982
1263
|
var DEFAULT_INPUT = "./aiopt-output/usage.jsonl";
|
|
983
1264
|
var DEFAULT_OUTPUT_DIR = "./aiopt-output";
|
|
984
1265
|
function loadRateTable() {
|
|
985
|
-
const p =
|
|
986
|
-
return JSON.parse(
|
|
1266
|
+
const p = import_path8.default.join(__dirname, "..", "rates", "rate_table.json");
|
|
1267
|
+
return JSON.parse(import_fs8.default.readFileSync(p, "utf8"));
|
|
987
1268
|
}
|
|
988
1269
|
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);
|
|
989
1270
|
program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.jsonl, aiopt-output/ \uC0DD\uC131").action(() => {
|
|
990
1271
|
ensureDir("./aiopt-input");
|
|
991
1272
|
ensureDir("./aiopt-output");
|
|
992
|
-
const sampleSrc =
|
|
993
|
-
const dst =
|
|
994
|
-
if (!
|
|
995
|
-
|
|
1273
|
+
const sampleSrc = import_path8.default.join(__dirname, "..", "samples", "sample_usage.jsonl");
|
|
1274
|
+
const dst = import_path8.default.join("./aiopt-input", "usage.jsonl");
|
|
1275
|
+
if (!import_fs8.default.existsSync(dst)) {
|
|
1276
|
+
import_fs8.default.copyFileSync(sampleSrc, dst);
|
|
996
1277
|
console.log("Created ./aiopt-input/usage.jsonl (sample)");
|
|
997
1278
|
} else {
|
|
998
1279
|
console.log("Exists ./aiopt-input/usage.jsonl (skip)");
|
|
@@ -1002,7 +1283,7 @@ program.command("init").description("aiopt-input/ \uBC0F \uC0D8\uD50C usage.json
|
|
|
1002
1283
|
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) => {
|
|
1003
1284
|
const inputPath = String(opts.input);
|
|
1004
1285
|
const outDir = String(opts.out);
|
|
1005
|
-
if (!
|
|
1286
|
+
if (!import_fs8.default.existsSync(inputPath)) {
|
|
1006
1287
|
console.error(`Input not found: ${inputPath}`);
|
|
1007
1288
|
process.exit(1);
|
|
1008
1289
|
}
|
|
@@ -1018,7 +1299,7 @@ program.command("scan").description("\uC785\uB825 \uB85C\uADF8(JSONL/CSV)\uB97C
|
|
|
1018
1299
|
const tag = f.status === "no-issue" ? "(no issue detected)" : `($${Math.round(f.impact_usd * 100) / 100})`;
|
|
1019
1300
|
console.log(`${i + 1}) ${f.title} ${tag}`);
|
|
1020
1301
|
});
|
|
1021
|
-
console.log(`Report: ${
|
|
1302
|
+
console.log(`Report: ${import_path8.default.join(outDir, "report.md")}`);
|
|
1022
1303
|
});
|
|
1023
1304
|
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) => {
|
|
1024
1305
|
const inputPath = String(opts.input);
|
|
@@ -1028,7 +1309,7 @@ program.command("policy").description("\uB9C8\uC9C0\uB9C9 scan \uACB0\uACFC \uAE
|
|
|
1028
1309
|
const { policy } = analyze(rt, events);
|
|
1029
1310
|
policy.generated_from.input = inputPath;
|
|
1030
1311
|
ensureDir(outDir);
|
|
1031
|
-
|
|
1312
|
+
import_fs8.default.writeFileSync(import_path8.default.join(outDir, "cost-policy.json"), JSON.stringify(policy, null, 2));
|
|
1032
1313
|
console.log(`OK: ${outDir}/cost-policy.json`);
|
|
1033
1314
|
});
|
|
1034
1315
|
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) => {
|
|
@@ -1050,10 +1331,58 @@ program.command("doctor").description("Check installation + print last 5 usage e
|
|
|
1050
1331
|
console.log(JSON.stringify(x));
|
|
1051
1332
|
}
|
|
1052
1333
|
});
|
|
1053
|
-
program.command("
|
|
1334
|
+
var licenseCmd = program.command("license").description("Offline license activate/verify (public key only; no server calls)");
|
|
1335
|
+
licenseCmd.command("activate").argument("<KEY>", "license key (<payloadB64Url>.<sigB64Url>)").option("--out <path>", "output license.json path (default: ./aiopt/license.json)").action(async (key, opts) => {
|
|
1336
|
+
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, verifyLicenseKey: verifyLicenseKey2, writeLicenseFile: writeLicenseFile2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
1337
|
+
const outPath = opts.out ? String(opts.out) : defaultLicensePath2(process.cwd());
|
|
1338
|
+
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
1339
|
+
const v = verifyLicenseKey2(String(key), pub);
|
|
1340
|
+
if (!v.payload) {
|
|
1341
|
+
console.error(`FAIL: ${v.reason || "invalid license"}`);
|
|
1342
|
+
process.exit(3);
|
|
1343
|
+
}
|
|
1344
|
+
writeLicenseFile2(outPath, String(key), v.payload, v.ok);
|
|
1345
|
+
console.log(v.ok ? `OK: activated (${outPath})` : `WARN: saved but not verified (${v.reason}) (${outPath})`);
|
|
1346
|
+
process.exit(v.ok ? 0 : 2);
|
|
1347
|
+
});
|
|
1348
|
+
licenseCmd.command("verify").option("--path <path>", "license.json path (default: ./aiopt/license.json)").action(async (opts) => {
|
|
1349
|
+
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
1350
|
+
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
1351
|
+
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
1352
|
+
if (!import_fs8.default.existsSync(p)) {
|
|
1353
|
+
console.error(`FAIL: license file not found: ${p}`);
|
|
1354
|
+
process.exit(3);
|
|
1355
|
+
}
|
|
1356
|
+
const f = readLicenseFile2(p);
|
|
1357
|
+
const v = verifyLicenseKey2(f.key, pub);
|
|
1358
|
+
if (v.ok) {
|
|
1359
|
+
console.log("OK: license verified");
|
|
1360
|
+
process.exit(0);
|
|
1361
|
+
}
|
|
1362
|
+
console.error(`FAIL: license invalid (${v.reason || "unknown"})`);
|
|
1363
|
+
process.exit(3);
|
|
1364
|
+
});
|
|
1365
|
+
licenseCmd.command("status").option("--path <path>", "license.json path (default: ./aiopt/license.json)").action(async (opts) => {
|
|
1366
|
+
const { DEFAULT_PUBLIC_KEY_PEM: DEFAULT_PUBLIC_KEY_PEM2, defaultLicensePath: defaultLicensePath2, readLicenseFile: readLicenseFile2, verifyLicenseKey: verifyLicenseKey2 } = await Promise.resolve().then(() => (init_license(), license_exports));
|
|
1367
|
+
const p = opts.path ? String(opts.path) : defaultLicensePath2(process.cwd());
|
|
1368
|
+
const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM2;
|
|
1369
|
+
if (!import_fs8.default.existsSync(p)) {
|
|
1370
|
+
console.log("NO_LICENSE");
|
|
1371
|
+
process.exit(2);
|
|
1372
|
+
}
|
|
1373
|
+
const f = readLicenseFile2(p);
|
|
1374
|
+
const v = verifyLicenseKey2(f.key, pub);
|
|
1375
|
+
if (v.ok) {
|
|
1376
|
+
console.log(`OK: ${f.payload.plan} exp=${f.payload.exp}`);
|
|
1377
|
+
process.exit(0);
|
|
1378
|
+
}
|
|
1379
|
+
console.log(`INVALID: ${v.reason || "unknown"}`);
|
|
1380
|
+
process.exit(3);
|
|
1381
|
+
});
|
|
1382
|
+
program.command("guard").description("Pre-deploy guardrail: compare baseline usage vs candidate change and print warnings (exit codes 0/2/3)").option("--input <path>", "baseline usage jsonl/csv (default: ./aiopt-output/usage.jsonl)", DEFAULT_INPUT).option("--provider <provider>", "candidate provider override").option("--model <model>", "candidate model override").option("--context-mult <n>", "multiply input_tokens by n", (v) => Number(v)).option("--output-mult <n>", "multiply output_tokens by n", (v) => Number(v)).option("--retries-delta <n>", "add n to retries", (v) => Number(v)).option("--call-mult <n>", "multiply call volume by n (traffic spike)", (v) => Number(v)).action(async (opts) => {
|
|
1054
1383
|
const rt = loadRateTable();
|
|
1055
1384
|
const inputPath = String(opts.input);
|
|
1056
|
-
if (!
|
|
1385
|
+
if (!import_fs8.default.existsSync(inputPath)) {
|
|
1057
1386
|
console.error(`FAIL: baseline not found: ${inputPath}`);
|
|
1058
1387
|
process.exit(3);
|
|
1059
1388
|
}
|
|
@@ -1066,11 +1395,23 @@ program.command("guard").description("Pre-deploy guardrail: compare baseline usa
|
|
|
1066
1395
|
model: opts.model,
|
|
1067
1396
|
contextMultiplier: opts.contextMult,
|
|
1068
1397
|
outputMultiplier: opts.outputMult,
|
|
1069
|
-
retriesDelta: opts.retriesDelta
|
|
1398
|
+
retriesDelta: opts.retriesDelta,
|
|
1399
|
+
callMultiplier: opts.callMult
|
|
1070
1400
|
}
|
|
1071
1401
|
});
|
|
1072
1402
|
console.log(r.message);
|
|
1403
|
+
try {
|
|
1404
|
+
const outDir = import_path8.default.resolve(DEFAULT_OUTPUT_DIR);
|
|
1405
|
+
import_fs8.default.mkdirSync(outDir, { recursive: true });
|
|
1406
|
+
import_fs8.default.writeFileSync(import_path8.default.join(outDir, "guard-last.txt"), r.message);
|
|
1407
|
+
import_fs8.default.writeFileSync(import_path8.default.join(outDir, "guard-last.json"), JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), exitCode: r.exitCode }, null, 2));
|
|
1408
|
+
} catch {
|
|
1409
|
+
}
|
|
1073
1410
|
process.exit(r.exitCode);
|
|
1074
1411
|
});
|
|
1412
|
+
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) => {
|
|
1413
|
+
const { startDashboard: startDashboard2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
|
|
1414
|
+
await startDashboard2(process.cwd(), { port: Number(opts.port || 3010) });
|
|
1415
|
+
});
|
|
1075
1416
|
program.parse(process.argv);
|
|
1076
1417
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cost.ts","../src/solutions.ts","../src/scan.ts","../package.json","../src/install.ts","../src/doctor.ts","../src/guard.ts","../src/cli.ts","../src/io.ts"],"sourcesContent":["import { RateTable, UsageEvent } from './types';\n\nexport type CostResult = {\n cost: number;\n used_rate: {\n kind: 'billed_cost' | 'official' | 'estimated';\n provider: string;\n model: string;\n input_per_m: number;\n output_per_m: number;\n };\n};\n\nexport function getRates(rt: RateTable, provider: string, model: string) {\n const prov = String(provider || '').toLowerCase();\n\n // local/offline LLM: treat cost as 0 (or user can add a provider rate later)\n if (prov === 'local' || prov === 'ollama' || prov === 'vllm') {\n return { kind: 'official' as const, input: 0, output: 0 };\n }\n\n const p = rt.providers[prov];\n if (!p) return null;\n const m = p.models[model];\n if (m) return { kind: 'official' as const, input: m.input, output: m.output };\n return { kind: 'estimated' as const, input: p.default_estimated.input, output: p.default_estimated.output };\n}\n\nexport function costOfEvent(rt: RateTable, ev: UsageEvent): CostResult {\n if (typeof ev.billed_cost === 'number' && Number.isFinite(ev.billed_cost)) {\n return {\n cost: ev.billed_cost,\n used_rate: {\n kind: 'billed_cost',\n provider: ev.provider,\n model: ev.model,\n input_per_m: 0,\n output_per_m: 0\n }\n };\n }\n\n const r = getRates(rt, ev.provider, ev.model);\n if (!r) {\n // Unknown provider: deterministic fallback estimate (kept stable for reproducibility)\n const input_per_m = 1.0;\n const output_per_m = 4.0;\n const cost = (ev.input_tokens / 1e6) * input_per_m + (ev.output_tokens / 1e6) * output_per_m;\n return {\n cost,\n used_rate: { kind: 'estimated', provider: String(ev.provider || '').toLowerCase(), model: ev.model, input_per_m, output_per_m }\n };\n }\n\n const cost = (ev.input_tokens / 1e6) * r.input + (ev.output_tokens / 1e6) * r.output;\n return {\n cost,\n used_rate: {\n kind: r.kind,\n provider: ev.provider,\n model: ev.model,\n input_per_m: r.input,\n output_per_m: r.output\n }\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { AnalysisJson, Savings } from './scan';\n\nexport type Fix = {\n id: string;\n title: string;\n impact_usd: number;\n why: string;\n what_to_change: string[];\n status: 'action' | 'no-issue';\n};\n\nconst EPS = 0.0001;\n\nexport function buildTopFixes(analysis: AnalysisJson, savings: Savings): Fix[] {\n const fixes: Fix[] = [];\n\n // Retry tuning\n fixes.push({\n id: 'fix-retry-tuning',\n title: 'Retry tuning',\n impact_usd: Number(savings.retry_waste || 0),\n status: Number(savings.retry_waste || 0) > EPS ? 'action' : 'no-issue',\n why: `Retry waste is estimated at $${round2(Number(savings.retry_waste || 0))}.`,\n what_to_change: [\n 'aiopt/policies/retry.json: lower max_attempts or adjust backoff_ms',\n 'Ensure idempotency keys are stable per trace_id',\n 'Log error_code to identify retryable classes'\n ]\n });\n\n // Output cap (use context_savings as proxy impact)\n fixes.push({\n id: 'fix-output-cap',\n title: 'Output cap',\n impact_usd: Number(savings.context_savings || 0),\n status: Number(savings.context_savings || 0) > EPS ? 'action' : 'no-issue',\n why: `Context savings estimate: $${round2(Number(savings.context_savings || 0))}. Output caps prevent runaway completions.`,\n what_to_change: [\n 'aiopt/policies/output.json: set max_output_tokens_default',\n 'aiopt/policies/output.json: set per_feature caps (summarize/classify/translate)'\n ]\n });\n\n // Routing\n const topFeature = analysis.by_feature_top?.[0]?.key;\n fixes.push({\n id: 'fix-routing',\n title: 'Routing rule',\n impact_usd: Number(savings.routing_savings || 0),\n status: Number(savings.routing_savings || 0) > EPS ? 'action' : 'no-issue',\n why: `Routing savings estimate: $${round2(Number(savings.routing_savings || 0))}.`,\n what_to_change: [\n 'aiopt/policies/routing.json: route summarize/classify/translate to cheap tier',\n `Consider adding feature_tag_in for top feature: ${topFeature || '(unknown)'}`\n ]\n });\n\n // sort by impact desc, deterministic tie-break\n fixes.sort((a, b) => (b.impact_usd - a.impact_usd) || a.id.localeCompare(b.id));\n return fixes;\n}\n\nfunction round2(n: number) {\n return Math.round(n * 100) / 100;\n}\n\nexport function writePatches(outDir: string, fixes: Fix[]) {\n const patchesDir = path.join(outDir, 'patches');\n fs.mkdirSync(patchesDir, { recursive: true });\n\n const readme = [\n '# AIOpt patches (MVP)',\n '',\n 'This folder contains suggested changes you can apply locally.',\n '',\n '## Top fixes',\n ...fixes.map((f, i) => `${i + 1}. ${f.title} — ${f.why}`),\n '',\n 'Files are stubs (human review required).',\n ''\n ].join('\\n');\n\n fs.writeFileSync(path.join(patchesDir, 'README.md'), readme);\n\n // Minimal stub files to satisfy DoD naming.\n fs.writeFileSync(path.join(patchesDir, 'policies.updated.routing.json'), JSON.stringify({ note: 'apply changes to aiopt/policies/routing.json', fixes: fixes.filter(f => f.id.includes('routing')) }, null, 2));\n fs.writeFileSync(path.join(patchesDir, 'policies.updated.retry.json'), JSON.stringify({ note: 'apply changes to aiopt/policies/retry.json', fixes: fixes.filter(f => f.id.includes('retry')) }, null, 2));\n fs.writeFileSync(path.join(patchesDir, 'policies.updated.output.json'), JSON.stringify({ note: 'apply changes to aiopt/policies/output.json', fixes: fixes.filter(f => f.id.includes('output')) }, null, 2));\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { RateTable, UsageEvent } from './types';\nimport { costOfEvent, getRates } from './cost';\nimport { buildTopFixes, writePatches } from './solutions';\n\nexport type AnalysisJson = {\n total_cost: number;\n by_model_top: Array<{ key: string; cost: number; events: number }>;\n by_feature_top: Array<{ key: string; cost: number; events: number }>;\n unknown_models: Array<{ provider: string; model: string; reason: string }>;\n rate_table_version: string;\n rate_table_date: string;\n};\n\nexport type Savings = {\n estimated_savings_total: number;\n routing_savings: number;\n context_savings: number;\n retry_waste: number;\n notes: [string, string, string];\n};\n\nexport type PolicyJson = {\n version: number;\n default_provider: string;\n rules: Array<any>;\n budgets: { currency: string; notes?: string };\n generated_from: { rate_table_version: string; input: string };\n};\n\nconst ROUTE_TO_CHEAP_FEATURES = new Set(['summarize', 'classify', 'translate']);\n\nfunction topN(map: Map<string, { cost: number; events: number }>, n: number) {\n return [...map.entries()]\n .map(([key, v]) => ({ key, cost: v.cost, events: v.events }))\n .sort((a, b) => b.cost - a.cost)\n .slice(0, n);\n}\n\nexport function analyze(rt: RateTable, events: UsageEvent[]): { analysis: AnalysisJson; savings: Savings; policy: PolicyJson; meta: { mode: 'attempt-log'|'legacy' } } {\n const byModel = new Map<string, { cost: number; events: number }>();\n const byFeature = new Map<string, { cost: number; events: number }>();\n const unknownModels: AnalysisJson['unknown_models'] = [];\n\n const perEventCosts: Array<{ ev: UsageEvent; cost: number }> = [];\n\n // Detect wrapper attempt-log mode: each JSONL line is an attempt (trace_id/attempt present)\n const isAttemptLog = events.some(e => (e.trace_id && String(e.trace_id).length > 0) || (e.attempt !== undefined && Number(e.attempt) > 0));\n\n let baseTotal = 0;\n let total = 0;\n for (const ev of events) {\n const cr = costOfEvent(rt, ev);\n\n baseTotal += cr.cost;\n\n if (isAttemptLog) {\n // Each event line is already one attempt; do NOT multiply by retries.\n total += cr.cost;\n } else {\n // Legacy aggregate mode: one line represents a request with retries count.\n const retries = Math.max(0, Number(ev.retries || 0));\n total += cr.cost * (1 + retries);\n }\n\n perEventCosts.push({ ev, cost: cr.cost });\n\n const mk = `${ev.provider}:${ev.model}`;\n const fk = ev.feature_tag || '(none)';\n\n const mv = byModel.get(mk) || { cost: 0, events: 0 };\n mv.cost += cr.cost; mv.events += 1;\n byModel.set(mk, mv);\n\n const fv = byFeature.get(fk) || { cost: 0, events: 0 };\n fv.cost += cr.cost; fv.events += 1;\n byFeature.set(fk, fv);\n\n const rr = getRates(rt, ev.provider, ev.model);\n if (!rr) {\n unknownModels.push({ provider: ev.provider, model: ev.model, reason: 'unknown provider (estimated)' });\n } else if (rr.kind === 'estimated') {\n unknownModels.push({ provider: ev.provider, model: ev.model, reason: 'unknown model (estimated)' });\n }\n }\n\n // --- Savings simulator (refactor): per-event before/after with caps + no double counting\n type Pot = { routing: number; context: number; retry: number; total: number; waste: number };\n const potByIdx: Pot[] = [];\n\n // Potential routing/context computed per event\n for (const { ev, cost } of perEventCosts) {\n const retries = Math.max(0, Number(ev.retries || 0));\n const attempt = Number(ev.attempt || 1);\n\n const total_i = isAttemptLog ? cost : cost * (1 + retries);\n // Retry waste:\n // - attempt-log mode: attempts >= 2 are retry waste\n // - legacy mode: base_cost * retries\n const waste_i = isAttemptLog ? (attempt >= 2 ? cost : 0) : cost * retries;\n\n let routing_i = 0;\n if (ROUTE_TO_CHEAP_FEATURES.has(String(ev.feature_tag || '').toLowerCase())) {\n const provider = ev.provider;\n const p = rt.providers[provider];\n if (p) {\n const entries = Object.entries(p.models);\n if (entries.length > 0) {\n const cheapest = entries\n .map(([name, r]) => ({ name, score: (r.input + r.output) / 2, r }))\n .sort((a, b) => a.score - b.score)[0];\n const currentRate = getRates(rt, provider, ev.model);\n if (currentRate && currentRate.kind !== 'estimated') {\n const currentCost = (ev.input_tokens / 1e6) * currentRate.input + (ev.output_tokens / 1e6) * currentRate.output;\n const cheapCost = (ev.input_tokens / 1e6) * cheapest.r.input + (ev.output_tokens / 1e6) * cheapest.r.output;\n const diff = (currentCost - cheapCost) * (1 + retries);\n routing_i = Math.max(0, diff);\n }\n }\n }\n }\n\n // context: top 20% rule is applied later by index set\n potByIdx.push({ routing: routing_i, context: 0, retry: waste_i, total: total_i, waste: waste_i });\n }\n\n // context potential assignment (deterministic): top 20% by input_tokens => 25% reduction\n // In attempt-log mode, only apply to attempt==1 to avoid overcounting retries.\n const sortedIdx = [...events.map((e, i) => ({ i, input: Number(e.input_tokens || 0), ok: !isAttemptLog || Number(e.attempt || 1) === 1 }))]\n .filter(x => x.ok)\n .sort((a, b) => b.input - a.input);\n const k = Math.max(1, Math.floor(sortedIdx.length * 0.2));\n const topIdx = new Set(sortedIdx.slice(0, k).map(x => x.i));\n for (let i = 0; i < events.length; i++) {\n if (!topIdx.has(i)) continue;\n const ev = events[i];\n const retries = Math.max(0, Number(ev.retries || 0));\n const r = getRates(rt, ev.provider, ev.model);\n if (!r) continue;\n const saveTokens = (Number(ev.input_tokens || 0)) * 0.25;\n const multiplier = isAttemptLog ? 1 : (1 + retries);\n const diff = (saveTokens / 1e6) * r.input * multiplier;\n potByIdx[i].context = Math.max(0, diff);\n }\n\n // Allocate savings without overlap (routing -> context -> retry), each capped by remaining cost.\n let routingSavings = 0;\n let contextSavings = 0;\n let retryWaste = 0;\n\n for (const p of potByIdx) {\n let remaining = p.total;\n\n const rSave = Math.min(p.routing, remaining);\n remaining -= rSave;\n routingSavings += rSave;\n\n const cSave = Math.min(p.context, remaining);\n remaining -= cSave;\n contextSavings += cSave;\n\n // Retry tuning can only save the waste portion, and cannot exceed remaining.\n const retrySave = Math.min(p.retry, remaining);\n retryWaste += retrySave;\n }\n\n const estimatedSavingsTotal = routingSavings + contextSavings + retryWaste;\n // Global guard\n const guardedSavingsTotal = Math.min(estimatedSavingsTotal, total);\n\n const analysis: AnalysisJson = {\n total_cost: round2(total),\n by_model_top: topN(byModel, 10).map(x => ({ ...x, cost: round2(x.cost) })),\n by_feature_top: topN(byFeature, 10).map(x => ({ ...x, cost: round2(x.cost) })),\n unknown_models: uniqUnknown(unknownModels),\n rate_table_version: rt.version,\n rate_table_date: rt.date\n };\n\n const savings: Savings = {\n estimated_savings_total: round2(guardedSavingsTotal),\n routing_savings: round2(routingSavings),\n context_savings: round2(contextSavings),\n retry_waste: round2(retryWaste),\n notes: [\n `a) 모델 라우팅 절감(추정): $${round2(routingSavings)}`,\n `b) 컨텍스트 감축(추정): $${round2(contextSavings)} (상위 20% input에 25% 감축 가정)` ,\n `c) 재시도/오류 낭비(상한 적용): $${round2(retryWaste)} (retries 기반)`\n ]\n };\n\n const policy: PolicyJson = buildPolicy(rt, events);\n\n return { analysis, savings, policy, meta: { mode: isAttemptLog ? 'attempt-log' : 'legacy' } };\n}\n\nfunction buildPolicy(rt: RateTable, events: UsageEvent[]): PolicyJson {\n // Default provider: most frequent\n const freq = new Map<string, number>();\n for (const ev of events) freq.set(ev.provider, (freq.get(ev.provider) || 0) + 1);\n const defaultProvider = [...freq.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] || 'openai';\n\n // For cheap features: recommend cheapest known model per provider.\n const rules: any[] = [];\n for (const provider of Object.keys(rt.providers)) {\n const p = rt.providers[provider];\n const entries = Object.entries(p.models);\n if (entries.length === 0) continue;\n const cheapest = entries\n .map(([name, r]) => ({ name, score: (r.input + r.output) / 2, r }))\n .sort((a, b) => a.score - b.score)[0];\n\n rules.push({\n match: { provider, feature_tag_in: ['summarize', 'classify', 'translate'] },\n action: { recommend_model: cheapest.name, reason: 'cheap-feature routing' }\n });\n }\n\n // Unknown models: keep (no policy)\n rules.push({ match: { model_unknown: true }, action: { keep: true, reason: 'unknown model -> no policy applied' } });\n\n return {\n version: 1,\n default_provider: defaultProvider,\n rules,\n budgets: { currency: rt.currency, notes: 'MVP: budgets not enforced' },\n generated_from: { rate_table_version: rt.version, input: './aiopt-input/usage.jsonl' }\n };\n}\n\nfunction uniqUnknown(list: AnalysisJson['unknown_models']) {\n const seen = new Set<string>();\n const out: AnalysisJson['unknown_models'] = [];\n for (const x of list) {\n const k = `${x.provider}:${x.model}:${x.reason}`;\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(x);\n }\n return out;\n}\n\nfunction round2(n: number) {\n return Math.round(n * 100) / 100;\n}\n\nexport function writeOutputs(outDir: string, analysis: AnalysisJson, savings: Savings, policy: PolicyJson, meta?: { mode?: 'attempt-log'|'legacy' }) {\n const mode = meta?.mode || 'legacy';\n\n fs.mkdirSync(outDir, { recursive: true });\n\n fs.writeFileSync(path.join(outDir, 'analysis.json'), JSON.stringify(analysis, null, 2));\n\n // report.json is the “one file to parse” summary for downstream tooling.\n const unknownCount = analysis.unknown_models?.length || 0;\n // confidence: downgrade if many unknowns; keep deterministic\n const confidence = unknownCount === 0 ? 'HIGH' : (unknownCount <= 3 ? 'MED' : 'LOW');\n const ratio = analysis.total_cost > 0 ? (savings.estimated_savings_total / analysis.total_cost) : 0;\n const warnings: string[] = [];\n if (ratio >= 0.9) warnings.push('estimated savings >= 90%');\n if (unknownCount > 0) warnings.push('unknown models/providers detected (estimated pricing used)');\n\n const reportJson = {\n version: 3,\n generated_at: new Date().toISOString(),\n confidence,\n warnings,\n assumptions: {\n no_double_counting: 'routing -> context -> retry allocation per-event with remaining-cost caps',\n retry_cost_model: mode === 'attempt-log'\n ? 'attempt-log mode: total_cost is sum of attempt lines; retry_waste is sum of attempts>=2'\n : 'legacy mode: total_cost includes retries as extra attempts (base_cost*(1+retries))',\n context_model: 'top 20% by input_tokens assume 25% input reduction',\n estimated_pricing_note: unknownCount > 0 ? 'some items use estimated rates; treat savings as a band' : 'all items used known rates'\n },\n summary: {\n total_cost_usd: analysis.total_cost,\n estimated_savings_usd: savings.estimated_savings_total,\n routing_savings_usd: savings.routing_savings,\n context_savings_usd: savings.context_savings,\n retry_waste_usd: savings.retry_waste\n },\n top: {\n by_model: analysis.by_model_top,\n by_feature: analysis.by_feature_top\n },\n unknown_models: analysis.unknown_models,\n notes: savings.notes\n };\n fs.writeFileSync(path.join(outDir, 'report.json'), JSON.stringify(reportJson, null, 2));\n\n // report.md: \"what to change\" guidance + confidence/assumptions (T4 DoD)\n const ratioMd = analysis.total_cost > 0 ? (savings.estimated_savings_total / analysis.total_cost) : 0;\n const warningsMd: string[] = [];\n if (ratioMd >= 0.9) warningsMd.push('WARNING: estimated savings >= 90% — check overlap/missing rate table');\n\n const reportMd = [\n '# AIOpt Report',\n '',\n `- Total cost: $${analysis.total_cost}`,\n `- Estimated savings: $${savings.estimated_savings_total} (guarded <= total_cost)`,\n `- Confidence: ${confidence}`,\n unknownCount > 0 ? `- Unknown models: ${unknownCount} (estimated pricing used)` : '- Unknown models: 0',\n ...warningsMd.map(w => `- ${w}`),\n '',\n '## ASSUMPTIONS',\n '- No double-counting: routing → context → retry savings allocated per-event with remaining-cost caps.',\n mode === 'attempt-log'\n ? '- Retry cost model: attempt-log mode (total_cost=sum attempts, retry_waste=sum attempt>=2).'\n : '- Retry cost model: legacy mode (total_cost=base_cost*(1+retries)).',\n '- Context savings: top 20% input_tokens events assume 25% input reduction.',\n '',\n '## WHAT TO CHANGE',\n '1) Retry tuning → edit `aiopt/policies/retry.json`',\n '2) Output cap → edit `aiopt/policies/output.json`',\n '3) Routing rule → edit `aiopt/policies/routing.json`',\n '',\n '## OUTPUTS',\n '- `aiopt-output/analysis.json`',\n '- `aiopt-output/report.json`',\n '- `aiopt-output/patches/*`',\n ''\n ].join('\\n');\n fs.writeFileSync(path.join(outDir, 'report.md'), reportMd);\n\n const reportTxt = [\n `총비용: $${analysis.total_cost}`,\n `절감 가능 금액(Estimated): $${savings.estimated_savings_total}`,\n `절감 근거 3줄:`,\n savings.notes[0],\n savings.notes[1],\n savings.notes[2],\n ''\n ].join('\\n');\n fs.writeFileSync(path.join(outDir, 'report.txt'), reportTxt);\n\n fs.writeFileSync(path.join(outDir, 'cost-policy.json'), JSON.stringify(policy, null, 2));\n\n // patches/*\n const fixes = buildTopFixes(analysis, savings);\n writePatches(outDir, fixes);\n}\n","{\n \"name\": \"aiopt\",\n \"version\": \"0.2.2\",\n \"description\": \"Serverless local CLI MVP for AI API cost analysis & cost-policy generation\",\n \"bin\": {\n \"aiopt\": \"dist/cli.js\"\n },\n \"type\": \"commonjs\",\n \"main\": \"dist/cli.js\",\n \"files\": [\n \"dist\",\n \"rates\",\n \"samples\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"node --enable-source-maps dist/cli.js\",\n \"prepack\": \"npm run build\",\n \"test:npx\": \"npm pack --silent && node -e \\\"const fs=require('fs');const p=fs.readdirSync('.').find(f=>/^aiopt-.*\\\\.tgz$/.test(f)); if(!p) throw new Error('tgz not found'); console.log('tgz',p);\\\" && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) init && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) scan --input ./aiopt-input/usage.jsonl && test -f ./aiopt-output/report.txt && echo OK\"\n },\n \"dependencies\": {\n \"commander\": \"^14.0.0\",\n \"csv-parse\": \"^6.1.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.2\"\n }\n}\n","import fs from 'fs';\nimport path from 'path';\n\nexport type InstallOptions = {\n force?: boolean;\n seedSample?: boolean;\n};\n\nfunction ensureDir(p: string) {\n fs.mkdirSync(p, { recursive: true });\n}\n\nfunction writeFile(filePath: string, content: string, force?: boolean) {\n if (!force && fs.existsSync(filePath)) return { wrote: false, reason: 'exists' as const };\n ensureDir(path.dirname(filePath));\n fs.writeFileSync(filePath, content);\n return { wrote: true as const };\n}\n\nexport function runInstall(cwd: string, opts: InstallOptions) {\n const force = Boolean(opts.force);\n\n const aioptDir = path.join(cwd, 'aiopt');\n const policiesDir = path.join(aioptDir, 'policies');\n const outDir = path.join(cwd, 'aiopt-output');\n\n ensureDir(aioptDir);\n ensureDir(policiesDir);\n ensureDir(outDir);\n\n const created: Array<{ path: string; status: 'created' | 'skipped' }> = [];\n\n // 1) aiopt/README.md\n const readme = `# AIOpt\n\nAIOpt는 **scan툴이 아니라 설치형 비용 가드레일이다.**\n\n## Quick start\n\\`\\`\\`bash\nnpx aiopt install --force\nnpx aiopt doctor\n# (your app runs, wrapper logs usage)\nnpx aiopt scan\n\\`\\`\\`\n\n- 서버/대시보드/계정/업로드/결제/프록시 없음\n- 로컬 파일 기반(정책 + usage.jsonl)\n- LLM 호출 금지(수학/룰 기반)\n\n## Wrapper usage (Node.js)\n\nAIOpt 설치 후, 앱 코드에서 wrapper를 불러서 사용량 JSONL을 자동 기록할 수 있습니다.\n\n\na) 최소 형태(토큰을 직접 넘김)\n\n\\`\\`\\`js\nconst { guardedCall } = require('./aiopt/aiopt-wrapper.js');\n\nawait guardedCall(process.cwd(), {\n provider: 'openai',\n model: 'gpt-5-mini',\n endpoint: 'responses.create',\n feature_tag: 'summarize',\n prompt_tokens: 1200,\n trace_id: 'my-trace'\n}, async (req) => {\n // req: { provider, model, endpoint, max_output_tokens, prompt_tokens, idempotency_key }\n // 여기서 실제 SDK 호출 후 결과를 반환\n return { status: 'ok', completion_tokens: 200 };\n});\n\\`\\`\\`\n\nb) OpenAI-style 응답(usage 자동 추출)\n\n\\`\\`\\`js\nreturn {\n status: 'ok',\n response: {\n usage: { prompt_tokens: 1200, completion_tokens: 200, total_tokens: 1400 }\n }\n};\n\\`\\`\\`\n`;\n\n const r1 = writeFile(path.join(aioptDir, 'README.md'), readme, force);\n created.push({ path: 'aiopt/README.md', status: r1.wrote ? 'created' : 'skipped' });\n\n // 2) aiopt/aiopt.config.json\n const config = {\n version: 1,\n installed_at: new Date().toISOString(),\n output_dir: './aiopt-output',\n usage_path: './aiopt-output/usage.jsonl',\n policies_dir: './aiopt/policies',\n rate_table: { path: './rates/rate_table.json' }\n };\n const r2 = writeFile(path.join(aioptDir, 'aiopt.config.json'), JSON.stringify(config, null, 2) + '\\n', force);\n created.push({ path: 'aiopt/aiopt.config.json', status: r2.wrote ? 'created' : 'skipped' });\n\n // 3) policies\n const routing = {\n version: 1,\n rules: [\n { match: { feature_tag_in: ['summarize', 'classify', 'translate'] }, action: { tier: 'cheap', reason: 'cheap feature routing' } },\n { match: { feature_tag_in: ['coding', 'reasoning'] }, action: { tier: 'default', reason: 'keep for quality' } }\n ]\n };\n const retry = {\n version: 1,\n max_attempts: 2,\n backoff_ms: [200, 500],\n retry_on_status: ['error', 'timeout'],\n notes: 'MVP deterministic retry tuning'\n };\n const output = {\n version: 1,\n max_output_tokens_default: 1024,\n per_feature: {\n summarize: 512,\n classify: 256,\n translate: 512\n }\n };\n const context = {\n version: 1,\n input_token_soft_cap: 12000,\n reduce_top_percentile: 0.2,\n assumed_reduction_ratio: 0.25\n };\n\n const p1 = writeFile(path.join(policiesDir, 'routing.json'), JSON.stringify(routing, null, 2) + '\\n', force);\n const p2 = writeFile(path.join(policiesDir, 'retry.json'), JSON.stringify(retry, null, 2) + '\\n', force);\n const p3 = writeFile(path.join(policiesDir, 'output.json'), JSON.stringify(output, null, 2) + '\\n', force);\n const p4 = writeFile(path.join(policiesDir, 'context.json'), JSON.stringify(context, null, 2) + '\\n', force);\n\n created.push({ path: 'aiopt/policies/routing.json', status: p1.wrote ? 'created' : 'skipped' });\n created.push({ path: 'aiopt/policies/retry.json', status: p2.wrote ? 'created' : 'skipped' });\n created.push({ path: 'aiopt/policies/output.json', status: p3.wrote ? 'created' : 'skipped' });\n created.push({ path: 'aiopt/policies/context.json', status: p4.wrote ? 'created' : 'skipped' });\n\n // 4) wrapper template (T2: real guardrails wrapper)\n const wrapperPath = path.join(aioptDir, 'aiopt-wrapper.js');\n const wrapper = \n`// AIOpt Wrapper (guardrails) — local-only (CommonJS)\n\nconst fs = require('fs');\n\nconst path = require('path');\n\nconst crypto = require('crypto');\n\nfunction readJson(p){ return JSON.parse(fs.readFileSync(p,'utf8')); }\nfunction ensureDir(p){ fs.mkdirSync(p,{recursive:true}); }\nfunction appendJsonl(filePath,obj){ ensureDir(path.dirname(filePath)); fs.appendFileSync(filePath, JSON.stringify(obj)+'\\\\n'); }\nfunction sleep(ms){ return new Promise(r=>setTimeout(r,ms)); }\n\nfunction loadConfig(cwd){ return readJson(path.join(cwd,'aiopt','aiopt.config.json')); }\nfunction loadPolicies(cwd,cfg){ const dir=path.isAbsolute(cfg.policies_dir)?cfg.policies_dir:path.join(cwd,cfg.policies_dir);\n return {\n retry: readJson(path.join(dir,'retry.json')) ,\n output: readJson(path.join(dir,'output.json'))\n };\n}\nfunction loadRates(cwd,cfg){\n const rp=path.isAbsolute(cfg.rate_table.path)?cfg.rate_table.path:path.join(cwd,cfg.rate_table.path);\n try{ return readJson(rp); }catch(e){\n // Fresh projects may not have a rates/ table yet. Fall back to a safe default.\n return { providers: {} };\n }\n}\n\nfunction costUsd(rt, provider, model, promptTokens, completionTokens){\n const p=rt.providers && rt.providers[provider];\n const r=(p && p.models && p.models[model]) || (p && p.default_estimated) || {input:1.0, output:4.0};\n return (promptTokens/1e6)*r.input + (completionTokens/1e6)*r.output;\n}\n\nfunction pickRoutedModel(rt, provider, featureTag, currentModel){\n const cheap=['summarize','classify','translate'];\n if(!cheap.includes(String(featureTag||'').toLowerCase())) return { model: currentModel, routed_from: null, hit: null };\n const p=rt.providers && rt.providers[provider];\n const entries=p && p.models ? Object.entries(p.models) : [];\n if(!entries.length) return { model: currentModel, routed_from: null, hit: null };\n const cheapest=entries.map(([name,r])=>({name,score:(r.input+r.output)/2})).sort((a,b)=>a.score-b.score)[0];\n if(!cheapest || cheapest.name===currentModel) return { model: currentModel, routed_from: null, hit: null };\n return { model: cheapest.name, routed_from: currentModel, hit: 'routing:cheap-feature' };\n}\n\nfunction outputCap(outputPolicy, featureTag, requested){\n const per=(outputPolicy && outputPolicy.per_feature) || {};\n const cap=per[String(featureTag||'').toLowerCase()] ?? (outputPolicy.max_output_tokens_default || 1024);\n const req=requested ?? cap;\n return Math.min(req, cap);\n}\n\nconst IDEMPOTENCY=new Map();\n\nfunction normalizeResult(out, input){\n // Accept either normalized return or provider raw.\n const o = (out && out.response) ? out.response : out;\n const status = (out && out.status) || o.status || (o.error ? 'error' : 'ok');\n const usage = o.usage || (o.data && o.data.usage) || null;\n\n const prompt_tokens = Number(\n (out && out.prompt_tokens) ??\n (o && o.prompt_tokens) ??\n (usage && usage.prompt_tokens) ??\n input.prompt_tokens ??\n 0\n );\n const completion_tokens = Number(\n (out && out.completion_tokens) ??\n (o && o.completion_tokens) ??\n (usage && usage.completion_tokens) ??\n 0\n );\n const total_tokens = Number(\n (out && out.total_tokens) ??\n (o && o.total_tokens) ??\n (usage && usage.total_tokens) ??\n (prompt_tokens + completion_tokens)\n );\n\n const error_code = status === 'ok' ? null : String((out && out.error_code) || (o && o.error_code) || (o && o.error && (o.error.code || o.error.type)) || status);\n const cost_usd = (out && typeof out.cost_usd === 'number') ? out.cost_usd : null;\n\n return { status, prompt_tokens, completion_tokens, total_tokens, error_code, cost_usd };\n}\n\n/**\n * guardedCall(cwd, input, fn)\n *\n * fn(req) can return either:\n * 1) Normalized shape:\n * { status: 'ok'|'error'|'timeout', prompt_tokens?, completion_tokens?, total_tokens?, cost_usd?, error_code? }\n * 2) Provider raw response (OpenAI-style), e.g.:\n * { status:'ok', response:{ usage:{prompt_tokens, completion_tokens, total_tokens} } }\n * { usage:{prompt_tokens, completion_tokens, total_tokens} }\n *\n * If token fields are missing, AIOpt will fall back to input.prompt_tokens and/or 0.\n */\nasync function guardedCall(cwd, input, fn){\n const cfg=loadConfig(cwd);\n const pol=loadPolicies(cwd,cfg);\n const rt=loadRates(cwd,cfg);\n\n const request_id=crypto.randomUUID();\n const trace_id=input.trace_id || request_id;\n const idem=input.idempotency_key || trace_id;\n if(IDEMPOTENCY.has(idem)) return IDEMPOTENCY.get(idem);\n\n const routed=pickRoutedModel(rt, input.provider, input.feature_tag, input.model);\n const maxOut=outputCap(pol.output, input.feature_tag, input.max_output_tokens);\n const usagePath=path.isAbsolute(cfg.usage_path)?cfg.usage_path:path.join(cwd,cfg.usage_path);\n\n const maxAttempts=Math.max(1, Number(pol.retry.max_attempts||1));\n const backoffs=pol.retry.backoff_ms || [200];\n const retryOn=new Set(pol.retry.retry_on_status || ['error','timeout']);\n\n let last={status:'error', completion_tokens:0, error_code:'unknown'};\n\n for(let attempt=1; attempt<=maxAttempts; attempt++){\n const t0=Date.now();\n const policy_hits=[];\n if(routed.hit) policy_hits.push(routed.hit);\n policy_hits.push('outputcap:'+maxOut);\n try{\n const out=await fn({ provider: input.provider, model: routed.model, endpoint: input.endpoint, max_output_tokens: maxOut, prompt_tokens: input.prompt_tokens, idempotency_key: idem });\n const latency_ms=Date.now()-t0;\n const norm=normalizeResult(out, input);\n const cost_usd=(typeof norm.cost_usd==='number') ? norm.cost_usd : costUsd(rt, input.provider, routed.model, norm.prompt_tokens, norm.completion_tokens);\n const feature_tag = input.feature_tag || input.endpoint || 'unknown';\n appendJsonl(usagePath, { ts:new Date().toISOString(), request_id, trace_id, attempt, status: norm.status, error_code: norm.error_code, provider: input.provider, model: routed.model, endpoint: input.endpoint, prompt_tokens: norm.prompt_tokens, completion_tokens: norm.completion_tokens, total_tokens: norm.total_tokens, cost_usd, latency_ms, meta:{ feature_tag, routed_from: routed.routed_from, policy_hits } });\n last={ status: norm.status, completion_tokens: norm.completion_tokens, error_code: norm.error_code };\n if(norm.status==='ok'){ IDEMPOTENCY.set(idem,out); return out; }\n if(retryOn.has(norm.status) && attempt<maxAttempts){ await sleep(Number(backoffs[Math.min(attempt-1, backoffs.length-1)]||200)); continue; }\n IDEMPOTENCY.set(idem,out); return out;\n }catch(e){\n const latency_ms=Date.now()-t0;\n const out={ status:'error', completion_tokens:0, error_code:String(e && (e.code||e.name) || 'exception') };\n const feature_tag = input.feature_tag || input.endpoint || 'unknown';\n appendJsonl(usagePath, { ts:new Date().toISOString(), request_id, trace_id, attempt, status: out.status, error_code: out.error_code, provider: input.provider, model: routed.model, endpoint: input.endpoint, prompt_tokens:Number(input.prompt_tokens||0), completion_tokens:0, total_tokens:Number(input.prompt_tokens||0), cost_usd:costUsd(rt, input.provider, routed.model, Number(input.prompt_tokens||0), 0), latency_ms, meta:{ feature_tag, routed_from: routed.routed_from, policy_hits:[routed.hit||'routing:none','outputcap:'+maxOut,'error:exception'] } });\n last=out;\n if(attempt<maxAttempts){ await sleep(Number(backoffs[Math.min(attempt-1, backoffs.length-1)]||200)); continue; }\n IDEMPOTENCY.set(idem,out); return out;\n }\n }\n IDEMPOTENCY.set(idem,last);\n return last;\n}\n\nmodule.exports = { guardedCall };\n`;\n;\n const w = writeFile(wrapperPath, wrapper, force);\n created.push({ path: 'aiopt/aiopt-wrapper.js', status: w.wrote ? 'created' : 'skipped' });\n\n // 5) usage.jsonl\n const usagePath = path.join(outDir, 'usage.jsonl');\n if (force || !fs.existsSync(usagePath)) {\n // default: empty file (avoid mixing demo data into real user logs)\n fs.writeFileSync(usagePath, '');\n created.push({ path: 'aiopt-output/usage.jsonl', status: 'created' });\n\n if (opts.seedSample) {\n const sample = {\n ts: new Date().toISOString(),\n request_id: 'sample',\n trace_id: 'sample',\n attempt: 1,\n status: 'ok',\n error_code: null,\n provider: 'openai',\n model: 'gpt-5-mini',\n endpoint: 'demo',\n prompt_tokens: 12,\n completion_tokens: 3,\n total_tokens: 15,\n cost_usd: 0.0,\n latency_ms: 1,\n meta: { feature_tag: 'demo', routed_from: null, policy_hits: ['install-sample'] }\n };\n fs.appendFileSync(usagePath, JSON.stringify(sample) + '\\n');\n }\n } else {\n created.push({ path: 'aiopt-output/usage.jsonl', status: 'skipped' });\n }\n\n return { created };\n}\n","import fs from 'fs';\nimport path from 'path';\n\nfunction canWrite(dir: string) {\n try {\n fs.mkdirSync(dir, { recursive: true });\n const p = path.join(dir, `.aiopt-write-test-${Date.now()}`);\n fs.writeFileSync(p, 'ok');\n fs.unlinkSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction tailLines(filePath: string, n: number): string[] {\n try {\n const raw = fs.readFileSync(filePath, 'utf8');\n const lines = raw.split(/\\r?\\n/).filter(l => l.trim().length > 0);\n return lines.slice(Math.max(0, lines.length - n));\n } catch {\n return [];\n }\n}\n\nexport type DoctorResult = {\n ok: boolean;\n checks: Array<{ name: string; ok: boolean; detail?: string }>;\n last5: Array<{ status?: string; provider?: string; model?: string; endpoint?: string; attempt?: number }>;\n};\n\nexport function runDoctor(cwd: string): DoctorResult {\n const aioptDir = path.join(cwd, 'aiopt');\n const policiesDir = path.join(aioptDir, 'policies');\n const outDir = path.join(cwd, 'aiopt-output');\n const usagePath = path.join(outDir, 'usage.jsonl');\n\n const checks: DoctorResult['checks'] = [];\n\n checks.push({ name: 'aiopt/ exists', ok: fs.existsSync(aioptDir) });\n checks.push({ name: 'aiopt/policies exists', ok: fs.existsSync(policiesDir) });\n checks.push({ name: 'aiopt-output/ writable', ok: canWrite(outDir) });\n checks.push({ name: 'usage.jsonl exists', ok: fs.existsSync(usagePath), detail: usagePath });\n\n const last5raw = tailLines(usagePath, 5);\n const last5 = last5raw.length === 0 ? [{ status: '(empty usage.jsonl)' }] as any : last5raw.map(l => {\n try {\n const j = JSON.parse(l);\n return {\n status: j.status,\n provider: j.provider,\n model: j.model,\n endpoint: j.endpoint,\n attempt: j.attempt,\n feature_tag: j?.meta?.feature_tag\n };\n } catch {\n return {};\n }\n });\n\n // Feature-tag quality check (sample last 50 lines)\n const last50 = tailLines(usagePath, 50);\n let missing = 0;\n let total50 = 0;\n for (const l of last50) {\n total50++;\n try {\n const j = JSON.parse(l);\n const ft = j?.meta?.feature_tag;\n if (!ft || String(ft).trim() === '') missing++;\n } catch {\n missing++;\n }\n }\n if (total50 > 0 && missing > 0) {\n checks.push({ name: 'feature_tag quality (last50)', ok: false, detail: `${missing}/${total50} missing meta.feature_tag` });\n } else {\n checks.push({ name: 'feature_tag quality (last50)', ok: true, detail: 'meta.feature_tag present' });\n }\n\n const ok = checks.every(c => c.ok);\n return { ok, checks, last5 };\n}\n\n","import { RateTable, UsageEvent } from './types';\nimport { analyze } from './scan';\n\nexport type GuardInput = {\n baselineEvents: UsageEvent[];\n candidate: {\n provider?: string;\n model?: string;\n contextMultiplier?: number; // multiplies input_tokens\n outputMultiplier?: number; // multiplies output_tokens\n retriesDelta?: number; // adds to retries\n };\n};\n\nexport type GuardResult = {\n exitCode: 0 | 2 | 3;\n message: string;\n};\n\nfunction round2(n: number) {\n return Math.round(n * 100) / 100;\n}\n\nfunction monthEstimate(delta: number) {\n // baseline is arbitrary; interpret delta as \"per current sample period\".\n // For guardrail messaging, we convert to a monthly estimate assuming logs represent 1 day.\n // deterministic heuristic: *30\n return delta * 30;\n}\n\nfunction applyCandidate(events: UsageEvent[], cand: GuardInput['candidate']): UsageEvent[] {\n const ctxM = cand.contextMultiplier ?? 1;\n const outM = cand.outputMultiplier ?? 1;\n const rDelta = cand.retriesDelta ?? 0;\n\n return events.map(ev => ({\n ...ev,\n provider: cand.provider ? String(cand.provider).toLowerCase() : ev.provider,\n model: cand.model ? String(cand.model) : ev.model,\n input_tokens: Math.max(0, Math.round((ev.input_tokens || 0) * ctxM)),\n output_tokens: Math.max(0, Math.round((ev.output_tokens || 0) * outM)),\n retries: Math.max(0, Math.round((ev.retries || 0) + rDelta)),\n // clear billed_cost so pricing recalculates for new model/provider\n billed_cost: undefined\n }));\n}\n\nfunction confidenceFromChange(cand: GuardInput['candidate']): { level: 'High'|'Medium'|'Low'; reasons: string[] } {\n const reasons: string[] = [];\n\n if (cand.retriesDelta && cand.retriesDelta !== 0) reasons.push('retries change');\n if (cand.model) reasons.push('model change');\n if (cand.provider) reasons.push('provider change');\n if (cand.contextMultiplier && cand.contextMultiplier !== 1) reasons.push('context length change');\n\n // Spec rule:\n // High: retries/failed/dup calls (we only model retries here)\n // Medium: model/provider change\n // Low: context trimming / prompt structure\n if (cand.retriesDelta && cand.retriesDelta !== 0) return { level: 'High', reasons };\n if (cand.model || cand.provider) return { level: 'Medium', reasons };\n if (cand.contextMultiplier && cand.contextMultiplier !== 1) return { level: 'Low', reasons };\n return { level: 'Medium', reasons: reasons.length ? reasons : ['unknown change'] };\n}\n\nexport function runGuard(rt: RateTable, input: GuardInput): GuardResult {\n if (!input.baselineEvents || input.baselineEvents.length === 0) {\n return { exitCode: 3, message: 'FAIL: baseline usage is empty (need aiopt-output/usage.jsonl)'};\n }\n\n const base = analyze(rt, input.baselineEvents);\n const candidateEvents = applyCandidate(input.baselineEvents, input.candidate);\n const cand = analyze(rt, candidateEvents);\n\n const baseCost = base.analysis.total_cost;\n const candCost = cand.analysis.total_cost;\n const delta = candCost - baseCost;\n\n const conf = confidenceFromChange(input.candidate);\n\n // Guard logic:\n // - Warning if delta > 0 and monthEstimate(delta) >= $10\n // - Fail if delta > 0 and monthEstimate(delta) >= $100 (merge-blocking)\n const monthly = monthEstimate(Math.max(0, delta));\n const monthlyRounded = round2(monthly);\n\n let exitCode: 0 | 2 | 3 = 0;\n let headline = 'OK: no cost accident risk detected';\n\n if (monthly >= 100) { exitCode = 3; headline = 'FAIL: high risk of LLM cost accident'; }\n else if (monthly >= 10) { exitCode = 2; headline = 'WARN: possible LLM cost accident'; }\n\n const reasons = conf.reasons.length ? conf.reasons.join(', ') : 'n/a';\n\n const msg = [\n headline,\n `Summary: baseline=$${baseCost} → candidate=$${candCost} (Δ=$${round2(delta)})`,\n `Impact (monthly est): +$${monthlyRounded}`,\n `Confidence: ${conf.level} (${reasons})`,\n 'Recommendation: review model/provider/retry/context changes before deploy.'\n ].join('\\n');\n\n return { exitCode, message: msg };\n}\n","#!/usr/bin/env node\n\nimport fs from 'fs';\nimport path from 'path';\nimport { Command } from 'commander';\nimport { ensureDir, isCsvPath, readCsv, readJsonl } from './io';\nimport { RateTable } from './types';\nimport { analyze, writeOutputs } from './scan';\n\nconst program = new Command();\n\nconst DEFAULT_INPUT = './aiopt-output/usage.jsonl';\nconst DEFAULT_OUTPUT_DIR = './aiopt-output';\n\nfunction loadRateTable(): RateTable {\n const p = path.join(__dirname, '..', 'rates', 'rate_table.json');\n return JSON.parse(fs.readFileSync(p, 'utf8'));\n}\n\nprogram\n .name('aiopt')\n .description('AI 비용 자동 절감 인프라 — 서버 없는 로컬 CLI MVP')\n // keep CLI version in sync with package.json\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n .version(require('../package.json').version);\n\nprogram\n .command('init')\n .description('aiopt-input/ 및 샘플 usage.jsonl, aiopt-output/ 생성')\n .action(() => {\n ensureDir('./aiopt-input');\n ensureDir('./aiopt-output');\n\n const sampleSrc = path.join(__dirname, '..', 'samples', 'sample_usage.jsonl');\n const dst = path.join('./aiopt-input', 'usage.jsonl');\n\n if (!fs.existsSync(dst)) {\n fs.copyFileSync(sampleSrc, dst);\n console.log('Created ./aiopt-input/usage.jsonl (sample)');\n } else {\n console.log('Exists ./aiopt-input/usage.jsonl (skip)');\n }\n\n console.log('Ready: ./aiopt-output/');\n });\n\nprogram\n .command('scan')\n .description('입력 로그(JSONL/CSV)를 분석하고 report.md/report.json + patches까지 생성')\n .option('--input <path>', 'input file path (default: ./aiopt-output/usage.jsonl)', DEFAULT_INPUT)\n .option('--out <dir>', 'output dir (default: ./aiopt-output)', DEFAULT_OUTPUT_DIR)\n .action(async (opts) => {\n const inputPath = String(opts.input);\n const outDir = String(opts.out);\n\n if (!fs.existsSync(inputPath)) {\n console.error(`Input not found: ${inputPath}`);\n process.exit(1);\n }\n\n const rt = loadRateTable();\n const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);\n\n const { analysis, savings, policy, meta } = analyze(rt, events);\n // For reproducibility: embed input path & rate table meta\n policy.generated_from.input = inputPath;\n\n writeOutputs(outDir, analysis, savings, policy, meta);\n\n // Console: Top Fix 3 (data-driven)\n const { buildTopFixes } = await import('./solutions');\n const fixes = buildTopFixes(analysis, savings).slice(0, 3);\n\n console.log('Top Fix 3:');\n fixes.forEach((f, i) => {\n const tag = f.status === 'no-issue' ? '(no issue detected)' : `($${Math.round(f.impact_usd * 100) / 100})`;\n console.log(`${i + 1}) ${f.title} ${tag}`);\n });\n console.log(`Report: ${path.join(outDir, 'report.md')}`);\n });\n\nprogram\n .command('policy')\n .description('마지막 scan 결과 기반으로 cost-policy.json만 재생성 (MVP: scan과 동일 로직)')\n .option('--input <path>', 'input file path (default: ./aiopt-input/usage.jsonl)', DEFAULT_INPUT)\n .option('--out <dir>', 'output dir (default: ./aiopt-output)', DEFAULT_OUTPUT_DIR)\n .action((opts) => {\n const inputPath = String(opts.input);\n const outDir = String(opts.out);\n const rt = loadRateTable();\n const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);\n const { policy } = analyze(rt, events);\n policy.generated_from.input = inputPath;\n\n ensureDir(outDir);\n fs.writeFileSync(path.join(outDir, 'cost-policy.json'), JSON.stringify(policy, null, 2));\n console.log(`OK: ${outDir}/cost-policy.json`);\n });\n\n// v0.2: install/doctor (no servers)\nprogram\n .command('install')\n .description('Install AIOpt guardrails: create aiopt/ + policies + usage.jsonl')\n .option('--force', 'overwrite existing files')\n .option('--seed-sample', 'seed 1 sample line into aiopt-output/usage.jsonl')\n .action(async (opts) => {\n const { runInstall } = await import('./install');\n const result = runInstall(process.cwd(), { force: Boolean(opts.force), seedSample: Boolean(opts.seedSample) });\n for (const c of result.created) {\n console.log(`${c.status === 'created' ? 'CREATED' : 'SKIP'}: ${c.path}`);\n }\n });\n\nprogram\n .command('doctor')\n .description('Check installation + print last 5 usage events')\n .action(async () => {\n const { runDoctor } = await import('./doctor');\n const r = runDoctor(process.cwd());\n console.log(r.ok ? 'OK: doctor' : 'WARN: doctor');\n for (const c of r.checks) {\n console.log(`${c.ok ? 'OK' : 'FAIL'}: ${c.name}${c.detail ? ` (${c.detail})` : ''}`);\n }\n console.log('--- last5 usage');\n for (const x of r.last5) {\n console.log(JSON.stringify(x));\n }\n });\n\n// vNext: guardrail mode (pre-deploy warning)\nprogram\n .command('guard')\n .description('Pre-deploy guardrail: compare baseline usage vs candidate change and print warnings (exit codes 0/2/3)')\n .option('--input <path>', 'baseline usage jsonl/csv (default: ./aiopt-output/usage.jsonl)', DEFAULT_INPUT)\n .option('--provider <provider>', 'candidate provider override')\n .option('--model <model>', 'candidate model override')\n .option('--context-mult <n>', 'multiply input_tokens by n', (v) => Number(v))\n .option('--output-mult <n>', 'multiply output_tokens by n', (v) => Number(v))\n .option('--retries-delta <n>', 'add n to retries', (v) => Number(v))\n .action(async (opts) => {\n const rt = loadRateTable();\n const inputPath = String(opts.input);\n if (!fs.existsSync(inputPath)) {\n console.error(`FAIL: baseline not found: ${inputPath}`);\n process.exit(3);\n }\n const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);\n\n const { runGuard } = await import('./guard');\n const r = runGuard(rt, {\n baselineEvents: events,\n candidate: {\n provider: opts.provider,\n model: opts.model,\n contextMultiplier: opts.contextMult,\n outputMultiplier: opts.outputMult,\n retriesDelta: opts.retriesDelta\n }\n });\n\n console.log(r.message);\n process.exit(r.exitCode);\n });\n\nprogram.parse(process.argv);\n","import fs from 'fs';\nimport path from 'path';\nimport { parse as parseCsv } from 'csv-parse/sync';\nimport { UsageEvent } from './types';\n\nexport function ensureDir(p: string) {\n fs.mkdirSync(p, { recursive: true });\n}\n\nexport function readJsonl(filePath: string): UsageEvent[] {\n const raw = fs.readFileSync(filePath, 'utf8');\n const lines = raw.split(/\\r?\\n/).filter(l => l.trim().length > 0);\n const out: UsageEvent[] = [];\n for (const line of lines) {\n const obj = JSON.parse(line);\n out.push(normalizeEvent(obj));\n }\n return out;\n}\n\nexport function readCsv(filePath: string): UsageEvent[] {\n const raw = fs.readFileSync(filePath, 'utf8');\n const records = parseCsv(raw, { columns: true, skip_empty_lines: true, trim: true });\n return records.map((r: any) => normalizeEvent(r));\n}\n\nfunction toNum(x: any, def = 0): number {\n const n = Number(x);\n return Number.isFinite(n) ? n : def;\n}\n\nfunction normalizeEvent(x: any): UsageEvent {\n // Supports two schemas:\n // 1) scan input schema: input_tokens/output_tokens/feature_tag/retries\n // 2) wrapper usage schema: prompt_tokens/completion_tokens/endpoint/attempt/trace_id/cost_usd\n\n const inputTokens = x.input_tokens ?? x.prompt_tokens;\n const outputTokens = x.output_tokens ?? x.completion_tokens;\n\n // feature_tag fallback: feature_tag -> meta.feature_tag -> endpoint\n const featureTag = x.feature_tag ?? x?.meta?.feature_tag ?? x.endpoint ?? '';\n\n // retries fallback: retries -> max(attempt-1,0)\n const retries = x.retries ?? (x.attempt !== undefined ? Math.max(0, toNum(x.attempt) - 1) : 0);\n\n // billed_cost fallback: billed_cost -> cost_usd\n const billed = x.billed_cost ?? x.cost_usd;\n\n return {\n ts: String(x.ts ?? ''),\n provider: String(x.provider ?? '').toLowerCase(),\n model: String(x.model ?? ''),\n input_tokens: toNum(inputTokens),\n output_tokens: toNum(outputTokens),\n feature_tag: String(featureTag ?? ''),\n retries: toNum(retries),\n status: String(x.status ?? ''),\n billed_cost: billed === undefined || billed === '' ? undefined : toNum(billed),\n\n trace_id: x.trace_id ? String(x.trace_id) : undefined,\n request_id: x.request_id ? String(x.request_id) : undefined,\n attempt: x.attempt === undefined ? undefined : toNum(x.attempt),\n endpoint: x.endpoint ? String(x.endpoint) : undefined\n };\n}\n\nexport function isCsvPath(p: string) {\n return path.extname(p).toLowerCase() === '.csv';\n}\n"],"mappings":"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","names":["cost","path","fs","import_fs","import_path","round2","fs","path","import_fs","import_path","exports","module","ensureDir","fs","path","import_fs","import_path","fs","path","import_fs","import_path","round2","import_fs","import_path","fs","parseCsv","path","path","fs","buildTopFixes","runInstall","runDoctor","runGuard"]}
|
|
1
|
+
{"version":3,"sources":["../src/cost.ts","../src/solutions.ts","../src/scan.ts","../package.json","../src/install.ts","../src/doctor.ts","../src/license.ts","../src/guard.ts","../src/dashboard.ts","../src/cli.ts","../src/io.ts"],"sourcesContent":["import { RateTable, UsageEvent } from './types';\n\nexport type CostResult = {\n cost: number;\n used_rate: {\n kind: 'billed_cost' | 'official' | 'estimated';\n provider: string;\n model: string;\n input_per_m: number;\n output_per_m: number;\n };\n};\n\nexport function getRates(rt: RateTable, provider: string, model: string) {\n const prov = String(provider || '').toLowerCase();\n\n // local/offline LLM: treat cost as 0 (or user can add a provider rate later)\n if (prov === 'local' || prov === 'ollama' || prov === 'vllm') {\n return { kind: 'official' as const, input: 0, output: 0 };\n }\n\n const p = rt.providers[prov];\n if (!p) return null;\n const m = p.models[model];\n if (m) return { kind: 'official' as const, input: m.input, output: m.output };\n return { kind: 'estimated' as const, input: p.default_estimated.input, output: p.default_estimated.output };\n}\n\nexport function costOfEvent(rt: RateTable, ev: UsageEvent): CostResult {\n if (typeof ev.billed_cost === 'number' && Number.isFinite(ev.billed_cost)) {\n return {\n cost: ev.billed_cost,\n used_rate: {\n kind: 'billed_cost',\n provider: ev.provider,\n model: ev.model,\n input_per_m: 0,\n output_per_m: 0\n }\n };\n }\n\n const r = getRates(rt, ev.provider, ev.model);\n if (!r) {\n // Unknown provider: deterministic fallback estimate (kept stable for reproducibility)\n const input_per_m = 1.0;\n const output_per_m = 4.0;\n const cost = (ev.input_tokens / 1e6) * input_per_m + (ev.output_tokens / 1e6) * output_per_m;\n return {\n cost,\n used_rate: { kind: 'estimated', provider: String(ev.provider || '').toLowerCase(), model: ev.model, input_per_m, output_per_m }\n };\n }\n\n const cost = (ev.input_tokens / 1e6) * r.input + (ev.output_tokens / 1e6) * r.output;\n return {\n cost,\n used_rate: {\n kind: r.kind,\n provider: ev.provider,\n model: ev.model,\n input_per_m: r.input,\n output_per_m: r.output\n }\n };\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { AnalysisJson, Savings } from './scan';\n\nexport type Fix = {\n id: string;\n title: string;\n impact_usd: number;\n why: string;\n what_to_change: string[];\n status: 'action' | 'no-issue';\n};\n\nconst EPS = 0.0001;\n\nexport function buildTopFixes(analysis: AnalysisJson, savings: Savings): Fix[] {\n const fixes: Fix[] = [];\n\n // Retry tuning\n fixes.push({\n id: 'fix-retry-tuning',\n title: 'Retry tuning',\n impact_usd: Number(savings.retry_waste || 0),\n status: Number(savings.retry_waste || 0) > EPS ? 'action' : 'no-issue',\n why: `Retry waste is estimated at $${round2(Number(savings.retry_waste || 0))}.`,\n what_to_change: [\n 'aiopt/policies/retry.json: lower max_attempts or adjust backoff_ms',\n 'Ensure idempotency keys are stable per trace_id',\n 'Log error_code to identify retryable classes'\n ]\n });\n\n // Output cap (use context_savings as proxy impact)\n fixes.push({\n id: 'fix-output-cap',\n title: 'Output cap',\n impact_usd: Number(savings.context_savings || 0),\n status: Number(savings.context_savings || 0) > EPS ? 'action' : 'no-issue',\n why: `Context savings estimate: $${round2(Number(savings.context_savings || 0))}. Output caps prevent runaway completions.`,\n what_to_change: [\n 'aiopt/policies/output.json: set max_output_tokens_default',\n 'aiopt/policies/output.json: set per_feature caps (summarize/classify/translate)'\n ]\n });\n\n // Routing\n const topFeature = analysis.by_feature_top?.[0]?.key;\n fixes.push({\n id: 'fix-routing',\n title: 'Routing rule',\n impact_usd: Number(savings.routing_savings || 0),\n status: Number(savings.routing_savings || 0) > EPS ? 'action' : 'no-issue',\n why: `Routing savings estimate: $${round2(Number(savings.routing_savings || 0))}.`,\n what_to_change: [\n 'aiopt/policies/routing.json: route summarize/classify/translate to cheap tier',\n `Consider adding feature_tag_in for top feature: ${topFeature || '(unknown)'}`\n ]\n });\n\n // sort by impact desc, deterministic tie-break\n fixes.sort((a, b) => (b.impact_usd - a.impact_usd) || a.id.localeCompare(b.id));\n return fixes;\n}\n\nfunction round2(n: number) {\n return Math.round(n * 100) / 100;\n}\n\nexport function writePatches(outDir: string, fixes: Fix[]) {\n const patchesDir = path.join(outDir, 'patches');\n fs.mkdirSync(patchesDir, { recursive: true });\n\n const readme = [\n '# AIOpt patches (MVP)',\n '',\n 'This folder contains suggested changes you can apply locally.',\n '',\n '## Top fixes',\n ...fixes.map((f, i) => `${i + 1}. ${f.title} — ${f.why}`),\n '',\n 'Files are stubs (human review required).',\n ''\n ].join('\\n');\n\n fs.writeFileSync(path.join(patchesDir, 'README.md'), readme);\n\n // Minimal stub files to satisfy DoD naming.\n fs.writeFileSync(path.join(patchesDir, 'policies.updated.routing.json'), JSON.stringify({ note: 'apply changes to aiopt/policies/routing.json', fixes: fixes.filter(f => f.id.includes('routing')) }, null, 2));\n fs.writeFileSync(path.join(patchesDir, 'policies.updated.retry.json'), JSON.stringify({ note: 'apply changes to aiopt/policies/retry.json', fixes: fixes.filter(f => f.id.includes('retry')) }, null, 2));\n fs.writeFileSync(path.join(patchesDir, 'policies.updated.output.json'), JSON.stringify({ note: 'apply changes to aiopt/policies/output.json', fixes: fixes.filter(f => f.id.includes('output')) }, null, 2));\n}\n","import fs from 'fs';\nimport path from 'path';\nimport { RateTable, UsageEvent } from './types';\nimport { costOfEvent, getRates } from './cost';\nimport { buildTopFixes, writePatches } from './solutions';\n\nexport type AnalysisJson = {\n total_cost: number;\n by_model_top: Array<{ key: string; cost: number; events: number }>;\n by_feature_top: Array<{ key: string; cost: number; events: number }>;\n unknown_models: Array<{ provider: string; model: string; reason: string }>;\n rate_table_version: string;\n rate_table_date: string;\n};\n\nexport type Savings = {\n estimated_savings_total: number;\n routing_savings: number;\n context_savings: number;\n retry_waste: number;\n notes: [string, string, string];\n};\n\nexport type PolicyJson = {\n version: number;\n default_provider: string;\n rules: Array<any>;\n budgets: { currency: string; notes?: string };\n generated_from: { rate_table_version: string; input: string };\n};\n\nconst ROUTE_TO_CHEAP_FEATURES = new Set(['summarize', 'classify', 'translate']);\n\nfunction topN(map: Map<string, { cost: number; events: number }>, n: number) {\n return [...map.entries()]\n .map(([key, v]) => ({ key, cost: v.cost, events: v.events }))\n .sort((a, b) => b.cost - a.cost)\n .slice(0, n);\n}\n\nexport function analyze(rt: RateTable, events: UsageEvent[]): { analysis: AnalysisJson; savings: Savings; policy: PolicyJson; meta: { mode: 'attempt-log'|'legacy' } } {\n const byModel = new Map<string, { cost: number; events: number }>();\n const byFeature = new Map<string, { cost: number; events: number }>();\n const unknownModels: AnalysisJson['unknown_models'] = [];\n\n const perEventCosts: Array<{ ev: UsageEvent; cost: number }> = [];\n\n // Detect wrapper attempt-log mode: each JSONL line is an attempt (trace_id/attempt present)\n const isAttemptLog = events.some(e => (e.trace_id && String(e.trace_id).length > 0) || (e.attempt !== undefined && Number(e.attempt) > 0));\n\n let baseTotal = 0;\n let total = 0;\n for (const ev of events) {\n const cr = costOfEvent(rt, ev);\n\n baseTotal += cr.cost;\n\n if (isAttemptLog) {\n // Each event line is already one attempt; do NOT multiply by retries.\n total += cr.cost;\n } else {\n // Legacy aggregate mode: one line represents a request with retries count.\n const retries = Math.max(0, Number(ev.retries || 0));\n total += cr.cost * (1 + retries);\n }\n\n perEventCosts.push({ ev, cost: cr.cost });\n\n const mk = `${ev.provider}:${ev.model}`;\n const fk = ev.feature_tag || '(none)';\n\n const mv = byModel.get(mk) || { cost: 0, events: 0 };\n mv.cost += cr.cost; mv.events += 1;\n byModel.set(mk, mv);\n\n const fv = byFeature.get(fk) || { cost: 0, events: 0 };\n fv.cost += cr.cost; fv.events += 1;\n byFeature.set(fk, fv);\n\n const rr = getRates(rt, ev.provider, ev.model);\n if (!rr) {\n unknownModels.push({ provider: ev.provider, model: ev.model, reason: 'unknown provider (estimated)' });\n } else if (rr.kind === 'estimated') {\n unknownModels.push({ provider: ev.provider, model: ev.model, reason: 'unknown model (estimated)' });\n }\n }\n\n // --- Savings simulator (refactor): per-event before/after with caps + no double counting\n type Pot = { routing: number; context: number; retry: number; total: number; waste: number };\n const potByIdx: Pot[] = [];\n\n // Potential routing/context computed per event\n for (const { ev, cost } of perEventCosts) {\n const retries = Math.max(0, Number(ev.retries || 0));\n const attempt = Number(ev.attempt || 1);\n\n const total_i = isAttemptLog ? cost : cost * (1 + retries);\n // Retry waste:\n // - attempt-log mode: attempts >= 2 are retry waste\n // - legacy mode: base_cost * retries\n const waste_i = isAttemptLog ? (attempt >= 2 ? cost : 0) : cost * retries;\n\n let routing_i = 0;\n if (ROUTE_TO_CHEAP_FEATURES.has(String(ev.feature_tag || '').toLowerCase())) {\n const provider = ev.provider;\n const p = rt.providers[provider];\n if (p) {\n const entries = Object.entries(p.models);\n if (entries.length > 0) {\n const cheapest = entries\n .map(([name, r]) => ({ name, score: (r.input + r.output) / 2, r }))\n .sort((a, b) => a.score - b.score)[0];\n const currentRate = getRates(rt, provider, ev.model);\n if (currentRate && currentRate.kind !== 'estimated') {\n const currentCost = (ev.input_tokens / 1e6) * currentRate.input + (ev.output_tokens / 1e6) * currentRate.output;\n const cheapCost = (ev.input_tokens / 1e6) * cheapest.r.input + (ev.output_tokens / 1e6) * cheapest.r.output;\n const diff = (currentCost - cheapCost) * (1 + retries);\n routing_i = Math.max(0, diff);\n }\n }\n }\n }\n\n // context: top 20% rule is applied later by index set\n potByIdx.push({ routing: routing_i, context: 0, retry: waste_i, total: total_i, waste: waste_i });\n }\n\n // context potential assignment (deterministic): top 20% by input_tokens => 25% reduction\n // In attempt-log mode, only apply to attempt==1 to avoid overcounting retries.\n const sortedIdx = [...events.map((e, i) => ({ i, input: Number(e.input_tokens || 0), ok: !isAttemptLog || Number(e.attempt || 1) === 1 }))]\n .filter(x => x.ok)\n .sort((a, b) => b.input - a.input);\n const k = Math.max(1, Math.floor(sortedIdx.length * 0.2));\n const topIdx = new Set(sortedIdx.slice(0, k).map(x => x.i));\n for (let i = 0; i < events.length; i++) {\n if (!topIdx.has(i)) continue;\n const ev = events[i];\n const retries = Math.max(0, Number(ev.retries || 0));\n const r = getRates(rt, ev.provider, ev.model);\n if (!r) continue;\n const saveTokens = (Number(ev.input_tokens || 0)) * 0.25;\n const multiplier = isAttemptLog ? 1 : (1 + retries);\n const diff = (saveTokens / 1e6) * r.input * multiplier;\n potByIdx[i].context = Math.max(0, diff);\n }\n\n // Allocate savings without overlap (routing -> context -> retry), each capped by remaining cost.\n let routingSavings = 0;\n let contextSavings = 0;\n let retryWaste = 0;\n\n for (const p of potByIdx) {\n let remaining = p.total;\n\n const rSave = Math.min(p.routing, remaining);\n remaining -= rSave;\n routingSavings += rSave;\n\n const cSave = Math.min(p.context, remaining);\n remaining -= cSave;\n contextSavings += cSave;\n\n // Retry tuning can only save the waste portion, and cannot exceed remaining.\n const retrySave = Math.min(p.retry, remaining);\n retryWaste += retrySave;\n }\n\n const estimatedSavingsTotal = routingSavings + contextSavings + retryWaste;\n // Global guard\n const guardedSavingsTotal = Math.min(estimatedSavingsTotal, total);\n\n const analysis: AnalysisJson = {\n total_cost: round2(total),\n by_model_top: topN(byModel, 10).map(x => ({ ...x, cost: round2(x.cost) })),\n by_feature_top: topN(byFeature, 10).map(x => ({ ...x, cost: round2(x.cost) })),\n unknown_models: uniqUnknown(unknownModels),\n rate_table_version: rt.version,\n rate_table_date: rt.date\n };\n\n const savings: Savings = {\n estimated_savings_total: round2(guardedSavingsTotal),\n routing_savings: round2(routingSavings),\n context_savings: round2(contextSavings),\n retry_waste: round2(retryWaste),\n notes: [\n `a) 모델 라우팅 절감(추정): $${round2(routingSavings)}`,\n `b) 컨텍스트 감축(추정): $${round2(contextSavings)} (상위 20% input에 25% 감축 가정)` ,\n `c) 재시도/오류 낭비(상한 적용): $${round2(retryWaste)} (retries 기반)`\n ]\n };\n\n const policy: PolicyJson = buildPolicy(rt, events);\n\n return { analysis, savings, policy, meta: { mode: isAttemptLog ? 'attempt-log' : 'legacy' } };\n}\n\nfunction buildPolicy(rt: RateTable, events: UsageEvent[]): PolicyJson {\n // Default provider: most frequent\n const freq = new Map<string, number>();\n for (const ev of events) freq.set(ev.provider, (freq.get(ev.provider) || 0) + 1);\n const defaultProvider = [...freq.entries()].sort((a, b) => b[1] - a[1])[0]?.[0] || 'openai';\n\n // For cheap features: recommend cheapest known model per provider.\n const rules: any[] = [];\n for (const provider of Object.keys(rt.providers)) {\n const p = rt.providers[provider];\n const entries = Object.entries(p.models);\n if (entries.length === 0) continue;\n const cheapest = entries\n .map(([name, r]) => ({ name, score: (r.input + r.output) / 2, r }))\n .sort((a, b) => a.score - b.score)[0];\n\n rules.push({\n match: { provider, feature_tag_in: ['summarize', 'classify', 'translate'] },\n action: { recommend_model: cheapest.name, reason: 'cheap-feature routing' }\n });\n }\n\n // Unknown models: keep (no policy)\n rules.push({ match: { model_unknown: true }, action: { keep: true, reason: 'unknown model -> no policy applied' } });\n\n return {\n version: 1,\n default_provider: defaultProvider,\n rules,\n budgets: { currency: rt.currency, notes: 'MVP: budgets not enforced' },\n generated_from: { rate_table_version: rt.version, input: './aiopt-input/usage.jsonl' }\n };\n}\n\nfunction uniqUnknown(list: AnalysisJson['unknown_models']) {\n const seen = new Set<string>();\n const out: AnalysisJson['unknown_models'] = [];\n for (const x of list) {\n const k = `${x.provider}:${x.model}:${x.reason}`;\n if (seen.has(k)) continue;\n seen.add(k);\n out.push(x);\n }\n return out;\n}\n\nfunction round2(n: number) {\n return Math.round(n * 100) / 100;\n}\n\nexport function writeOutputs(outDir: string, analysis: AnalysisJson, savings: Savings, policy: PolicyJson, meta?: { mode?: 'attempt-log'|'legacy' }) {\n const mode = meta?.mode || 'legacy';\n\n fs.mkdirSync(outDir, { recursive: true });\n\n fs.writeFileSync(path.join(outDir, 'analysis.json'), JSON.stringify(analysis, null, 2));\n\n // report.json is the “one file to parse” summary for downstream tooling.\n const unknownCount = analysis.unknown_models?.length || 0;\n // confidence: downgrade if many unknowns; keep deterministic\n const confidence = unknownCount === 0 ? 'HIGH' : (unknownCount <= 3 ? 'MED' : 'LOW');\n const ratio = analysis.total_cost > 0 ? (savings.estimated_savings_total / analysis.total_cost) : 0;\n const warnings: string[] = [];\n if (ratio >= 0.9) warnings.push('estimated savings >= 90%');\n if (unknownCount > 0) warnings.push('unknown models/providers detected (estimated pricing used)');\n\n const reportJson = {\n version: 3,\n generated_at: new Date().toISOString(),\n confidence,\n warnings,\n assumptions: {\n no_double_counting: 'routing -> context -> retry allocation per-event with remaining-cost caps',\n retry_cost_model: mode === 'attempt-log'\n ? 'attempt-log mode: total_cost is sum of attempt lines; retry_waste is sum of attempts>=2'\n : 'legacy mode: total_cost includes retries as extra attempts (base_cost*(1+retries))',\n context_model: 'top 20% by input_tokens assume 25% input reduction',\n estimated_pricing_note: unknownCount > 0 ? 'some items use estimated rates; treat savings as a band' : 'all items used known rates'\n },\n summary: {\n total_cost_usd: analysis.total_cost,\n estimated_savings_usd: savings.estimated_savings_total,\n routing_savings_usd: savings.routing_savings,\n context_savings_usd: savings.context_savings,\n retry_waste_usd: savings.retry_waste\n },\n top: {\n by_model: analysis.by_model_top,\n by_feature: analysis.by_feature_top\n },\n unknown_models: analysis.unknown_models,\n notes: savings.notes\n };\n fs.writeFileSync(path.join(outDir, 'report.json'), JSON.stringify(reportJson, null, 2));\n\n // report.md: \"what to change\" guidance + confidence/assumptions (T4 DoD)\n const ratioMd = analysis.total_cost > 0 ? (savings.estimated_savings_total / analysis.total_cost) : 0;\n const warningsMd: string[] = [];\n if (ratioMd >= 0.9) warningsMd.push('WARNING: estimated savings >= 90% — check overlap/missing rate table');\n\n const reportMd = [\n '# AIOpt Report',\n '',\n `- Total cost: $${analysis.total_cost}`,\n `- Estimated savings: $${savings.estimated_savings_total} (guarded <= total_cost)`,\n `- Confidence: ${confidence}`,\n unknownCount > 0 ? `- Unknown models: ${unknownCount} (estimated pricing used)` : '- Unknown models: 0',\n ...warningsMd.map(w => `- ${w}`),\n '',\n '## ASSUMPTIONS',\n '- No double-counting: routing → context → retry savings allocated per-event with remaining-cost caps.',\n mode === 'attempt-log'\n ? '- Retry cost model: attempt-log mode (total_cost=sum attempts, retry_waste=sum attempt>=2).'\n : '- Retry cost model: legacy mode (total_cost=base_cost*(1+retries)).',\n '- Context savings: top 20% input_tokens events assume 25% input reduction.',\n '',\n '## WHAT TO CHANGE',\n '1) Retry tuning → edit `aiopt/policies/retry.json`',\n '2) Output cap → edit `aiopt/policies/output.json`',\n '3) Routing rule → edit `aiopt/policies/routing.json`',\n '',\n '## OUTPUTS',\n '- `aiopt-output/analysis.json`',\n '- `aiopt-output/report.json`',\n '- `aiopt-output/patches/*`',\n ''\n ].join('\\n');\n fs.writeFileSync(path.join(outDir, 'report.md'), reportMd);\n\n const reportTxt = [\n `총비용: $${analysis.total_cost}`,\n `절감 가능 금액(Estimated): $${savings.estimated_savings_total}`,\n `절감 근거 3줄:`,\n savings.notes[0],\n savings.notes[1],\n savings.notes[2],\n ''\n ].join('\\n');\n fs.writeFileSync(path.join(outDir, 'report.txt'), reportTxt);\n\n fs.writeFileSync(path.join(outDir, 'cost-policy.json'), JSON.stringify(policy, null, 2));\n\n // patches/*\n const fixes = buildTopFixes(analysis, savings);\n writePatches(outDir, fixes);\n}\n","{\n \"name\": \"aiopt\",\n \"version\": \"0.2.4\",\n \"description\": \"Serverless local CLI MVP for AI API cost analysis & cost-policy generation\",\n \"bin\": {\n \"aiopt\": \"dist/cli.js\"\n },\n \"type\": \"commonjs\",\n \"main\": \"dist/cli.js\",\n \"files\": [\n \"dist\",\n \"rates\",\n \"samples\",\n \"README.md\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"node --enable-source-maps dist/cli.js\",\n \"prepack\": \"npm run build\",\n \"test:npx\": \"npm pack --silent && node -e \\\"const fs=require('fs');const p=fs.readdirSync('.').find(f=>/^aiopt-.*\\\\.tgz$/.test(f)); if(!p) throw new Error('tgz not found'); console.log('tgz',p);\\\" && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) install --force && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) doctor && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) scan && test -f ./aiopt-output/report.md && echo OK\",\n \"test:guard\": \"npm run build --silent && node scripts/test-guard.js\",\n \"test:license\": \"npm run build --silent && node scripts/test-license.js\"\n },\n \"dependencies\": {\n \"commander\": \"^14.0.0\",\n \"csv-parse\": \"^6.1.0\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^24.0.0\",\n \"tsup\": \"^8.5.0\",\n \"typescript\": \"^5.9.2\"\n }\n}\n","import fs from 'fs';\nimport path from 'path';\n\nexport type InstallOptions = {\n force?: boolean;\n seedSample?: boolean;\n};\n\nfunction ensureDir(p: string) {\n fs.mkdirSync(p, { recursive: true });\n}\n\nfunction writeFile(filePath: string, content: string, force?: boolean) {\n if (!force && fs.existsSync(filePath)) return { wrote: false, reason: 'exists' as const };\n ensureDir(path.dirname(filePath));\n fs.writeFileSync(filePath, content);\n return { wrote: true as const };\n}\n\nexport function runInstall(cwd: string, opts: InstallOptions) {\n const force = Boolean(opts.force);\n\n const aioptDir = path.join(cwd, 'aiopt');\n const policiesDir = path.join(aioptDir, 'policies');\n const outDir = path.join(cwd, 'aiopt-output');\n\n ensureDir(aioptDir);\n ensureDir(policiesDir);\n ensureDir(outDir);\n\n const created: Array<{ path: string; status: 'created' | 'skipped' }> = [];\n\n // 1) aiopt/README.md\n const readme = `# AIOpt\n\nAIOpt는 **scan툴이 아니라 설치형 비용 가드레일이다.**\n\n## Quick start\n\\`\\`\\`bash\nnpx aiopt install --force\nnpx aiopt doctor\n# (your app runs, wrapper logs usage)\nnpx aiopt scan\n\\`\\`\\`\n\n- 서버/대시보드/계정/업로드/결제/프록시 없음\n- 로컬 파일 기반(정책 + usage.jsonl)\n- LLM 호출 금지(수학/룰 기반)\n\n## Wrapper usage (Node.js)\n\nAIOpt 설치 후, 앱 코드에서 wrapper를 불러서 사용량 JSONL을 자동 기록할 수 있습니다.\n\n\na) 최소 형태(토큰을 직접 넘김)\n\n\\`\\`\\`js\nconst { guardedCall } = require('./aiopt/aiopt-wrapper.js');\n\nawait guardedCall(process.cwd(), {\n provider: 'openai',\n model: 'gpt-5-mini',\n endpoint: 'responses.create',\n feature_tag: 'summarize',\n prompt_tokens: 1200,\n trace_id: 'my-trace'\n}, async (req) => {\n // req: { provider, model, endpoint, max_output_tokens, prompt_tokens, idempotency_key }\n // 여기서 실제 SDK 호출 후 결과를 반환\n return { status: 'ok', completion_tokens: 200 };\n});\n\\`\\`\\`\n\nb) OpenAI-style 응답(usage 자동 추출)\n\n\\`\\`\\`js\nreturn {\n status: 'ok',\n response: {\n usage: { prompt_tokens: 1200, completion_tokens: 200, total_tokens: 1400 }\n }\n};\n\\`\\`\\`\n`;\n\n const r1 = writeFile(path.join(aioptDir, 'README.md'), readme, force);\n created.push({ path: 'aiopt/README.md', status: r1.wrote ? 'created' : 'skipped' });\n\n // 2) aiopt/aiopt.config.json\n const config = {\n version: 1,\n installed_at: new Date().toISOString(),\n output_dir: './aiopt-output',\n usage_path: './aiopt-output/usage.jsonl',\n policies_dir: './aiopt/policies',\n rate_table: { path: './rates/rate_table.json' }\n };\n const r2 = writeFile(path.join(aioptDir, 'aiopt.config.json'), JSON.stringify(config, null, 2) + '\\n', force);\n created.push({ path: 'aiopt/aiopt.config.json', status: r2.wrote ? 'created' : 'skipped' });\n\n // 3) policies\n const routing = {\n version: 1,\n rules: [\n { match: { feature_tag_in: ['summarize', 'classify', 'translate'] }, action: { tier: 'cheap', reason: 'cheap feature routing' } },\n { match: { feature_tag_in: ['coding', 'reasoning'] }, action: { tier: 'default', reason: 'keep for quality' } }\n ]\n };\n const retry = {\n version: 1,\n max_attempts: 2,\n backoff_ms: [200, 500],\n retry_on_status: ['error', 'timeout'],\n notes: 'MVP deterministic retry tuning'\n };\n const output = {\n version: 1,\n max_output_tokens_default: 1024,\n per_feature: {\n summarize: 512,\n classify: 256,\n translate: 512\n }\n };\n const context = {\n version: 1,\n input_token_soft_cap: 12000,\n reduce_top_percentile: 0.2,\n assumed_reduction_ratio: 0.25\n };\n\n const p1 = writeFile(path.join(policiesDir, 'routing.json'), JSON.stringify(routing, null, 2) + '\\n', force);\n const p2 = writeFile(path.join(policiesDir, 'retry.json'), JSON.stringify(retry, null, 2) + '\\n', force);\n const p3 = writeFile(path.join(policiesDir, 'output.json'), JSON.stringify(output, null, 2) + '\\n', force);\n const p4 = writeFile(path.join(policiesDir, 'context.json'), JSON.stringify(context, null, 2) + '\\n', force);\n\n created.push({ path: 'aiopt/policies/routing.json', status: p1.wrote ? 'created' : 'skipped' });\n created.push({ path: 'aiopt/policies/retry.json', status: p2.wrote ? 'created' : 'skipped' });\n created.push({ path: 'aiopt/policies/output.json', status: p3.wrote ? 'created' : 'skipped' });\n created.push({ path: 'aiopt/policies/context.json', status: p4.wrote ? 'created' : 'skipped' });\n\n // 4) wrapper template (T2: real guardrails wrapper)\n const wrapperPath = path.join(aioptDir, 'aiopt-wrapper.js');\n const wrapper = \n`// AIOpt Wrapper (guardrails) — local-only (CommonJS)\n\nconst fs = require('fs');\n\nconst path = require('path');\n\nconst crypto = require('crypto');\n\nfunction readJson(p){ return JSON.parse(fs.readFileSync(p,'utf8')); }\nfunction ensureDir(p){ fs.mkdirSync(p,{recursive:true}); }\nfunction appendJsonl(filePath,obj){ ensureDir(path.dirname(filePath)); fs.appendFileSync(filePath, JSON.stringify(obj)+'\\\\n'); }\nfunction sleep(ms){ return new Promise(r=>setTimeout(r,ms)); }\n\nfunction loadConfig(cwd){ return readJson(path.join(cwd,'aiopt','aiopt.config.json')); }\nfunction loadPolicies(cwd,cfg){ const dir=path.isAbsolute(cfg.policies_dir)?cfg.policies_dir:path.join(cwd,cfg.policies_dir);\n return {\n retry: readJson(path.join(dir,'retry.json')) ,\n output: readJson(path.join(dir,'output.json'))\n };\n}\nfunction loadRates(cwd,cfg){\n const rp=path.isAbsolute(cfg.rate_table.path)?cfg.rate_table.path:path.join(cwd,cfg.rate_table.path);\n try{ return readJson(rp); }catch(e){\n // Fresh projects may not have a rates/ table yet. Fall back to a safe default.\n return { providers: {} };\n }\n}\n\nfunction costUsd(rt, provider, model, promptTokens, completionTokens){\n const p=rt.providers && rt.providers[provider];\n const r=(p && p.models && p.models[model]) || (p && p.default_estimated) || {input:1.0, output:4.0};\n return (promptTokens/1e6)*r.input + (completionTokens/1e6)*r.output;\n}\n\nfunction pickRoutedModel(rt, provider, featureTag, currentModel){\n const cheap=['summarize','classify','translate'];\n if(!cheap.includes(String(featureTag||'').toLowerCase())) return { model: currentModel, routed_from: null, hit: null };\n const p=rt.providers && rt.providers[provider];\n const entries=p && p.models ? Object.entries(p.models) : [];\n if(!entries.length) return { model: currentModel, routed_from: null, hit: null };\n const cheapest=entries.map(([name,r])=>({name,score:(r.input+r.output)/2})).sort((a,b)=>a.score-b.score)[0];\n if(!cheapest || cheapest.name===currentModel) return { model: currentModel, routed_from: null, hit: null };\n return { model: cheapest.name, routed_from: currentModel, hit: 'routing:cheap-feature' };\n}\n\nfunction outputCap(outputPolicy, featureTag, requested){\n const per=(outputPolicy && outputPolicy.per_feature) || {};\n const cap=per[String(featureTag||'').toLowerCase()] ?? (outputPolicy.max_output_tokens_default || 1024);\n const req=requested ?? cap;\n return Math.min(req, cap);\n}\n\nconst IDEMPOTENCY=new Map();\n\nfunction normalizeResult(out, input){\n // Accept either normalized return or provider raw.\n const o = (out && out.response) ? out.response : out;\n const status = (out && out.status) || o.status || (o.error ? 'error' : 'ok');\n const usage = o.usage || (o.data && o.data.usage) || null;\n\n const prompt_tokens = Number(\n (out && out.prompt_tokens) ??\n (o && o.prompt_tokens) ??\n (usage && usage.prompt_tokens) ??\n input.prompt_tokens ??\n 0\n );\n const completion_tokens = Number(\n (out && out.completion_tokens) ??\n (o && o.completion_tokens) ??\n (usage && usage.completion_tokens) ??\n 0\n );\n const total_tokens = Number(\n (out && out.total_tokens) ??\n (o && o.total_tokens) ??\n (usage && usage.total_tokens) ??\n (prompt_tokens + completion_tokens)\n );\n\n const error_code = status === 'ok' ? null : String((out && out.error_code) || (o && o.error_code) || (o && o.error && (o.error.code || o.error.type)) || status);\n const cost_usd = (out && typeof out.cost_usd === 'number') ? out.cost_usd : null;\n\n return { status, prompt_tokens, completion_tokens, total_tokens, error_code, cost_usd };\n}\n\n/**\n * guardedCall(cwd, input, fn)\n *\n * fn(req) can return either:\n * 1) Normalized shape:\n * { status: 'ok'|'error'|'timeout', prompt_tokens?, completion_tokens?, total_tokens?, cost_usd?, error_code? }\n * 2) Provider raw response (OpenAI-style), e.g.:\n * { status:'ok', response:{ usage:{prompt_tokens, completion_tokens, total_tokens} } }\n * { usage:{prompt_tokens, completion_tokens, total_tokens} }\n *\n * If token fields are missing, AIOpt will fall back to input.prompt_tokens and/or 0.\n */\nasync function guardedCall(cwd, input, fn){\n const cfg=loadConfig(cwd);\n const pol=loadPolicies(cwd,cfg);\n const rt=loadRates(cwd,cfg);\n\n const request_id=crypto.randomUUID();\n const trace_id=input.trace_id || request_id;\n const idem=input.idempotency_key || trace_id;\n if(IDEMPOTENCY.has(idem)) return IDEMPOTENCY.get(idem);\n\n const routed=pickRoutedModel(rt, input.provider, input.feature_tag, input.model);\n const maxOut=outputCap(pol.output, input.feature_tag, input.max_output_tokens);\n const usagePath=path.isAbsolute(cfg.usage_path)?cfg.usage_path:path.join(cwd,cfg.usage_path);\n\n const maxAttempts=Math.max(1, Number(pol.retry.max_attempts||1));\n const backoffs=pol.retry.backoff_ms || [200];\n const retryOn=new Set(pol.retry.retry_on_status || ['error','timeout']);\n\n let last={status:'error', completion_tokens:0, error_code:'unknown'};\n\n for(let attempt=1; attempt<=maxAttempts; attempt++){\n const t0=Date.now();\n const policy_hits=[];\n if(routed.hit) policy_hits.push(routed.hit);\n policy_hits.push('outputcap:'+maxOut);\n try{\n const out=await fn({ provider: input.provider, model: routed.model, endpoint: input.endpoint, max_output_tokens: maxOut, prompt_tokens: input.prompt_tokens, idempotency_key: idem });\n const latency_ms=Date.now()-t0;\n const norm=normalizeResult(out, input);\n const cost_usd=(typeof norm.cost_usd==='number') ? norm.cost_usd : costUsd(rt, input.provider, routed.model, norm.prompt_tokens, norm.completion_tokens);\n const feature_tag = input.feature_tag || input.endpoint || 'unknown';\n appendJsonl(usagePath, { ts:new Date().toISOString(), request_id, trace_id, attempt, status: norm.status, error_code: norm.error_code, provider: input.provider, model: routed.model, endpoint: input.endpoint, prompt_tokens: norm.prompt_tokens, completion_tokens: norm.completion_tokens, total_tokens: norm.total_tokens, cost_usd, latency_ms, meta:{ feature_tag, routed_from: routed.routed_from, policy_hits } });\n last={ status: norm.status, completion_tokens: norm.completion_tokens, error_code: norm.error_code };\n if(norm.status==='ok'){ IDEMPOTENCY.set(idem,out); return out; }\n if(retryOn.has(norm.status) && attempt<maxAttempts){ await sleep(Number(backoffs[Math.min(attempt-1, backoffs.length-1)]||200)); continue; }\n IDEMPOTENCY.set(idem,out); return out;\n }catch(e){\n const latency_ms=Date.now()-t0;\n const out={ status:'error', completion_tokens:0, error_code:String(e && (e.code||e.name) || 'exception') };\n const feature_tag = input.feature_tag || input.endpoint || 'unknown';\n appendJsonl(usagePath, { ts:new Date().toISOString(), request_id, trace_id, attempt, status: out.status, error_code: out.error_code, provider: input.provider, model: routed.model, endpoint: input.endpoint, prompt_tokens:Number(input.prompt_tokens||0), completion_tokens:0, total_tokens:Number(input.prompt_tokens||0), cost_usd:costUsd(rt, input.provider, routed.model, Number(input.prompt_tokens||0), 0), latency_ms, meta:{ feature_tag, routed_from: routed.routed_from, policy_hits:[routed.hit||'routing:none','outputcap:'+maxOut,'error:exception'] } });\n last=out;\n if(attempt<maxAttempts){ await sleep(Number(backoffs[Math.min(attempt-1, backoffs.length-1)]||200)); continue; }\n IDEMPOTENCY.set(idem,out); return out;\n }\n }\n IDEMPOTENCY.set(idem,last);\n return last;\n}\n\nmodule.exports = { guardedCall };\n`;\n;\n const w = writeFile(wrapperPath, wrapper, force);\n created.push({ path: 'aiopt/aiopt-wrapper.js', status: w.wrote ? 'created' : 'skipped' });\n\n // 5) usage.jsonl\n const usagePath = path.join(outDir, 'usage.jsonl');\n if (force || !fs.existsSync(usagePath)) {\n // default: empty file (avoid mixing demo data into real user logs)\n fs.writeFileSync(usagePath, '');\n created.push({ path: 'aiopt-output/usage.jsonl', status: 'created' });\n\n if (opts.seedSample) {\n const sample = {\n ts: new Date().toISOString(),\n request_id: 'sample',\n trace_id: 'sample',\n attempt: 1,\n status: 'ok',\n error_code: null,\n provider: 'openai',\n model: 'gpt-5-mini',\n endpoint: 'demo',\n prompt_tokens: 12,\n completion_tokens: 3,\n total_tokens: 15,\n cost_usd: 0.0,\n latency_ms: 1,\n meta: { feature_tag: 'demo', routed_from: null, policy_hits: ['install-sample'] }\n };\n fs.appendFileSync(usagePath, JSON.stringify(sample) + '\\n');\n }\n } else {\n created.push({ path: 'aiopt-output/usage.jsonl', status: 'skipped' });\n }\n\n return { created };\n}\n","import fs from 'fs';\nimport path from 'path';\n\nfunction canWrite(dir: string) {\n try {\n fs.mkdirSync(dir, { recursive: true });\n const p = path.join(dir, `.aiopt-write-test-${Date.now()}`);\n fs.writeFileSync(p, 'ok');\n fs.unlinkSync(p);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction tailLines(filePath: string, n: number): string[] {\n try {\n const raw = fs.readFileSync(filePath, 'utf8');\n const lines = raw.split(/\\r?\\n/).filter(l => l.trim().length > 0);\n return lines.slice(Math.max(0, lines.length - n));\n } catch {\n return [];\n }\n}\n\nexport type DoctorResult = {\n ok: boolean;\n checks: Array<{ name: string; ok: boolean; detail?: string }>;\n last5: Array<{ status?: string; provider?: string; model?: string; endpoint?: string; attempt?: number }>;\n};\n\nexport function runDoctor(cwd: string): DoctorResult {\n const aioptDir = path.join(cwd, 'aiopt');\n const policiesDir = path.join(aioptDir, 'policies');\n const outDir = path.join(cwd, 'aiopt-output');\n const usagePath = path.join(outDir, 'usage.jsonl');\n\n const checks: DoctorResult['checks'] = [];\n\n checks.push({ name: 'aiopt/ exists', ok: fs.existsSync(aioptDir) });\n checks.push({ name: 'aiopt/policies exists', ok: fs.existsSync(policiesDir) });\n checks.push({ name: 'aiopt-output/ writable', ok: canWrite(outDir) });\n checks.push({ name: 'usage.jsonl exists', ok: fs.existsSync(usagePath), detail: usagePath });\n\n const last5raw = tailLines(usagePath, 5);\n const last5 = last5raw.length === 0 ? [{ status: '(empty usage.jsonl)' }] as any : last5raw.map(l => {\n try {\n const j = JSON.parse(l);\n return {\n status: j.status,\n provider: j.provider,\n model: j.model,\n endpoint: j.endpoint,\n attempt: j.attempt,\n feature_tag: j?.meta?.feature_tag\n };\n } catch {\n return {};\n }\n });\n\n // Feature-tag quality check (sample last 50 lines)\n const last50 = tailLines(usagePath, 50);\n let missing = 0;\n let total50 = 0;\n for (const l of last50) {\n total50++;\n try {\n const j = JSON.parse(l);\n const ft = j?.meta?.feature_tag;\n if (!ft || String(ft).trim() === '') missing++;\n } catch {\n missing++;\n }\n }\n if (total50 > 0 && missing > 0) {\n checks.push({ name: 'feature_tag quality (last50)', ok: false, detail: `${missing}/${total50} missing meta.feature_tag` });\n } else {\n checks.push({ name: 'feature_tag quality (last50)', ok: true, detail: 'meta.feature_tag present' });\n }\n\n const ok = checks.every(c => c.ok);\n return { ok, checks, last5 };\n}\n\n","import crypto from 'crypto';\nimport fs from 'fs';\nimport path from 'path';\n\nexport type LicensePayload = {\n sub: string; // customer id/email/etc\n plan: 'pro' | 'team' | 'trial' | string;\n iat: number; // issued at (unix seconds)\n exp: number; // expires at (unix seconds)\n features?: Record<string, boolean>;\n};\n\nexport type LicenseFile = {\n key: string;\n payload: LicensePayload;\n verified: boolean;\n verified_at: string;\n};\n\n// NOTE: Public key only. No servers.\n// Replace this with your own RSA public key in PEM (SPKI) format.\nexport const DEFAULT_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz1LLE/pXIx5TloDa0LAf\njg9NSIW6STWhsAFP2ZzXgpWoQ3cCmW6xcB/4QNEmPpGlfMWhyRfkxsdKuhnjUMTg\n8MpMAcbjjF8JrGS9iLnW4yrLm7jzsOcndjkGO7pH+32GopZk98dVzmIRPok2Je76\n3MQRaxLi0jWytaCmacEB4R7HyuquOQlHPg0vD9NOEwrC/+br2GdQbD1lKPyLeLv3\nRidwAs8Iw2xx5g8G+BsVSM/HRC3jQT5GynfnuDsvMHCvGLRct/76ajiR71/NFZEP\nZ7liILNnZzCTlKGGZfZmG70t+zkg8HKdpRuWy8rZ0DPWyQg5MKm6TZOMV6dC0Rpg\nDwIDAQAB\n-----END PUBLIC KEY-----`;\n\nfunction b64urlDecodeToBuffer(s: string): Buffer {\n const padLen = (4 - (s.length % 4)) % 4;\n const padded = s.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(padLen);\n return Buffer.from(padded, 'base64');\n}\n\nfunction safeJsonParse(buf: Buffer): any {\n const txt = buf.toString('utf8');\n return JSON.parse(txt);\n}\n\nexport function parseLicenseKey(key: string): { payload: LicensePayload; signature: Buffer; payloadB64Url: string } {\n const parts = String(key).trim().split('.');\n if (parts.length !== 2) throw new Error('invalid license key format: expected <payloadB64Url>.<sigB64Url>');\n const [payloadB64Url, sigB64Url] = parts;\n const payloadBuf = b64urlDecodeToBuffer(payloadB64Url);\n const sigBuf = b64urlDecodeToBuffer(sigB64Url);\n const payload = safeJsonParse(payloadBuf) as LicensePayload;\n return { payload, signature: sigBuf, payloadB64Url };\n}\n\nexport function verifyLicenseKey(key: string, publicKeyPem: string): { ok: boolean; reason?: string; payload?: LicensePayload } {\n let parsed: { payload: LicensePayload; signature: Buffer; payloadB64Url: string };\n try {\n parsed = parseLicenseKey(key);\n } catch (e: any) {\n return { ok: false, reason: e?.message || 'parse error' };\n }\n\n const now = Math.floor(Date.now() / 1000);\n if (typeof parsed.payload?.exp !== 'number' || parsed.payload.exp < now) {\n return { ok: false, reason: 'expired', payload: parsed.payload };\n }\n\n try {\n const verifier = crypto.createVerify('RSA-SHA256');\n verifier.update(parsed.payloadB64Url);\n verifier.end();\n const ok = verifier.verify(publicKeyPem, parsed.signature);\n if (!ok) return { ok: false, reason: 'bad signature', payload: parsed.payload };\n return { ok: true, payload: parsed.payload };\n } catch (e: any) {\n return { ok: false, reason: e?.message || 'verify error', payload: parsed.payload };\n }\n}\n\nexport function defaultLicensePath(cwd: string): string {\n return path.join(cwd, 'aiopt', 'license.json');\n}\n\nexport function writeLicenseFile(p: string, key: string, payload: LicensePayload, verified: boolean) {\n const out: LicenseFile = {\n key,\n payload,\n verified,\n verified_at: new Date().toISOString()\n };\n fs.mkdirSync(path.dirname(p), { recursive: true });\n fs.writeFileSync(p, JSON.stringify(out, null, 2));\n}\n\nexport function readLicenseFile(p: string): LicenseFile {\n return JSON.parse(fs.readFileSync(p, 'utf8')) as LicenseFile;\n}\n","import { RateTable, UsageEvent } from './types';\nimport { analyze } from './scan';\nimport { costOfEvent } from './cost';\n\nexport type GuardInput = {\n baselineEvents: UsageEvent[];\n candidate: {\n provider?: string;\n model?: string;\n contextMultiplier?: number; // multiplies input_tokens\n outputMultiplier?: number; // multiplies output_tokens\n retriesDelta?: number; // adds to retries\n callMultiplier?: number; // multiplies call volume\n };\n};\n\nexport type GuardResult = {\n exitCode: 0 | 2 | 3;\n message: string;\n};\n\nfunction accidentRiskFromMonthly(monthly: number): 'Low' | 'Medium' | 'High' {\n if (!Number.isFinite(monthly)) return 'Medium';\n if (monthly >= 100) return 'High';\n if (monthly >= 10) return 'Medium';\n return 'Low';\n}\n\nfunction round2(n: number) {\n return Math.round(n * 100) / 100;\n}\n\nfunction monthEstimate(delta: number, events: UsageEvent[]) {\n // Convert observed delta (over the sample window) into a monthly estimate.\n // Prefer timestamp-derived window length for determinism; fall back to 1 day.\n let days = 1;\n try {\n const times = events\n .map(e => Date.parse(e.ts))\n .filter(t => Number.isFinite(t))\n .sort((a, b) => a - b);\n if (times.length >= 2) {\n const spanMs = Math.max(0, times[times.length - 1] - times[0]);\n const spanDays = spanMs / (1000 * 60 * 60 * 24);\n // If logs cover <1 day (or identical timestamps), treat as 1 day.\n days = Math.max(1, spanDays);\n }\n } catch {\n days = 1;\n }\n return (delta * 30) / days;\n}\n\nfunction applyCandidate(events: UsageEvent[], cand: GuardInput['candidate']): UsageEvent[] {\n const ctxM = cand.contextMultiplier ?? 1;\n const outM = cand.outputMultiplier ?? 1;\n const rDelta = cand.retriesDelta ?? 0;\n\n return events.map(ev => ({\n ...ev,\n provider: cand.provider ? String(cand.provider).toLowerCase() : ev.provider,\n model: cand.model ? String(cand.model) : ev.model,\n input_tokens: Math.max(0, Math.round((ev.input_tokens || 0) * ctxM)),\n output_tokens: Math.max(0, Math.round((ev.output_tokens || 0) * outM)),\n retries: Math.max(0, Math.round((ev.retries || 0) + rDelta)),\n // clear billed_cost so pricing recalculates for new model/provider\n billed_cost: undefined\n }));\n}\n\nfunction confidenceFromChange(cand: GuardInput['candidate']): { level: 'High'|'Medium'|'Low'; reasons: string[] } {\n const reasons: string[] = [];\n\n if (cand.retriesDelta && cand.retriesDelta !== 0) reasons.push('retries change');\n if (cand.model) reasons.push('model change');\n if (cand.provider) reasons.push('provider change');\n if (cand.contextMultiplier && cand.contextMultiplier !== 1) reasons.push('context length change');\n if (cand.outputMultiplier && cand.outputMultiplier !== 1) reasons.push('output length change');\n\n // Spec rule:\n // High: retries/failed/dup calls (we only model retries here)\n // Medium: model/provider change\n // Low: context trimming / prompt structure\n if (cand.retriesDelta && cand.retriesDelta !== 0) return { level: 'High', reasons };\n if (cand.model || cand.provider) return { level: 'Medium', reasons };\n if ((cand.contextMultiplier && cand.contextMultiplier !== 1) || (cand.outputMultiplier && cand.outputMultiplier !== 1)) {\n return { level: 'Low', reasons };\n }\n return { level: 'Medium', reasons: reasons.length ? reasons : ['unknown change'] };\n}\n\nfunction assessDataQuality(baselineEvents: UsageEvent[], base: ReturnType<typeof analyze>) {\n const reasons: string[] = [];\n\n // ts span\n const times = baselineEvents\n .map(e => Date.parse(e.ts))\n .filter(t => Number.isFinite(t))\n .sort((a, b) => a - b);\n if (times.length < 2) reasons.push('ts span unknown');\n else {\n const spanDays = Math.max(0, (times[times.length - 1] - times[0]) / (1000 * 60 * 60 * 24));\n if (spanDays < 0.25) reasons.push('ts span too short');\n }\n\n // feature tag coverage\n const missingFt = baselineEvents.filter(e => !e.feature_tag && !(e.meta && (e.meta as any).feature_tag)).length;\n if (missingFt / Math.max(1, baselineEvents.length) > 0.2) reasons.push('feature_tag missing >20%');\n\n // unknown model/provider\n const unknown = (base.analysis.unknown_models || []).length;\n if (unknown / Math.max(1, baselineEvents.length) > 0.2) reasons.push('unknown model/provider >20%');\n\n // Map to a confidence penalty.\n let penalty: 'none'|'low'|'medium' = 'none';\n if (reasons.length >= 2) penalty = 'medium';\n else if (reasons.length === 1) penalty = 'low';\n\n return { reasons, penalty };\n}\n\nfunction degrade(level: 'High'|'Medium'|'Low', penalty: 'none'|'low'|'medium'): 'High'|'Medium'|'Low' {\n if (penalty === 'none') return level;\n if (penalty === 'low') return level === 'High' ? 'Medium' : 'Low';\n // medium\n return 'Low';\n}\n\nexport function runGuard(rt: RateTable, input: GuardInput): GuardResult {\n if (!input.baselineEvents || input.baselineEvents.length === 0) {\n const conf = { level: 'Low' as const, reasons: ['baseline empty'] };\n const msg = [\n 'FAIL: baseline usage is empty (need aiopt-output/usage.jsonl)',\n 'Impact (monthly est): +$0 (insufficient baseline)',\n `Accident risk: ${accidentRiskFromMonthly(100)}`,\n `Confidence: ${conf.level} (${(conf.reasons.length ? conf.reasons.join(', ') : 'baseline empty')})`,\n 'Recommendation: run the wrapper to collect baseline usage before using guard.'\n ].join('\\n');\n return { exitCode: 3, message: msg };\n }\n\n // For fair comparison, ignore billed_cost/cost_usd and recompute both sides from rate table.\n const baselineEvents = input.baselineEvents.map(e => ({ ...e, billed_cost: undefined }));\n const base = analyze(rt, baselineEvents);\n const candidateEvents = applyCandidate(baselineEvents, input.candidate);\n const cand = analyze(rt, candidateEvents);\n\n const baseCost = base.analysis.total_cost;\n let candCost = cand.analysis.total_cost;\n\n // call volume multiplier (traffic spike)\n const callMult = input.candidate.callMultiplier && input.candidate.callMultiplier > 0 ? input.candidate.callMultiplier : 1;\n if (callMult !== 1) {\n candCost = candCost * callMult;\n }\n\n // attempt-log baseline: retriesDelta should be interpreted as \"extra attempts\".\n const attemptLog = baselineEvents.some(e => (e.trace_id && String(e.trace_id).length > 0) || (e.attempt !== undefined && Number(e.attempt) > 0));\n if (attemptLog && input.candidate.retriesDelta && input.candidate.retriesDelta > 0) {\n // More accurate deterministic approximation:\n // - If baseline already has retry attempts (attempt>=2), use their average cost as retry unit.\n // - Else fall back to avg baseline attempt cost.\n let retryUnit = 0;\n let retryCount = 0;\n for (const ev of baselineEvents) {\n const attempt = Number(ev.attempt || 1);\n if (attempt >= 2) {\n retryUnit += costOfEvent(rt, ev).cost;\n retryCount += 1;\n }\n }\n if (retryCount > 0) {\n retryUnit = retryUnit / retryCount;\n } else {\n retryUnit = baseCost / Math.max(1, baselineEvents.length);\n }\n candCost += retryUnit * input.candidate.retriesDelta;\n }\n\n const delta = candCost - baseCost;\n\n const changeConf = confidenceFromChange(input.candidate);\n const dq = assessDataQuality(baselineEvents, base);\n const conf = { level: degrade(changeConf.level, dq.penalty), reasons: [...changeConf.reasons, ...dq.reasons.map(r => `data: ${r}`)] };\n\n // Guard logic:\n // - Warning if delta > 0 and monthEstimate(delta) >= $10\n // - Fail if delta > 0 and monthEstimate(delta) >= $100 (merge-blocking)\n const monthly = monthEstimate(Math.max(0, delta), baselineEvents);\n const monthlyRounded = round2(monthly);\n\n let exitCode: 0 | 2 | 3 = 0;\n let headline = 'OK: no cost accident risk detected';\n\n if (monthly >= 100) { exitCode = 3; headline = 'FAIL: high risk of LLM cost accident'; }\n else if (monthly >= 10) { exitCode = 2; headline = 'WARN: possible LLM cost accident'; }\n\n const reasons = conf.reasons.length ? conf.reasons.join(', ') : 'n/a';\n\n const msg = [\n headline,\n `Summary: baseline=$${round2(baseCost)} → candidate=$${round2(candCost)} (Δ=$${round2(delta)})`,\n callMult !== 1 ? `Call volume multiplier: x${callMult}` : null,\n `Impact (monthly est): +$${monthlyRounded}`,\n `Accident risk: ${accidentRiskFromMonthly(monthly)}`,\n `Confidence: ${conf.level} (${reasons})`,\n 'Recommendation: review model/provider/retry/context changes before deploy.'\n ].filter(Boolean).join('\\n');\n\n return { exitCode, message: msg };\n}\n","import http from 'http';\nimport fs from 'fs';\nimport path from 'path';\n\nexport async function startDashboard(cwd: string, opts: { port: number }) {\n const host = '127.0.0.1';\n const port = opts.port || 3010;\n\n const outDir = path.join(cwd, 'aiopt-output');\n const file = (name: string) => path.join(outDir, name);\n\n function readOrNull(p: string) {\n try {\n if (!fs.existsSync(p)) return null;\n return fs.readFileSync(p, 'utf8');\n } catch {\n return null;\n }\n }\n\n const indexHtml = `<!doctype html>\n<html>\n<head>\n <meta charset=\"utf-8\"/>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n <title>AIOpt Local Dashboard</title>\n <style>\n body{font-family:ui-sans-serif,system-ui,Arial; margin:16px; max-width:980px}\n .row{display:flex; gap:12px; flex-wrap:wrap}\n .card{border:1px solid #ddd; border-radius:10px; padding:12px; flex:1; min-width:320px}\n pre{white-space:pre-wrap; word-break:break-word; background:#0b1020; color:#e6e6e6; padding:12px; border-radius:10px; overflow:auto}\n a{color:#2d6cdf}\n </style>\n</head>\n<body>\n <h1>AIOpt Local Dashboard</h1>\n <p>Local-only (bind: 127.0.0.1). Reads files from <code>./aiopt-output/</code>.</p>\n\n <div class=\"row\">\n <div class=\"card\">\n <h2>Last Guard</h2>\n <div id=\"guardMeta\"></div>\n <pre id=\"guard\">loading...</pre>\n <p><a href=\"/api/guard-last.txt\" target=\"_blank\">raw</a></p>\n </div>\n <div class=\"card\">\n <h2>Last Scan</h2>\n <pre id=\"scan\">loading...</pre>\n <p>\n <a href=\"/api/report.md\" target=\"_blank\">report.md</a> ·\n <a href=\"/api/report.json\" target=\"_blank\">report.json</a>\n </p>\n </div>\n </div>\n\n<script>\nasync function load() {\n const guardTxt = await fetch('/api/guard-last.txt').then(r=>r.ok?r.text():null);\n const guardMeta = await fetch('/api/guard-last.json').then(r=>r.ok?r.json():null);\n document.getElementById('guard').textContent = guardTxt || '(no guard-last.txt yet)';\n document.getElementById('guardMeta').textContent = guardMeta ? ('exit=' + guardMeta.exitCode + ' @ ' + guardMeta.ts) : '';\n\n const reportMd = await fetch('/api/report.md').then(r=>r.ok?r.text():null);\n document.getElementById('scan').textContent = reportMd || '(no report.md yet — run: aiopt scan)';\n}\nload();\n</script>\n</body>\n</html>`;\n\n const server = http.createServer((req, res) => {\n const url = req.url || '/';\n if (url === '/' || url === '/index.html') {\n res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });\n res.end(indexHtml);\n return;\n }\n\n if (url.startsWith('/api/')) {\n const name = url.replace('/api/', '');\n const allow = new Set(['guard-last.txt', 'guard-last.json', 'report.md', 'report.json']);\n if (!allow.has(name)) {\n res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n res.end('not found');\n return;\n }\n\n const p = file(name);\n const txt = readOrNull(p);\n if (txt === null) {\n res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n res.end('missing');\n return;\n }\n\n const ct = name.endsWith('.json') ? 'application/json; charset=utf-8' : 'text/plain; charset=utf-8';\n res.writeHead(200, { 'content-type': ct });\n res.end(txt);\n return;\n }\n\n res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });\n res.end('not found');\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject);\n server.listen(port, host, () => resolve());\n });\n\n console.log(`OK: dashboard http://${host}:${port}/`);\n console.log('Tip: run `aiopt guard ...` and `aiopt scan` to populate aiopt-output files.');\n\n // keep alive\n await new Promise(() => {});\n}\n","#!/usr/bin/env node\n\nimport fs from 'fs';\nimport path from 'path';\nimport { Command } from 'commander';\nimport { ensureDir, isCsvPath, readCsv, readJsonl } from './io';\nimport { RateTable } from './types';\nimport { analyze, writeOutputs } from './scan';\n\nconst program = new Command();\n\nconst DEFAULT_INPUT = './aiopt-output/usage.jsonl';\nconst DEFAULT_OUTPUT_DIR = './aiopt-output';\n\nfunction loadRateTable(): RateTable {\n const p = path.join(__dirname, '..', 'rates', 'rate_table.json');\n return JSON.parse(fs.readFileSync(p, 'utf8'));\n}\n\nprogram\n .name('aiopt')\n .description('AI 비용 자동 절감 인프라 — 서버 없는 로컬 CLI MVP')\n // keep CLI version in sync with package.json\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n .version(require('../package.json').version);\n\nprogram\n .command('init')\n .description('aiopt-input/ 및 샘플 usage.jsonl, aiopt-output/ 생성')\n .action(() => {\n ensureDir('./aiopt-input');\n ensureDir('./aiopt-output');\n\n const sampleSrc = path.join(__dirname, '..', 'samples', 'sample_usage.jsonl');\n const dst = path.join('./aiopt-input', 'usage.jsonl');\n\n if (!fs.existsSync(dst)) {\n fs.copyFileSync(sampleSrc, dst);\n console.log('Created ./aiopt-input/usage.jsonl (sample)');\n } else {\n console.log('Exists ./aiopt-input/usage.jsonl (skip)');\n }\n\n console.log('Ready: ./aiopt-output/');\n });\n\nprogram\n .command('scan')\n .description('입력 로그(JSONL/CSV)를 분석하고 report.md/report.json + patches까지 생성')\n .option('--input <path>', 'input file path (default: ./aiopt-output/usage.jsonl)', DEFAULT_INPUT)\n .option('--out <dir>', 'output dir (default: ./aiopt-output)', DEFAULT_OUTPUT_DIR)\n .action(async (opts) => {\n const inputPath = String(opts.input);\n const outDir = String(opts.out);\n\n if (!fs.existsSync(inputPath)) {\n console.error(`Input not found: ${inputPath}`);\n process.exit(1);\n }\n\n const rt = loadRateTable();\n const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);\n\n const { analysis, savings, policy, meta } = analyze(rt, events);\n // For reproducibility: embed input path & rate table meta\n policy.generated_from.input = inputPath;\n\n writeOutputs(outDir, analysis, savings, policy, meta);\n\n // Console: Top Fix 3 (data-driven)\n const { buildTopFixes } = await import('./solutions');\n const fixes = buildTopFixes(analysis, savings).slice(0, 3);\n\n console.log('Top Fix 3:');\n fixes.forEach((f, i) => {\n const tag = f.status === 'no-issue' ? '(no issue detected)' : `($${Math.round(f.impact_usd * 100) / 100})`;\n console.log(`${i + 1}) ${f.title} ${tag}`);\n });\n console.log(`Report: ${path.join(outDir, 'report.md')}`);\n });\n\nprogram\n .command('policy')\n .description('마지막 scan 결과 기반으로 cost-policy.json만 재생성 (MVP: scan과 동일 로직)')\n .option('--input <path>', 'input file path (default: ./aiopt-input/usage.jsonl)', DEFAULT_INPUT)\n .option('--out <dir>', 'output dir (default: ./aiopt-output)', DEFAULT_OUTPUT_DIR)\n .action((opts) => {\n const inputPath = String(opts.input);\n const outDir = String(opts.out);\n const rt = loadRateTable();\n const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);\n const { policy } = analyze(rt, events);\n policy.generated_from.input = inputPath;\n\n ensureDir(outDir);\n fs.writeFileSync(path.join(outDir, 'cost-policy.json'), JSON.stringify(policy, null, 2));\n console.log(`OK: ${outDir}/cost-policy.json`);\n });\n\n// v0.2: install/doctor (no servers)\nprogram\n .command('install')\n .description('Install AIOpt guardrails: create aiopt/ + policies + usage.jsonl')\n .option('--force', 'overwrite existing files')\n .option('--seed-sample', 'seed 1 sample line into aiopt-output/usage.jsonl')\n .action(async (opts) => {\n const { runInstall } = await import('./install');\n const result = runInstall(process.cwd(), { force: Boolean(opts.force), seedSample: Boolean(opts.seedSample) });\n for (const c of result.created) {\n console.log(`${c.status === 'created' ? 'CREATED' : 'SKIP'}: ${c.path}`);\n }\n });\n\nprogram\n .command('doctor')\n .description('Check installation + print last 5 usage events')\n .action(async () => {\n const { runDoctor } = await import('./doctor');\n const r = runDoctor(process.cwd());\n console.log(r.ok ? 'OK: doctor' : 'WARN: doctor');\n for (const c of r.checks) {\n console.log(`${c.ok ? 'OK' : 'FAIL'}: ${c.name}${c.detail ? ` (${c.detail})` : ''}`);\n }\n console.log('--- last5 usage');\n for (const x of r.last5) {\n console.log(JSON.stringify(x));\n }\n });\n\n// Offline license (no servers)\nconst licenseCmd = program\n .command('license')\n .description('Offline license activate/verify (public key only; no server calls)');\n\nlicenseCmd\n .command('activate')\n .argument('<KEY>', 'license key (<payloadB64Url>.<sigB64Url>)')\n .option('--out <path>', 'output license.json path (default: ./aiopt/license.json)')\n .action(async (key, opts) => {\n const { DEFAULT_PUBLIC_KEY_PEM, defaultLicensePath, verifyLicenseKey, writeLicenseFile } = await import('./license');\n const outPath = opts.out ? String(opts.out) : defaultLicensePath(process.cwd());\n const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM;\n const v = verifyLicenseKey(String(key), pub);\n if (!v.payload) {\n console.error(`FAIL: ${v.reason || 'invalid license'}`);\n process.exit(3);\n }\n writeLicenseFile(outPath, String(key), v.payload, v.ok);\n console.log(v.ok ? `OK: activated (${outPath})` : `WARN: saved but not verified (${v.reason}) (${outPath})`);\n process.exit(v.ok ? 0 : 2);\n });\n\nlicenseCmd\n .command('verify')\n .option('--path <path>', 'license.json path (default: ./aiopt/license.json)')\n .action(async (opts) => {\n const { DEFAULT_PUBLIC_KEY_PEM, defaultLicensePath, readLicenseFile, verifyLicenseKey } = await import('./license');\n const p = opts.path ? String(opts.path) : defaultLicensePath(process.cwd());\n const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM;\n if (!fs.existsSync(p)) {\n console.error(`FAIL: license file not found: ${p}`);\n process.exit(3);\n }\n const f = readLicenseFile(p);\n const v = verifyLicenseKey(f.key, pub);\n if (v.ok) {\n console.log('OK: license verified');\n process.exit(0);\n }\n console.error(`FAIL: license invalid (${v.reason || 'unknown'})`);\n process.exit(3);\n });\n\nlicenseCmd\n .command('status')\n .option('--path <path>', 'license.json path (default: ./aiopt/license.json)')\n .action(async (opts) => {\n const { DEFAULT_PUBLIC_KEY_PEM, defaultLicensePath, readLicenseFile, verifyLicenseKey } = await import('./license');\n const p = opts.path ? String(opts.path) : defaultLicensePath(process.cwd());\n const pub = process.env.AIOPT_LICENSE_PUBKEY || DEFAULT_PUBLIC_KEY_PEM;\n if (!fs.existsSync(p)) {\n console.log('NO_LICENSE');\n process.exit(2);\n }\n const f = readLicenseFile(p);\n const v = verifyLicenseKey(f.key, pub);\n if (v.ok) {\n console.log(`OK: ${f.payload.plan} exp=${f.payload.exp}`);\n process.exit(0);\n }\n console.log(`INVALID: ${v.reason || 'unknown'}`);\n process.exit(3);\n });\n\n// vNext: guardrail mode (pre-deploy warning)\nprogram\n .command('guard')\n .description('Pre-deploy guardrail: compare baseline usage vs candidate change and print warnings (exit codes 0/2/3)')\n .option('--input <path>', 'baseline usage jsonl/csv (default: ./aiopt-output/usage.jsonl)', DEFAULT_INPUT)\n .option('--provider <provider>', 'candidate provider override')\n .option('--model <model>', 'candidate model override')\n .option('--context-mult <n>', 'multiply input_tokens by n', (v) => Number(v))\n .option('--output-mult <n>', 'multiply output_tokens by n', (v) => Number(v))\n .option('--retries-delta <n>', 'add n to retries', (v) => Number(v))\n .option('--call-mult <n>', 'multiply call volume by n (traffic spike)', (v) => Number(v))\n .action(async (opts) => {\n const rt = loadRateTable();\n const inputPath = String(opts.input);\n if (!fs.existsSync(inputPath)) {\n console.error(`FAIL: baseline not found: ${inputPath}`);\n process.exit(3);\n }\n const events = isCsvPath(inputPath) ? readCsv(inputPath) : readJsonl(inputPath);\n\n const { runGuard } = await import('./guard');\n const r = runGuard(rt, {\n baselineEvents: events,\n candidate: {\n provider: opts.provider,\n model: opts.model,\n contextMultiplier: opts.contextMult,\n outputMultiplier: opts.outputMult,\n retriesDelta: opts.retriesDelta,\n callMultiplier: opts.callMult\n }\n });\n\n console.log(r.message);\n\n // Persist last guard output for local dashboard / CI attachments\n try {\n const outDir = path.resolve(DEFAULT_OUTPUT_DIR);\n fs.mkdirSync(outDir, { recursive: true });\n fs.writeFileSync(path.join(outDir, 'guard-last.txt'), r.message);\n fs.writeFileSync(path.join(outDir, 'guard-last.json'), JSON.stringify({ ts: new Date().toISOString(), exitCode: r.exitCode }, null, 2));\n } catch {\n // ignore\n }\n\n process.exit(r.exitCode);\n });\n\n// Local-only dashboard (no auth; binds to 127.0.0.1)\nprogram\n .command('dashboard')\n .description('Local dashboard (localhost only): view last guard + last scan outputs')\n .option('--port <n>', 'port (default: 3010)', (v) => Number(v), 3010)\n .action(async (opts) => {\n const { startDashboard } = await import('./dashboard');\n await startDashboard(process.cwd(), { port: Number(opts.port || 3010) });\n });\n\nprogram.parse(process.argv);\n","import fs from 'fs';\nimport path from 'path';\nimport { parse as parseCsv } from 'csv-parse/sync';\nimport { UsageEvent } from './types';\n\nexport function ensureDir(p: string) {\n fs.mkdirSync(p, { recursive: true });\n}\n\nexport function readJsonl(filePath: string): UsageEvent[] {\n const raw = fs.readFileSync(filePath, 'utf8');\n const lines = raw.split(/\\r?\\n/).filter(l => l.trim().length > 0);\n const out: UsageEvent[] = [];\n for (const line of lines) {\n const obj = JSON.parse(line);\n out.push(normalizeEvent(obj));\n }\n return out;\n}\n\nexport function readCsv(filePath: string): UsageEvent[] {\n const raw = fs.readFileSync(filePath, 'utf8');\n const records = parseCsv(raw, { columns: true, skip_empty_lines: true, trim: true });\n return records.map((r: any) => normalizeEvent(r));\n}\n\nfunction toNum(x: any, def = 0): number {\n const n = Number(x);\n return Number.isFinite(n) ? n : def;\n}\n\nfunction normalizeEvent(x: any): UsageEvent {\n // Supports two schemas:\n // 1) scan input schema: input_tokens/output_tokens/feature_tag/retries\n // 2) wrapper usage schema: prompt_tokens/completion_tokens/endpoint/attempt/trace_id/cost_usd\n\n const inputTokens = x.input_tokens ?? x.prompt_tokens;\n const outputTokens = x.output_tokens ?? x.completion_tokens;\n\n // feature_tag fallback: feature_tag -> meta.feature_tag -> endpoint\n const featureTag = x.feature_tag ?? x?.meta?.feature_tag ?? x.endpoint ?? '';\n\n // retries fallback: retries -> max(attempt-1,0)\n const retries = x.retries ?? (x.attempt !== undefined ? Math.max(0, toNum(x.attempt) - 1) : 0);\n\n // billed_cost fallback: billed_cost -> cost_usd\n const billed = x.billed_cost ?? x.cost_usd;\n\n return {\n ts: String(x.ts ?? ''),\n provider: String(x.provider ?? '').toLowerCase(),\n model: String(x.model ?? ''),\n input_tokens: toNum(inputTokens),\n output_tokens: toNum(outputTokens),\n feature_tag: String(featureTag ?? ''),\n retries: toNum(retries),\n status: String(x.status ?? ''),\n billed_cost: billed === undefined || billed === '' ? undefined : toNum(billed),\n\n trace_id: x.trace_id ? String(x.trace_id) : undefined,\n request_id: x.request_id ? String(x.request_id) : undefined,\n attempt: x.attempt === undefined ? undefined : toNum(x.attempt),\n endpoint: x.endpoint ? String(x.endpoint) : undefined\n };\n}\n\nexport function isCsvPath(p: string) {\n return path.extname(p).toLowerCase() === '.csv';\n}\n"],"mappings":"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","names":["cost","path","fs","import_fs","import_path","round2","fs","path","import_fs","import_path","exports","module","ensureDir","fs","path","import_fs","import_path","fs","path","import_fs","import_path","crypto","path","fs","import_fs","import_path","round2","conf","msg","path","fs","http","import_fs","import_path","import_fs","import_path","fs","parseCsv","path","path","fs","buildTopFixes","runInstall","runDoctor","DEFAULT_PUBLIC_KEY_PEM","defaultLicensePath","verifyLicenseKey","writeLicenseFile","readLicenseFile","runGuard","startDashboard"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiopt",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Serverless local CLI MVP for AI API cost analysis & cost-policy generation",
|
|
5
5
|
"bin": {
|
|
6
6
|
"aiopt": "dist/cli.js"
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"build": "tsup",
|
|
18
18
|
"dev": "node --enable-source-maps dist/cli.js",
|
|
19
19
|
"prepack": "npm run build",
|
|
20
|
-
"test:npx": "npm pack --silent && node -e \"const fs=require('fs');const p=fs.readdirSync('.').find(f=>/^aiopt-.*\\.tgz$/.test(f)); if(!p) throw new Error('tgz not found'); console.log('tgz',p);\" && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1)
|
|
20
|
+
"test:npx": "npm pack --silent && node -e \"const fs=require('fs');const p=fs.readdirSync('.').find(f=>/^aiopt-.*\\.tgz$/.test(f)); if(!p) throw new Error('tgz not found'); console.log('tgz',p);\" && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) install --force && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) doctor && npx --yes ./$(ls -1 aiopt-*.tgz | tail -n 1) scan && test -f ./aiopt-output/report.md && echo OK",
|
|
21
|
+
"test:guard": "npm run build --silent && node scripts/test-guard.js",
|
|
22
|
+
"test:license": "npm run build --silent && node scripts/test-license.js"
|
|
21
23
|
},
|
|
22
24
|
"dependencies": {
|
|
23
25
|
"commander": "^14.0.0",
|