Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions skills/agentguard/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Supported CLI commands and options:
|---|---|---|
| `agentguard init` | `--level <level>`, `--agent <agent>`, `--cloud <url>`, `--force` | Creates local config and optionally installs agent templates |
| `agentguard connect` | `--key <key>`, `--api-key <key>`, `--url <url>`, `--cloud <url>` | Prefer `AGENTGUARD_API_KEY` over passing secrets in flags |
| `agentguard disconnect` | none | Removes local Cloud API key, connection timestamp, pending event spool, and cached Cloud policy; keeps Cloud URL, audit log, and installed hooks/templates |
| `agentguard status` | none | Shows local config, Cloud URL/API key status, policy cache, audit path |
| `agentguard policy pull` | `--json` | Pulls Cloud effective runtime policy into the local cache |
| `agentguard doctor` | none | Checks local setup and Cloud reachability when connected |
Expand Down
11 changes: 11 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Command } from 'commander';
import { AgentGuardCloudClient } from './cloud/client.js';
import {
connectCloud,
disconnectCloud,
ensureConfig,
getAgentGuardPaths,
loadConfig,
Expand Down Expand Up @@ -93,6 +94,16 @@ async function main() {
}
});

program
.command('disconnect')
.description('Disconnect local AgentGuard from AgentGuard Cloud')
.action(() => {
const config = disconnectCloud();
console.log('Disconnected from AgentGuard Cloud.');
console.log('Removed local Cloud API key, connection timestamp, pending event spool, and cached Cloud policy.');
console.log(`Local protection remains active using the built-in policy. Audit log: ${config.auditPath}`);
});

program
.command('status')
.description('Show local and Cloud connection status')
Expand Down
13 changes: 12 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { homedir } from 'node:os';

Expand Down Expand Up @@ -104,6 +104,17 @@ export function connectCloud(options: { apiKey: string; cloudUrl?: string }): Ag
return next;
}

export function disconnectCloud(): AgentGuardConfig {
const current = ensureConfig();
const next: AgentGuardConfig = { ...current };
delete next.apiKey;
delete next.connectedAt;
rmSync(current.eventSpoolPath, { force: true });
rmSync(current.policyCachePath, { force: true });
saveConfig(next);
return next;
}

export function maskApiKey(apiKey?: string): string {
if (!apiKey) return 'not configured';
if (apiKey.length <= 12) return `${apiKey.slice(0, 4)}…`;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export {
loadConfig as loadAgentGuardConfig,
saveConfig as saveAgentGuardConfig,
connectCloud,
disconnectCloud,
getAgentGuardPaths,
type AgentGuardConfig,
} from './config.js';
Expand Down
34 changes: 32 additions & 2 deletions src/tests/runtime-cloud.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { existsSync, mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { evaluateLocalAction } from '../runtime/evaluator.js';
import { getDefaultEffectiveRuntimePolicy } from '../runtime/policy.js';
import { redactText } from '../runtime/redaction.js';
import { flushEventSpool, spoolEvent } from '../runtime/audit.js';
import { protectAction } from '../runtime/protect.js';
import { connectCloud, getAgentGuardPaths } from '../config.js';
import { connectCloud, disconnectCloud, getAgentGuardPaths } from '../config.js';
import { AgentGuardCloudClient } from '../cloud/client.js';
import type { AgentGuardConfig } from '../config.js';
import type { RuntimeAuditEvent } from '../runtime/types.js';
Expand Down Expand Up @@ -60,6 +60,36 @@ describe('Runtime Cloud bridge', () => {
}
});

it('disconnects Cloud without deleting the local audit log', () => {
const previousHome = process.env.AGENTGUARD_HOME;
process.env.AGENTGUARD_HOME = mkdtempSync(join(tmpdir(), 'agentguard-disconnect-'));
try {
const config = connectCloud({
apiKey: 'ag_live_test_key_123456',
cloudUrl: 'https://agentguard.example',
});
writeFileSync(config.eventSpoolPath, `${JSON.stringify(sampleEvent())}\n`);
writeFileSync(config.policyCachePath, JSON.stringify(getDefaultEffectiveRuntimePolicy()));
writeFileSync(config.auditPath, `${JSON.stringify(sampleEvent())}\n`);

const disconnected = disconnectCloud();
const saved = JSON.parse(readFileSync(getAgentGuardPaths().configPath, 'utf8')) as AgentGuardConfig;

assert.equal(disconnected.apiKey, undefined);
assert.equal(disconnected.connectedAt, undefined);
assert.equal(disconnected.cloudUrl, 'https://agentguard.example');
assert.equal(saved.apiKey, undefined);
assert.equal(saved.connectedAt, undefined);
assert.equal(saved.cloudUrl, 'https://agentguard.example');
assert.equal(existsSync(config.eventSpoolPath), false);
assert.equal(existsSync(config.policyCachePath), false);
assert.equal(existsSync(config.auditPath), true);
} finally {
if (previousHome === undefined) delete process.env.AGENTGUARD_HOME;
else process.env.AGENTGUARD_HOME = previousHome;
}
});

it('evaluates local action with cached Cloud policy shape', async () => {
const policy = getDefaultEffectiveRuntimePolicy();
policy.policyVersion = 'runtime-test';
Expand Down
Loading