@aiaiai-pt/martha-cli 0.9.1 → 0.14.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/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to `@aiaiai-pt/martha-cli`. Format: [Keep a Changelog](https
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.10.0] — 2026-06-10
8
+
9
+ ### Added — #540 Phase 2.0/2.1 chat copilot + task operator
10
+ - `martha init --agent` provisions a personal **chat copilot** alongside the run-as bootstrap: a `cloud` agent (no extra credentials) linked to your client, with the intent's tools granted to the **agent** surface (where agent grants fire in chat). `--agent-name` names it; `--model` overrides the model (defaults to the platform default). Persists `default_agent` to the profile.
11
+ - `martha chat` now defaults to your bootstrapped client + copilot so its tools work out of the box; `--agent <name>` selects a specific copilot.
12
+ - **Task operator**: with the `tasks` intent, the copilot can `create_task` / `list_tasks` / `get_task` — tasks run on Martha's task pipeline and are scoped to the copilot's own tasks. (Backend platform functions; surfaced through chat.)
13
+
7
14
  ## [0.9.1] — 2026-06-10
8
15
 
9
16
  ### Fixed
package/dist/index.js CHANGED
@@ -10295,6 +10295,33 @@ async function statusCommand(ctx) {
10295
10295
  init_token_store();
10296
10296
  import { createServer } from "node:http";
10297
10297
  import { randomBytes, createHash } from "node:crypto";
10298
+
10299
+ // src/lib/browser.ts
10300
+ async function openBrowser(url) {
10301
+ const { execFile } = await import("node:child_process");
10302
+ const { platform } = await import("node:os");
10303
+ const os3 = platform();
10304
+ return new Promise((resolve, reject) => {
10305
+ if (os3 === "win32") {
10306
+ execFile("cmd", ["/c", "start", "", url], (err) => {
10307
+ if (err)
10308
+ reject(err);
10309
+ else
10310
+ resolve();
10311
+ });
10312
+ } else {
10313
+ const cmd = os3 === "darwin" ? "open" : "xdg-open";
10314
+ execFile(cmd, [url], (err) => {
10315
+ if (err)
10316
+ reject(err);
10317
+ else
10318
+ resolve();
10319
+ });
10320
+ }
10321
+ });
10322
+ }
10323
+
10324
+ // src/lib/auth/oidc.ts
10298
10325
  function generateCodeVerifier() {
10299
10326
  return randomBytes(32).toString("base64url");
10300
10327
  }
@@ -10413,29 +10440,6 @@ function waitForAuthCode(opts) {
10413
10440
  }, timeoutMs);
10414
10441
  });
10415
10442
  }
10416
- async function openBrowser(url) {
10417
- const { execFile } = await import("node:child_process");
10418
- const { platform } = await import("node:os");
10419
- const os3 = platform();
10420
- return new Promise((resolve, reject) => {
10421
- if (os3 === "win32") {
10422
- execFile("cmd", ["/c", "start", "", url], (err) => {
10423
- if (err)
10424
- reject(err);
10425
- else
10426
- resolve();
10427
- });
10428
- } else {
10429
- const cmd = os3 === "darwin" ? "open" : "xdg-open";
10430
- execFile(cmd, [url], (err) => {
10431
- if (err)
10432
- reject(err);
10433
- else
10434
- resolve();
10435
- });
10436
- }
10437
- });
10438
- }
10439
10443
  async function exchangeCode(opts) {
10440
10444
  const tokenUrl = `${opts.keycloakUrl}/realms/${opts.realm}/protocol/openid-connect/token`;
10441
10445
  const body = new URLSearchParams({
@@ -10817,6 +10821,28 @@ async function confirm(message) {
10817
10821
  });
10818
10822
  });
10819
10823
  }
10824
+ async function promptSecret(message) {
10825
+ if (!process.stdin.isTTY)
10826
+ return;
10827
+ const rl = createInterface({
10828
+ input: process.stdin,
10829
+ output: process.stderr,
10830
+ terminal: true
10831
+ });
10832
+ const muted = rl;
10833
+ const original = muted._writeToOutput?.bind(rl);
10834
+ process.stderr.write(`${message}: `);
10835
+ muted._writeToOutput = () => {};
10836
+ return new Promise((resolve) => {
10837
+ rl.question("", (answer) => {
10838
+ muted._writeToOutput = original;
10839
+ rl.close();
10840
+ process.stderr.write(`
10841
+ `);
10842
+ resolve(answer);
10843
+ });
10844
+ });
10845
+ }
10820
10846
 
10821
10847
  // src/commands/definitions.ts
10822
10848
  init_errors();
@@ -11051,7 +11077,7 @@ import { createInterface as createInterface2 } from "node:readline";
11051
11077
  init_errors();
11052
11078
 
11053
11079
  // src/version.ts
11054
- var CLI_VERSION = "0.9.1";
11080
+ var CLI_VERSION = "0.14.0";
11055
11081
 
11056
11082
  // src/commands/sessions.ts
11057
11083
  function relativeTime(iso) {
@@ -11204,8 +11230,13 @@ async function sendMessage(ctx, sessionId, message, opts = {}) {
11204
11230
  signal: opts.signal,
11205
11231
  timeout: opts.timeoutMs ?? DEFAULT_CHAT_TIMEOUT_MS
11206
11232
  };
11207
- if (opts.clientId) {
11208
- reqOpts.params = { selected_client: opts.clientId };
11233
+ if (opts.clientId || opts.agentName) {
11234
+ const params = {};
11235
+ if (opts.clientId)
11236
+ params.selected_client = opts.clientId;
11237
+ if (opts.agentName)
11238
+ params.selected_agent = opts.agentName;
11239
+ reqOpts.params = params;
11209
11240
  }
11210
11241
  const res = await ctx.api.postRaw(`/api/chat/${encodeURIComponent(sessionId)}`, { content: message }, reqOpts);
11211
11242
  const contentType = res.headers.get("content-type") ?? "";
@@ -11375,7 +11406,7 @@ async function showHistoryPicker(ctx, rl) {
11375
11406
  }
11376
11407
  return data.items[idx].session_id;
11377
11408
  }
11378
- async function runRepl(ctx, initialSessionId, clientId, showTools, timeoutMs) {
11409
+ async function runRepl(ctx, initialSessionId, clientId, agentName, showTools, timeoutMs) {
11379
11410
  let sessionId = initialSessionId;
11380
11411
  process.stderr.write(source_default.dim(`martha v${CLI_VERSION} | session: ${sessionId.slice(0, 8)}...
11381
11412
  `));
@@ -11483,7 +11514,8 @@ async function runRepl(ctx, initialSessionId, clientId, showTools, timeoutMs) {
11483
11514
  onClear: showTools ? () => process.stderr.write(source_default.dim(` --- tool iteration ---
11484
11515
  `)) : undefined,
11485
11516
  signal: activeAbort.signal,
11486
- clientId
11517
+ clientId,
11518
+ agentName
11487
11519
  });
11488
11520
  if (!result.aborted) {
11489
11521
  process.stdout.write(`
@@ -11511,7 +11543,7 @@ Error: ${err instanceof Error ? err.message : String(err)}
11511
11543
  process.stderr.write(`
11512
11544
  `);
11513
11545
  }
11514
- async function runOneShot(ctx, sessionId, message, isJson, clientId, showTools, timeoutMs) {
11546
+ async function runOneShot(ctx, sessionId, message, isJson, clientId, agentName, showTools, timeoutMs) {
11515
11547
  const toolCalls = [];
11516
11548
  function recordToolStatus(data) {
11517
11549
  try {
@@ -11533,6 +11565,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, showTools,
11533
11565
  }
11534
11566
  const { response } = await sendMessage(ctx, sessionId, message, {
11535
11567
  clientId,
11568
+ agentName,
11536
11569
  timeoutMs,
11537
11570
  onToolStatus: isJson ? recordToolStatus : showTools ? (data) => {
11538
11571
  recordToolStatus(data);
@@ -11557,7 +11590,7 @@ async function runOneShot(ctx, sessionId, message, isJson, clientId, showTools,
11557
11590
  }
11558
11591
  }
11559
11592
  function registerChatCommand(program2) {
11560
- program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
11593
+ program2.command("chat").description("Chat with a Martha agent").option("--session <id>", "Resume an existing session").option("--client <nameOrId>", "Use a specific client (name or ID)").option("--agent <name>", "Drive the chat with a specific copilot agent (defaults to profile.default_agent)").option("--message <text>", "Send a single message (non-interactive)").option("--show-tools", "Show tool calls and results during chat").option("--timeout <seconds>", "Per-message HTTP timeout in seconds (default: 600)").action(async (opts) => {
11561
11594
  const ctx = createContext({
11562
11595
  profileOverride: program2.opts().profile,
11563
11596
  verbose: program2.opts().verbose
@@ -11580,6 +11613,10 @@ function registerChatCommand(program2) {
11580
11613
  clientId = String(match.id);
11581
11614
  }
11582
11615
  }
11616
+ if (!clientId && ctx.profile.default_client) {
11617
+ clientId = ctx.profile.default_client;
11618
+ }
11619
+ const agentName = opts.agent ?? ctx.profile.default_agent;
11583
11620
  let sessionId;
11584
11621
  if (opts.session) {
11585
11622
  sessionId = opts.session;
@@ -11589,12 +11626,12 @@ function registerChatCommand(program2) {
11589
11626
  const showTools = !!opts.showTools;
11590
11627
  const timeoutMs = opts.timeout ? parseTimeoutSeconds(opts.timeout) * 1000 : undefined;
11591
11628
  if (opts.message) {
11592
- await runOneShot(ctx, sessionId, opts.message, isJson, clientId, showTools, timeoutMs);
11629
+ await runOneShot(ctx, sessionId, opts.message, isJson, clientId, agentName, showTools, timeoutMs);
11593
11630
  } else {
11594
11631
  if (isJson) {
11595
11632
  throw new CLIError("The --json flag requires --message for non-interactive mode.", 4 /* Validation */, 'Example: martha --json chat --message "hello"');
11596
11633
  }
11597
- await runRepl(ctx, sessionId, clientId, showTools, timeoutMs);
11634
+ await runRepl(ctx, sessionId, clientId, agentName, showTools, timeoutMs);
11598
11635
  }
11599
11636
  });
11600
11637
  }
@@ -13270,6 +13307,25 @@ function formatConfig(config) {
13270
13307
  const full = parts.join(", ");
13271
13308
  return full.length > 40 ? full.slice(0, 37) + "..." : full;
13272
13309
  }
13310
+ function policyView(a) {
13311
+ return {
13312
+ self_grant_enabled: a.self_grant_enabled === true,
13313
+ max_self_grant_scope: a.max_self_grant_scope ?? "none",
13314
+ self_grant_max_pending: a.self_grant_max_pending ?? 5,
13315
+ self_grant_allowlist: a.self_grant_allowlist ?? [],
13316
+ self_grant_collection_roots: a.self_grant_collection_roots ?? []
13317
+ };
13318
+ }
13319
+ function printPolicy(a) {
13320
+ const enabled = a.self_grant_enabled === true;
13321
+ const allow = a.self_grant_allowlist ?? [];
13322
+ const roots = a.self_grant_collection_roots ?? [];
13323
+ console.log(` Self-provisioning: ${enabled ? source_default.green("enabled") : source_default.dim("disabled")}`);
13324
+ console.log(` Scope ceiling: ${a.max_self_grant_scope ?? "none"}`);
13325
+ console.log(` Max pending: ${a.self_grant_max_pending ?? 5}`);
13326
+ console.log(` Allow-list: ${allow.length ? allow.join(", ") : source_default.dim("(none — nothing requestable)")}`);
13327
+ console.log(` Collection roots: ${roots.length ? roots.join(", ") : source_default.dim("(none)")}`);
13328
+ }
13273
13329
  var API_PATH = "/api/admin/definitions/agents";
13274
13330
  var LLM_CONFIG_KEYS = new Set([
13275
13331
  "model",
@@ -13520,6 +13576,70 @@ Usage:
13520
13576
  ].join(" "));
13521
13577
  }
13522
13578
  });
13579
+ parentCmd.command("self-grant <agent>").description("View or configure an agent's self-provisioning policy (request-then-approve). " + "With no flags, prints the current policy.").option("--enable", "Allow the agent to REQUEST capability grants").option("--disable", "Disallow self-provisioning").option("--scope <scope>", "Scope ceiling: none | read_only | read_write").option("--allow <ref...>", "Add allow-list refs (function:NAME or mcp:integration/name)").option("--remove <ref...>", "Remove allow-list refs").option("--collection-root <id...>", "Add collection-subtree roots the agent may self-grant collection-scoped functions into").option("--remove-collection-root <id...>", "Remove collection roots").option("--max-pending <n>", "Max concurrently-pending requests").action(async (agent, opts) => {
13580
+ const ctx = getCtx();
13581
+ const path5 = `${API_PATH}/${encodeURIComponent(agent)}`;
13582
+ const current = await ctx.api.get(path5);
13583
+ const mutating = opts.enable || opts.disable || opts.scope !== undefined || (opts.allow?.length ?? 0) > 0 || (opts.remove?.length ?? 0) > 0 || (opts.collectionRoot?.length ?? 0) > 0 || (opts.removeCollectionRoot?.length ?? 0) > 0 || opts.maxPending !== undefined;
13584
+ if (!mutating) {
13585
+ if (isJson()) {
13586
+ console.log(JSON.stringify(policyView(current), null, 2));
13587
+ return;
13588
+ }
13589
+ console.log(source_default.bold(`Self-provisioning policy: ${agent}
13590
+ `));
13591
+ printPolicy(current);
13592
+ return;
13593
+ }
13594
+ if (opts.enable && opts.disable) {
13595
+ throw new CLIError("Cannot use --enable and --disable together.", 4 /* Validation */);
13596
+ }
13597
+ if (opts.scope !== undefined && !["none", "read_only", "read_write"].includes(opts.scope)) {
13598
+ throw new CLIError(`Invalid --scope "${opts.scope}". Use none | read_only | read_write.`, 4 /* Validation */);
13599
+ }
13600
+ const body = {};
13601
+ if (opts.enable)
13602
+ body.self_grant_enabled = true;
13603
+ if (opts.disable)
13604
+ body.self_grant_enabled = false;
13605
+ if (opts.scope !== undefined)
13606
+ body.max_self_grant_scope = opts.scope;
13607
+ if (opts.maxPending !== undefined) {
13608
+ const n = parseInt(opts.maxPending, 10);
13609
+ if (Number.isNaN(n) || n < 0) {
13610
+ throw new CLIError("--max-pending must be a non-negative integer.", 4 /* Validation */);
13611
+ }
13612
+ body.self_grant_max_pending = n;
13613
+ }
13614
+ if (opts.allow?.length || opts.remove?.length) {
13615
+ const refs = new Set(current.self_grant_allowlist ?? []);
13616
+ for (const r of opts.allow ?? []) {
13617
+ if (!r.includes(":")) {
13618
+ throw new CLIError(`Invalid ref "${r}". Expected function:NAME or mcp:integration/name.`, 4 /* Validation */);
13619
+ }
13620
+ refs.add(r);
13621
+ }
13622
+ for (const r of opts.remove ?? [])
13623
+ refs.delete(r);
13624
+ body.self_grant_allowlist = [...refs];
13625
+ }
13626
+ if (opts.collectionRoot?.length || opts.removeCollectionRoot?.length) {
13627
+ const roots = new Set((current.self_grant_collection_roots ?? []).map(String));
13628
+ for (const c of opts.collectionRoot ?? [])
13629
+ roots.add(c);
13630
+ for (const c of opts.removeCollectionRoot ?? [])
13631
+ roots.delete(c);
13632
+ body.self_grant_collection_roots = [...roots];
13633
+ }
13634
+ const updated = await ctx.api.put(path5, body);
13635
+ if (isJson()) {
13636
+ console.log(JSON.stringify(policyView(updated), null, 2));
13637
+ return;
13638
+ }
13639
+ console.log(source_default.bold(`Updated self-provisioning policy: ${agent}
13640
+ `));
13641
+ printPolicy(updated);
13642
+ });
13523
13643
  }
13524
13644
  };
13525
13645
 
@@ -17482,6 +17602,154 @@ function registerMCPCommands(program2) {
17482
17602
  });
17483
17603
  }
17484
17604
  });
17605
+ cmd.command("add [target]").description("Connect an MCP server to your copilot in one step: create-or-adopt the " + "connection, discover its tools, and grant access. [target] is a catalog " + "name (e.g. `github`, `linear` — see `mcp catalog`) or an existing " + "connection (id or name); --url adds a custom server. Catalog/OAuth " + "servers need no credential and no --auth-type.").option("--url <serverUrl>", "Create a connection from a direct MCP URL").option("--name <name>", "Connection name when creating (default: derived)").option("--integration <name>", "Integration name when creating", "mcp").option("--auth-type <type>", "Auth type when creating (bearer|api_key|basic)", "bearer").option("--credential-value <value>", "Secret for a new connection ('-' stdin, '@path' file). Prompted if omitted in a TTY.").option("--agent <name>", "Grant to this agent (default: profile.default_agent)").option("--no-agent", "Do not grant the agent surface (chat)").option("--client <id>", "Also grant this client (workflow-node surface)").option("--tools <list>", "Comma-separated allow-list of tool names").option("--disabled", "Create the grant disabled").option("--no-browser", "For OAuth servers: print the consent URL instead of auto-opening it, " + "then poll (use on headless/SSH/CI where you'll open the URL yourself).").option("--yes", "Assume yes for prompts (required in non-TTY)").action(async (connection, opts) => {
17606
+ const ctx = getCtx();
17607
+ const json = isJson();
17608
+ const conn = await resolveOrCreateConnection(ctx, connection, opts);
17609
+ const toolCount = await discoverOrDriveOAuth(ctx, conn, opts, json);
17610
+ const { agent, client } = resolveTargets(opts, ctx.profile);
17611
+ if (!agent && !client) {
17612
+ throw new CLIError("No grant target. Pass --agent <name>, set profile.default_agent, or --client <id>.", 4 /* Validation */);
17613
+ }
17614
+ const config = buildGrantConfig(opts.tools);
17615
+ const enabled = !opts.disabled;
17616
+ const granted = [];
17617
+ if (agent) {
17618
+ await ctx.api.post(`/api/admin/definitions/agents/${encodeURIComponent(agent)}/mcp`, {
17619
+ connection_id: conn.id,
17620
+ enabled,
17621
+ config
17622
+ });
17623
+ granted.push(`agent '${agent}'`);
17624
+ }
17625
+ if (client) {
17626
+ await ctx.api.post(`/api/admin/definitions/clients/${encodeURIComponent(client)}/mcp`, {
17627
+ connection_id: conn.id,
17628
+ enabled,
17629
+ config
17630
+ });
17631
+ granted.push(`client ${client}`);
17632
+ }
17633
+ if (json) {
17634
+ console.log(JSON.stringify({
17635
+ connection_id: conn.id,
17636
+ connection_name: conn.name,
17637
+ tools: toolCount,
17638
+ granted_to: granted,
17639
+ enabled
17640
+ }, null, 2));
17641
+ return;
17642
+ }
17643
+ console.log(source_default.green(`Connected '${conn.name}' — ${toolCount} tool${toolCount === 1 ? "" : "s"}. ` + `Granted to ${granted.join(", ")}${enabled ? "" : " (disabled)"}.`));
17644
+ });
17645
+ cmd.command("catalog").description("Browse Martha's catalog of known MCP servers. Add one by name with " + "`mcp add <name>` — no URL, no credential for OAuth servers.").option("--search <query>", "Filter by name/description").action(async (opts) => {
17646
+ const ctx = getCtx();
17647
+ const qs = opts.search ? `?search=${encodeURIComponent(opts.search)}` : "";
17648
+ const entries = await ctx.api.get(`/api/admin/mcp/catalog${qs}`);
17649
+ if (isJson()) {
17650
+ console.log(JSON.stringify(entries, null, 2));
17651
+ return;
17652
+ }
17653
+ if (!entries.length) {
17654
+ console.log(source_default.dim("No catalog entries match."));
17655
+ return;
17656
+ }
17657
+ for (const e of entries) {
17658
+ const auth = e.auth_mode === "oauth2" ? source_default.dim("(OAuth)") : source_default.dim(`(${e.auth_mode})`);
17659
+ console.log(` ${source_default.cyan(e.catalog_key)} ${e.display_name} ${auth} ` + `${source_default.dim(e.trust_tier)}`);
17660
+ if (e.description)
17661
+ console.log(source_default.dim(` ${e.description}`));
17662
+ }
17663
+ console.log(source_default.dim(`
17664
+ Add one: martha mcp add <name> --agent <agent>`));
17665
+ });
17666
+ cmd.command("ls").description("List MCP connections granted to an agent (and optionally a client)").option("--agent <name>", "Agent to inspect (default: profile.default_agent)").option("--client <id>", "Also list this client's grants").action(async (opts) => {
17667
+ const ctx = getCtx();
17668
+ const json = isJson();
17669
+ const agent = opts.agent ?? ctx.profile.default_agent;
17670
+ const out = {};
17671
+ if (agent) {
17672
+ out[`agent:${agent}`] = await ctx.api.get(`/api/admin/definitions/agents/${encodeURIComponent(agent)}/mcp`);
17673
+ }
17674
+ if (opts.client) {
17675
+ out[`client:${opts.client}`] = await ctx.api.get(`/api/admin/definitions/clients/${encodeURIComponent(opts.client)}/mcp`);
17676
+ }
17677
+ if (!agent && !opts.client) {
17678
+ throw new CLIError("Nothing to list. Pass --agent, set profile.default_agent, or --client <id>.", 4 /* Validation */);
17679
+ }
17680
+ if (json) {
17681
+ console.log(JSON.stringify(out, null, 2));
17682
+ return;
17683
+ }
17684
+ for (const [scope, grants] of Object.entries(out)) {
17685
+ console.log(source_default.bold(scope));
17686
+ if (!grants.length) {
17687
+ console.log(source_default.dim(" (none)"));
17688
+ continue;
17689
+ }
17690
+ for (const g of grants) {
17691
+ const allow = g.config?.tools?.length;
17692
+ console.log(` ${source_default.cyan(g.connection_name ?? g.connection_id)}` + `${g.enabled ? "" : source_default.dim(" [disabled]")}` + `${allow ? source_default.dim(` (${allow} tool allow-list)`) : ""}`);
17693
+ }
17694
+ }
17695
+ });
17696
+ cmd.command("grant <connection>").description("Grant an existing connection to an agent and/or client").option("--agent <name>", "Agent (default: profile.default_agent)").option("--no-agent", "Skip the agent surface").option("--client <id>", "Client (workflow-node surface)").option("--tools <list>", "Comma-separated allow-list").action(async (connection, opts) => {
17697
+ const ctx = getCtx();
17698
+ const conn = await resolveConnection(ctx, connection);
17699
+ const { agent, client } = resolveTargets(opts, ctx.profile);
17700
+ if (!agent && !client) {
17701
+ throw new CLIError("No grant target (--agent / default_agent / --client).", 4 /* Validation */);
17702
+ }
17703
+ const config = buildGrantConfig(opts.tools);
17704
+ if (agent)
17705
+ await ctx.api.post(`/api/admin/definitions/agents/${encodeURIComponent(agent)}/mcp`, {
17706
+ connection_id: conn.id,
17707
+ enabled: true,
17708
+ config
17709
+ });
17710
+ if (client)
17711
+ await ctx.api.post(`/api/admin/definitions/clients/${encodeURIComponent(client)}/mcp`, {
17712
+ connection_id: conn.id,
17713
+ enabled: true,
17714
+ config
17715
+ });
17716
+ if (isJson())
17717
+ console.log(JSON.stringify({ connection_id: conn.id, granted: true }, null, 2));
17718
+ else
17719
+ console.log(source_default.green(`Granted '${conn.name}'.`));
17720
+ });
17721
+ cmd.command("revoke <connection>").description("Revoke a connection from an agent and/or client").option("--agent <name>", "Agent (default: profile.default_agent)").option("--no-agent", "Skip the agent surface").option("--client <id>", "Client").action(async (connection, opts) => {
17722
+ const ctx = getCtx();
17723
+ const conn = await resolveConnection(ctx, connection);
17724
+ const { agent, client } = resolveTargets(opts, ctx.profile);
17725
+ if (agent)
17726
+ await ctx.api.del(`/api/admin/definitions/agents/${encodeURIComponent(agent)}/mcp/${conn.id}`);
17727
+ if (client)
17728
+ await ctx.api.del(`/api/admin/definitions/clients/${encodeURIComponent(client)}/mcp/${conn.id}`);
17729
+ if (isJson())
17730
+ console.log(JSON.stringify({ connection_id: conn.id, revoked: true }, null, 2));
17731
+ else
17732
+ console.log(source_default.green(`Revoked '${conn.name}'.`));
17733
+ });
17734
+ cmd.command("disable <connection>").description("Disable a grant without revoking it (keeps the row, enabled=false)").option("--agent <name>", "Agent (default: profile.default_agent)").option("--no-agent", "Skip the agent surface").option("--client <id>", "Client").action(async (connection, opts) => {
17735
+ const ctx = getCtx();
17736
+ const conn = await resolveConnection(ctx, connection);
17737
+ const { agent, client } = resolveTargets(opts, ctx.profile);
17738
+ if (agent)
17739
+ await ctx.api.post(`/api/admin/definitions/agents/${encodeURIComponent(agent)}/mcp`, {
17740
+ connection_id: conn.id,
17741
+ enabled: false
17742
+ });
17743
+ if (client)
17744
+ await ctx.api.post(`/api/admin/definitions/clients/${encodeURIComponent(client)}/mcp`, {
17745
+ connection_id: conn.id,
17746
+ enabled: false
17747
+ });
17748
+ if (isJson())
17749
+ console.log(JSON.stringify({ connection_id: conn.id, enabled: false }, null, 2));
17750
+ else
17751
+ console.log(source_default.green(`Disabled '${conn.name}'.`));
17752
+ });
17485
17753
  }
17486
17754
  function mcpOAuthAuthorizationUrl(err) {
17487
17755
  if (!(err instanceof MarthaAPIError) || err.status !== 428)
@@ -17497,6 +17765,68 @@ function mcpOAuthAuthorizationUrl(err) {
17497
17765
  return null;
17498
17766
  return typeof nested.authorization_url === "string" ? nested.authorization_url : null;
17499
17767
  }
17768
+ async function discoverOrDriveOAuth(ctx, conn, opts, json) {
17769
+ try {
17770
+ const disc = await ctx.api.post("/api/admin/mcp/discover", {
17771
+ connection_id: conn.id
17772
+ });
17773
+ return disc.tools?.length ?? 0;
17774
+ } catch (err) {
17775
+ const authUrl = mcpOAuthAuthorizationUrl(err);
17776
+ if (!authUrl) {
17777
+ throw new CLIError(`Could not reach MCP server for connection '${conn.name}'.`, 1 /* Error */, err instanceof Error ? err.message : undefined);
17778
+ }
17779
+ await driveMcpOAuth(ctx, conn, authUrl, opts, json);
17780
+ const disc = await ctx.api.post("/api/admin/mcp/discover", {
17781
+ connection_id: conn.id
17782
+ });
17783
+ return disc.tools?.length ?? 0;
17784
+ }
17785
+ }
17786
+ async function driveMcpOAuth(ctx, conn, authUrl, opts, json) {
17787
+ const wantsBrowser = opts.browser !== false;
17788
+ const interactive = process.stdin.isTTY || !wantsBrowser;
17789
+ if (!interactive) {
17790
+ throw new CLIError(`Connection '${conn.name}' needs OAuth, which requires interactive browser consent.`, 4 /* Validation */, `Open this URL in a browser to authorize:
17791
+ ${authUrl}
17792
+
17793
+ ` + `Then re-run 'martha mcp add ${conn.name} ...' to adopt the now-authorized connection. ` + `In CI, pass --no-browser to print the URL and poll, or pre-create the ` + `connection in the admin UI (Integrations → Connections).`);
17794
+ }
17795
+ const log = (msg) => process.stderr.write(`${msg}
17796
+ `);
17797
+ log(source_default.bold(`Authorize '${conn.name}' in your browser:`));
17798
+ log(` ${authUrl}`);
17799
+ if (process.stdin.isTTY && wantsBrowser) {
17800
+ openBrowser(authUrl).catch(() => {
17801
+ log(source_default.dim("(couldn't open a browser automatically — open the URL above)"));
17802
+ });
17803
+ }
17804
+ log(source_default.dim("Waiting for authorization to complete…"));
17805
+ await pollConnectionActive(ctx, conn.id);
17806
+ if (!json)
17807
+ log(source_default.green("Authorized."));
17808
+ }
17809
+ async function pollConnectionActive(ctx, connectionId) {
17810
+ const intervalMs = positiveIntEnv("MARTHA_MCP_OAUTH_POLL_MS", 2000);
17811
+ const timeoutMs = positiveIntEnv("MARTHA_MCP_OAUTH_TIMEOUT_MS", 180000);
17812
+ const started = Date.now();
17813
+ while (Date.now() - started < timeoutMs) {
17814
+ const conn = await ctx.api.get(`/api/admin/connections/${encodeURIComponent(connectionId)}`).catch(() => {
17815
+ return;
17816
+ });
17817
+ if (conn?.status === "active")
17818
+ return;
17819
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
17820
+ }
17821
+ throw new CLIError(`Timed out waiting for OAuth authorization (${Math.round(timeoutMs / 1000)}s).`, 1 /* Error */, "The browser consent may not have completed. Re-run to try again, or check " + "the connection status in the admin UI.");
17822
+ }
17823
+ function positiveIntEnv(name, fallback) {
17824
+ const raw = process.env[name];
17825
+ if (!raw)
17826
+ return fallback;
17827
+ const n = Number.parseInt(raw, 10);
17828
+ return Number.isFinite(n) && n > 0 ? n : fallback;
17829
+ }
17500
17830
  function buildLocatorBody(opts) {
17501
17831
  const body = {};
17502
17832
  if (opts.url)
@@ -17564,6 +17894,251 @@ function extractMCPOutput(status) {
17564
17894
  }
17565
17895
  return output;
17566
17896
  }
17897
+ function resolveTargets(opts, profile) {
17898
+ let agent;
17899
+ if (opts.agent === false)
17900
+ agent = undefined;
17901
+ else if (typeof opts.agent === "string")
17902
+ agent = opts.agent;
17903
+ else
17904
+ agent = profile.default_agent;
17905
+ return { agent, client: opts.client };
17906
+ }
17907
+ function buildGrantConfig(tools) {
17908
+ if (!tools)
17909
+ return;
17910
+ const list = tools.split(",").map((t) => t.trim()).filter(Boolean);
17911
+ return list.length ? { tools: list } : undefined;
17912
+ }
17913
+ async function findConnection(ctx, idOrName) {
17914
+ const conns = await ctx.api.get("/api/admin/connections");
17915
+ const byId = conns.find((c) => c.id === idOrName);
17916
+ if (byId)
17917
+ return byId;
17918
+ const byName = conns.filter((c) => c.name === idOrName);
17919
+ if (byName.length === 1)
17920
+ return byName[0];
17921
+ if (byName.length > 1) {
17922
+ throw new CLIError(`Multiple connections named '${idOrName}'; pass the connection id instead.`, 4 /* Validation */);
17923
+ }
17924
+ return null;
17925
+ }
17926
+ async function resolveConnection(ctx, idOrName) {
17927
+ const conn = await findConnection(ctx, idOrName);
17928
+ if (!conn) {
17929
+ throw new CLIError(`Connection '${idOrName}' not found.`, 3 /* NotFound */);
17930
+ }
17931
+ return conn;
17932
+ }
17933
+ async function lookupCatalogEntry(ctx, key) {
17934
+ const entries = await ctx.api.get(`/api/admin/mcp/catalog?search=${encodeURIComponent(key)}`).catch(() => []);
17935
+ return entries.find((e) => e.catalog_key === key) ?? null;
17936
+ }
17937
+ async function resolveOrCreateConnection(ctx, positional, opts) {
17938
+ if (positional) {
17939
+ const existing = await findConnection(ctx, positional);
17940
+ if (existing)
17941
+ return existing;
17942
+ const entry = await lookupCatalogEntry(ctx, positional);
17943
+ if (entry) {
17944
+ return createConnectionFromSpec(ctx, {
17945
+ name: opts.name ?? entry.catalog_key,
17946
+ integration: opts.integration,
17947
+ serverUrl: entry.server_url,
17948
+ authType: entry.auth_mode,
17949
+ scopes: entry.default_scopes ?? undefined
17950
+ }, opts);
17951
+ }
17952
+ throw new CLIError(`'${positional}' is not a known connection or catalog entry.`, 3 /* NotFound */, "Pass --url to add a custom MCP server, or run `martha mcp catalog` to see known servers.");
17953
+ }
17954
+ if (!opts.url) {
17955
+ throw new CLIError("Provide a catalog name, an existing connection (id or name), or --url.", 4 /* Validation */);
17956
+ }
17957
+ return createConnectionFromSpec(ctx, {
17958
+ name: opts.name ?? deriveConnectionName(opts.url),
17959
+ integration: opts.integration,
17960
+ serverUrl: opts.url,
17961
+ authType: opts.authType
17962
+ }, opts);
17963
+ }
17964
+ async function createConnectionFromSpec(ctx, spec, opts) {
17965
+ const existing = (await ctx.api.get("/api/admin/connections")).filter((c) => c.name === spec.name);
17966
+ if (existing.length === 1)
17967
+ return existing[0];
17968
+ const body = {
17969
+ integration_name: spec.integration,
17970
+ name: spec.name,
17971
+ auth_type: spec.authType,
17972
+ config: { server_url: spec.serverUrl },
17973
+ scope: "tenant",
17974
+ is_default: true
17975
+ };
17976
+ if (spec.authType === "oauth2") {
17977
+ if (spec.scopes?.length)
17978
+ body.oauth_config = { scopes: spec.scopes };
17979
+ return ctx.api.post("/api/admin/connections", body);
17980
+ }
17981
+ let credential = await resolveCredentialFlag(opts.credentialValue);
17982
+ if (!credential) {
17983
+ credential = await promptSecret(`${spec.authType} secret for '${spec.name}'`);
17984
+ }
17985
+ if (!credential) {
17986
+ throw new CLIError("A credential is required to create a connection (use --credential-value, '-' for stdin, '@path', or run in a TTY to be prompted).", 4 /* Validation */);
17987
+ }
17988
+ body.credential_value = credential;
17989
+ return ctx.api.post("/api/admin/connections", body);
17990
+ }
17991
+ function deriveConnectionName(url) {
17992
+ try {
17993
+ const host = new URL(url).hostname.replace(/^www\./, "");
17994
+ return `mcp-${host}`.toLowerCase();
17995
+ } catch {
17996
+ return `mcp-${Date.now().toString(36)}`;
17997
+ }
17998
+ }
17999
+ async function resolveCredentialFlag(value) {
18000
+ if (!value)
18001
+ return;
18002
+ if (value === "-") {
18003
+ const chunks = [];
18004
+ for await (const c of process.stdin)
18005
+ chunks.push(c);
18006
+ return Buffer.concat(chunks).toString("utf-8").trim();
18007
+ }
18008
+ if (value.startsWith("@")) {
18009
+ const fs9 = await import("node:fs/promises");
18010
+ return (await fs9.readFile(value.slice(1), "utf-8")).trim();
18011
+ }
18012
+ return value;
18013
+ }
18014
+
18015
+ // src/commands/policy.ts
18016
+ init_errors();
18017
+ var ACTIONS = ["allow", "require_approval", "block"];
18018
+ var KINDS = ["risk_tag", "risk_level", "capability"];
18019
+ var SCOPES = ["any", "agent", "client"];
18020
+ var truncate4 = (s, n) => s.length > n ? s.slice(0, n - 1) + "…" : s;
18021
+ function assertOneOf(value, allowed, flag) {
18022
+ if (!allowed.includes(value)) {
18023
+ throw new CLIError(`${flag} must be one of: ${allowed.join(", ")}`, 4 /* Validation */);
18024
+ }
18025
+ }
18026
+ function registerPolicyCommands(program2) {
18027
+ const cmd = program2.command("policy").description("Manage the tenant's invoke-time capability policy");
18028
+ function getCtx() {
18029
+ const ctx = createContext({
18030
+ profileOverride: program2.opts().profile,
18031
+ verbose: program2.opts().verbose
18032
+ });
18033
+ if (program2.opts().apiUrl)
18034
+ ctx.profile.api_url = program2.opts().apiUrl;
18035
+ return ctx;
18036
+ }
18037
+ const isJson = () => !!program2.opts().json;
18038
+ cmd.command("show").description("Show the current policy: settings + rules").action(async () => {
18039
+ const ctx = getCtx();
18040
+ const cfg = await ctx.api.get("/api/admin/policy");
18041
+ if (isJson()) {
18042
+ console.log(JSON.stringify(cfg, null, 2));
18043
+ return;
18044
+ }
18045
+ const s = cfg.settings;
18046
+ console.log(source_default.bold("Policy settings"));
18047
+ console.log(source_default.dim("-".repeat(40)));
18048
+ console.log(`${source_default.cyan("Default action:")} ${s.default_action}` + source_default.dim(" (applied when no rule matches)"));
18049
+ const sd = s.safe_default_enabled ? source_default.yellow("on") : source_default.dim("off");
18050
+ console.log(`${source_default.cyan("Safe-default:")} ${sd}`);
18051
+ console.log(source_default.dim(` bundle -> require_approval for: ${cfg.safe_default_tags.join(", ")}`));
18052
+ console.log();
18053
+ console.log(source_default.bold(`Rules (${cfg.rules.length})`));
18054
+ console.log(source_default.dim("-".repeat(40)));
18055
+ if (cfg.rules.length === 0) {
18056
+ console.log(source_default.dim("No explicit rules — fully opt-in."));
18057
+ return;
18058
+ }
18059
+ const columns = [
18060
+ {
18061
+ header: "ID",
18062
+ get: (r) => String(r.id ?? "").slice(0, 8)
18063
+ },
18064
+ {
18065
+ header: "KIND",
18066
+ get: (r) => String(r.match_kind ?? "-")
18067
+ },
18068
+ {
18069
+ header: "VALUE",
18070
+ get: (r) => truncate4(String(r.match_value ?? "-"), 28)
18071
+ },
18072
+ {
18073
+ header: "SCOPE",
18074
+ get: (r) => String(r.principal_scope ?? "any")
18075
+ },
18076
+ {
18077
+ header: "ACTION",
18078
+ get: (r) => String(r.action ?? "-")
18079
+ }
18080
+ ];
18081
+ const widths = columns.map((c) => Math.max(c.header.length, ...cfg.rules.map((r) => c.get(r).length)));
18082
+ const head = columns.map((c, i) => c.header.padEnd(widths[i])).join(" ");
18083
+ console.log(source_default.bold(head));
18084
+ console.log(source_default.dim("-".repeat(head.length)));
18085
+ for (const r of cfg.rules) {
18086
+ console.log(columns.map((c, i) => c.get(r).padEnd(widths[i])).join(" "));
18087
+ }
18088
+ });
18089
+ cmd.command("set-default <action>").description(`Set the opt-in default action (${ACTIONS.join("|")})`).action(async (action) => {
18090
+ assertOneOf(action, ACTIONS, "action");
18091
+ const ctx = getCtx();
18092
+ const res = await ctx.api.put("/api/admin/policy/settings", { default_action: action });
18093
+ if (isJson()) {
18094
+ console.log(JSON.stringify(res, null, 2));
18095
+ return;
18096
+ }
18097
+ console.log(`Default action set to ${source_default.cyan(action)}.`);
18098
+ });
18099
+ cmd.command("safe-default <state>").description("Turn the safe-default bundle on|off").action(async (state) => {
18100
+ const normalized = state.toLowerCase();
18101
+ if (!["on", "off", "true", "false"].includes(normalized)) {
18102
+ throw new CLIError("state must be on|off", 4 /* Validation */);
18103
+ }
18104
+ const enabled = normalized === "on" || normalized === "true";
18105
+ const ctx = getCtx();
18106
+ const res = await ctx.api.put("/api/admin/policy/settings", { safe_default_enabled: enabled });
18107
+ if (isJson()) {
18108
+ console.log(JSON.stringify(res, null, 2));
18109
+ return;
18110
+ }
18111
+ console.log(`Safe-default bundle ${enabled ? source_default.yellow("enabled") : source_default.dim("disabled")}.`);
18112
+ });
18113
+ const rule = cmd.command("rule").description("Manage explicit policy rules");
18114
+ rule.command("add").description("Create or update a rule").requiredOption(`--kind <kind>`, `Match kind (${KINDS.join("|")})`).requiredOption("--value <value>", "Match value (a risk tag, level, or capability ref)").requiredOption(`--action <action>`, `Action (${ACTIONS.join("|")})`).option(`--scope <scope>`, `Principal scope (${SCOPES.join("|")})`, "any").option("--note <text>", "Optional human note").action(async (opts) => {
18115
+ assertOneOf(opts.kind, KINDS, "--kind");
18116
+ assertOneOf(opts.action, ACTIONS, "--action");
18117
+ assertOneOf(opts.scope, SCOPES, "--scope");
18118
+ const ctx = getCtx();
18119
+ const res = await ctx.api.post("/api/admin/policy/rules", {
18120
+ match_kind: opts.kind,
18121
+ match_value: opts.value,
18122
+ action: opts.action,
18123
+ principal_scope: opts.scope,
18124
+ ...opts.note ? { note: opts.note } : {}
18125
+ });
18126
+ if (isJson()) {
18127
+ console.log(JSON.stringify(res, null, 2));
18128
+ return;
18129
+ }
18130
+ console.log(`Rule ${source_default.dim(String(res.id).slice(0, 8))}: ` + `${opts.kind}=${source_default.cyan(opts.value)} (${opts.scope}) -> ${source_default.cyan(opts.action)}`);
18131
+ });
18132
+ rule.command("rm <id>").description("Delete a rule by id").action(async (id) => {
18133
+ const ctx = getCtx();
18134
+ await ctx.api.del(`/api/admin/policy/rules/${encodeURIComponent(id)}`);
18135
+ if (isJson()) {
18136
+ console.log(JSON.stringify({ deleted: id }, null, 2));
18137
+ return;
18138
+ }
18139
+ console.log(`Deleted rule ${id}.`);
18140
+ });
18141
+ }
17567
18142
 
17568
18143
  // src/commands/init.ts
17569
18144
  import readline from "node:readline/promises";
@@ -17732,26 +18307,36 @@ Provision an access principal now so calls just work?`, true);
17732
18307
  if (intents.length === 0 && workflows.length === 0 && functions.length === 0) {
17733
18308
  throw new CLIError("Nothing to grant: declare at least one --intent (rag/documents/workflows/custom) or a --workflow/--function.", 4 /* Validation */);
17734
18309
  }
18310
+ let wantAgent = mode === "human" && !!opts.agent;
18311
+ if (mode === "human" && !opts.agent && interactive) {
18312
+ wantAgent = await askYesNo(`
18313
+ Also set up a chat copilot you can talk to (martha chat)?`, true);
18314
+ }
17735
18315
  const ctx = createContext({ profileOverride: profileName });
17736
18316
  if (opts.apiUrl)
17737
18317
  ctx.profile.api_url = opts.apiUrl;
17738
18318
  await ensureAuthenticated(ctx, interactive, jsonMode);
17739
18319
  if (!jsonMode) {
17740
18320
  console.log();
17741
- console.log(source_default.dim(` Provisioning ${mode === "integration" ? "an integration service account" : "your access principal"}…`));
18321
+ console.log(source_default.dim(` Provisioning ${mode === "integration" ? "an integration service account" : "your access principal"}${wantAgent ? " + copilot" : ""}…`));
17742
18322
  }
17743
18323
  const body = {
17744
18324
  mode,
17745
18325
  intents,
17746
18326
  workflows,
17747
18327
  functions,
17748
- ...opts.label ? { label: opts.label } : {}
18328
+ ...opts.label ? { label: opts.label } : {},
18329
+ ...wantAgent ? { agent: true } : {},
18330
+ ...wantAgent && opts.agentName ? { agent_name: opts.agentName } : {},
18331
+ ...wantAgent && opts.model ? { model: opts.model } : {}
17749
18332
  };
17750
18333
  const resp = await ctx.api.post("/api/admin/definitions/principals/bootstrap", body);
17751
18334
  if (mode === "human" && resp.client_key) {
17752
18335
  const cfg = loadConfig();
17753
18336
  if (cfg.profiles[profileName]) {
17754
18337
  cfg.profiles[profileName].default_client = resp.client_key;
18338
+ if (resp.agent)
18339
+ cfg.profiles[profileName].default_agent = resp.agent.name;
17755
18340
  saveConfig(cfg);
17756
18341
  }
17757
18342
  }
@@ -17799,6 +18384,15 @@ function printBootstrapResult(resp, profileName) {
17799
18384
  for (const s of resp.skipped) {
17800
18385
  console.log(source_default.yellow(` Skipped ${s.name} — ${s.reason}`));
17801
18386
  }
18387
+ if (resp.agent) {
18388
+ const a = resp.agent;
18389
+ console.log();
18390
+ const averb = a.adopted ? "Using existing" : "Created";
18391
+ console.log(source_default.green(` ${averb} copilot ${source_default.bold(a.name)}.`));
18392
+ if (a.granted_functions.length > 0) {
18393
+ console.log(` Copilot tools: ${source_default.cyan(a.granted_functions.join(", "))}`);
18394
+ }
18395
+ }
17802
18396
  if (resp.service_account) {
17803
18397
  const sa = resp.service_account;
17804
18398
  console.log();
@@ -17813,6 +18407,9 @@ function printBootstrapResult(resp, profileName) {
17813
18407
  console.log(source_default.dim(` Profile ${source_default.cyan(profileName)} will now run as this client.`));
17814
18408
  console.log();
17815
18409
  console.log(source_default.dim(" Next:"));
18410
+ if (resp.agent) {
18411
+ console.log(source_default.dim(' martha chat "what can you do?"'));
18412
+ }
17816
18413
  console.log(source_default.dim(" martha workflows list"));
17817
18414
  console.log(source_default.dim(" martha workflows execute <name> --inputs '{}'"));
17818
18415
  }
@@ -17869,7 +18466,7 @@ async function askIntents() {
17869
18466
  return parseIntents(raw);
17870
18467
  }
17871
18468
  function registerInitCommand(program2) {
17872
- program2.command("init").description("Create or update a profile, and optionally bootstrap an access principal").option("--preset <name>", "Preset: cloud or local", "cloud").option("--name <name>", "Profile name (defaults to preset name)").option("--force", "Overwrite an existing profile").option("--api-url <url>", "Override the API URL").option("--keycloak-url <url>", "Override the Keycloak URL").option("--keycloak-realm <realm>", "Override the Keycloak realm").option("--no-interactive", "Skip prompts; use defaults / overrides only").option("--bootstrap", "Provision an intent-scoped principal + grants after saving the profile").option("--mode <mode>", "Bootstrap mode: 'human' (your own client) or 'integration' (service account)").option("--intent <intents...>", "Usage buckets to grant: rag, documents, workflows, custom").option("--workflow <names...>", "Workflow name(s) to grant (workflows/custom intent)").option("--function <names...>", "Extra function name(s) to grant (custom intent)").option("--label <name>", "Integration mode: label for the SA client id").option("--yes", "Acknowledge non-interactive bootstrap side effects").action(async (opts, command) => {
18469
+ program2.command("init").description("Create or update a profile, and optionally bootstrap an access principal").option("--preset <name>", "Preset: cloud or local", "cloud").option("--name <name>", "Profile name (defaults to preset name)").option("--force", "Overwrite an existing profile").option("--api-url <url>", "Override the API URL").option("--keycloak-url <url>", "Override the Keycloak URL").option("--keycloak-realm <realm>", "Override the Keycloak realm").option("--no-interactive", "Skip prompts; use defaults / overrides only").option("--bootstrap", "Provision an intent-scoped principal + grants after saving the profile").option("--mode <mode>", "Bootstrap mode: 'human' (your own client) or 'integration' (service account)").option("--intent <intents...>", "Usage buckets to grant: rag, documents, workflows, custom").option("--workflow <names...>", "Workflow name(s) to grant (workflows/custom intent)").option("--function <names...>", "Extra function name(s) to grant (custom intent)").option("--label <name>", "Integration mode: label for the SA client id").option("--agent", "Also provision a personal chat copilot (human mode) and set it as the default").option("--agent-name <name>", "Copilot agent name (defaults to copilot-{user})").option("--model <id>", "Copilot LLM model id (defaults to the platform default)").option("--yes", "Acknowledge non-interactive bootstrap side effects").action(async (opts, command) => {
17873
18470
  const globals = command.parent?.opts() ?? {};
17874
18471
  await initCommand({ ...opts, json: !!globals.json });
17875
18472
  });
@@ -18212,6 +18809,7 @@ registerClientCommands(program2);
18212
18809
  registerModelsCommand(program2);
18213
18810
  registerSessionCommands(program2);
18214
18811
  registerMCPCommands(program2);
18812
+ registerPolicyCommands(program2);
18215
18813
  registerInitCommand(program2);
18216
18814
  registerDoctorCommand(program2);
18217
18815
  registerSkillCommand(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiaiai-pt/martha-cli",
3
- "version": "0.9.1",
3
+ "version": "0.14.0",
4
4
  "description": "Terminal-first client for the Martha AI platform",
5
5
  "homepage": "https://docs.martha.nomadriver.co",
6
6
  "repository": {