import * as fs from 'fs'; import * as path from 'path'; import { AGENTGUARD_SPEND_VERSION } from '../index'; import { agentguardHome, banner, dim, green, statusBar } from './colors'; export interface AgentGuardTip { id: string; surface: string; severity: 'info' | 'warning'; text: string; learn_more_url: string; dismissible: boolean; } interface TipsState { seen?: string[]; } export const TIPS: AgentGuardTip[] = [ { id: 'cheap-read-only', surface: 'models', severity: 'info', text: "Use 'openai/gpt-4o-mini' for read-only tasks. It is far cheaper than gpt-5 with the same audit guarantees.", learn_more_url: 'https://agentguard.run/docs/models', dismissible: true, }, { id: 'openrouter-cfo', surface: 'auth', severity: 'info', text: 'One OpenRouter key gives access to hundreds of models across many providers. Your CFO sees one invoice while AgentGuard enforces who uses what.', learn_more_url: 'https://agentguard.run/docs/openrouter', dismissible: true, }, { id: 'public-verify', surface: 'verify', severity: 'info', text: 'Share https://agentguard.run/verify with your auditor. Paste any receipt and get browser-based cryptographic verification with no install.', learn_more_url: 'https://agentguard.run/verify', dismissible: true, }, { id: 'weekly-pricing', surface: 'models', severity: 'info', text: "Run 'agentguard models --sync-pricing' weekly to keep cost math current with OpenRouter live prices.", learn_more_url: 'https://agentguard.run/docs/pricing', dismissible: true, }, { id: 'payment-attestation', surface: 'wizard', severity: 'warning', text: "Capability tier 'payment_execute' should require a verified attestation. Do not accept caller-supplied claims for real money movement.", learn_more_url: 'https://agentguard.run/docs/capabilities', dismissible: true, }, { id: 'shadow-rollout', surface: 'policy', severity: 'info', text: "Use mode 'shadow' for the first day of a rollout. You get signed decisions before enforcement changes traffic.", learn_more_url: 'https://agentguard.run/docs/policies', dismissible: true, }, { id: 'verify-chain', surface: 'verify', severity: 'info', text: "Run 'agentguard verify ' before sharing an audit packet. Whole-chain verification catches missing entries.", learn_more_url: 'https://agentguard.run/docs/verify', dismissible: true, }, { id: 'local-overrides', surface: 'models', severity: 'info', text: 'Negotiated rates belong in ~/.agentguard/cost-overrides.json. Keep pricing math local with the rest of the policy state.', learn_more_url: 'https://agentguard.run/docs/pricing', dismissible: true, }, { id: 'bedrock-routing', surface: 'bindings', severity: 'info', text: 'Use the native Bedrock binding when AWS procurement requires direct provider calls. AgentGuard still signs every local decision.', learn_more_url: 'https://agentguard.run/docs/bedrock', dismissible: true, }, ]; export function tipsStatePath(): string { return path.join(agentguardHome(), 'tips-seen.json'); } export function readTipsState(filePath: string = tipsStatePath()): TipsState { try { const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8')) as TipsState; return Array.isArray(parsed.seen) ? parsed : { seen: [] }; } catch { return { seen: [] }; } } export function writeTipsState(state: TipsState, filePath: string = tipsStatePath()): void { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify({ seen: [...new Set(state.seen ?? [])] }, null, 2) + '\n', { mode: 0o600 }); try { fs.chmodSync(filePath, 0o600); } catch { return; } } export function unseenTips(state: TipsState = readTipsState()): AgentGuardTip[] { const seen = new Set(state.seen ?? []); return TIPS.filter((tip) => !seen.has(tip.id)); } export function markTipsSeen(ids: string[], filePath: string = tipsStatePath()): void { const state = readTipsState(filePath); writeTipsState({ seen: [...new Set([...(state.seen ?? []), ...ids])] }, filePath); } export function maybeShowStartupTips(force = false): void { const state = readTipsState(); const startupIds = ['startup-wizard', 'startup-verify']; const seen = new Set(state.seen ?? []); if (!force && startupIds.every((id) => seen.has(id))) return; console.log(''); console.log(' ' + green("Tip: try 'agentguard wizard' to set up in 90 seconds.")); console.log(' ' + green('Tip: share https://agentguard.run/verify with your auditor.')); markTipsSeen(startupIds); } export async function runTips(argv: string[]): Promise { if (argv.includes('--help') || argv.includes('-h')) { console.log('agentguard tips [--reset] [--json]'); return 0; } if (argv.includes('--reset')) { writeTipsState({ seen: [] }); console.log('AgentGuard tips reset.'); return 0; } if (argv.includes('--json')) { console.log(JSON.stringify(TIPS, null, 2)); return 0; } console.log(''); console.log(' ' + banner(AGENTGUARD_SPEND_VERSION)); console.log(''); for (const tip of TIPS) { const label = tip.severity === 'warning' ? 'warning' : 'tip'; console.log(` ${green(label)} ${tip.text}`); console.log(` ${dim(tip.learn_more_url)}`); } console.log(''); console.log(' ' + statusBar()); console.log(''); markTipsSeen(TIPS.filter((tip) => tip.dismissible).map((tip) => tip.id)); return 0; }