@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.
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +159 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +67 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/health.js +25 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +71 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +45 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +33 -0
- package/dist/commands/logs.d.ts +2 -0
- package/dist/commands/logs.js +32 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +150 -0
- package/dist/commands/passport.d.ts +2 -0
- package/dist/commands/passport.js +41 -0
- package/dist/commands/permissions.d.ts +2 -0
- package/dist/commands/permissions.js +74 -0
- package/dist/commands/run.d.ts +4 -0
- package/dist/commands/run.js +106 -0
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.js +37 -0
- package/dist/commands/whoami.d.ts +2 -0
- package/dist/commands/whoami.js +49 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +68 -0
- package/dist/lib/client.d.ts +13 -0
- package/dist/lib/client.js +62 -0
- package/dist/lib/config.d.ts +13 -0
- package/dist/lib/config.js +48 -0
- package/dist/lib/context-generator.d.ts +3 -0
- package/dist/lib/context-generator.js +70 -0
- package/dist/lib/mcp-server.d.ts +5 -0
- package/dist/lib/mcp-server.js +140 -0
- package/dist/lib/output.d.ts +10 -0
- package/dist/lib/output.js +48 -0
- package/dist/lib/passport-cache.d.ts +31 -0
- package/dist/lib/passport-cache.js +43 -0
- package/dist/lib/prompt.d.ts +3 -0
- package/dist/lib/prompt.js +53 -0
- package/package.json +33 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { apiRequest, resolveBaseUrl } from "../lib/client.js";
|
|
3
|
+
import { isJsonMode, printJson, printKv, printSuccess, printTable, runAction } from "../lib/output.js";
|
|
4
|
+
import { patchConfig, readSession } from "../lib/config.js";
|
|
5
|
+
function requireSession() {
|
|
6
|
+
if (!readSession()) {
|
|
7
|
+
throw new Error("This command requires you to be logged in. Run `behalf login`.");
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function agentsCommand() {
|
|
11
|
+
const cmd = new Command("agents").description("manage agents");
|
|
12
|
+
cmd
|
|
13
|
+
.command("list")
|
|
14
|
+
.description("list all agents in your account")
|
|
15
|
+
.action(runAction(async () => {
|
|
16
|
+
requireSession();
|
|
17
|
+
const baseUrl = resolveBaseUrl();
|
|
18
|
+
const data = await apiRequest("/api/dashboard/agents", { baseUrl });
|
|
19
|
+
if (isJsonMode()) {
|
|
20
|
+
printJson(data);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (!data.agents.length) {
|
|
24
|
+
console.log("No agents yet. Run `behalf agents create --name <name>` to create one.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
printTable(data.agents.map(a => ({
|
|
28
|
+
agentId: a.agentId,
|
|
29
|
+
name: a.name,
|
|
30
|
+
status: a.status,
|
|
31
|
+
type: a.agentType ?? "native",
|
|
32
|
+
provider: a.provider ?? "",
|
|
33
|
+
created: a.createdAt.slice(0, 10),
|
|
34
|
+
})));
|
|
35
|
+
}));
|
|
36
|
+
cmd
|
|
37
|
+
.command("create")
|
|
38
|
+
.description("create a new agent")
|
|
39
|
+
.requiredOption("-n, --name <name>", "agent name")
|
|
40
|
+
.option("--type <type>", "agent type: native or connected (default: native)")
|
|
41
|
+
.option("--provider <provider>", "provider (for connected agents): ollie, chatgpt, claude, etc.")
|
|
42
|
+
.option("--description <desc>", "agent description")
|
|
43
|
+
.option("--external-id <id>", "external agent ID (for connected agents)")
|
|
44
|
+
.option("--external-label <label>", "external agent label (for connected agents)")
|
|
45
|
+
.option("--save", "save the new agent ID and API key to ~/.behalf/config.json")
|
|
46
|
+
.action(runAction(async (opts) => {
|
|
47
|
+
requireSession();
|
|
48
|
+
const baseUrl = resolveBaseUrl();
|
|
49
|
+
const body = { name: opts.name };
|
|
50
|
+
if (opts.type)
|
|
51
|
+
body.agentType = opts.type;
|
|
52
|
+
if (opts.provider)
|
|
53
|
+
body.provider = opts.provider;
|
|
54
|
+
if (opts.description)
|
|
55
|
+
body.description = opts.description;
|
|
56
|
+
if (opts.externalId)
|
|
57
|
+
body.externalAgentId = opts.externalId;
|
|
58
|
+
if (opts.externalLabel)
|
|
59
|
+
body.externalAgentLabel = opts.externalLabel;
|
|
60
|
+
const data = await apiRequest("/api/dashboard/agents", { method: "POST", body, baseUrl });
|
|
61
|
+
if (opts.save) {
|
|
62
|
+
patchConfig({ agentId: data.agent.agentId, apiKey: data.apiKey });
|
|
63
|
+
}
|
|
64
|
+
if (isJsonMode()) {
|
|
65
|
+
printJson(data);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log(`\nAgent created: ${data.agent.agentId}`);
|
|
69
|
+
console.log(`\nAPI Key (shown once — save it now):\n`);
|
|
70
|
+
console.log(` ${data.apiKey}\n`);
|
|
71
|
+
if (opts.save) {
|
|
72
|
+
console.log(`Saved agent ID and API key to config.`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(`To save to config, run:`);
|
|
76
|
+
console.log(` behalf config set agent-id ${data.agent.agentId}`);
|
|
77
|
+
console.log(` behalf config set api-key ${data.apiKey}`);
|
|
78
|
+
}
|
|
79
|
+
}));
|
|
80
|
+
cmd
|
|
81
|
+
.command("show <agentId>")
|
|
82
|
+
.description("show agent details and permissions")
|
|
83
|
+
.action(runAction(async (agentId) => {
|
|
84
|
+
requireSession();
|
|
85
|
+
const baseUrl = resolveBaseUrl();
|
|
86
|
+
const data = await apiRequest(`/api/dashboard/agents/${encodeURIComponent(agentId)}`, { baseUrl });
|
|
87
|
+
if (isJsonMode()) {
|
|
88
|
+
printJson(data);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const a = data.agent;
|
|
92
|
+
printKv({
|
|
93
|
+
agentId: a.agentId,
|
|
94
|
+
name: a.name,
|
|
95
|
+
status: a.status,
|
|
96
|
+
type: a.agentType ?? "native",
|
|
97
|
+
provider: a.provider ?? "",
|
|
98
|
+
description: a.description ?? "",
|
|
99
|
+
"last used": a.lastUsedAt ?? "never",
|
|
100
|
+
created: a.createdAt,
|
|
101
|
+
});
|
|
102
|
+
if (Array.isArray(data.permissions) && data.permissions.length) {
|
|
103
|
+
console.log("\nPermissions:");
|
|
104
|
+
printTable(data.permissions.map(p => ({
|
|
105
|
+
permissionId: String(p.permissionId ?? ""),
|
|
106
|
+
action: String(p.action ?? ""),
|
|
107
|
+
resource: String(p.resource ?? ""),
|
|
108
|
+
status: String(p.status ?? ""),
|
|
109
|
+
expires: p.expiresAt ? String(p.expiresAt).slice(0, 10) : "never",
|
|
110
|
+
})));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.log("\nNo permissions.");
|
|
114
|
+
}
|
|
115
|
+
}));
|
|
116
|
+
cmd
|
|
117
|
+
.command("disable <agentId>")
|
|
118
|
+
.description("disable an agent")
|
|
119
|
+
.action(runAction(async (agentId) => {
|
|
120
|
+
requireSession();
|
|
121
|
+
const baseUrl = resolveBaseUrl();
|
|
122
|
+
await apiRequest(`/api/dashboard/agents/${encodeURIComponent(agentId)}/disable`, { method: "POST", baseUrl });
|
|
123
|
+
if (isJsonMode())
|
|
124
|
+
printJson({ disabled: true, agentId });
|
|
125
|
+
else
|
|
126
|
+
printSuccess(`Agent ${agentId} disabled.`);
|
|
127
|
+
}));
|
|
128
|
+
cmd
|
|
129
|
+
.command("enable <agentId>")
|
|
130
|
+
.description("enable a disabled agent")
|
|
131
|
+
.action(runAction(async (agentId) => {
|
|
132
|
+
requireSession();
|
|
133
|
+
const baseUrl = resolveBaseUrl();
|
|
134
|
+
await apiRequest(`/api/dashboard/agents/${encodeURIComponent(agentId)}/enable`, { method: "POST", baseUrl });
|
|
135
|
+
if (isJsonMode())
|
|
136
|
+
printJson({ enabled: true, agentId });
|
|
137
|
+
else
|
|
138
|
+
printSuccess(`Agent ${agentId} enabled.`);
|
|
139
|
+
}));
|
|
140
|
+
cmd
|
|
141
|
+
.command("rotate-key <agentId>")
|
|
142
|
+
.description("rotate an agent's API key")
|
|
143
|
+
.option("-k, --api-key <key>", "current agent API key (overrides config)")
|
|
144
|
+
.action(runAction(async (agentId, opts) => {
|
|
145
|
+
const baseUrl = resolveBaseUrl();
|
|
146
|
+
const session = readSession();
|
|
147
|
+
const data = await apiRequest(session
|
|
148
|
+
? `/api/dashboard/agents/${encodeURIComponent(agentId)}/rotate-key`
|
|
149
|
+
: `/api/agents/${encodeURIComponent(agentId)}/rotate-key`, { method: "POST", apiKey: opts.apiKey, baseUrl });
|
|
150
|
+
if (isJsonMode()) {
|
|
151
|
+
printJson(data);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log(`\nNew API Key for ${agentId} (shown once — save it now):\n`);
|
|
155
|
+
console.log(` ${data.apiKey}\n`);
|
|
156
|
+
console.log(`Run: behalf config set api-key ${data.apiKey}`);
|
|
157
|
+
}));
|
|
158
|
+
return cmd;
|
|
159
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { CONFIG_FILE_PATH, patchConfig, readConfig, writeConfig } from "../lib/config.js";
|
|
3
|
+
import { isJsonMode, printJson, printTable, runAction } from "../lib/output.js";
|
|
4
|
+
const VALID_KEYS = ["api-key", "agent-id", "base-url"];
|
|
5
|
+
const KEY_MAP = {
|
|
6
|
+
"api-key": "apiKey",
|
|
7
|
+
"agent-id": "agentId",
|
|
8
|
+
"base-url": "baseUrl",
|
|
9
|
+
};
|
|
10
|
+
function assertKey(key) {
|
|
11
|
+
if (!VALID_KEYS.includes(key)) {
|
|
12
|
+
throw new Error(`Unknown key "${key}". Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
13
|
+
}
|
|
14
|
+
return key;
|
|
15
|
+
}
|
|
16
|
+
export function configCommand() {
|
|
17
|
+
const cmd = new Command("config").description("manage local CLI config");
|
|
18
|
+
cmd
|
|
19
|
+
.command("set <key> <value>")
|
|
20
|
+
.description("set a config value (keys: api-key, agent-id, base-url)")
|
|
21
|
+
.action(runAction(async (key, value) => {
|
|
22
|
+
const k = assertKey(key);
|
|
23
|
+
patchConfig({ [KEY_MAP[k]]: value });
|
|
24
|
+
if (!isJsonMode())
|
|
25
|
+
console.log(`Set ${key}.`);
|
|
26
|
+
else
|
|
27
|
+
printJson({ key, value });
|
|
28
|
+
}));
|
|
29
|
+
cmd
|
|
30
|
+
.command("get <key>")
|
|
31
|
+
.description("get a config value")
|
|
32
|
+
.action(runAction(async (key) => {
|
|
33
|
+
const k = assertKey(key);
|
|
34
|
+
const val = readConfig()[KEY_MAP[k]];
|
|
35
|
+
if (isJsonMode())
|
|
36
|
+
printJson({ [key]: val ?? null });
|
|
37
|
+
else
|
|
38
|
+
console.log(val ?? "(not set)");
|
|
39
|
+
}));
|
|
40
|
+
cmd
|
|
41
|
+
.command("list")
|
|
42
|
+
.description("list all config values")
|
|
43
|
+
.action(runAction(async () => {
|
|
44
|
+
const cfg = readConfig();
|
|
45
|
+
const rows = {
|
|
46
|
+
"api-key": cfg.apiKey ? `${cfg.apiKey.slice(0, 15)}…` : "(not set)",
|
|
47
|
+
"agent-id": cfg.agentId ?? "(not set)",
|
|
48
|
+
"base-url": cfg.baseUrl ?? "(not set)",
|
|
49
|
+
"config file": CONFIG_FILE_PATH,
|
|
50
|
+
};
|
|
51
|
+
if (isJsonMode())
|
|
52
|
+
printJson(rows);
|
|
53
|
+
else
|
|
54
|
+
printTable([rows]);
|
|
55
|
+
}));
|
|
56
|
+
cmd
|
|
57
|
+
.command("clear")
|
|
58
|
+
.description("clear all config values")
|
|
59
|
+
.action(runAction(async () => {
|
|
60
|
+
writeConfig({});
|
|
61
|
+
if (!isJsonMode())
|
|
62
|
+
console.log("Config cleared.");
|
|
63
|
+
else
|
|
64
|
+
printJson({ cleared: true });
|
|
65
|
+
}));
|
|
66
|
+
return cmd;
|
|
67
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { apiRequest, resolveBaseUrl } from "../lib/client.js";
|
|
3
|
+
import { isJsonMode, printJson, printKv, runAction } from "../lib/output.js";
|
|
4
|
+
export function healthCommand() {
|
|
5
|
+
return new Command("health")
|
|
6
|
+
.description("check API health")
|
|
7
|
+
.option("--db", "also check database connectivity (requires setup token or console auth)")
|
|
8
|
+
.action(runAction(async (opts) => {
|
|
9
|
+
const baseUrl = resolveBaseUrl();
|
|
10
|
+
const data = await apiRequest("/api/health", { baseUrl });
|
|
11
|
+
if (opts.db) {
|
|
12
|
+
const dbData = await apiRequest("/api/health/db", { baseUrl });
|
|
13
|
+
const merged = { ...data, ...dbData };
|
|
14
|
+
if (isJsonMode())
|
|
15
|
+
printJson(merged);
|
|
16
|
+
else
|
|
17
|
+
printKv(merged);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (isJsonMode())
|
|
21
|
+
printJson(data);
|
|
22
|
+
else
|
|
23
|
+
printKv(data);
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { patchConfig, readConfig, readSession, writeSession } from "../lib/config.js";
|
|
3
|
+
import { DEFAULT_BASE_URL, originOf } from "../lib/client.js";
|
|
4
|
+
import { ask, askPassword } from "../lib/prompt.js";
|
|
5
|
+
import { runAction } from "../lib/output.js";
|
|
6
|
+
export function initCommand() {
|
|
7
|
+
return new Command("init")
|
|
8
|
+
.description("interactive setup wizard")
|
|
9
|
+
.action(runAction(async function () {
|
|
10
|
+
const config = readConfig();
|
|
11
|
+
const hasSession = !!readSession();
|
|
12
|
+
console.log("\nWelcome to BehalfID CLI\n");
|
|
13
|
+
if (config.baseUrl || config.apiKey || hasSession) {
|
|
14
|
+
console.log("Current config:");
|
|
15
|
+
if (config.baseUrl)
|
|
16
|
+
console.log(` base url: ${config.baseUrl}`);
|
|
17
|
+
if (config.apiKey)
|
|
18
|
+
console.log(` api key: ${config.apiKey.slice(0, 15)}…`);
|
|
19
|
+
if (hasSession)
|
|
20
|
+
console.log(` session: active`);
|
|
21
|
+
console.log("");
|
|
22
|
+
}
|
|
23
|
+
// Base URL
|
|
24
|
+
const baseUrlInput = await ask("Base URL", config.baseUrl ?? DEFAULT_BASE_URL);
|
|
25
|
+
const baseUrl = baseUrlInput.replace(/\/+$/, "");
|
|
26
|
+
if (baseUrl !== config.baseUrl)
|
|
27
|
+
patchConfig({ baseUrl });
|
|
28
|
+
// Auth
|
|
29
|
+
console.log("\nHow would you like to authenticate?");
|
|
30
|
+
console.log(" 1. Log in with email and password");
|
|
31
|
+
console.log(" 2. Enter an agent API key");
|
|
32
|
+
console.log(" 3. Skip");
|
|
33
|
+
const choice = await ask("Choice", "1");
|
|
34
|
+
if (choice === "1") {
|
|
35
|
+
const email = await ask("Email");
|
|
36
|
+
const password = await askPassword("Password");
|
|
37
|
+
const response = await fetch(`${baseUrl}/api/auth/login`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
Accept: "application/json",
|
|
42
|
+
Origin: originOf(baseUrl),
|
|
43
|
+
},
|
|
44
|
+
body: JSON.stringify({ email, password }),
|
|
45
|
+
});
|
|
46
|
+
const setCookie = response.headers.get("set-cookie");
|
|
47
|
+
const match = setCookie?.match(/behalfid_developer=([^;]+)/);
|
|
48
|
+
const sessionCookie = match ? `behalfid_developer=${match[1]}` : null;
|
|
49
|
+
const body = (await response.json().catch(() => null));
|
|
50
|
+
if (!response.ok || !sessionCookie) {
|
|
51
|
+
const msg = typeof body === "object" &&
|
|
52
|
+
body !== null &&
|
|
53
|
+
"error" in body &&
|
|
54
|
+
typeof body.error === "string"
|
|
55
|
+
? body.error
|
|
56
|
+
: "Login failed.";
|
|
57
|
+
throw new Error(msg);
|
|
58
|
+
}
|
|
59
|
+
writeSession(sessionCookie);
|
|
60
|
+
console.log(`\nLogged in as ${body?.user.email}.`);
|
|
61
|
+
}
|
|
62
|
+
else if (choice === "2") {
|
|
63
|
+
const apiKey = await ask("Agent API key (bhf_sk_...)");
|
|
64
|
+
if (apiKey) {
|
|
65
|
+
patchConfig({ apiKey });
|
|
66
|
+
console.log("API key saved.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
console.log("\nSetup complete. Run `behalf --help` to see available commands.\n");
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readSession, writeSession } from "../lib/config.js";
|
|
3
|
+
import { originOf, resolveBaseUrl } from "../lib/client.js";
|
|
4
|
+
import { isJsonMode, printJson, printSuccess, runAction } from "../lib/output.js";
|
|
5
|
+
import { ask, askPassword, confirm } from "../lib/prompt.js";
|
|
6
|
+
export function loginCommand() {
|
|
7
|
+
return new Command("login")
|
|
8
|
+
.description("log in to your BehalfID developer account")
|
|
9
|
+
.option("-e, --email <email>", "developer account email")
|
|
10
|
+
.option("-p, --password <password>", "developer account password")
|
|
11
|
+
.action(runAction(async (opts) => {
|
|
12
|
+
if (readSession()) {
|
|
13
|
+
const ok = await confirm("You are already logged in. Log in again?");
|
|
14
|
+
if (!ok)
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const baseUrl = resolveBaseUrl();
|
|
18
|
+
const email = opts.email ?? (await ask("Email"));
|
|
19
|
+
const password = opts.password ?? (await askPassword("Password"));
|
|
20
|
+
if (!email || !password)
|
|
21
|
+
throw new Error("Email and password are required.");
|
|
22
|
+
const response = await fetch(`${baseUrl}/api/auth/login`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", Origin: originOf(baseUrl) },
|
|
25
|
+
body: JSON.stringify({ email, password }),
|
|
26
|
+
});
|
|
27
|
+
const setCookie = response.headers.get("set-cookie");
|
|
28
|
+
const match = setCookie?.match(/behalfid_developer=([^;]+)/);
|
|
29
|
+
const sessionCookie = match ? `behalfid_developer=${match[1]}` : null;
|
|
30
|
+
const body = (await response.json().catch(() => null));
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
const msg = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string"
|
|
33
|
+
? body.error
|
|
34
|
+
: `Login failed with status ${response.status}.`;
|
|
35
|
+
throw new Error(msg);
|
|
36
|
+
}
|
|
37
|
+
if (!sessionCookie)
|
|
38
|
+
throw new Error("Login succeeded but no session cookie was returned.");
|
|
39
|
+
writeSession(sessionCookie);
|
|
40
|
+
if (isJsonMode())
|
|
41
|
+
printJson({ loggedIn: true, email: body?.user.email });
|
|
42
|
+
else
|
|
43
|
+
printSuccess(`Logged in as ${body?.user.email}.`);
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { clearSession, readSession } from "../lib/config.js";
|
|
3
|
+
import { apiRequest, resolveBaseUrl } from "../lib/client.js";
|
|
4
|
+
import { isJsonMode, printJson, printSuccess, runAction } from "../lib/output.js";
|
|
5
|
+
export function logoutCommand() {
|
|
6
|
+
return new Command("logout")
|
|
7
|
+
.description("log out of your BehalfID developer account")
|
|
8
|
+
.action(runAction(async function () {
|
|
9
|
+
const session = readSession();
|
|
10
|
+
if (!session) {
|
|
11
|
+
if (!isJsonMode())
|
|
12
|
+
console.log("Not logged in.");
|
|
13
|
+
else
|
|
14
|
+
printJson({ loggedOut: false, reason: "not logged in" });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const baseUrl = resolveBaseUrl();
|
|
18
|
+
try {
|
|
19
|
+
await apiRequest("/api/auth/logout", {
|
|
20
|
+
method: "POST",
|
|
21
|
+
baseUrl,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Server-side logout is best-effort; always clear local session.
|
|
26
|
+
}
|
|
27
|
+
clearSession();
|
|
28
|
+
if (isJsonMode())
|
|
29
|
+
printJson({ loggedOut: true });
|
|
30
|
+
else
|
|
31
|
+
printSuccess("Logged out.");
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { apiRequest, resolveApiKey, resolveBaseUrl } from "../lib/client.js";
|
|
3
|
+
import { isJsonMode, printJson, printTable, runAction } from "../lib/output.js";
|
|
4
|
+
export function logsCommand() {
|
|
5
|
+
return new Command("logs")
|
|
6
|
+
.description("show recent verification logs for an agent")
|
|
7
|
+
.argument("<agentId>", "agent ID")
|
|
8
|
+
.option("-k, --api-key <key>", "agent API key (overrides config)")
|
|
9
|
+
.action(runAction(async (agentId, opts) => {
|
|
10
|
+
const apiKey = opts.apiKey ?? resolveApiKey();
|
|
11
|
+
if (!apiKey)
|
|
12
|
+
throw new Error("An agent API key is required. Set it with `behalf config set api-key <key>` or pass --api-key.");
|
|
13
|
+
const baseUrl = resolveBaseUrl();
|
|
14
|
+
const data = await apiRequest(`/api/logs/${encodeURIComponent(agentId)}`, { apiKey, baseUrl });
|
|
15
|
+
if (isJsonMode()) {
|
|
16
|
+
printJson(data);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
20
|
+
console.log("No logs yet.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
printTable(data.map(l => ({
|
|
24
|
+
requestId: l.requestId,
|
|
25
|
+
action: l.action,
|
|
26
|
+
vendor: l.vendor ?? "",
|
|
27
|
+
allowed: l.allowed ? "yes" : "no",
|
|
28
|
+
risk: l.risk,
|
|
29
|
+
when: l.createdAt.replace("T", " ").slice(0, 19),
|
|
30
|
+
})));
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { resolveApiKey, resolveBaseUrl } from "../lib/client.js";
|
|
5
|
+
import { readConfig } from "../lib/config.js";
|
|
6
|
+
import { generateContextMd, generateMcpJson } from "../lib/context-generator.js";
|
|
7
|
+
import { fetchAndCacheDetail, readCachedDetail } from "../lib/passport-cache.js";
|
|
8
|
+
import { isJsonMode, printJson, printKv, runAction } from "../lib/output.js";
|
|
9
|
+
import { confirm } from "../lib/prompt.js";
|
|
10
|
+
function readJsonFile(path) {
|
|
11
|
+
if (!existsSync(path))
|
|
12
|
+
return null;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function mcpCommand() {
|
|
21
|
+
const cmd = new Command("mcp").description("BehalfID MCP server — real-time agent enforcement");
|
|
22
|
+
cmd
|
|
23
|
+
.command("start")
|
|
24
|
+
.description("start the BehalfID MCP server on stdio (used by .mcp.json)")
|
|
25
|
+
.action(runAction(async () => {
|
|
26
|
+
const config = readConfig();
|
|
27
|
+
const agentId = config.agentId ?? process.env.BEHALFID_AGENT_ID;
|
|
28
|
+
const apiKey = resolveApiKey();
|
|
29
|
+
const baseUrl = resolveBaseUrl();
|
|
30
|
+
if (!agentId) {
|
|
31
|
+
throw new Error("Agent ID not configured. Run `behalf config set agent-id <agentId>` first.");
|
|
32
|
+
}
|
|
33
|
+
if (!apiKey) {
|
|
34
|
+
throw new Error("API key not configured. Run `behalf config set api-key <bhf_sk_xxx>` first.");
|
|
35
|
+
}
|
|
36
|
+
// Start the MCP server — this takes over the process
|
|
37
|
+
const { startMcpServer } = await import("../lib/mcp-server.js");
|
|
38
|
+
await startMcpServer({ agentId, apiKey, baseUrl });
|
|
39
|
+
}));
|
|
40
|
+
cmd
|
|
41
|
+
.command("init")
|
|
42
|
+
.description("set up BehalfID enforcement in the current directory")
|
|
43
|
+
.option("--refresh", "force-refresh the permissions cache from the server")
|
|
44
|
+
.option("--no-inject", "skip patching CLAUDE.md / AGENTS.md")
|
|
45
|
+
.option("--dry-run", "show what would be written without writing anything")
|
|
46
|
+
.action(runAction(async (opts) => {
|
|
47
|
+
const config = readConfig();
|
|
48
|
+
const agentId = config.agentId ?? process.env.BEHALFID_AGENT_ID;
|
|
49
|
+
const baseUrl = resolveBaseUrl();
|
|
50
|
+
if (!agentId) {
|
|
51
|
+
throw new Error("Agent ID not configured. Run `behalf config set agent-id <agentId>` first.");
|
|
52
|
+
}
|
|
53
|
+
if (!isJsonMode())
|
|
54
|
+
console.log(`Initializing BehalfID enforcement for agent ${agentId}…\n`);
|
|
55
|
+
// Fetch permissions
|
|
56
|
+
let detail = opts.refresh ? null : readCachedDetail(agentId);
|
|
57
|
+
if (!detail) {
|
|
58
|
+
if (!isJsonMode())
|
|
59
|
+
process.stdout.write("Fetching permissions from server… ");
|
|
60
|
+
detail = await fetchAndCacheDetail(agentId, baseUrl, opts.refresh ?? false);
|
|
61
|
+
if (!isJsonMode())
|
|
62
|
+
console.log("done.");
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
if (!isJsonMode())
|
|
66
|
+
console.log("Using cached permissions (run with --refresh to update).");
|
|
67
|
+
}
|
|
68
|
+
const cwd = process.cwd();
|
|
69
|
+
const behalfDir = join(cwd, ".behalf");
|
|
70
|
+
const contextFile = join(behalfDir, "context.md");
|
|
71
|
+
const mcpJsonFile = join(cwd, ".mcp.json");
|
|
72
|
+
const contextMd = generateContextMd(detail);
|
|
73
|
+
const existingMcp = readJsonFile(mcpJsonFile);
|
|
74
|
+
const mcpJson = generateMcpJson(existingMcp ?? undefined);
|
|
75
|
+
if (opts.dryRun) {
|
|
76
|
+
if (isJsonMode()) {
|
|
77
|
+
printJson({ wouldWrite: [contextFile, mcpJsonFile] });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(`Would write:\n ${contextFile}\n ${mcpJsonFile}`);
|
|
81
|
+
console.log("\n--- .behalf/context.md ---\n");
|
|
82
|
+
console.log(contextMd);
|
|
83
|
+
}
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Write .behalf/context.md
|
|
87
|
+
if (!existsSync(behalfDir))
|
|
88
|
+
mkdirSync(behalfDir, { recursive: true });
|
|
89
|
+
writeFileSync(contextFile, contextMd);
|
|
90
|
+
// Write / merge .mcp.json
|
|
91
|
+
writeFileSync(mcpJsonFile, mcpJson);
|
|
92
|
+
// Inject into CLAUDE.md if present or if user confirms
|
|
93
|
+
if (opts.inject !== false) {
|
|
94
|
+
const claudeMdPath = join(cwd, "CLAUDE.md");
|
|
95
|
+
const agentsMdPath = join(cwd, "AGENTS.md");
|
|
96
|
+
for (const [label, path] of [["CLAUDE.md", claudeMdPath], ["AGENTS.md", agentsMdPath]]) {
|
|
97
|
+
if (!existsSync(path))
|
|
98
|
+
continue;
|
|
99
|
+
const content = readFileSync(path, "utf-8");
|
|
100
|
+
const include = "@.behalf/context.md";
|
|
101
|
+
if (content.includes(include))
|
|
102
|
+
continue;
|
|
103
|
+
const ok = opts.dryRun
|
|
104
|
+
? false
|
|
105
|
+
: await confirm(`Add \`${include}\` to ${label}?`, true);
|
|
106
|
+
if (ok) {
|
|
107
|
+
writeFileSync(path, content + `\n\n${include}\n`);
|
|
108
|
+
if (!isJsonMode())
|
|
109
|
+
console.log(` Patched ${label}.`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (isJsonMode()) {
|
|
114
|
+
printJson({ initialized: true, agentId, contextFile, mcpJsonFile });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
console.log("");
|
|
118
|
+
printKv({
|
|
119
|
+
"context file": resolve(contextFile),
|
|
120
|
+
"mcp config": resolve(mcpJsonFile),
|
|
121
|
+
permissions: `${detail.permissions.filter(p => p.status === "active").length} active`,
|
|
122
|
+
});
|
|
123
|
+
console.log(`\nBehalfID enforcement is active. Launch your AI tool normally — or run \`behalf claude\` to start Claude Code.\n`);
|
|
124
|
+
}));
|
|
125
|
+
cmd
|
|
126
|
+
.command("status")
|
|
127
|
+
.description("show current MCP config and cached permissions for this directory")
|
|
128
|
+
.action(runAction(async () => {
|
|
129
|
+
const config = readConfig();
|
|
130
|
+
const agentId = config.agentId ?? process.env.BEHALFID_AGENT_ID;
|
|
131
|
+
const cwd = process.cwd();
|
|
132
|
+
const mcpJsonPath = join(cwd, ".mcp.json");
|
|
133
|
+
const contextPath = join(cwd, ".behalf/context.md");
|
|
134
|
+
const mcpJson = readJsonFile(mcpJsonPath);
|
|
135
|
+
const hasMcp = !!mcpJson?.mcpServers?.behalfid;
|
|
136
|
+
const hasContext = existsSync(contextPath);
|
|
137
|
+
const cached = agentId ? readCachedDetail(agentId) : null;
|
|
138
|
+
if (isJsonMode()) {
|
|
139
|
+
printJson({ agentId, hasMcp, hasContext, cachedPermissions: cached?.permissions?.length ?? 0 });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
printKv({
|
|
143
|
+
"agent id": agentId ?? "(not set)",
|
|
144
|
+
".mcp.json": hasMcp ? "✓ behalfid server configured" : "✗ not configured",
|
|
145
|
+
"context file": hasContext ? "✓ present" : "✗ missing",
|
|
146
|
+
"cached permissions": cached ? `${cached.permissions.filter(p => p.status === "active").length} active` : "none (run mcp init)",
|
|
147
|
+
});
|
|
148
|
+
}));
|
|
149
|
+
return cmd;
|
|
150
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { apiRequest, resolveApiKey, resolveBaseUrl } from "../lib/client.js";
|
|
3
|
+
import { isJsonMode, printJson, printKv, printTable, runAction } from "../lib/output.js";
|
|
4
|
+
export function passportCommand() {
|
|
5
|
+
return new Command("passport")
|
|
6
|
+
.description("show the public passport (active permissions) for an agent")
|
|
7
|
+
.argument("<agentId>", "agent ID")
|
|
8
|
+
.option("-k, --api-key <key>", "agent API key or passport token (overrides config)")
|
|
9
|
+
.action(runAction(async (agentId, opts) => {
|
|
10
|
+
const apiKey = opts.apiKey ?? resolveApiKey();
|
|
11
|
+
if (!apiKey)
|
|
12
|
+
throw new Error("An agent API key or passport token is required. Pass --api-key or set it with `behalf config set api-key <key>`.");
|
|
13
|
+
const baseUrl = resolveBaseUrl();
|
|
14
|
+
const data = await apiRequest(`/api/passport/${encodeURIComponent(agentId)}`, { apiKey, baseUrl });
|
|
15
|
+
if (isJsonMode()) {
|
|
16
|
+
printJson(data);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
printKv({
|
|
20
|
+
agentId: data.agent.agentId,
|
|
21
|
+
name: data.agent.name,
|
|
22
|
+
type: data.agent.agentType ?? "native",
|
|
23
|
+
provider: data.agent.provider ?? "",
|
|
24
|
+
description: data.agent.description ?? "",
|
|
25
|
+
version: data.passportVersion,
|
|
26
|
+
});
|
|
27
|
+
if (data.permissions.length === 0) {
|
|
28
|
+
console.log("\nNo active permissions.");
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.log("\nPermissions:");
|
|
32
|
+
printTable(data.permissions.map(p => ({
|
|
33
|
+
action: p.action,
|
|
34
|
+
resource: p.resource ?? "",
|
|
35
|
+
scope: p.scope ?? "",
|
|
36
|
+
status: p.status,
|
|
37
|
+
expires: p.expiresAt ? p.expiresAt.slice(0, 10) : "never",
|
|
38
|
+
})));
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
}
|