@behalfid/cli 0.1.0

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 (43) hide show
  1. package/dist/commands/agents.d.ts +2 -0
  2. package/dist/commands/agents.js +159 -0
  3. package/dist/commands/config.d.ts +2 -0
  4. package/dist/commands/config.js +67 -0
  5. package/dist/commands/health.d.ts +2 -0
  6. package/dist/commands/health.js +25 -0
  7. package/dist/commands/init.d.ts +2 -0
  8. package/dist/commands/init.js +71 -0
  9. package/dist/commands/login.d.ts +2 -0
  10. package/dist/commands/login.js +45 -0
  11. package/dist/commands/logout.d.ts +2 -0
  12. package/dist/commands/logout.js +33 -0
  13. package/dist/commands/logs.d.ts +2 -0
  14. package/dist/commands/logs.js +32 -0
  15. package/dist/commands/mcp.d.ts +2 -0
  16. package/dist/commands/mcp.js +150 -0
  17. package/dist/commands/passport.d.ts +2 -0
  18. package/dist/commands/passport.js +41 -0
  19. package/dist/commands/permissions.d.ts +2 -0
  20. package/dist/commands/permissions.js +74 -0
  21. package/dist/commands/run.d.ts +4 -0
  22. package/dist/commands/run.js +106 -0
  23. package/dist/commands/verify.d.ts +2 -0
  24. package/dist/commands/verify.js +37 -0
  25. package/dist/commands/whoami.d.ts +2 -0
  26. package/dist/commands/whoami.js +49 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +68 -0
  29. package/dist/lib/client.d.ts +13 -0
  30. package/dist/lib/client.js +62 -0
  31. package/dist/lib/config.d.ts +13 -0
  32. package/dist/lib/config.js +48 -0
  33. package/dist/lib/context-generator.d.ts +3 -0
  34. package/dist/lib/context-generator.js +70 -0
  35. package/dist/lib/mcp-server.d.ts +5 -0
  36. package/dist/lib/mcp-server.js +140 -0
  37. package/dist/lib/output.d.ts +10 -0
  38. package/dist/lib/output.js +48 -0
  39. package/dist/lib/passport-cache.d.ts +31 -0
  40. package/dist/lib/passport-cache.js +43 -0
  41. package/dist/lib/prompt.d.ts +3 -0
  42. package/dist/lib/prompt.js +53 -0
  43. package/package.json +33 -0
@@ -0,0 +1,74 @@
1
+ import { Command } from "commander";
2
+ import { apiRequest, resolveApiKey, resolveBaseUrl } from "../lib/client.js";
3
+ import { isJsonMode, printJson, printKv, printSuccess, runAction } from "../lib/output.js";
4
+ export function permissionsCommand() {
5
+ const cmd = new Command("permissions").description("manage agent permissions");
6
+ cmd
7
+ .command("create <agentId>")
8
+ .description("create a permission for an agent (requires agent API key)")
9
+ .requiredOption("-a, --action <action>", "action to permit (e.g. purchase, access_data, schedule)")
10
+ .option("-r, --resource <resource>", "resource or vendor (e.g. amazon.com, gmail.com)")
11
+ .option("-s, --scope <scope>", "plain-English scope description")
12
+ .option("-d, --description <desc>", "permission description")
13
+ .option("--template <template>", "template: purchase, access_data, create_content, schedule, custom")
14
+ .option("--max-amount <n>", "maximum transaction amount (for purchase permissions)")
15
+ .option("--expires <date>", "expiry date (ISO 8601, e.g. 2027-01-01)")
16
+ .option("--allowed <actions>", "comma-separated allowed actions")
17
+ .option("--blocked <actions>", "comma-separated blocked actions")
18
+ .option("--requires-approval", "require human approval before acting")
19
+ .option("-k, --api-key <key>", "agent API key (overrides config)")
20
+ .action(runAction(async (agentId, opts) => {
21
+ const apiKey = opts.apiKey ?? resolveApiKey();
22
+ if (!apiKey)
23
+ throw new Error("An agent API key is required. Set it with `behalf config set api-key <key>` or pass --api-key.");
24
+ const baseUrl = resolveBaseUrl();
25
+ const body = { agentId, action: opts.action };
26
+ if (opts.resource)
27
+ body.resource = opts.resource;
28
+ if (opts.scope)
29
+ body.scope = opts.scope;
30
+ if (opts.description)
31
+ body.description = opts.description;
32
+ if (opts.template)
33
+ body.template = opts.template;
34
+ if (opts.requiresApproval)
35
+ body.requiresApproval = true;
36
+ if (opts.allowed)
37
+ body.allowedActions = opts.allowed.split(",").map(s => s.trim()).filter(Boolean);
38
+ if (opts.blocked)
39
+ body.blockedActions = opts.blocked.split(",").map(s => s.trim()).filter(Boolean);
40
+ if (opts.maxAmount || opts.expires || opts.resource) {
41
+ const constraints = {};
42
+ if (opts.maxAmount)
43
+ constraints.maxAmount = Number(opts.maxAmount);
44
+ if (opts.expires)
45
+ constraints.expiresAt = new Date(opts.expires).toISOString();
46
+ if (opts.resource)
47
+ constraints.allowedVendors = [opts.resource];
48
+ body.constraints = constraints;
49
+ }
50
+ const data = await apiRequest("/api/permissions", {
51
+ method: "POST", body, apiKey, baseUrl,
52
+ });
53
+ if (isJsonMode())
54
+ printJson(data);
55
+ else
56
+ printKv({ permissionId: data.permissionId, status: data.status });
57
+ }));
58
+ cmd
59
+ .command("revoke <permissionId>")
60
+ .description("revoke a permission (requires agent API key)")
61
+ .option("-k, --api-key <key>", "agent API key (overrides config)")
62
+ .action(runAction(async (permissionId, opts) => {
63
+ const apiKey = opts.apiKey ?? resolveApiKey();
64
+ if (!apiKey)
65
+ throw new Error("An agent API key is required. Set it with `behalf config set api-key <key>` or pass --api-key.");
66
+ const baseUrl = resolveBaseUrl();
67
+ const data = await apiRequest(`/api/permissions/${encodeURIComponent(permissionId)}/revoke`, { method: "POST", apiKey, baseUrl });
68
+ if (isJsonMode())
69
+ printJson(data);
70
+ else
71
+ printSuccess(`Permission ${permissionId} revoked.`);
72
+ }));
73
+ return cmd;
74
+ }
@@ -0,0 +1,4 @@
1
+ import { Command } from "commander";
2
+ export declare function runCommand(): Command;
3
+ export declare function claudeCommand(): Command;
4
+ export declare function codexCommand(): Command;
@@ -0,0 +1,106 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { spawnSync } from "node:child_process";
4
+ import { Command } from "commander";
5
+ import { resolveBaseUrl } from "../lib/client.js";
6
+ import { readConfig } from "../lib/config.js";
7
+ import { generateContextMd, generateMcpJson } from "../lib/context-generator.js";
8
+ import { fetchAndCacheDetail, readCachedDetail } from "../lib/passport-cache.js";
9
+ import { runAction } from "../lib/output.js";
10
+ import { mkdirSync } from "node:fs";
11
+ const TOOLS = {
12
+ claude: {
13
+ binary: "claude",
14
+ contextFiles: ["CLAUDE.md"],
15
+ injectLine: "@.behalf/context.md",
16
+ },
17
+ codex: {
18
+ binary: "codex",
19
+ contextFiles: ["AGENTS.md"],
20
+ injectLine: "@.behalf/context.md",
21
+ },
22
+ cursor: {
23
+ binary: "cursor",
24
+ contextFiles: [".cursorrules"],
25
+ injectLine: "@.behalf/context.md",
26
+ },
27
+ };
28
+ async function launchTool(toolKey, extraArgs) {
29
+ const tool = TOOLS[toolKey];
30
+ if (!tool)
31
+ throw new Error(`Unknown tool "${toolKey}". Supported: ${Object.keys(TOOLS).join(", ")}`);
32
+ const config = readConfig();
33
+ const agentId = config.agentId ?? process.env.BEHALFID_AGENT_ID;
34
+ const baseUrl = resolveBaseUrl();
35
+ if (!agentId) {
36
+ throw new Error("Agent ID not configured.\nRun: behalf config set agent-id <agentId>");
37
+ }
38
+ const cwd = process.cwd();
39
+ const behalfDir = join(cwd, ".behalf");
40
+ const contextFile = join(behalfDir, "context.md");
41
+ const mcpJsonPath = join(cwd, ".mcp.json");
42
+ // Fetch or refresh permissions
43
+ let detail = readCachedDetail(agentId);
44
+ if (!detail) {
45
+ process.stderr.write("Fetching BehalfID permissions… ");
46
+ try {
47
+ detail = await fetchAndCacheDetail(agentId, baseUrl, false);
48
+ process.stderr.write("done.\n");
49
+ }
50
+ catch (err) {
51
+ process.stderr.write(`failed (${err instanceof Error ? err.message : String(err)}).\n`);
52
+ process.stderr.write("Continuing without live permissions. Run `behalf mcp init` to populate the cache.\n");
53
+ }
54
+ }
55
+ // Write .behalf/context.md
56
+ if (detail) {
57
+ if (!existsSync(behalfDir))
58
+ mkdirSync(behalfDir, { recursive: true });
59
+ writeFileSync(contextFile, generateContextMd(detail));
60
+ }
61
+ // Write .mcp.json (merge with existing)
62
+ let existingMcp = null;
63
+ if (existsSync(mcpJsonPath)) {
64
+ try {
65
+ existingMcp = JSON.parse(readFileSync(mcpJsonPath, "utf-8"));
66
+ }
67
+ catch { /* ignore */ }
68
+ }
69
+ writeFileSync(mcpJsonPath, generateMcpJson(existingMcp ?? undefined));
70
+ // Inject include into tool config files (idempotent)
71
+ for (const fileName of tool.contextFiles) {
72
+ const filePath = join(cwd, fileName);
73
+ if (!existsSync(filePath))
74
+ continue;
75
+ const content = readFileSync(filePath, "utf-8");
76
+ if (!content.includes(tool.injectLine)) {
77
+ writeFileSync(filePath, content + `\n\n${tool.injectLine}\n`);
78
+ }
79
+ }
80
+ // Launch the tool
81
+ const result = spawnSync(tool.binary, extraArgs, { stdio: "inherit" });
82
+ process.exit(result.status ?? 0);
83
+ }
84
+ function toolCommand(toolKey, description) {
85
+ return new Command(toolKey)
86
+ .description(description)
87
+ .allowUnknownOption(true)
88
+ .passThroughOptions(true)
89
+ .argument("[args...]", `arguments to pass to ${toolKey}`)
90
+ .action(runAction(async (args) => {
91
+ await launchTool(toolKey, args);
92
+ }));
93
+ }
94
+ export function runCommand() {
95
+ return new Command("run")
96
+ .description("launch an AI tool with BehalfID enforcement active")
97
+ .allowUnknownOption(true)
98
+ .passThroughOptions(true)
99
+ .argument("<tool>", `tool to launch: ${Object.keys(TOOLS).join(", ")}`)
100
+ .argument("[args...]", "arguments to pass through to the tool")
101
+ .action(runAction(async (toolKey, args) => {
102
+ await launchTool(toolKey, args);
103
+ }));
104
+ }
105
+ export function claudeCommand() { return toolCommand("claude", "launch Claude Code with BehalfID enforcement"); }
106
+ export function codexCommand() { return toolCommand("codex", "launch Codex CLI with BehalfID enforcement"); }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function verifyCommand(): Command;
@@ -0,0 +1,37 @@
1
+ import { Command } from "commander";
2
+ import { apiRequest, resolveApiKey, resolveBaseUrl } from "../lib/client.js";
3
+ import { isJsonMode, printJson, printKv, runAction } from "../lib/output.js";
4
+ export function verifyCommand() {
5
+ return new Command("verify")
6
+ .description("verify whether an agent may perform an action")
7
+ .argument("<agentId>", "agent ID")
8
+ .requiredOption("-a, --action <action>", "action to verify (e.g. purchase, access_data)")
9
+ .option("-v, --vendor <vendor>", "vendor or resource (e.g. amazon.com, gmail.com)")
10
+ .option("-r, --resource <resource>", "alias for --vendor")
11
+ .option("--amount <n>", "transaction amount (for purchase actions)")
12
+ .option("-k, --api-key <key>", "agent API key (overrides config)")
13
+ .action(runAction(async (agentId, opts) => {
14
+ const apiKey = opts.apiKey ?? resolveApiKey();
15
+ if (!apiKey)
16
+ throw new Error("An agent API key is required. Set it with `behalf config set api-key <key>` or pass --api-key.");
17
+ const baseUrl = resolveBaseUrl();
18
+ const body = { agentId, action: opts.action };
19
+ const vendor = opts.vendor ?? opts.resource;
20
+ if (vendor)
21
+ body.vendor = vendor;
22
+ if (opts.amount)
23
+ body.amount = Number(opts.amount);
24
+ const data = await apiRequest("/api/verify", {
25
+ method: "POST", body, apiKey, baseUrl,
26
+ });
27
+ if (isJsonMode()) {
28
+ printJson(data);
29
+ return;
30
+ }
31
+ console.log(`\n${data.allowed ? "✓ ALLOWED" : "✗ DENIED"}`);
32
+ printKv({ requestId: data.requestId, reason: data.reason, risk: data.risk });
33
+ console.log("");
34
+ if (!data.allowed)
35
+ process.exit(1);
36
+ }));
37
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function whoamiCommand(): Command;
@@ -0,0 +1,49 @@
1
+ import { Command } from "commander";
2
+ import { readConfig, readSession } from "../lib/config.js";
3
+ import { apiRequest, resolveBaseUrl } from "../lib/client.js";
4
+ import { isJsonMode, printJson, printKv, runAction } from "../lib/output.js";
5
+ export function whoamiCommand() {
6
+ return new Command("whoami")
7
+ .description("show current authentication status")
8
+ .action(runAction(async function () {
9
+ const config = readConfig();
10
+ const session = readSession();
11
+ const baseUrl = resolveBaseUrl();
12
+ let user = null;
13
+ if (session) {
14
+ try {
15
+ const data = await apiRequest("/api/auth/me", { baseUrl });
16
+ user = data.user;
17
+ }
18
+ catch {
19
+ // session may be expired
20
+ }
21
+ }
22
+ if (isJsonMode()) {
23
+ printJson({
24
+ loggedIn: !!user,
25
+ email: user?.email ?? null,
26
+ userId: user?.userId ?? null,
27
+ apiKey: config.apiKey ? `${config.apiKey.slice(0, 15)}…` : null,
28
+ baseUrl: config.baseUrl ?? null,
29
+ });
30
+ return;
31
+ }
32
+ if (user) {
33
+ printKv({
34
+ email: user.email,
35
+ userId: user.userId,
36
+ "api key": config.apiKey ? `${config.apiKey.slice(0, 15)}…` : "(not set)",
37
+ "base url": baseUrl,
38
+ });
39
+ }
40
+ else {
41
+ console.log("Not logged in.");
42
+ if (config.apiKey) {
43
+ console.log(`API key: ${config.apiKey.slice(0, 15)}…`);
44
+ }
45
+ console.log(`Base URL: ${baseUrl}`);
46
+ console.log('\nRun `behalf login` to authenticate.');
47
+ }
48
+ }));
49
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Command } from "commander";
6
+ import { setJsonMode } from "./lib/output.js";
7
+ import { configCommand } from "./commands/config.js";
8
+ import { initCommand } from "./commands/init.js";
9
+ import { loginCommand } from "./commands/login.js";
10
+ import { logoutCommand } from "./commands/logout.js";
11
+ import { whoamiCommand } from "./commands/whoami.js";
12
+ import { agentsCommand } from "./commands/agents.js";
13
+ import { permissionsCommand } from "./commands/permissions.js";
14
+ import { verifyCommand } from "./commands/verify.js";
15
+ import { logsCommand } from "./commands/logs.js";
16
+ import { passportCommand } from "./commands/passport.js";
17
+ import { healthCommand } from "./commands/health.js";
18
+ import { mcpCommand } from "./commands/mcp.js";
19
+ import { runCommand, claudeCommand, codexCommand } from "./commands/run.js";
20
+ const rawArgs = process.argv.slice(2);
21
+ const jsonMode = rawArgs.includes("--json");
22
+ if (jsonMode)
23
+ setJsonMode(true);
24
+ const filteredArgs = rawArgs.filter(a => a !== "--json");
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = dirname(__filename);
27
+ const { version } = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
28
+ const program = new Command();
29
+ program
30
+ .name("behalf")
31
+ .description("Official CLI for BehalfID — agent permission management and enforcement")
32
+ .version(version)
33
+ .addHelpText("after", `
34
+ Examples:
35
+ behalf init interactive setup wizard
36
+ behalf login log in to your account
37
+ behalf agents create --name "My Bot" --save create agent and save credentials
38
+ behalf mcp init set up BehalfID enforcement in this directory
39
+ behalf claude launch Claude Code with enforcement active
40
+ behalf verify agent_xxx --action purchase -v amazon.com --amount 25
41
+ behalf permissions create agent_xxx --action purchase -r amazon.com --max-amount 50
42
+ behalf logs agent_xxx view recent verification logs
43
+ `);
44
+ program.enablePositionalOptions();
45
+ program.addCommand(initCommand());
46
+ program.addCommand(configCommand());
47
+ program.addCommand(loginCommand());
48
+ program.addCommand(logoutCommand());
49
+ program.addCommand(whoamiCommand());
50
+ program.addCommand(agentsCommand());
51
+ program.addCommand(permissionsCommand());
52
+ program.addCommand(verifyCommand());
53
+ program.addCommand(logsCommand());
54
+ program.addCommand(passportCommand());
55
+ program.addCommand(healthCommand());
56
+ program.addCommand(mcpCommand());
57
+ program.addCommand(runCommand());
58
+ program.addCommand(claudeCommand());
59
+ program.addCommand(codexCommand());
60
+ program.parseAsync(["", "", ...filteredArgs]).catch(err => {
61
+ if (jsonMode) {
62
+ console.error(JSON.stringify({ error: err.message }));
63
+ }
64
+ else {
65
+ console.error(`Error: ${err.message}`);
66
+ }
67
+ process.exit(1);
68
+ });
@@ -0,0 +1,13 @@
1
+ export declare const DEFAULT_BASE_URL = "https://behalfid.com";
2
+ export type RequestOptions = {
3
+ method?: "GET" | "POST" | "PATCH" | "DELETE";
4
+ body?: unknown;
5
+ apiKey?: string;
6
+ baseUrl?: string;
7
+ skipAuth?: boolean;
8
+ onHeaders?: (headers: Headers) => void;
9
+ };
10
+ export declare function resolveBaseUrl(override?: string): string;
11
+ export declare function resolveApiKey(override?: string): string | undefined;
12
+ export declare function originOf(baseUrl: string): string;
13
+ export declare function apiRequest<T = unknown>(path: string, opts?: RequestOptions): Promise<T>;
@@ -0,0 +1,62 @@
1
+ import { readConfig, readSession } from "./config.js";
2
+ export const DEFAULT_BASE_URL = "https://behalfid.com";
3
+ export function resolveBaseUrl(override) {
4
+ const config = readConfig();
5
+ return (override ??
6
+ process.env.BEHALFID_BASE_URL ??
7
+ config.baseUrl ??
8
+ DEFAULT_BASE_URL).replace(/\/+$/, "");
9
+ }
10
+ export function resolveApiKey(override) {
11
+ const config = readConfig();
12
+ return override ?? process.env.BEHALFID_API_KEY ?? config.apiKey;
13
+ }
14
+ export function originOf(baseUrl) {
15
+ try {
16
+ return new URL(baseUrl).origin;
17
+ }
18
+ catch {
19
+ return baseUrl;
20
+ }
21
+ }
22
+ export async function apiRequest(path, opts = {}) {
23
+ const baseUrl = resolveBaseUrl(opts.baseUrl);
24
+ const apiKey = opts.skipAuth ? undefined : opts.apiKey ?? resolveApiKey();
25
+ const session = opts.skipAuth ? null : readSession();
26
+ const headers = {
27
+ Accept: "application/json",
28
+ Origin: originOf(baseUrl),
29
+ };
30
+ if (apiKey)
31
+ headers.Authorization = `Bearer ${apiKey}`;
32
+ if (session)
33
+ headers.Cookie = session;
34
+ if (opts.body !== undefined)
35
+ headers["Content-Type"] = "application/json";
36
+ let response;
37
+ try {
38
+ response = await fetch(`${baseUrl}${path}`, {
39
+ method: opts.method ?? "GET",
40
+ headers,
41
+ body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
42
+ });
43
+ }
44
+ catch {
45
+ throw new Error("Network request failed. Check your connection and base URL.");
46
+ }
47
+ if (opts.onHeaders)
48
+ opts.onHeaders(response.headers);
49
+ const body = await response.json().catch(() => null);
50
+ if (!response.ok) {
51
+ const message = typeof body === "object" &&
52
+ body !== null &&
53
+ "error" in body &&
54
+ typeof body.error === "string"
55
+ ? body.error
56
+ : `Request failed with status ${response.status}.`;
57
+ throw new Error(message);
58
+ }
59
+ if (body === null)
60
+ throw new Error("Expected JSON response.");
61
+ return body;
62
+ }
@@ -0,0 +1,13 @@
1
+ export type Config = {
2
+ apiKey?: string;
3
+ agentId?: string;
4
+ baseUrl?: string;
5
+ };
6
+ export declare function readConfig(): Config;
7
+ export declare function writeConfig(config: Config): void;
8
+ export declare function patchConfig(patch: Partial<Config>): void;
9
+ export declare function readSession(): string | null;
10
+ export declare function writeSession(cookie: string): void;
11
+ export declare function clearSession(): void;
12
+ export declare const CONFIG_FILE_PATH: string;
13
+ export declare const CONFIG_DIR_PATH: string;
@@ -0,0 +1,48 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const CONFIG_DIR = join(homedir(), ".behalf");
5
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
6
+ const SESSION_FILE = join(CONFIG_DIR, "session");
7
+ function ensureDir() {
8
+ if (!existsSync(CONFIG_DIR))
9
+ mkdirSync(CONFIG_DIR, { recursive: true });
10
+ }
11
+ export function readConfig() {
12
+ if (!existsSync(CONFIG_FILE))
13
+ return {};
14
+ try {
15
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ export function writeConfig(config) {
22
+ ensureDir();
23
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", { mode: 0o600 });
24
+ }
25
+ export function patchConfig(patch) {
26
+ writeConfig({ ...readConfig(), ...patch });
27
+ }
28
+ export function readSession() {
29
+ if (!existsSync(SESSION_FILE))
30
+ return null;
31
+ try {
32
+ const val = readFileSync(SESSION_FILE, "utf-8").trim();
33
+ return val || null;
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ export function writeSession(cookie) {
40
+ ensureDir();
41
+ writeFileSync(SESSION_FILE, cookie, { mode: 0o600 });
42
+ }
43
+ export function clearSession() {
44
+ if (existsSync(SESSION_FILE))
45
+ writeFileSync(SESSION_FILE, "", { mode: 0o600 });
46
+ }
47
+ export const CONFIG_FILE_PATH = CONFIG_FILE;
48
+ export const CONFIG_DIR_PATH = CONFIG_DIR;
@@ -0,0 +1,3 @@
1
+ import type { AgentDetail } from "./passport-cache.js";
2
+ export declare function generateContextMd(detail: AgentDetail, updatedAt?: string): string;
3
+ export declare function generateMcpJson(existing?: Record<string, unknown>): string;
@@ -0,0 +1,70 @@
1
+ function formatConstraints(p) {
2
+ const lines = [];
3
+ if (p.constraints?.maxAmount)
4
+ lines.push(`max $${p.constraints.maxAmount}`);
5
+ if (p.constraints?.allowedVendors?.length)
6
+ lines.push(`vendors: ${p.constraints.allowedVendors.join(", ")}`);
7
+ if (p.constraints?.expiresAt)
8
+ lines.push(`expires: ${p.constraints.expiresAt.slice(0, 10)}`);
9
+ return lines;
10
+ }
11
+ export function generateContextMd(detail, updatedAt) {
12
+ const { agent, permissions } = detail;
13
+ const active = permissions.filter(p => p.status === "active");
14
+ const timestamp = updatedAt ?? new Date().toISOString().slice(0, 16).replace("T", " ");
15
+ const lines = [
16
+ `<!-- BehalfID Agent Context — auto-generated by \`behalf mcp init\` -->`,
17
+ `<!-- Run \`behalf mcp init --refresh\` to update -->`,
18
+ ``,
19
+ `## BehalfID Agent: ${agent.name}`,
20
+ ``,
21
+ `You are operating as BehalfID agent **\`${agent.agentId}\`** ("${agent.name}").`,
22
+ ``,
23
+ `The \`behalfid\` MCP server is active in this session.`,
24
+ ``,
25
+ `**ENFORCEMENT RULE: Before executing any action that touches an external service,`,
26
+ `you MUST call the \`verify_action\` tool from the \`behalfid\` MCP server.**`,
27
+ `If \`verify_action\` returns \`"allowed": false\`, stop immediately and tell the user`,
28
+ `why the action was blocked. Never bypass or skip this check.`,
29
+ ``,
30
+ ];
31
+ if (active.length === 0) {
32
+ lines.push(`### Permissions`, ``, `No active permissions. All external actions will be denied.`, ``);
33
+ }
34
+ else {
35
+ lines.push(`### Active Permissions`, ``);
36
+ for (const p of active) {
37
+ const constraints = formatConstraints(p);
38
+ const constraintStr = constraints.length ? ` (${constraints.join(", ")})` : "";
39
+ lines.push(`- **${p.action}**${p.resource ? ` → \`${p.resource}\`` : ""}${constraintStr}${p.scope ? `: ${p.scope}` : ""}`);
40
+ if (p.allowedActions?.length) {
41
+ lines.push(` - Allowed: ${p.allowedActions.map(a => `"${a}"`).join(", ")}`);
42
+ }
43
+ if (p.blockedActions?.length) {
44
+ lines.push(` - Blocked: ${p.blockedActions.map(a => `"${a}"`).join(", ")}`);
45
+ }
46
+ if (p.requiresApproval) {
47
+ lines.push(` - ⚠ Requires human approval before proceeding`);
48
+ }
49
+ }
50
+ lines.push(``);
51
+ }
52
+ lines.push(`### How to call \`verify_action\``, ``, `\`\`\``, `verify_action(action: string, vendor?: string, amount?: number)`, `// action: "purchase" | "access_data" | "browse_web" | "schedule" | "create_content" | ...`, `// vendor: the service or domain being accessed (e.g. "amazon.com", "gmail.com")`, `// amount: only for purchase actions`, `\`\`\``, ``, `---`, `*Updated ${timestamp} · Agent \`${agent.agentId}\` · [BehalfID](https://behalfid.com)*`);
53
+ return lines.join("\n");
54
+ }
55
+ export function generateMcpJson(existing) {
56
+ const base = existing ?? {};
57
+ const servers = base.mcpServers ?? {};
58
+ const updated = {
59
+ ...base,
60
+ mcpServers: {
61
+ ...servers,
62
+ behalfid: {
63
+ type: "stdio",
64
+ command: "behalf",
65
+ args: ["mcp", "start"],
66
+ },
67
+ },
68
+ };
69
+ return JSON.stringify(updated, null, 2) + "\n";
70
+ }
@@ -0,0 +1,5 @@
1
+ export declare function startMcpServer(config: {
2
+ agentId: string;
3
+ apiKey: string;
4
+ baseUrl: string;
5
+ }): Promise<void>;