ai-spend-agent 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +60 -12
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -124,7 +124,10 @@ async function quickstartCommand(args) {
|
|
|
124
124
|
mode,
|
|
125
125
|
nextSteps,
|
|
126
126
|
deadContext,
|
|
127
|
-
detectedPlans
|
|
127
|
+
detectedPlans,
|
|
128
|
+
// An explicit --group-by is a drill-down question: answer with just the
|
|
129
|
+
// table + window instead of repeating the whole readout.
|
|
130
|
+
view: args.groupBy ? "breakdown" : "full"
|
|
128
131
|
});
|
|
129
132
|
const header = [` ${dataModeBanner(mode)}`, ...warnings.map((warning) => ` ! ${warning}`)].join("\n");
|
|
130
133
|
return ok(`${header}\n${summaryText}`);
|
|
@@ -172,7 +175,10 @@ async function loadInstantReadData(args) {
|
|
|
172
175
|
codexSessionsDir: process.env.AI_SPEND_CODEX_LOGS_DIR
|
|
173
176
|
}).catch(() => undefined);
|
|
174
177
|
if (logs && logs.records.length > 0) {
|
|
175
|
-
|
|
178
|
+
// Persisted local_logs state (written by report/apply-artifact) is the
|
|
179
|
+
// same data source we just re-read — superseding it silently is correct,
|
|
180
|
+
// not worth a scary "sample/legacy" warning.
|
|
181
|
+
if (persisted && persisted.records.length > 0 && persisted.mode !== "connected_provider" && persisted.mode !== "local_logs") {
|
|
176
182
|
warnings.push("Ignored persisted sample/legacy state in .ai-spend-agent/spend.json — showing your real local agent logs. Run `ai-spend-agent reset` to clear it, or pass --ignore-state.");
|
|
177
183
|
}
|
|
178
184
|
return { records: logs.records, mode: "local-logs", warnings };
|
|
@@ -828,14 +834,19 @@ async function reportCommand(args) {
|
|
|
828
834
|
`verification plan: ${artifactPaths.verificationPlan}`,
|
|
829
835
|
`demo package: ${artifactPaths.demoPackage}`,
|
|
830
836
|
`total spend: $${reportInput.summary.totalUsd.toFixed(2)}`,
|
|
831
|
-
"privacy: local files only; no cloud upload performed"
|
|
837
|
+
"privacy: local files only; no cloud upload performed",
|
|
838
|
+
"",
|
|
839
|
+
"next:",
|
|
840
|
+
` open ${htmlPath} view the full report in your browser`,
|
|
841
|
+
` less ${markdownPath} read it in the terminal`,
|
|
842
|
+
" npx ai-spend-agent apply-artifact print the paste-ready coding-agent prompt"
|
|
832
843
|
].join("\n"));
|
|
833
844
|
}
|
|
834
845
|
catch (error) {
|
|
835
846
|
return {
|
|
836
847
|
exitCode: 1,
|
|
837
848
|
stdout: "",
|
|
838
|
-
stderr: `
|
|
849
|
+
stderr: `Couldn't build a report: ${error instanceof Error ? error.message : String(error)}`
|
|
839
850
|
};
|
|
840
851
|
}
|
|
841
852
|
}
|
|
@@ -882,30 +893,56 @@ async function applyArtifactCommand(args) {
|
|
|
882
893
|
try {
|
|
883
894
|
const reportInput = await buildReportInput(stateDir, rootPath);
|
|
884
895
|
const artifactPaths = await writeApplyArtifacts(stateDir, reportInput);
|
|
896
|
+
// The prompt IS the product of this command — print it so a terminal
|
|
897
|
+
// user can copy it right here instead of hunting for a file path.
|
|
898
|
+
const codingPrompt = await readFile(artifactPaths.codingPrompt, "utf8");
|
|
885
899
|
return ok([
|
|
886
900
|
"AI Spend Analyst Agent apply-artifact",
|
|
887
901
|
`path: ${rootPath}`,
|
|
888
|
-
`coding prompt: ${artifactPaths.codingPrompt}`,
|
|
889
902
|
`action plan: ${artifactPaths.actionPlan}`,
|
|
890
903
|
`policy/config draft: ${artifactPaths.policyConfigDraft}`,
|
|
891
904
|
`verification plan: ${artifactPaths.verificationPlan}`,
|
|
892
905
|
`demo package: ${artifactPaths.demoPackage}`,
|
|
893
|
-
"safety: generated artifacts only; no external systems changed"
|
|
906
|
+
"safety: generated artifacts only; no external systems changed",
|
|
907
|
+
"",
|
|
908
|
+
`──── copy everything below into Claude Code / Codex (also saved at ${artifactPaths.codingPrompt}) ────`,
|
|
909
|
+
"",
|
|
910
|
+
codingPrompt.trimEnd()
|
|
894
911
|
].join("\n"));
|
|
895
912
|
}
|
|
896
913
|
catch (error) {
|
|
897
914
|
return {
|
|
898
915
|
exitCode: 1,
|
|
899
916
|
stdout: "",
|
|
900
|
-
stderr: `
|
|
917
|
+
stderr: `Couldn't build apply artifacts: ${error instanceof Error ? error.message : String(error)}`
|
|
901
918
|
};
|
|
902
919
|
}
|
|
903
920
|
}
|
|
904
921
|
async function buildReportInput(stateDir, rootPath) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
922
|
+
let spendState = await readOptionalJson(join(stateDir, "spend.json"), undefined);
|
|
923
|
+
let mappings = await readOptionalJson(join(stateDir, "mappings.json"), undefined);
|
|
924
|
+
// The quickstart intentionally writes nothing — so `report`/`apply-artifact`
|
|
925
|
+
// right after a first run must fall back to the SAME live local-log read
|
|
926
|
+
// (never sample data), then persist it so subsequent runs are consistent.
|
|
927
|
+
if (!spendState?.summary || !spendState.records || spendState.records.length === 0) {
|
|
928
|
+
const logs = await loadLocalAgentUsage({
|
|
929
|
+
claudeProjectsDir: process.env.AI_SPEND_CLAUDE_LOGS_DIR,
|
|
930
|
+
codexSessionsDir: process.env.AI_SPEND_CODEX_LOGS_DIR
|
|
931
|
+
}).catch(() => undefined);
|
|
932
|
+
if (!logs || logs.records.length === 0) {
|
|
933
|
+
throw new Error("no persisted spend state and no local Claude Code/Codex logs found. " +
|
|
934
|
+
"Run `npx ai-spend-agent` first (or `npx ai-spend-agent scan --sample --path <dir>` for a demo-data report).");
|
|
935
|
+
}
|
|
936
|
+
const records = logs.records;
|
|
937
|
+
const summary = analyzeSpend(records);
|
|
938
|
+
const liveMappings = attributeUsageRecords(records);
|
|
939
|
+
await mkdir(stateDir, { recursive: true });
|
|
940
|
+
await writeLocalSpendState(stateDir, records, summary, liveMappings, "local_logs");
|
|
941
|
+
spendState = { summary, records, mode: "local_logs" };
|
|
942
|
+
mappings = liveMappings;
|
|
943
|
+
}
|
|
944
|
+
const [discovery, sourceRegistry, missingSourcePrompts, confirmedMappings, providerRecordsState] = await Promise.all([
|
|
945
|
+
readOptionalJson(join(stateDir, "discovery.json"), emptyDiscovery(rootPath)),
|
|
909
946
|
readSourceRegistry(stateDir, rootPath),
|
|
910
947
|
readOptionalJson(join(stateDir, "missing-sources.json"), []),
|
|
911
948
|
readConfirmedMappings(stateDir),
|
|
@@ -918,7 +955,7 @@ async function buildReportInput(stateDir, rootPath) {
|
|
|
918
955
|
allRecords: spendState.records ?? [],
|
|
919
956
|
dataMode: spendState.mode,
|
|
920
957
|
discovery,
|
|
921
|
-
mappings,
|
|
958
|
+
mappings: mappings ?? [],
|
|
922
959
|
sourceRegistry,
|
|
923
960
|
missingSourcePrompts,
|
|
924
961
|
confirmedMappings,
|
|
@@ -926,6 +963,17 @@ async function buildReportInput(stateDir, rootPath) {
|
|
|
926
963
|
providerQa: providerRecordsState.qa ? [providerRecordsState.qa] : []
|
|
927
964
|
};
|
|
928
965
|
}
|
|
966
|
+
function emptyDiscovery(rootPath) {
|
|
967
|
+
return {
|
|
968
|
+
rootPath,
|
|
969
|
+
scannedFiles: 0,
|
|
970
|
+
skippedDirectories: [],
|
|
971
|
+
unreadablePaths: [],
|
|
972
|
+
signals: [],
|
|
973
|
+
secretsDetected: [],
|
|
974
|
+
redactedEvidence: []
|
|
975
|
+
};
|
|
976
|
+
}
|
|
929
977
|
async function writeApplyArtifacts(stateDir, reportInput) {
|
|
930
978
|
const paths = {
|
|
931
979
|
codingPrompt: join(stateDir, "ai-spend-coding-agent-prompt.md"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-spend-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Your AI spend in one view, in 90 seconds — local-first CLI for OpenAI, Anthropic, Cursor, Copilot + Claude Code/Codex session logs, with a ranked savings cut list, and flags the tools your agent loads but never uses (dead context).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"prepack": "npm run build"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@agent-finops/core": "0.2.
|
|
53
|
-
"@agent-finops/report": "0.2.
|
|
52
|
+
"@agent-finops/core": "0.2.2",
|
|
53
|
+
"@agent-finops/report": "0.2.2",
|
|
54
54
|
"yocto-spinner": "^1.2.0"
|
|
55
55
|
}
|
|
56
56
|
}
|