@agentguard-run/spend 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.es-419.md +28 -0
  3. package/README.md +28 -0
  4. package/README.pt-BR.md +28 -0
  5. package/dist/advisor/anomaly.d.ts +26 -0
  6. package/dist/advisor/anomaly.d.ts.map +1 -0
  7. package/dist/advisor/anomaly.js +119 -0
  8. package/dist/advisor/anomaly.js.map +1 -0
  9. package/dist/advisor/conversation.d.ts +75 -0
  10. package/dist/advisor/conversation.d.ts.map +1 -0
  11. package/dist/advisor/conversation.js +264 -0
  12. package/dist/advisor/conversation.js.map +1 -0
  13. package/dist/advisor/forecast.d.ts +19 -0
  14. package/dist/advisor/forecast.d.ts.map +1 -0
  15. package/dist/advisor/forecast.js +57 -0
  16. package/dist/advisor/forecast.js.map +1 -0
  17. package/dist/advisor/llm-client.d.ts +41 -0
  18. package/dist/advisor/llm-client.d.ts.map +1 -0
  19. package/dist/advisor/llm-client.js +248 -0
  20. package/dist/advisor/llm-client.js.map +1 -0
  21. package/dist/advisor/output.d.ts +41 -0
  22. package/dist/advisor/output.d.ts.map +1 -0
  23. package/dist/advisor/output.js +202 -0
  24. package/dist/advisor/output.js.map +1 -0
  25. package/dist/advisor/posture.d.ts +26 -0
  26. package/dist/advisor/posture.d.ts.map +1 -0
  27. package/dist/advisor/posture.js +99 -0
  28. package/dist/advisor/posture.js.map +1 -0
  29. package/dist/advisor/system-prompt.d.ts +20 -0
  30. package/dist/advisor/system-prompt.d.ts.map +1 -0
  31. package/dist/advisor/system-prompt.js +190 -0
  32. package/dist/advisor/system-prompt.js.map +1 -0
  33. package/dist/cli/advisor.d.ts +5 -0
  34. package/dist/cli/advisor.d.ts.map +1 -0
  35. package/dist/cli/advisor.js +270 -0
  36. package/dist/cli/advisor.js.map +1 -0
  37. package/dist/cli/main.d.ts.map +1 -1
  38. package/dist/cli/main.js +6 -0
  39. package/dist/cli/main.js.map +1 -1
  40. package/dist/index.d.ts +4 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +15 -2
  43. package/dist/index.js.map +1 -1
  44. package/dist/telemetry.js +1 -1
  45. package/package.json +9 -2
  46. package/src/advisor/anomaly.ts +98 -0
  47. package/src/advisor/conversation.ts +289 -0
  48. package/src/advisor/forecast.ts +64 -0
  49. package/src/advisor/llm-client.ts +247 -0
  50. package/src/advisor/output.ts +201 -0
  51. package/src/advisor/posture.ts +111 -0
  52. package/src/advisor/system-prompt.ts +195 -0
  53. package/src/cli/advisor.ts +262 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * `agentguard advisor`: local LLM-driven policy setup.
3
+ */
4
+
5
+ import * as readline from 'readline';
6
+ import { AGENTGUARD_SPEND_VERSION } from '../index';
7
+ import { AdvisorConversation, formatCents } from '../advisor/conversation';
8
+ import { normalizePosture, type GovernancePosture } from '../advisor/posture';
9
+ import { forecastMonthEnd } from '../advisor/forecast';
10
+ import { createAdvisorClient, resolveAdvisorApiKey, type AdvisorChatMessage, type AdvisorProvider } from '../advisor/llm-client';
11
+ import { readDecisionSpend, reviewAnomalies } from '../advisor/anomaly';
12
+ import { ADVISOR_SYSTEM_PROMPT } from '../advisor/system-prompt';
13
+ import { createAdvisorSessionLogger, writeAdvisorOutputs } from '../advisor/output';
14
+ import { banner, cyanBold, dim, green, yellow } from './colors';
15
+
16
+ interface AdvisorCliOptions {
17
+ provider: AdvisorProvider;
18
+ baseUrl?: string;
19
+ model?: string;
20
+ language?: 'ts' | 'py';
21
+ posture?: GovernancePosture | 'custom';
22
+ defaults: boolean;
23
+ yes: boolean;
24
+ }
25
+
26
+ const HELP = `agentguard advisor: local LLM-driven policy setup
27
+
28
+ usage:
29
+ agentguard advisor [--provider openrouter|openai|anthropic|compatible] [--base-url <url>] [--model <model>] [--posture velocity|standard|compliance|custom]
30
+ agentguard advisor review [--scope <name>]
31
+ agentguard advisor forecast [--scope <name>] [--cap-cents <cents>]
32
+
33
+ examples:
34
+ agentguard auth openrouter
35
+ agentguard advisor
36
+ agentguard advisor --posture velocity --provider mock --defaults
37
+ agentguard advisor --base-url https://api.deepseek.com/v1 --model deepseek-chat
38
+ agentguard advisor review
39
+ `;
40
+
41
+ export async function runAdvisor(argv: string[]): Promise<number> {
42
+ if (argv.includes('--help') || argv.includes('-h')) {
43
+ console.log(HELP);
44
+ return 0;
45
+ }
46
+ if (argv[0] === 'review') return runAdvisorReview(argv.slice(1));
47
+ if (argv[0] === 'forecast') return runAdvisorForecast(argv.slice(1));
48
+
49
+ const options = parseOptions(argv);
50
+ if (options.posture === 'custom') {
51
+ console.log('Solo tier required for custom governance posture. Choose velocity, standard, or compliance in this release.');
52
+ return 0;
53
+ }
54
+ const apiKey = resolveAdvisorApiKey(options.provider);
55
+ if (options.provider !== 'mock' && !apiKey) {
56
+ console.log('');
57
+ console.log(' ' + banner(AGENTGUARD_SPEND_VERSION));
58
+ console.log('');
59
+ console.log(` ${yellow('agentguard advisor needs a local provider key')}`);
60
+ console.log(' Run agentguard auth openrouter, set OPENROUTER_API_KEY, or pass --base-url with AGENTGUARD_ADVISOR_API_KEY.');
61
+ console.log(' Prompts and policy details stay in your terminal and go only to your chosen provider.');
62
+ console.log('');
63
+ return 0;
64
+ }
65
+
66
+ let cancelled = false;
67
+ const logger = createAdvisorSessionLogger();
68
+ const onSigint = () => {
69
+ cancelled = true;
70
+ logger.append('cancelled', { reason: 'sigint' });
71
+ process.stdout.write('\nadvisor cancelled. No policy files were written.\n');
72
+ };
73
+ process.once('SIGINT', onSigint);
74
+
75
+ try {
76
+ const conversation = new AdvisorConversation({ ...(options.language ? { language: options.language } : {}), ...(options.posture ? { posture: options.posture } : {}) });
77
+ const history: AdvisorChatMessage[] = [{ role: 'system', content: ADVISOR_SYSTEM_PROMPT }];
78
+ const client = createAdvisorClient({ provider: options.provider, baseUrl: options.baseUrl, model: options.model, apiKey: apiKey ?? undefined });
79
+
80
+ console.log('');
81
+ console.log(' ' + banner(AGENTGUARD_SPEND_VERSION));
82
+ console.log('');
83
+ console.log(cyanBold('AgentGuard Advisor'));
84
+ console.log(dim('Local LLM setup. No AgentGuard service receives prompts, completions, keys, or policies.'));
85
+ console.log('');
86
+
87
+ if (options.defaults || !process.stdin.isTTY) {
88
+ fillDefaults(conversation);
89
+ logger.append('answers_defaulted', { answers: conversation.snapshot() });
90
+ } else {
91
+ await runInteractiveQuestions(conversation, logger, history, client, () => cancelled);
92
+ }
93
+
94
+ if (cancelled) return 130;
95
+ if (!conversation.isComplete()) {
96
+ console.log(yellow('advisor did not collect enough answers to write policy files.'));
97
+ return 0;
98
+ }
99
+
100
+ const profile = conversation.profile(process.cwd());
101
+ if (options.language) profile.language = options.language;
102
+ if (options.posture) profile.posture = options.posture;
103
+ logger.append('profile_built', { profile });
104
+
105
+ console.log('');
106
+ await streamAdvisorSummary(client, history, profileSummaryPrompt(profile), logger, () => cancelled);
107
+ if (cancelled) return 130;
108
+
109
+ const outputs = writeAdvisorOutputs(profile, { language: profile.language, overwrite: options.yes || options.defaults });
110
+ logger.append('files_written', { policyPath: outputs.policyPath, quickstartPath: outputs.quickstartPath, sessionLogPath: logger.path });
111
+
112
+ console.log('');
113
+ console.log(outputs.savingsTable);
114
+ console.log('');
115
+ console.log(`${green('created')} ${outputs.policyPath}`);
116
+ console.log(`${green('created')} ${outputs.quickstartPath}`);
117
+ console.log(`${green('session')} ${logger.path}`);
118
+ console.log('');
119
+ console.log('Next: agentguard demo --policy ~/.agentguard/policy.yaml');
120
+ console.log('Verify receipts: https://agentguard.run/verify');
121
+ console.log('');
122
+ return 0;
123
+ } finally {
124
+ process.removeListener('SIGINT', onSigint);
125
+ }
126
+ }
127
+
128
+ async function runAdvisorReview(argv: string[]): Promise<number> {
129
+ const scope = valueAfter(argv, '--scope') ?? 'default';
130
+ const anomalies = reviewAnomalies(readDecisionSpend(scope));
131
+ console.log('');
132
+ console.log(' ' + banner(AGENTGUARD_SPEND_VERSION));
133
+ console.log('');
134
+ if (anomalies.length === 0) {
135
+ console.log('No local spend anomalies found for the last 24 hours.');
136
+ return 0;
137
+ }
138
+ for (const item of anomalies) {
139
+ console.log(`${item.scope}/${item.agentId}: ${formatCents(item.last24hCents)} in last 24h. Baseline ${formatCents(item.baselineCents)}, sigma ${formatCents(item.sigmaCents)}.`);
140
+ console.log(`Suggestion: ${item.suggestion}`);
141
+ }
142
+ return 0;
143
+ }
144
+
145
+ async function runAdvisorForecast(argv: string[]): Promise<number> {
146
+ const scope = valueAfter(argv, '--scope') ?? 'default';
147
+ const cap = valueAfter(argv, '--cap-cents');
148
+ const forecast = forecastMonthEnd(readDecisionSpend(scope), cap ? Number(cap) : null);
149
+ console.log('');
150
+ console.log(' ' + banner(AGENTGUARD_SPEND_VERSION));
151
+ console.log('');
152
+ console.log(`Days observed: ${forecast.daysObserved}`);
153
+ console.log(`Projected month-end: ${formatCents(forecast.monthEndCents)}`);
154
+ if (forecast.capCents !== null) console.log(`Cap: ${formatCents(forecast.capCents)}`);
155
+ console.log(forecast.message);
156
+ return 0;
157
+ }
158
+
159
+ async function runInteractiveQuestions(
160
+ conversation: AdvisorConversation,
161
+ logger: ReturnType<typeof createAdvisorSessionLogger>,
162
+ history: AdvisorChatMessage[],
163
+ client: ReturnType<typeof createAdvisorClient>,
164
+ isCancelled: () => boolean,
165
+ ): Promise<void> {
166
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
167
+ const ask = (prompt: string) => new Promise<string>((resolve) => rl.question(`${prompt}\n> `, resolve));
168
+ try {
169
+ while (!conversation.isComplete() && !isCancelled()) {
170
+ const question = conversation.currentQuestion();
171
+ if (!question) break;
172
+ const answer = (await ask(question.prompt)).trim();
173
+ if (answer.toLowerCase() === 'back') {
174
+ conversation.back();
175
+ logger.append('back', { to: conversation.currentQuestion()?.id });
176
+ continue;
177
+ }
178
+ if (!answer) continue;
179
+ conversation.answer(answer);
180
+ logger.append('answer', { question: question.id, answer });
181
+ history.push({ role: 'user', content: `${question.prompt}\n${answer}` });
182
+ await streamAdvisorSummary(client, history, advisorQuestionPrompt(question.id), logger, isCancelled);
183
+ conversation.next();
184
+ }
185
+ } finally {
186
+ rl.close();
187
+ }
188
+ }
189
+
190
+ async function streamAdvisorSummary(
191
+ client: ReturnType<typeof createAdvisorClient>,
192
+ history: AdvisorChatMessage[],
193
+ prompt: string,
194
+ logger: ReturnType<typeof createAdvisorSessionLogger>,
195
+ isCancelled: () => boolean,
196
+ ): Promise<void> {
197
+ history.push({ role: 'user', content: prompt });
198
+ let content = '';
199
+ process.stdout.write('\n');
200
+ try {
201
+ for await (const token of client.streamChat(history)) {
202
+ if (isCancelled()) break;
203
+ content += token;
204
+ process.stdout.write(token);
205
+ }
206
+ process.stdout.write('\n');
207
+ } catch (err) {
208
+ const message = err instanceof Error ? err.message : String(err);
209
+ content = `Advisor provider unavailable: ${message}`;
210
+ console.log(content);
211
+ }
212
+ history.push({ role: 'assistant', content });
213
+ logger.append('assistant', { content });
214
+ }
215
+
216
+ function fillDefaults(conversation: AdvisorConversation): void {
217
+ const answers: Record<string, string> = {
218
+ building: 'A customer support agent for an ecommerce business',
219
+ posture: '',
220
+ scale: 'Team of 5, around 2000 support tickets and orders per month',
221
+ tasks: 'triage refunds, draft support replies, assemble chargeback evidence',
222
+ budget: '$199 monthly budget with low per-call costs',
223
+ confirm: 'Confirmed',
224
+ };
225
+ while (conversation.currentQuestion()) {
226
+ const question = conversation.currentQuestion();
227
+ if (!question) break;
228
+ conversation.answer(answers[question.id] ?? 'Confirmed');
229
+ conversation.next();
230
+ }
231
+ }
232
+
233
+ function advisorQuestionPrompt(id: string): string {
234
+ return `Acknowledge the ${id} answer in 2 short sentences. Ask only the next unanswered setup question. Do not write files yet.`;
235
+ }
236
+
237
+ function profileSummaryPrompt(profile: ReturnType<AdvisorConversation['profile']>): string {
238
+ return `Prepare a concise final setup summary for this profile before files are written: ${JSON.stringify(profile)}. Include projected savings math in words. Do not invent model names or pricing.`;
239
+ }
240
+
241
+ function parseOptions(argv: string[]): AdvisorCliOptions {
242
+ const baseUrl = valueAfter(argv, '--base-url');
243
+ const providerFlag = valueAfter(argv, '--provider') as AdvisorProvider | undefined;
244
+ const provider = providerFlag ?? (baseUrl ? 'compatible' : 'openrouter');
245
+ const language = valueAfter(argv, '--language') as 'ts' | 'py' | undefined;
246
+ const postureValue = valueAfter(argv, '--posture');
247
+ const posture = postureValue === 'custom' ? 'custom' : normalizePosture(postureValue);
248
+ return {
249
+ provider,
250
+ baseUrl,
251
+ model: valueAfter(argv, '--model'),
252
+ language: language === 'py' ? 'py' : language === 'ts' ? 'ts' : undefined,
253
+ posture: posture ?? undefined,
254
+ defaults: argv.includes('--defaults'),
255
+ yes: argv.includes('--yes') || argv.includes('-y'),
256
+ };
257
+ }
258
+
259
+ function valueAfter(argv: string[], flag: string): string | undefined {
260
+ const index = argv.indexOf(flag);
261
+ return index >= 0 ? argv[index + 1] : undefined;
262
+ }