@agentguard-run/spend 0.15.5 → 0.15.7
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 +11 -0
- package/dist/adapters/ndjson-store.d.ts +45 -0
- package/dist/adapters/ndjson-store.d.ts.map +1 -0
- package/dist/adapters/ndjson-store.js +132 -0
- package/dist/adapters/ndjson-store.js.map +1 -0
- package/dist/cli/demo.d.ts.map +1 -1
- package/dist/cli/demo.js +73 -0
- package/dist/cli/demo.js.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +5 -0
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/serve.d.ts +1 -1
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +155 -34
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/usage.d.ts +13 -0
- package/dist/cli/usage.d.ts.map +1 -0
- package/dist/cli/usage.js +122 -0
- package/dist/cli/usage.js.map +1 -0
- package/dist/cli/wizard.d.ts +12 -1
- package/dist/cli/wizard.d.ts.map +1 -1
- package/dist/cli/wizard.js +35 -5
- package/dist/cli/wizard.js.map +1 -1
- package/dist/free-tier/usage.d.ts +104 -0
- package/dist/free-tier/usage.d.ts.map +1 -0
- package/dist/free-tier/usage.js +250 -0
- package/dist/free-tier/usage.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/wizard.ts +40 -5
package/src/cli/wizard.ts
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as readline from 'readline';
|
|
4
|
+
import { randomBytes } from 'crypto';
|
|
5
|
+
import * as ed from '@noble/ed25519';
|
|
4
6
|
import { AGENTGUARD_SPEND_VERSION } from '../index';
|
|
5
7
|
import { TASK_TEMPLATES, getTaskTemplate, listTaskTemplates, type TaskTemplate } from '../templates';
|
|
6
8
|
import { agentguardHome, banner, cyanBold, green, statusBar } from './colors';
|
|
7
9
|
|
|
10
|
+
const SIGNING_PRIVATE_KEY_FILE = 'signing-private.key';
|
|
11
|
+
const SIGNING_PUBLIC_KEY_FILE = 'signing-public.key';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Read the existing local signing keypair or generate one. The private seed is
|
|
15
|
+
* stored mode 0600 under ~/.agentguard/ and never leaves the machine. Returns
|
|
16
|
+
* both keys as lowercase hex.
|
|
17
|
+
*/
|
|
18
|
+
export async function ensureSigningKeys(home = agentguardHome()): Promise<{ privateKeyHex: string; publicKeyHex: string }> {
|
|
19
|
+
fs.mkdirSync(home, { recursive: true });
|
|
20
|
+
const privatePath = path.join(home, SIGNING_PRIVATE_KEY_FILE);
|
|
21
|
+
const publicPath = path.join(home, SIGNING_PUBLIC_KEY_FILE);
|
|
22
|
+
if (fs.existsSync(privatePath) && fs.existsSync(publicPath)) {
|
|
23
|
+
return {
|
|
24
|
+
privateKeyHex: fs.readFileSync(privatePath, 'utf-8').trim(),
|
|
25
|
+
publicKeyHex: fs.readFileSync(publicPath, 'utf-8').trim(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const privateSeed = new Uint8Array(randomBytes(32));
|
|
29
|
+
const publicKey = await ed.getPublicKeyAsync(privateSeed);
|
|
30
|
+
const privateKeyHex = Buffer.from(privateSeed).toString('hex');
|
|
31
|
+
const publicKeyHex = Buffer.from(publicKey).toString('hex');
|
|
32
|
+
fs.writeFileSync(privatePath, privateKeyHex, { mode: 0o600 });
|
|
33
|
+
fs.writeFileSync(publicPath, publicKeyHex);
|
|
34
|
+
return { privateKeyHex, publicKeyHex };
|
|
35
|
+
}
|
|
36
|
+
|
|
8
37
|
const BUILD_TYPES = ['chatbot', 'agent', 'batch job', 'API endpoint'];
|
|
9
38
|
const CAPABILITY_TIERS = [
|
|
10
39
|
['read_only', 'Read records and produce findings'],
|
|
@@ -58,7 +87,8 @@ export async function runWizard(argv: string[]): Promise<number> {
|
|
|
58
87
|
const templateSlug = valueAfter(argv, '--template') ?? 'risk-review';
|
|
59
88
|
const useDefaults = argv.includes('--defaults') || !process.stdin.isTTY;
|
|
60
89
|
const answers = useDefaults ? defaultAnswers(templateSlug) : await promptAnswers(templateSlug);
|
|
61
|
-
const
|
|
90
|
+
const signingKeys = await ensureSigningKeys();
|
|
91
|
+
const outputs = writeWizardOutputs(answers, agentguardHome(), signingKeys);
|
|
62
92
|
console.log('');
|
|
63
93
|
console.log(' ' + banner(AGENTGUARD_SPEND_VERSION));
|
|
64
94
|
console.log('');
|
|
@@ -92,13 +122,17 @@ export function defaultAnswers(templateSlug = 'risk-review'): WizardAnswers {
|
|
|
92
122
|
};
|
|
93
123
|
}
|
|
94
124
|
|
|
95
|
-
export function writeWizardOutputs(
|
|
125
|
+
export function writeWizardOutputs(
|
|
126
|
+
answers: WizardAnswers,
|
|
127
|
+
home = agentguardHome(),
|
|
128
|
+
signingKeys?: { publicKeyHex: string },
|
|
129
|
+
): { policyPath: string; quickstartTsPath: string; quickstartPyPath: string; snippet: string } {
|
|
96
130
|
fs.mkdirSync(home, { recursive: true });
|
|
97
131
|
const policyPath = path.join(home, 'policy.yaml');
|
|
98
132
|
const quickstartTsPath = path.join(home, 'quickstart.ts');
|
|
99
133
|
const quickstartPyPath = path.join(home, 'quickstart.py');
|
|
100
134
|
const policy = policyYaml(answers);
|
|
101
|
-
const ts = quickstartTs(answers);
|
|
135
|
+
const ts = quickstartTs(answers, signingKeys?.publicKeyHex);
|
|
102
136
|
const py = quickstartPy(answers);
|
|
103
137
|
fs.writeFileSync(policyPath, policy);
|
|
104
138
|
fs.writeFileSync(quickstartTsPath, ts);
|
|
@@ -137,8 +171,9 @@ function policyYaml(answers: WizardAnswers): string {
|
|
|
137
171
|
return `# AgentGuard Spend policy generated by agentguard wizard\nid: ${id}\nname: ${answers.template} policy\nversion: 1\neffectiveFrom: "${new Date().toISOString()}"\nmode: enforce\nrequiredCapability: ${answers.capability}\nscope:\n tenantId: my-tenant\nmodels:\n allowed:\n${allowed}\n fallback: ${answers.fallbackModel}\ncaps:\n # WHY: Per-call cap bounds one agent action.\n - amountCents: ${answers.perCallCents}\n window: per_call\n action: downgrade\n downgradeTo: ${answers.fallbackModel}\n reason: "Per-call budget reached, routing to fallback model"\n # WHY: Daily cap catches runaway loops and unexpected volume.\n - amountCents: ${answers.perDayCents}\n window: per_day\n action: block\n reason: "Daily budget reached"\n # WHY: Monthly cap keeps the finance view predictable.\n - amountCents: ${answers.perMonthCents}\n window: per_month\n action: block\n reason: "Monthly budget reached"\nsystemInstructions: |\n${indent(answers.systemInstructions, 2)}\n`;
|
|
138
172
|
}
|
|
139
173
|
|
|
140
|
-
function quickstartTs(answers: WizardAnswers): string {
|
|
141
|
-
|
|
174
|
+
function quickstartTs(answers: WizardAnswers, publicKeyHex?: string): string {
|
|
175
|
+
const pubHex = publicKeyHex ?? '';
|
|
176
|
+
return `import OpenAI from 'openai';\nimport * as fs from 'fs';\nimport * as os from 'os';\nimport * as path from 'path';\nimport { withSpendGuard, NdjsonDecisionLogStore, type SpendPolicy } from '@agentguard-run/spend';\n\nconst policy: SpendPolicy = {\n id: 'agentguard-${answers.template}-v1',\n name: '${answers.template} policy',\n scope: { tenantId: 'my-tenant' },\n caps: [\n { amountCents: ${answers.perCallCents}, window: 'per_call', action: 'downgrade', downgradeTo: '${answers.fallbackModel}' },\n { amountCents: ${answers.perDayCents}, window: 'per_day', action: 'block' },\n { amountCents: ${answers.perMonthCents}, window: 'per_month', action: 'block' },\n ],\n mode: 'enforce',\n requiredCapability: '${answers.capability}',\n version: 1,\n effectiveFrom: new Date().toISOString(),\n};\n\nconst openrouter = new OpenAI({\n baseURL: 'https://openrouter.ai/api/v1',\n apiKey: process.env.OPENROUTER_API_KEY,\n});\n\n// Local Ed25519 signing key generated by \`agentguard wizard\` (never leaves this machine).\nconst home = path.join(os.homedir(), '.agentguard');\nconst signingKeys = {\n privateKey: Uint8Array.from(Buffer.from(fs.readFileSync(path.join(home, 'signing-private.key'), 'utf-8').trim(), 'hex')),\n publicKey: Uint8Array.from(Buffer.from(fs.readFileSync(path.join(home, 'signing-public.key'), 'utf-8').trim(), 'hex')),\n};\nconst publicKeyHex = '${pubHex}' || Buffer.from(signingKeys.publicKey).toString('hex');\n\nexport const guardedClient = withSpendGuard(openrouter, {\n policy,\n scope: { tenantId: 'my-tenant', agentId: '${answers.template}' },\n capabilityClaim: '${answers.capability}',\n config: {\n // Persists signed receipts to ~/.agentguard/my-tenant/decisions.ndjson so\n // \`agentguard serve\` shows governed spend, $ saved, and a verifiable trail.\n logStore: new NdjsonDecisionLogStore('my-tenant', { publicKeyHex }),\n signingKeys,\n },\n});\n`;
|
|
142
177
|
}
|
|
143
178
|
|
|
144
179
|
function quickstartPy(answers: WizardAnswers): string {
|