@uru-intelligence/cli 0.4.15 → 0.4.17

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/uru.mjs CHANGED
@@ -80,6 +80,20 @@ var cliRemoteOperationContracts = [
80
80
  errorResponses: [],
81
81
  reason: "The tool parameters and result are intentionally open-world transport values. Individual tools remain CLI-only until their backend-owned exact output schemas are available."
82
82
  },
83
+ {
84
+ id: "tool_control_plane.catalog.list",
85
+ operationId: "list_catalog_api_tool_control_plane_catalog_get",
86
+ method: "get",
87
+ path: "/api/tool-control-plane/catalog",
88
+ authentication: "bearer_required",
89
+ workspaceAuthorization: "optional_selector_server_validated",
90
+ inputSchema: "zListCatalogApiToolControlPlaneCatalogGetQuery",
91
+ successStatus: "200",
92
+ outputSchema: "CanonicalToolCatalogEntry[]",
93
+ responseShape: "exact",
94
+ stream: "none",
95
+ errorResponses: []
96
+ },
83
97
  {
84
98
  id: "auth.logout",
85
99
  operationId: "logout_user_api_auth_logout_post",
@@ -936,6 +950,134 @@ var CURRENT_GEM_SDK_REPLACEMENT = new Map([
936
950
  var externalCompanyMcpDeniedToolIds = new Set([
937
951
  "MAGUIRE_WRITE_SQL"
938
952
  ]);
953
+ // ../../packages/platform-operations/src/tool_address.ts
954
+ var TOOL_OWNER_CLASSES = [
955
+ "platform",
956
+ "personal",
957
+ "adopted",
958
+ "company"
959
+ ];
960
+ function isToolOwnerClass(value) {
961
+ return typeof value === "string" && TOOL_OWNER_CLASSES.includes(value);
962
+ }
963
+ var TOOL_CONNECTION_TYPES = [
964
+ "platform",
965
+ "composio",
966
+ "managed_mcp",
967
+ "external_mcp",
968
+ "company"
969
+ ];
970
+ function isToolConnectionType(value) {
971
+ return typeof value === "string" && TOOL_CONNECTION_TYPES.includes(value);
972
+ }
973
+ var TOOL_CONNECTION_TYPE_LABELS = {
974
+ platform: "Uru",
975
+ composio: "Marketplace",
976
+ managed_mcp: "Managed MCP",
977
+ external_mcp: "Custom MCP",
978
+ company: "Company"
979
+ };
980
+ function toolConnectionTypeLabel(type) {
981
+ return TOOL_CONNECTION_TYPE_LABELS[type];
982
+ }
983
+ function parseToolAddress(address) {
984
+ const segments = address.trim().split(".");
985
+ if (segments.length !== 4) {
986
+ return null;
987
+ }
988
+ const [provider, owner, connection, tool] = segments;
989
+ if (provider === undefined || owner === undefined || connection === undefined || tool === undefined || provider === "" || tool === "" || !isToolOwnerClass(owner)) {
990
+ return null;
991
+ }
992
+ return { provider, owner, connection, tool };
993
+ }
994
+ var TOOL_SEARCH_MIN_TERM_LENGTH = 2;
995
+ var TOOL_SEARCH_MIN_PENALTY_TERM_LENGTH = 3;
996
+ function scoreToolRelevance(fields, query, options = {}) {
997
+ const terms = query.toLowerCase().split(/[^a-z0-9]+/u).filter((term) => term.length >= TOOL_SEARCH_MIN_TERM_LENGTH);
998
+ if (terms.length === 0) {
999
+ return 0;
1000
+ }
1001
+ const address = fields.address.toLowerCase();
1002
+ const tool = fields.tool.toLowerCase();
1003
+ const text = `${fields.title ?? ""} ${fields.description} ${fields.category ?? ""}`.toLowerCase().trim();
1004
+ let score = 0;
1005
+ for (const term of terms) {
1006
+ let hit = false;
1007
+ if (tool.includes(term)) {
1008
+ score += 3;
1009
+ hit = true;
1010
+ } else if (address.includes(term)) {
1011
+ score += 2;
1012
+ hit = true;
1013
+ }
1014
+ if (text.includes(term)) {
1015
+ score += 1;
1016
+ hit = true;
1017
+ }
1018
+ if (!hit && options.missPenalty === true && term.length >= TOOL_SEARCH_MIN_PENALTY_TERM_LENGTH) {
1019
+ score -= 2;
1020
+ }
1021
+ }
1022
+ return score;
1023
+ }
1024
+ // ../../packages/platform-operations/src/tool_refusal_envelope.ts
1025
+ var TOOL_REFUSAL_STATUSES = [
1026
+ "approval_required",
1027
+ "refused_by_policy"
1028
+ ];
1029
+ function isToolRefusalStatus(value) {
1030
+ return typeof value === "string" && TOOL_REFUSAL_STATUSES.includes(value);
1031
+ }
1032
+ var TOOL_REFUSAL_ENVELOPE_FIELDS = [
1033
+ "status",
1034
+ "questionId",
1035
+ "writeClass",
1036
+ "toolName"
1037
+ ];
1038
+ function asRecord(value) {
1039
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1040
+ return null;
1041
+ }
1042
+ return value;
1043
+ }
1044
+ function readToolRefusalEnvelope(value) {
1045
+ const record = asRecord(value);
1046
+ if (record === null) {
1047
+ return { outcome: "absent" };
1048
+ }
1049
+ const status = record["status"];
1050
+ if (!isToolRefusalStatus(status)) {
1051
+ return { outcome: "absent" };
1052
+ }
1053
+ const declared = TOOL_REFUSAL_ENVELOPE_FIELDS;
1054
+ for (const key of Object.keys(record).sort()) {
1055
+ if (!declared.includes(key)) {
1056
+ return { outcome: "undeclared_field", field: key };
1057
+ }
1058
+ }
1059
+ const questionId = record["questionId"];
1060
+ if (typeof questionId !== "string" && questionId !== null) {
1061
+ return { outcome: "malformed_field", field: "questionId" };
1062
+ }
1063
+ const writeClass = record["writeClass"];
1064
+ if (typeof writeClass !== "string") {
1065
+ return { outcome: "malformed_field", field: "writeClass" };
1066
+ }
1067
+ const toolName = record["toolName"];
1068
+ if (toolName !== undefined && typeof toolName !== "string") {
1069
+ return { outcome: "malformed_field", field: "toolName" };
1070
+ }
1071
+ return {
1072
+ outcome: "envelope",
1073
+ envelope: {
1074
+ status,
1075
+ questionId,
1076
+ writeClass,
1077
+ ...toolName === undefined ? {} : { toolName }
1078
+ }
1079
+ };
1080
+ }
939
1081
  // ../../packages/platform-operations/src/wrapper_tool_names.ts
940
1082
  var WRAPPER_LIST_TOOLS_SUFFIX = "__list_tools";
941
1083
  var WRAPPER_EXECUTE_TOOL_SUFFIX = "__execute_tool";
@@ -1103,11 +1245,7 @@ var remoteContractsByCommand = {
1103
1245
  "platform.tools.schema",
1104
1246
  ...TOOL_EXECUTION_CONTRACTS
1105
1247
  ],
1106
- tools: [
1107
- "platform.tools.list",
1108
- "platform.tools.schema",
1109
- ...TOOL_EXECUTION_CONTRACTS
1110
- ],
1248
+ tools: ["tool_control_plane.catalog.list", ...TOOL_EXECUTION_CONTRACTS],
1111
1249
  init: TOOL_EXECUTION_CONTRACTS,
1112
1250
  build: TOOL_EXECUTION_CONTRACTS,
1113
1251
  deploy: TOOL_EXECUTION_CONTRACTS,
@@ -1150,7 +1288,7 @@ var cliCommandSpecs = [
1150
1288
  spec("mcp", "Print or install MCP client configuration", "optional", "optional", "write", TEXT_AND_JSON),
1151
1289
  spec("complete", "Generate shell completion metadata", "none", "none", "read", TEXT_AND_JSON),
1152
1290
  spec("update", "Check for a newer CLI and print the install command", "none", "none", "read", TEXT_AND_JSON),
1153
- spec("tools", "Inspect or run a platform tool", "required", "required", "write", ALL_OUTPUTS),
1291
+ spec("tools", "Search, inspect and run every tool you can reach", "required", "required", "write", ALL_OUTPUTS),
1154
1292
  spec("init", "Initialize a Gem project", "required", "required", "write", TEXT_AND_JSON),
1155
1293
  spec("build", "Build a Gem", "required", "required", "write", TEXT_AND_JSON),
1156
1294
  spec("deploy", "Save and deploy a Gem Version", "required", "required", "write", ALL_OUTPUTS),
@@ -1196,6 +1334,14 @@ var booleanFlag = (token) => ({
1196
1334
  });
1197
1335
  var valueFlag = (token, options = {}) => ({ token, value: "required", ...options });
1198
1336
  var paramsJsonFlag = () => valueFlag("--params-json", { aliases: ["--args-json"] });
1337
+ var toolParamsJsonFlag = () => valueFlag("--params-json", { aliases: ["--args-json", "-d", "--data"] });
1338
+ var toolCatalogFilterFlags = [
1339
+ valueFlag("--namespace"),
1340
+ valueFlag("--owner"),
1341
+ valueFlag("--type"),
1342
+ valueFlag("--category"),
1343
+ valueFlag("--limit")
1344
+ ];
1199
1345
  var enumFlag = (token, values) => ({ token, value: "one_of", values });
1200
1346
  function pathSpec(path, binding, positionals = [], localFlags = [], options = {}) {
1201
1347
  return {
@@ -1322,33 +1468,61 @@ var cliCommandPathSpecs = [
1322
1468
  positionals: [required("operation-id")],
1323
1469
  localFlags: [paramsJsonFlag()]
1324
1470
  },
1471
+ {
1472
+ path: ["tools", "search"],
1473
+ binding: "tools",
1474
+ helpKey: "tools",
1475
+ positionals: [variadic("task")],
1476
+ localFlags: [...toolCatalogFilterFlags, valueFlag("--offset")]
1477
+ },
1325
1478
  {
1326
1479
  path: ["tools", "ls"],
1480
+ aliases: [["tools", "list"]],
1327
1481
  binding: "tools",
1328
1482
  helpKey: "tools",
1329
1483
  positionals: [],
1330
- localFlags: []
1484
+ localFlags: [...toolCatalogFilterFlags]
1485
+ },
1486
+ {
1487
+ path: ["tools", "connections"],
1488
+ binding: "tools",
1489
+ helpKey: "tools",
1490
+ positionals: [],
1491
+ localFlags: [valueFlag("--owner"), valueFlag("--type")]
1331
1492
  },
1332
1493
  {
1333
1494
  path: ["tools", "schema"],
1334
1495
  binding: "tools",
1335
1496
  helpKey: "tools",
1336
- positionals: [required("name")],
1497
+ positionals: [required("address")],
1337
1498
  localFlags: []
1338
1499
  },
1339
1500
  {
1340
1501
  path: ["tools", "inspect"],
1341
1502
  binding: "tools",
1342
1503
  helpKey: "tools",
1343
- positionals: [required("name")],
1504
+ positionals: [required("address")],
1344
1505
  localFlags: []
1345
1506
  },
1346
1507
  {
1347
1508
  path: ["tools", "run"],
1348
1509
  binding: "tools",
1349
1510
  helpKey: "tools",
1350
- positionals: [required("name")],
1351
- localFlags: [paramsJsonFlag()]
1511
+ positionals: [required("address")],
1512
+ localFlags: [
1513
+ toolParamsJsonFlag(),
1514
+ booleanFlag("--get-schema"),
1515
+ booleanFlag("--dry-run"),
1516
+ booleanFlag("--parallel"),
1517
+ valueFlag("--mcp-url")
1518
+ ]
1519
+ },
1520
+ {
1521
+ path: ["tools", "approve"],
1522
+ binding: "tools",
1523
+ helpKey: "tools",
1524
+ positionals: [required("question-id")],
1525
+ localFlags: [booleanFlag("--accept"), booleanFlag("--decline")]
1352
1526
  },
1353
1527
  ...libraryPathSpecs(),
1354
1528
  ...datasetPathSpecs(),
@@ -1632,7 +1806,7 @@ function topLevelPathSpecs() {
1632
1806
  pathSpec(["secrets"], "secrets"),
1633
1807
  pathSpec(["env"], "env"),
1634
1808
  pathSpec(["operations"], "operations"),
1635
- pathSpec(["tools"], "tools"),
1809
+ pathSpec(["tools"], "tools", [variadic("address")], [valueFlag("--match"), valueFlag("--limit")]),
1636
1810
  pathSpec(["mcp"], "mcp")
1637
1811
  ];
1638
1812
  }
@@ -1765,6 +1939,25 @@ function gemPathSpecs() {
1765
1939
  ...gemRefFlags
1766
1940
  ]),
1767
1941
  pathSpec(["gems", "status"], "gems", [optional("gem")], gemRefFlags),
1942
+ pathSpec(["gems", "source", "status"], "gems", [optional("gem")], gemRefFlags),
1943
+ pathSpec(["gems", "source", "refresh"], "gems", [optional("gem")], gemRefFlags),
1944
+ pathSpec(["gems", "source", "copy"], "gems", [optional("gem")], gemRefFlags),
1945
+ pathSpec(["gems", "source", "retry"], "gems", [optional("gem")], [...gemRefFlags, valueFlag("--import-id"), valueFlag("--idempotency-key")]),
1946
+ pathSpec(["gems", "source", "cancel"], "gems", [optional("gem")], [...gemRefFlags, valueFlag("--import-id"), booleanFlag("--yes")]),
1947
+ pathSpec(["gems", "source", "policy"], "gems", [optional("gem")], [
1948
+ ...gemRefFlags,
1949
+ valueFlag("--expected-revision"),
1950
+ valueFlag("--automatic-publishing"),
1951
+ valueFlag("--previews-enabled")
1952
+ ], { aliases: [["gems", "source", "change-policy"]] }),
1953
+ pathSpec(["gems", "source", "disconnect"], "gems", [optional("gem")], [...gemRefFlags, valueFlag("--expected-revision"), booleanFlag("--yes")]),
1954
+ pathSpec(["gems", "source", "change-source"], "gems", [optional("gem")], [
1955
+ ...gemRefFlags,
1956
+ valueFlag("--expected-revision"),
1957
+ valueFlag("--ref"),
1958
+ valueFlag("--app-directory"),
1959
+ valueFlag("--commit-sha")
1960
+ ], { aliases: [["gems", "source", "source-change"]] }),
1768
1961
  pathSpec(["gems", "read"], "gems", [required("gem-path"), required("file")]),
1769
1962
  pathSpec(["gems", "write"], "gems", [required("gem-path"), required("file")], [valueFlag("--file"), valueFlag("--content"), booleanFlag("--no-create")]),
1770
1963
  pathSpec(["gems", "versions", "save"], "gems", [optional("gem")], [
@@ -2388,7 +2581,21 @@ var cliRootHelpGroups = [
2388
2581
  {
2389
2582
  command: "tools",
2390
2583
  lines: [
2391
- " uru tools ls|schema|inspect <name>|run <name> Full platform-tool access"
2584
+ ' uru tools search "<task>" [--limit N] [--namespace NS] [--owner OWNER] [--type TYPE] [--category CAT]',
2585
+ " Rank every reachable tool against a task",
2586
+ " uru tools ls [--namespace NS] [--owner OWNER] [--type TYPE] [--category CAT]",
2587
+ " Every tool you can reach, by connection",
2588
+ " --owner platform|personal|adopted|company (whose it is)",
2589
+ " --type platform|composio|managed_mcp|external_mcp|company (what kind)",
2590
+ " uru tools <partial address> --help [--match TEXT] [--limit N]",
2591
+ " Drill into <provider>.<owner>.<connection>.<tool>",
2592
+ " uru tools schema <address> Input and output shape for one tool",
2593
+ " uru tools inspect <address> Owner, type, connection, write class, policy answer",
2594
+ " uru tools run <address> -d '{...}' Run a platform or connector tool",
2595
+ " uru tools run <address> --get-schema|--dry-run|--parallel",
2596
+ " uru tools approve <question-id> --accept|--decline Answer a call that parked for approval",
2597
+ " uru tools connections [--owner OWNER] [--type TYPE]",
2598
+ " Every connection behind your tools"
2392
2599
  ]
2393
2600
  },
2394
2601
  {
@@ -2462,7 +2669,7 @@ function spec(id, summary, auth, workspace, safety, outputModes, cancellation =
2462
2669
  // package.json
2463
2670
  var package_default = {
2464
2671
  name: "@uru-intelligence/cli",
2465
- version: "0.4.15",
2672
+ version: "0.4.17",
2466
2673
  private: false,
2467
2674
  description: "Uru full-platform command line interface",
2468
2675
  repository: {
@@ -2492,9 +2699,7 @@ var package_default = {
2492
2699
  "type-check": "tsc --noEmit",
2493
2700
  test: "bun test",
2494
2701
  "test:unit": "bun test",
2495
- format: "prettier --write . --ignore-path ../../.prettierignore",
2496
- "format:check": "prettier --check . --ignore-path ../../.prettierignore",
2497
- ci: "bun run lint && bun run type-check && bun run test:unit && bun run docs:check && bun run changelog:check && bun run format:check && bun run build && bun run smoke:package && bun run smoke:binaries",
2702
+ ci: "bun run lint && bun run type-check && bun run test:unit && bun run docs:check && bun run changelog:check && bun run build && bun run smoke:package && bun run smoke:binaries",
2498
2703
  build: "node scripts/build.mjs",
2499
2704
  prepack: "bun run build",
2500
2705
  "docs:generate": "bun scripts/generate-docs.mjs",
@@ -9548,6 +9753,7 @@ var zBulkPermissionsUpdateResponse = object({
9548
9753
  updatedCount: int()
9549
9754
  });
9550
9755
  var zCanonicalToolCatalogEntry = object({
9756
+ address: string2(),
9551
9757
  agentAliasFragment: string2().nullish(),
9552
9758
  agentVisibleNamespace: string2().nullish(),
9553
9759
  annotations: record(string2(), unknown()).nullish(),
@@ -9569,6 +9775,7 @@ var zCanonicalToolCatalogEntry = object({
9569
9775
  namespace: string2(),
9570
9776
  normalizedSubtoolName: string2(),
9571
9777
  outputSchema: record(string2(), unknown()).nullish(),
9778
+ owner: _enum(["platform", "personal", "adopted", "company"]),
9572
9779
  ownerUserId: uuid2().nullable(),
9573
9780
  personaLinkId: uuid2().nullish(),
9574
9781
  personaName: string2().nullish(),
@@ -9577,6 +9784,8 @@ var zCanonicalToolCatalogEntry = object({
9577
9784
  serviceSlug: string2(),
9578
9785
  sourceUserName: string2().nullish(),
9579
9786
  title: string2().nullish(),
9787
+ type: _enum(["platform", "composio", "managed_mcp", "external_mcp", "company"]),
9788
+ typeLabel: string2(),
9580
9789
  visibilitySource: string2().nullish(),
9581
9790
  workspaceId: uuid2().nullable(),
9582
9791
  wrapperToolName: string2()
@@ -19410,6 +19619,9 @@ var zListAvailableToolDefinitionsApiToolControlPlaneAvailableGetQuery = object({
19410
19619
  enabledOnly: _enum(["true", "false"]).optional().default("true")
19411
19620
  });
19412
19621
  var zListAvailableToolDefinitionsApiToolControlPlaneAvailableGetResponse = array(zToolDefinitionResponse);
19622
+ var zListCatalogApiToolControlPlaneCatalogGetHeaders = object({
19623
+ "X-Uru-Workspace-Id": string2().min(1).optional()
19624
+ });
19413
19625
  var zListCatalogApiToolControlPlaneCatalogGetQuery = object({
19414
19626
  enabledOnly: _enum(["true", "false"]).optional().default("true")
19415
19627
  });
@@ -20389,8 +20601,7 @@ function parseOAuthCallbackUrl(callbackUrl, expectedState) {
20389
20601
  }
20390
20602
  const error2 = url2.searchParams.get("error");
20391
20603
  if (error2 !== null && error2 !== "") {
20392
- const description = url2.searchParams.get("error_description");
20393
- throw new CliError(description === null || description === "" ? `OAuth authorization failed: ${error2}` : `OAuth authorization failed: ${error2}: ${description}`, { code: ExitCode.AuthRequired, errorCode: "oauth_authorization_failed" });
20604
+ throw new CliError("OAuth authorization failed. Start sign-in again and approve access.", { code: ExitCode.AuthRequired, errorCode: "oauth_authorization_failed" });
20394
20605
  }
20395
20606
  const code = url2.searchParams.get("code") ?? "";
20396
20607
  if (code === "") {
@@ -20723,12 +20934,21 @@ function errorMessage2(payload) {
20723
20934
  if (!isRecord(payload)) {
20724
20935
  return;
20725
20936
  }
20726
- const description = payload["error_description"];
20727
- if (typeof description === "string" && description.trim() !== "") {
20728
- return description;
20729
- }
20730
20937
  const error2 = payload["error"];
20731
- return typeof error2 === "string" && error2.trim() !== "" ? error2 : undefined;
20938
+ switch (error2) {
20939
+ case "access_denied":
20940
+ return "Authorization was denied. Start sign-in again.";
20941
+ case "invalid_grant":
20942
+ return "Authorization expired or was revoked. Sign in again.";
20943
+ case "invalid_client":
20944
+ return "The sign-in client is not accepted by the server.";
20945
+ case "expired_token":
20946
+ return "The sign-in request expired. Start sign-in again.";
20947
+ case "temporarily_unavailable":
20948
+ return "The sign-in service is temporarily unavailable.";
20949
+ default:
20950
+ return;
20951
+ }
20732
20952
  }
20733
20953
  function defaultSleep(milliseconds) {
20734
20954
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
@@ -20964,32 +21184,6 @@ function formatWorkspaces(workspaces, style = PLAIN_VISUAL, currentWorkspaceId)
20964
21184
  currentId !== undefined && workspace.id === currentId ? "current" : ""
20965
21185
  ]));
20966
21186
  }
20967
- function formatTools(tools, style = PLAIN_VISUAL) {
20968
- if (tools.length === 0) {
20969
- return "No tools found";
20970
- }
20971
- if (!style.rich) {
20972
- return tools.map((tool) => {
20973
- const description = tool.description === undefined ? "" : ` ${tool.description}`;
20974
- return `${tool.name}${description}`;
20975
- }).join(`
20976
- `);
20977
- }
20978
- return formatTable(style, ["NAME", "DESCRIPTION"], tools.map((tool) => [tool.name, tool.description ?? ""]));
20979
- }
20980
- function formatToolContract(contract) {
20981
- const name = typeof contract["name"] === "string" ? contract["name"] : "unknown";
20982
- const description = typeof contract["description"] === "string" ? contract["description"] : "";
20983
- const schema = contract["inputSchema"];
20984
- const lines = [`Name: ${name}`];
20985
- if (description !== "") {
20986
- lines.push(`Description: ${description}`);
20987
- }
20988
- lines.push("Input schema:");
20989
- lines.push(JSON.stringify(schema ?? {}, null, 2));
20990
- return lines.join(`
20991
- `);
20992
- }
20993
21187
  function formatOperationContract(contract) {
20994
21188
  const id = typeof contract["id"] === "string" ? contract["id"] : "unknown";
20995
21189
  const family = typeof contract["family"] === "string" ? contract["family"] : "unknown";
@@ -21222,7 +21416,7 @@ or --output-format stream-json before the command to emit NDJSON events.
21222
21416
  Print one explicit active Link URL. When a Gem has more than one active Link,
21223
21417
  use --link to choose the Link rather than guessing which URL you mean.
21224
21418
  `,
21225
- gems: `Usage: uru gems <inspect|validate|build|deploy|verify|fs|status|read|write|edits|versions|releases|links|runs|capabilities|schedules|bindings|components|delegations|api|domains|analytics|power|logs> [...]
21419
+ gems: `Usage: uru gems <inspect|validate|build|deploy|verify|fs|status|source|read|write|edits|versions|releases|links|runs|capabilities|schedules|bindings|components|delegations|api|domains|analytics|power|logs> [...]
21226
21420
  uru gems validate [library/name.gem] [--wait] [--timeout-ms 600000] [--poll-interval-ms 2000]
21227
21421
  uru gems build [library/name.gem] [--force] [--wait] [--timeout-ms 600000] [--poll-interval-ms 2000]
21228
21422
 
@@ -21511,13 +21705,29 @@ env pull writes a metadata-only template (names and placeholders). It never
21511
21705
  downloads remote secret values. env run overlays local values and redacts
21512
21706
  those values from captured output.
21513
21707
  `,
21514
- tools: `Usage: uru tools ls
21515
- uru tools schema <name>
21516
- uru tools inspect <name>
21517
- uru tools run <name> [--params-json|--args-json '{...}'|--params-json @file]
21708
+ tools: `Usage: uru tools search "<task>" [--limit N] [--namespace NS] [--owner OWNER]
21709
+ uru tools ls [--namespace NS] [--owner OWNER]
21710
+ uru tools <partial address> --help [--match TEXT] [--limit N]
21711
+ uru tools schema <address>
21712
+ uru tools inspect <address>
21713
+ uru tools run <address> -d '{...}' [--get-schema] [--dry-run] [--parallel]
21714
+ uru tools connections
21518
21715
 
21519
- Use the full platform-tool catalog directly before dedicated command UX exists.
21520
- \`uru tools inspect\` prints name, description, and input schema.
21716
+ Every tool you can reach: platform tools, your own connections, connections you
21717
+ adopted from a workspace offer, workspace connections, and MCP servers of any type.
21718
+
21719
+ An address is <provider>.<owner>.<connection>.<tool>, and owner is one of
21720
+ platform, personal, adopted, company. The connection type (Uru, Marketplace,
21721
+ Managed MCP, Custom MCP, Company) and its category print beside the address
21722
+ and filter with --type and --category. The flat <namespace>__<tool> wire name
21723
+ resolves to the same tool.
21724
+
21725
+ \`--get-schema\` and \`--dry-run\` run nothing. \`--dry-run\` prints the resolved
21726
+ call and the policy answer. \`--parallel\` takes a JSON array and returns one
21727
+ result per input object. \`-d\` is the same flag as --params-json.
21728
+
21729
+ A refusal is always named: no_match, catalog_unavailable, namespace_unavailable,
21730
+ not_connected, not_adopted, approval_required, refused_by_policy.
21521
21731
  `,
21522
21732
  operations: `Usage: uru operations ls [--family <family>]
21523
21733
  uru operations inspect <operation-id>
@@ -23309,7 +23519,10 @@ function adaptRegistryFamilyParams(params) {
23309
23519
  var REGISTRY_FAMILY_SNAKE_CASE_ALIASES = new Set([
23310
23520
  "automation_id",
23311
23521
  "dataset_ids",
23522
+ "idempotency_key",
23312
23523
  "item_id",
23524
+ "label_id",
23525
+ "label_value",
23313
23526
  "max_results"
23314
23527
  ]);
23315
23528
  function canonicalCliFieldToToolProtocol(key) {
@@ -23321,54 +23534,6 @@ function canonicalCliFieldToToolProtocol(key) {
23321
23534
  }
23322
23535
  return key.replace(/[A-Z]/g, (character) => `_${character.toLowerCase()}`);
23323
23536
  }
23324
- async function dispatchTools(ctx, sub, third, command) {
23325
- if (sub === "ls") {
23326
- await withWorkspace(ctx);
23327
- const tools = await client(ctx).listTools();
23328
- ctx.json ? writeJson(ctx.io, tools.map((tool) => tool.raw)) : writeText(ctx.io, formatTools(tools, visualFromTerminal(ctx.terminal, ctx.outputFormat)));
23329
- return;
23330
- }
23331
- if (sub === "schema") {
23332
- await withWorkspace(ctx);
23333
- const name = requireCommandValue(third, "Usage: uru tools schema <name>");
23334
- emitValue(ctx, await client(ctx).getToolSchema(name));
23335
- return;
23336
- }
23337
- if (sub === "inspect") {
23338
- await withWorkspace(ctx);
23339
- const name = requireCommandValue(third, "Usage: uru tools inspect <name>");
23340
- const schema = await client(ctx).getToolSchema(name);
23341
- const contract = toolInspectContract(name, schema);
23342
- emitValue(ctx, contract, formatToolContract(contract));
23343
- return;
23344
- }
23345
- if (sub === "run") {
23346
- await withWorkspace(ctx);
23347
- const name = requireCommandValue(third, "Usage: uru tools run <name> --params-json '{...}'");
23348
- const params = await paramsJson(command.slice(3), ["tools", "run"], paramsJsonOptions(ctx));
23349
- const operation = getPlatformOperationForToolCall(name, params);
23350
- if (operation !== undefined) {
23351
- requireOperationConfirmation(ctx, operation, params);
23352
- } else if (isRegisteredPlatformToolName(name)) {
23353
- throw new CliError(unmappedPlatformToolCallMessage(name, params));
23354
- }
23355
- emitValue(ctx, await client(ctx).runTool(name, params));
23356
- return;
23357
- }
23358
- throw new CliError("Usage: uru tools ls|schema|inspect|run");
23359
- }
23360
- function unmappedPlatformToolCallMessage(toolName, params) {
23361
- const op2 = typeof params["op"] === "string" ? params["op"] : undefined;
23362
- const normalizedTool = toolName.trim().toLowerCase();
23363
- const normalizedOp = op2?.trim().toLowerCase();
23364
- if (normalizedTool === "library_fs" && (normalizedOp === "ls" || normalizedOp === "stat" || normalizedOp === "read" || normalizedOp === "search")) {
23365
- return `library_fs does not serve read op=${normalizedOp}. ` + "Use library_query (or `uru library ls|stat|search|read` / " + "`uru operations run library.<op>`).";
23366
- }
23367
- if (normalizedOp !== undefined) {
23368
- return `No registered safety policy for ${toolName} op=${normalizedOp}. ` + "Refuse to execute. Check `uru operations search` for the supported operation id.";
23369
- }
23370
- return `No registered safety policy for ${toolName}. ` + "Refuse to execute without a mapped platform operation.";
23371
- }
23372
23537
  async function dispatchLibrary(ctx, sub, args) {
23373
23538
  if (sub === "ls") {
23374
23539
  const flags = parseLocalFlags(args, ["library", "ls"]);
@@ -24084,18 +24249,6 @@ function copyOptionalFlag(flags, target, flagName, paramName) {
24084
24249
  target[paramName] = value;
24085
24250
  }
24086
24251
  }
24087
- function toolInspectContract(name, schema) {
24088
- if (!isJsonObject2(schema)) {
24089
- return { name, inputSchema: {} };
24090
- }
24091
- const inputSchema = isJsonObject2(schema["inputSchema"]) ? schema["inputSchema"] : schema;
24092
- const description = typeof schema["description"] === "string" ? schema["description"] : undefined;
24093
- return {
24094
- name: typeof schema["name"] === "string" ? schema["name"] : name,
24095
- ...description === undefined ? {} : { description },
24096
- inputSchema
24097
- };
24098
- }
24099
24252
  function familyPrefix(family) {
24100
24253
  if (family === "automations") {
24101
24254
  return "automation";
@@ -25126,8 +25279,275 @@ async function dispatchGemFs(ctx, verb, args, linkedProject) {
25126
25279
  }
25127
25280
  }
25128
25281
 
25282
+ // src/gem-git-source-commands.ts
25283
+ import { randomUUID } from "node:crypto";
25284
+ var GIT_SOURCE_PATH = "/api/v1/artifacts";
25285
+ async function dispatchGemGitSource(ctx, verb, args, linkedProject) {
25286
+ if (verb === undefined || verb === "help") {
25287
+ throw new CliError("Usage: uru gems source status|refresh|retry|cancel|copy|change-source|policy|disconnect <gem> ...");
25288
+ }
25289
+ if ((verb === "cancel" || verb === "disconnect") && !ctx.yes) {
25290
+ throw new CliError(`${verb} is destructive. Re-run with --yes to confirm.`, {
25291
+ errorCode: "confirmation_required",
25292
+ confirmationRequired: true,
25293
+ suggestedCommand: suggestCommandWithYes(ctx.argv)
25294
+ });
25295
+ }
25296
+ const flags = parseLocalFlags(args, ["gems", "source", verb]);
25297
+ const reference = await resolveArtifactReference(ctx, flags, linkedProject);
25298
+ if (verb === "copy") {
25299
+ const result2 = await client(ctx).api(`/api/platform/artifacts/${encodeURIComponent(reference.slug)}/clone`, { method: "POST" });
25300
+ emitValue(ctx, result2, formatCloneResult(result2));
25301
+ return;
25302
+ }
25303
+ if (verb === "status") {
25304
+ const result2 = await client(ctx).api(`${GIT_SOURCE_PATH}/${encodeURIComponent(reference.artifactId)}/git-source`, { method: "GET" });
25305
+ emitValue(ctx, result2, formatGitSourceState(result2));
25306
+ return;
25307
+ }
25308
+ const command = commandFromFlags(verb, flags);
25309
+ const result = await client(ctx).api(`${GIT_SOURCE_PATH}/${encodeURIComponent(reference.artifactId)}/git-source/commands`, { method: "POST", body: command });
25310
+ emitValue(ctx, result, formatGitSourceState(result));
25311
+ }
25312
+ function commandFromFlags(verb, flags) {
25313
+ switch (verb) {
25314
+ case "refresh":
25315
+ return toJsonCommand({ action: "refresh" });
25316
+ case "retry":
25317
+ return toJsonCommand({
25318
+ action: "retry",
25319
+ importId: requiredImportId(flags, "retry"),
25320
+ idempotencyKey: flags.values["--idempotency-key"]?.trim() || randomUUID()
25321
+ });
25322
+ case "cancel":
25323
+ return toJsonCommand({
25324
+ action: "cancel",
25325
+ importId: requiredImportId(flags, "cancel")
25326
+ });
25327
+ case "policy":
25328
+ case "change-policy":
25329
+ return toJsonCommand({
25330
+ action: "change_policy",
25331
+ expectedRevision: requiredInteger(flags, "--expected-revision"),
25332
+ automaticPublishing: requiredBoolean(flags, "--automatic-publishing"),
25333
+ previewsEnabled: requiredBoolean(flags, "--previews-enabled")
25334
+ });
25335
+ case "disconnect":
25336
+ return toJsonCommand({
25337
+ action: "disconnect",
25338
+ expectedRevision: requiredInteger(flags, "--expected-revision")
25339
+ });
25340
+ case "change-source":
25341
+ case "source-change":
25342
+ return toJsonCommand({
25343
+ action: "change_source",
25344
+ expectedRevision: requiredInteger(flags, "--expected-revision"),
25345
+ ref: requiredValue2(flags, "--ref"),
25346
+ appDirectory: requiredValue2(flags, "--app-directory"),
25347
+ commitSha: requiredValue2(flags, "--commit-sha")
25348
+ });
25349
+ default:
25350
+ throw new CliError("Usage: uru gems source status|refresh|retry|cancel|copy|change-source|policy|disconnect <gem> ...");
25351
+ }
25352
+ }
25353
+ async function resolveArtifactReference(ctx, flags, linkedProject) {
25354
+ const reference = gemPathFromArgs(flags, linkedProject);
25355
+ const inspected = await executeOperation(ctx, "gem.inspect", {
25356
+ path: reference
25357
+ });
25358
+ const artifactId = (isUuid(reference) ? reference : undefined) ?? findArtifactId(inspected);
25359
+ if (artifactId === undefined) {
25360
+ throw new CliError(`Gem inspection did not return an artifact id for ${reference}.`);
25361
+ }
25362
+ return {
25363
+ artifactId,
25364
+ slug: findArtifactSlug(inspected) ?? artifactId
25365
+ };
25366
+ }
25367
+ function findArtifactId(value) {
25368
+ if (isJsonObject2(value)) {
25369
+ const id = stringField2(value, [
25370
+ "artifact_id",
25371
+ "artifactId",
25372
+ "gem_id",
25373
+ "gemId"
25374
+ ]);
25375
+ if (id !== undefined && isUuid(id)) {
25376
+ return id;
25377
+ }
25378
+ for (const nested of Object.values(value)) {
25379
+ const found = findArtifactId(nested);
25380
+ if (found !== undefined) {
25381
+ return found;
25382
+ }
25383
+ }
25384
+ return;
25385
+ }
25386
+ if (Array.isArray(value)) {
25387
+ for (const nested of value) {
25388
+ const found = findArtifactId(nested);
25389
+ if (found !== undefined) {
25390
+ return found;
25391
+ }
25392
+ }
25393
+ }
25394
+ return;
25395
+ }
25396
+ function isUuid(value) {
25397
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value);
25398
+ }
25399
+ function findArtifactSlug(value) {
25400
+ if (isJsonObject2(value)) {
25401
+ const slug = stringField2(value, [
25402
+ "artifact_slug",
25403
+ "artifactSlug",
25404
+ "slug"
25405
+ ]);
25406
+ if (slug !== undefined && !slug.includes("/")) {
25407
+ return slug;
25408
+ }
25409
+ for (const nested of Object.values(value)) {
25410
+ const found = findArtifactSlug(nested);
25411
+ if (found !== undefined) {
25412
+ return found;
25413
+ }
25414
+ }
25415
+ return;
25416
+ }
25417
+ if (Array.isArray(value)) {
25418
+ for (const nested of value) {
25419
+ const found = findArtifactSlug(nested);
25420
+ if (found !== undefined) {
25421
+ return found;
25422
+ }
25423
+ }
25424
+ }
25425
+ return;
25426
+ }
25427
+ function requiredImportId(flags, verb) {
25428
+ const importId = flags.values["--import-id"]?.trim();
25429
+ if (!importId) {
25430
+ throw new CliError(`Usage: uru gems source ${verb} [gem] --import-id <import-id>`);
25431
+ }
25432
+ return importId;
25433
+ }
25434
+ function requiredValue2(flags, token) {
25435
+ const value = flags.values[token]?.trim();
25436
+ if (value === undefined || value === "") {
25437
+ throw new CliError(`Missing ${token}.`);
25438
+ }
25439
+ return value;
25440
+ }
25441
+ function requiredInteger(flags, token) {
25442
+ const raw = requiredValue2(flags, token);
25443
+ const parsed = Number(raw);
25444
+ if (!Number.isInteger(parsed) || parsed < 0) {
25445
+ throw new CliError(`${token} must be a non-negative integer.`);
25446
+ }
25447
+ return parsed;
25448
+ }
25449
+ function requiredBoolean(flags, token) {
25450
+ const raw = requiredValue2(flags, token).toLowerCase();
25451
+ if (raw === "true" || raw === "1" || raw === "yes" || raw === "on") {
25452
+ return true;
25453
+ }
25454
+ if (raw === "false" || raw === "0" || raw === "no" || raw === "off") {
25455
+ return false;
25456
+ }
25457
+ throw new CliError(`${token} must be true or false.`);
25458
+ }
25459
+ function toJsonCommand(command) {
25460
+ switch (command.action) {
25461
+ case "refresh":
25462
+ return { action: "refresh" };
25463
+ case "retry":
25464
+ return {
25465
+ action: "retry",
25466
+ importId: command.importId,
25467
+ idempotencyKey: command.idempotencyKey
25468
+ };
25469
+ case "cancel":
25470
+ return { action: "cancel", importId: command.importId };
25471
+ case "change_policy":
25472
+ return {
25473
+ action: "change_policy",
25474
+ expectedRevision: command.expectedRevision,
25475
+ automaticPublishing: command.automaticPublishing,
25476
+ previewsEnabled: command.previewsEnabled
25477
+ };
25478
+ case "publish":
25479
+ return {
25480
+ action: "publish",
25481
+ importId: command.importId,
25482
+ expectedRevision: command.expectedRevision
25483
+ };
25484
+ case "approve_preview":
25485
+ return {
25486
+ action: "approve_preview",
25487
+ previewId: command.previewId,
25488
+ headSha: command.headSha,
25489
+ resourcePolicyHash: command.resourcePolicyHash
25490
+ };
25491
+ case "expire_preview":
25492
+ return { action: "expire_preview", previewId: command.previewId };
25493
+ case "disconnect":
25494
+ return {
25495
+ action: "disconnect",
25496
+ expectedRevision: command.expectedRevision
25497
+ };
25498
+ case "reconnect":
25499
+ return {
25500
+ action: "reconnect",
25501
+ expectedRevision: command.expectedRevision
25502
+ };
25503
+ case "change_source":
25504
+ return {
25505
+ action: "change_source",
25506
+ expectedRevision: command.expectedRevision,
25507
+ ref: command.ref,
25508
+ appDirectory: command.appDirectory,
25509
+ commitSha: command.commitSha
25510
+ };
25511
+ }
25512
+ throw new CliError("Unsupported Git source command.");
25513
+ }
25514
+ function formatGitSourceState(value) {
25515
+ if (!isJsonObject2(value)) {
25516
+ return JSON.stringify(value, null, 2);
25517
+ }
25518
+ const artifactId = stringField2(value, ["artifactId", "artifact_id"]);
25519
+ const binding = isJsonObject2(value["binding"]) ? value["binding"] : undefined;
25520
+ const policy = isJsonObject2(value["policy"]) ? value["policy"] : undefined;
25521
+ const lines = [
25522
+ artifactId === undefined ? undefined : `Artifact: ${artifactId}`,
25523
+ binding === undefined ? "Source binding: none" : `Source binding: ${binding["active"] === true ? "active" : "inactive"}`,
25524
+ binding === undefined ? undefined : stringField2(binding, ["ref"]) === undefined ? undefined : `Ref: ${stringField2(binding, ["ref"])}`,
25525
+ binding === undefined ? undefined : stringField2(binding, ["currentCommitSha", "current_commit_sha"]) === undefined ? undefined : `Current commit: ${stringField2(binding, [
25526
+ "currentCommitSha",
25527
+ "current_commit_sha"
25528
+ ])}`,
25529
+ policy === undefined ? undefined : `Policy: automatic publishing ${policy["automaticPublishing"] === true ? "on" : "off"}, previews ${policy["previewsEnabled"] === true ? "on" : "off"}`
25530
+ ].filter((line) => line !== undefined);
25531
+ return lines.length === 0 ? JSON.stringify(value, null, 2) : lines.join(`
25532
+ `);
25533
+ }
25534
+ function formatCloneResult(value) {
25535
+ const record2 = isJsonObject2(value) ? value : undefined;
25536
+ const artifactId = record2 === undefined ? undefined : stringField2(record2, ["id", "artifactId", "artifact_id"]);
25537
+ const title = record2 === undefined ? undefined : stringField2(record2, ["title"]);
25538
+ if (artifactId === undefined && title === undefined) {
25539
+ return JSON.stringify(value, null, 2);
25540
+ }
25541
+ return [
25542
+ "Gem copied.",
25543
+ artifactId === undefined ? undefined : `Artifact: ${artifactId}`,
25544
+ title === undefined ? undefined : `Title: ${title}`
25545
+ ].filter((line) => line !== undefined).join(`
25546
+ `);
25547
+ }
25548
+
25129
25549
  // src/held-idempotency-keys.ts
25130
- import { createHash as createHash3, randomUUID } from "node:crypto";
25550
+ import { createHash as createHash3, randomUUID as randomUUID2 } from "node:crypto";
25131
25551
  import { constants } from "node:fs";
25132
25552
  import { chmod, link, mkdir as mkdir3, open, readFile as readFile7, rename, rm as rm2 } from "node:fs/promises";
25133
25553
  import { dirname as dirname4, join as join2 } from "node:path";
@@ -25142,7 +25562,7 @@ async function holdIdempotencyKey(io, stateHome, intent, now = new Date) {
25142
25562
  return withHeldEntriesLock(path, async () => {
25143
25563
  const stored = await readHeldEntries(path);
25144
25564
  const existing = stored[fingerprint];
25145
- const owner = `${process.pid}:${randomUUID()}`;
25565
+ const owner = `${process.pid}:${randomUUID2()}`;
25146
25566
  if (existing !== undefined) {
25147
25567
  const updated = {
25148
25568
  ...existing,
@@ -25154,7 +25574,7 @@ async function holdIdempotencyKey(io, stateHome, intent, now = new Date) {
25154
25574
  release: () => releaseHold(io, path, fingerprint, existing.key, owner)
25155
25575
  };
25156
25576
  }
25157
- const key = randomUUID();
25577
+ const key = randomUUID2();
25158
25578
  await writeHeldEntries(path, {
25159
25579
  ...stored,
25160
25580
  [fingerprint]: { key, heldAt: now.toISOString(), owners: [owner] }
@@ -25264,7 +25684,7 @@ async function withHeldEntriesLock(path, action) {
25264
25684
  continue;
25265
25685
  }
25266
25686
  try {
25267
- const lockToken = randomUUID();
25687
+ const lockToken = randomUUID2();
25268
25688
  const contents = `${process.pid}
25269
25689
  ${lockToken}
25270
25690
  `;
@@ -25347,7 +25767,7 @@ async function tryCreateHeldLock(path) {
25347
25767
  }
25348
25768
  try {
25349
25769
  const contents = `${process.pid}
25350
- ${randomUUID()}
25770
+ ${randomUUID2()}
25351
25771
  `;
25352
25772
  await handle.writeFile(contents, "utf8");
25353
25773
  await handle.sync();
@@ -25406,7 +25826,7 @@ async function readHeldLockObservation(path) {
25406
25826
  }
25407
25827
  }
25408
25828
  async function removeHeldLockIfMatches(path, expected) {
25409
- const quarantinePath = `${path}.${process.pid}.${randomUUID()}.quarantine`;
25829
+ const quarantinePath = `${path}.${process.pid}.${randomUUID2()}.quarantine`;
25410
25830
  try {
25411
25831
  await rename(path, quarantinePath);
25412
25832
  await syncParentDirectory(path);
@@ -25466,7 +25886,7 @@ async function writeHeldEntries(path, entries) {
25466
25886
  }
25467
25887
  const body = `${JSON.stringify(entries, null, 2)}
25468
25888
  `;
25469
- const temporaryPath = `${path}.${process.pid}.${randomUUID()}.tmp`;
25889
+ const temporaryPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
25470
25890
  let handle = null;
25471
25891
  try {
25472
25892
  handle = await open(temporaryPath, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, constants.S_IRUSR | constants.S_IWUSR);
@@ -26309,6 +26729,11 @@ async function dispatchGems(ctx, sub, args, linkedProject) {
26309
26729
  await dispatchGemFs(ctx, verb, rest, linkedProject);
26310
26730
  return;
26311
26731
  }
26732
+ if (sub === "source") {
26733
+ const [verb, ...rest] = args;
26734
+ await dispatchGemGitSource(ctx, verb, rest, linkedProject);
26735
+ return;
26736
+ }
26312
26737
  if (await dispatchGemLifecycleResourceCommand(ctx, sub, args, linkedProject)) {
26313
26738
  return;
26314
26739
  }
@@ -26348,7 +26773,7 @@ async function dispatchGems(ctx, sub, args, linkedProject) {
26348
26773
  await gemWrite(ctx, args);
26349
26774
  return;
26350
26775
  }
26351
- throw new CliError("Usage: uru gems inspect|validate|build|deploy|verify|fs|edits|versions|releases|links|runs|capabilities|schedules|bindings|components|delegations|api|domains|analytics|power|checks|status|read|write");
26776
+ throw new CliError("Usage: uru gems inspect|validate|build|deploy|verify|fs|edits|versions|releases|links|runs|capabilities|schedules|bindings|components|delegations|api|domains|analytics|power|checks|status|source|read|write");
26352
26777
  }
26353
26778
  async function dispatchGemOperationalResourceCommand(ctx, sub, args, linkedProject) {
26354
26779
  if (sub === "domains") {
@@ -27647,6 +28072,892 @@ function safeProjectPath(path) {
27647
28072
  return parts.join("/");
27648
28073
  }
27649
28074
 
28075
+ // src/tools-catalog.ts
28076
+ function isPlatformTool(tool) {
28077
+ return tool.type === "platform";
28078
+ }
28079
+ var TOOL_REFUSALS = {
28080
+ noMatch: "no_match",
28081
+ catalogUnavailable: "catalog_unavailable",
28082
+ namespaceUnavailable: "namespace_unavailable",
28083
+ notConnected: "not_connected",
28084
+ notAdopted: "not_adopted",
28085
+ approvalRequired: "approval_required",
28086
+ refusedByPolicy: "refused_by_policy"
28087
+ };
28088
+ function refuse(errorCode, message, suggestedCommand, cause) {
28089
+ const wrapped = cause instanceof CliError ? cause : undefined;
28090
+ return new CliError(message, {
28091
+ errorCode,
28092
+ ...suggestedCommand === undefined ? {} : { suggestedCommand },
28093
+ ...wrapped?.errorId === undefined ? {} : { errorId: wrapped.errorId },
28094
+ ...wrapped?.httpStatus === undefined ? {} : { httpStatus: wrapped.httpStatus },
28095
+ ...cause === undefined ? {} : { cause }
28096
+ });
28097
+ }
28098
+ function stringField5(row, key) {
28099
+ const value = row[key];
28100
+ return typeof value === "string" ? value : "";
28101
+ }
28102
+ function nullableStringField(row, key) {
28103
+ const value = row[key];
28104
+ return typeof value === "string" && value.trim() !== "" ? value : null;
28105
+ }
28106
+ function objectField(row, key) {
28107
+ const value = row[key];
28108
+ return isJsonObject(value) ? value : null;
28109
+ }
28110
+ function readCatalogRow(value) {
28111
+ if (!isJsonObject(value)) {
28112
+ return null;
28113
+ }
28114
+ const address = stringField5(value, "address");
28115
+ const owner = stringField5(value, "owner");
28116
+ const type = stringField5(value, "type");
28117
+ const parsed = parseToolAddress(address);
28118
+ if (parsed === null || !isToolOwnerClass(owner) || parsed.owner !== owner || !isToolConnectionType(type)) {
28119
+ return null;
28120
+ }
28121
+ const { provider, connection, tool } = parsed;
28122
+ const namespace = nullableStringField(value, "agentVisibleNamespace") ?? stringField5(value, "namespace");
28123
+ const subtool = stringField5(value, "normalizedSubtoolName");
28124
+ return {
28125
+ address,
28126
+ owner,
28127
+ type,
28128
+ typeLabel: toolConnectionTypeLabel(type),
28129
+ provider,
28130
+ connection,
28131
+ tool,
28132
+ flatName: type === "platform" ? subtool.toLowerCase() : `${namespace}__${tool}`,
28133
+ namespace,
28134
+ wrapperToolName: stringField5(value, "wrapperToolName"),
28135
+ providerNativeToolName: stringField5(value, "providerNativeToolName") || subtool,
28136
+ capabilityId: stringField5(value, "capabilityId"),
28137
+ connectionId: stringField5(value, "connectionId"),
28138
+ connectionLabel: stringField5(value, "displayName") || connection,
28139
+ description: stringField5(value, "description"),
28140
+ title: nullableStringField(value, "title"),
28141
+ category: nullableStringField(value, "category"),
28142
+ writeClass: stringField5(value, "classification") || "unknown",
28143
+ enabled: value["enabled"] !== false,
28144
+ inputSchema: objectField(value, "inputSchema") ?? {},
28145
+ outputSchema: objectField(value, "outputSchema")
28146
+ };
28147
+ }
28148
+ async function fetchToolCatalog(ctx) {
28149
+ let body;
28150
+ try {
28151
+ body = await client(ctx).api("/api/tool-control-plane/catalog", {
28152
+ method: "GET"
28153
+ });
28154
+ } catch (error2) {
28155
+ throw refuse(TOOL_REFUSALS.catalogUnavailable, `The tool catalog could not be read: ${error2 instanceof Error ? error2.message : String(error2)}`, "uru doctor", error2);
28156
+ }
28157
+ if (!Array.isArray(body)) {
28158
+ throw refuse(TOOL_REFUSALS.catalogUnavailable, "The tool catalog answered with something other than a list of tools.");
28159
+ }
28160
+ const tools = [];
28161
+ for (const row of body) {
28162
+ const tool = readCatalogRow(row);
28163
+ if (tool !== null) {
28164
+ tools.push(tool);
28165
+ }
28166
+ }
28167
+ return tools.sort((left, right) => left.address.localeCompare(right.address));
28168
+ }
28169
+ function filterTools(tools, filters) {
28170
+ const namespace = filters.namespace?.trim().toLowerCase();
28171
+ const owner = filters.owner?.trim().toLowerCase();
28172
+ const type = filters.type?.trim().toLowerCase();
28173
+ const category = filters.category?.trim().toLowerCase();
28174
+ return tools.filter((tool) => {
28175
+ if (namespace !== undefined && namespace !== "") {
28176
+ if (tool.namespace.toLowerCase() !== namespace && tool.provider.toLowerCase() !== namespace) {
28177
+ return false;
28178
+ }
28179
+ }
28180
+ if (owner !== undefined && owner !== "" && tool.owner !== owner) {
28181
+ return false;
28182
+ }
28183
+ if (type !== undefined && type !== "" && tool.type !== type) {
28184
+ return false;
28185
+ }
28186
+ if (category !== undefined && category !== "" && (tool.category ?? "").toLowerCase() !== category) {
28187
+ return false;
28188
+ }
28189
+ return true;
28190
+ });
28191
+ }
28192
+ function candidateNames(tool) {
28193
+ return [
28194
+ tool.address.toLowerCase(),
28195
+ tool.flatName.toLowerCase(),
28196
+ tool.wrapperToolName.toLowerCase(),
28197
+ tool.providerNativeToolName.toLowerCase(),
28198
+ tool.tool.toLowerCase()
28199
+ ];
28200
+ }
28201
+ function resolveTool(tools, query) {
28202
+ const wanted = query.trim().toLowerCase();
28203
+ if (wanted === "") {
28204
+ return { kind: "no_match", near: [] };
28205
+ }
28206
+ for (let rank = 0;rank < 5; rank += 1) {
28207
+ const matches = tools.filter((tool) => candidateNames(tool)[rank] === wanted);
28208
+ if (matches.length === 1) {
28209
+ return { kind: "exact", tool: matches[0] };
28210
+ }
28211
+ if (matches.length > 1) {
28212
+ return { kind: "ambiguous", matches };
28213
+ }
28214
+ }
28215
+ return { kind: "no_match", near: nearAddresses(tools, wanted) };
28216
+ }
28217
+ function nearAddresses(tools, query, limit = 5) {
28218
+ const parts = query.toLowerCase().split(/[.\s_]+/u).filter((part) => part.length > 1);
28219
+ if (parts.length === 0) {
28220
+ return [];
28221
+ }
28222
+ const scored = tools.map((tool) => {
28223
+ const haystack = `${tool.address} ${tool.flatName}`.toLowerCase();
28224
+ const hits = parts.filter((part) => haystack.includes(part)).length;
28225
+ return { tool, hits };
28226
+ }).filter((entry) => entry.hits > 0);
28227
+ scored.sort((left, right) => right.hits - left.hits || left.tool.address.localeCompare(right.tool.address));
28228
+ return scored.slice(0, limit).map((entry) => entry.tool);
28229
+ }
28230
+ function noMatchError(query, near) {
28231
+ const suffix = near.length === 0 ? " Run `uru tools ls` to see every tool you can reach." : ` Did you mean: ${near.map((tool) => tool.address).join(", ")}?`;
28232
+ return refuse(TOOL_REFUSALS.noMatch, `No tool answers ${JSON.stringify(query)}.${suffix}`, "uru tools search");
28233
+ }
28234
+ async function fetchUnadoptedOffers(ctx) {
28235
+ let body;
28236
+ try {
28237
+ body = await client(ctx).api("/api/tool-control-plane/connection-offers", {
28238
+ method: "GET"
28239
+ });
28240
+ } catch {
28241
+ return [];
28242
+ }
28243
+ if (!Array.isArray(body)) {
28244
+ return [];
28245
+ }
28246
+ const offers = [];
28247
+ for (const row of body) {
28248
+ if (!isJsonObject(row))
28249
+ continue;
28250
+ const adoption = row["currentAdoptionStatus"];
28251
+ if (row["canAdopt"] !== true || adoption === "pending" || adoption === "approved") {
28252
+ continue;
28253
+ }
28254
+ const offerId = row["id"];
28255
+ const label = row["connectionDisplayName"];
28256
+ const service = row["connectionServiceName"];
28257
+ if (typeof offerId !== "string" || typeof label !== "string" || typeof service !== "string") {
28258
+ continue;
28259
+ }
28260
+ offers.push({ offerId, label, service: service.toLowerCase() });
28261
+ }
28262
+ return offers.sort((left, right) => left.label.localeCompare(right.label));
28263
+ }
28264
+ function offersMatching(offers, query) {
28265
+ const terms = query.toLowerCase().split(/[^a-z0-9]+/u).filter((term) => term.length > 1);
28266
+ if (terms.length === 0) {
28267
+ return [];
28268
+ }
28269
+ return offers.filter((offer) => {
28270
+ const haystack = `${offer.service} ${offer.label}`.toLowerCase();
28271
+ return terms.some((term) => haystack.includes(term));
28272
+ });
28273
+ }
28274
+ function notAdoptedError(query, offers) {
28275
+ const labels = offers.map((offer) => offer.label).join(", ");
28276
+ return refuse(TOOL_REFUSALS.notAdopted, `No tool answers ${JSON.stringify(query)} yet. This workspace offers ` + `${labels}, and you have not adopted it. Ask the owner or an admin ` + "to approve the adoption.", "uru tools connections");
28277
+ }
28278
+ function ambiguousError(query, matches) {
28279
+ return refuse(TOOL_REFUSALS.noMatch, `${JSON.stringify(query)} names ${String(matches.length)} tools. Use one full address: ${matches.slice(0, 5).map((tool) => tool.address).join(", ")}.`, "uru tools ls");
28280
+ }
28281
+ function searchTools(tools, query, limit, offset = 0) {
28282
+ const scored = tools.map((tool) => ({
28283
+ tool,
28284
+ score: scoreToolRelevance({
28285
+ address: tool.address,
28286
+ tool: tool.tool,
28287
+ title: tool.title,
28288
+ description: tool.description,
28289
+ category: tool.category
28290
+ }, query)
28291
+ })).filter((entry) => entry.score > 0);
28292
+ scored.sort((left, right) => right.score - left.score || left.tool.address.localeCompare(right.tool.address));
28293
+ return { hits: scored.slice(offset, offset + limit), total: scored.length, offset };
28294
+ }
28295
+ function summarizeConnections(tools) {
28296
+ const byConnection = new Map;
28297
+ for (const tool of tools) {
28298
+ const key = `${tool.provider}.${tool.owner}.${tool.connection}`;
28299
+ const existing = byConnection.get(key);
28300
+ byConnection.set(key, {
28301
+ connection: key,
28302
+ owner: tool.owner,
28303
+ provider: tool.provider,
28304
+ type: tool.type,
28305
+ typeLabel: tool.typeLabel,
28306
+ label: existing?.label ?? tool.connectionLabel,
28307
+ connectionId: existing?.connectionId ?? tool.connectionId,
28308
+ toolCount: (existing?.toolCount ?? 0) + 1,
28309
+ disabledToolCount: (existing?.disabledToolCount ?? 0) + (tool.enabled ? 0 : 1)
28310
+ });
28311
+ }
28312
+ return [...byConnection.values()].sort((left, right) => left.connection.localeCompare(right.connection));
28313
+ }
28314
+ function addressBranches(tools, prefix) {
28315
+ const wanted = prefix.trim().toLowerCase().replace(/\.+$/u, "");
28316
+ const depth = wanted === "" ? 0 : wanted.split(".").length;
28317
+ const branches = new Map;
28318
+ for (const tool of tools) {
28319
+ const segments = tool.address.split(".");
28320
+ if (wanted !== "" && !tool.address.toLowerCase().startsWith(`${wanted}.`)) {
28321
+ continue;
28322
+ }
28323
+ const segment = segments[depth];
28324
+ if (segment === undefined) {
28325
+ continue;
28326
+ }
28327
+ const existing = branches.get(segment);
28328
+ branches.set(segment, {
28329
+ count: (existing?.count ?? 0) + 1,
28330
+ leaf: depth === segments.length - 1
28331
+ });
28332
+ }
28333
+ return [...branches.entries()].map(([segment, value]) => ({
28334
+ segment,
28335
+ prefix: wanted === "" ? segment : `${wanted}.${segment}`,
28336
+ toolCount: value.count,
28337
+ leaf: value.leaf
28338
+ })).sort((left, right) => left.segment.localeCompare(right.segment));
28339
+ }
28340
+
28341
+ // src/tools-mcp.ts
28342
+ var MCP_PROTOCOL_VERSION = "2026-07-28";
28343
+ var PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion";
28344
+ var CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities";
28345
+ var CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo";
28346
+ function encodeMcpParamValue(value) {
28347
+ if (/^[ -~]*$/u.test(value)) {
28348
+ return value;
28349
+ }
28350
+ return `=?base64?${Buffer.from(value, "utf8").toString("base64")}?=`;
28351
+ }
28352
+ function resolveMcpUrl(args) {
28353
+ const chosen = args.explicit?.trim() || mcpProxyUrlFromApiUrl(args.apiUrl);
28354
+ if (chosen === undefined || chosen === "") {
28355
+ throw new CliError(`No MCP proxy is known for ${args.apiUrl}. Pass --mcp-url <url> to name one.`, { errorCode: "namespace_unavailable" });
28356
+ }
28357
+ const url2 = new URL(chosen);
28358
+ if (url2.pathname === "" || url2.pathname === "/") {
28359
+ url2.pathname = "/mcp";
28360
+ }
28361
+ return url2.toString().replace(/\/$/u, "");
28362
+ }
28363
+
28364
+ class McpCallError extends Error {
28365
+ status;
28366
+ data;
28367
+ constructor(message, status, data) {
28368
+ super(message);
28369
+ this.name = "McpCallError";
28370
+ this.status = status;
28371
+ this.data = data ?? null;
28372
+ }
28373
+ }
28374
+ function errorBodyOf(text) {
28375
+ try {
28376
+ const parsed = JSON.parse(text);
28377
+ return isJsonObject(parsed) ? parsed : null;
28378
+ } catch {
28379
+ return null;
28380
+ }
28381
+ }
28382
+ function parseEventStream(body) {
28383
+ let last;
28384
+ for (const rawEvent of body.split(/\n\n/u)) {
28385
+ const data = rawEvent.split(`
28386
+ `).filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("");
28387
+ if (data === "") {
28388
+ continue;
28389
+ }
28390
+ last = JSON.parse(data);
28391
+ }
28392
+ if (last === undefined) {
28393
+ throw new McpCallError("The MCP event stream carried no data frame.");
28394
+ }
28395
+ return last;
28396
+ }
28397
+ async function mcpRequest(options, method, params) {
28398
+ const headers = {
28399
+ "content-type": "application/json",
28400
+ accept: "application/json, text/event-stream",
28401
+ authorization: `Bearer ${options.token}`,
28402
+ "mcp-protocol-version": MCP_PROTOCOL_VERSION,
28403
+ "mcp-method": method
28404
+ };
28405
+ const name = params["name"];
28406
+ if (method === "tools/call" && typeof name === "string") {
28407
+ headers["mcp-name"] = encodeMcpParamValue(name);
28408
+ }
28409
+ if (options.workspaceId !== undefined && options.workspaceId !== "") {
28410
+ headers["x-uru-workspace-id"] = options.workspaceId;
28411
+ }
28412
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? 60000);
28413
+ const signal = options.signal === undefined ? timeout : AbortSignal.any([options.signal, timeout]);
28414
+ const response = await options.fetchImpl(options.url, {
28415
+ method: "POST",
28416
+ headers,
28417
+ signal,
28418
+ body: JSON.stringify({
28419
+ jsonrpc: "2.0",
28420
+ id: 1,
28421
+ method,
28422
+ params: {
28423
+ ...params,
28424
+ _meta: {
28425
+ [PROTOCOL_VERSION_META_KEY]: MCP_PROTOCOL_VERSION,
28426
+ [CLIENT_CAPABILITIES_META_KEY]: {},
28427
+ [CLIENT_INFO_META_KEY]: { name: "uru-cli", version: "1" }
28428
+ }
28429
+ }
28430
+ })
28431
+ });
28432
+ const text = await response.text();
28433
+ if (!response.ok) {
28434
+ throw new McpCallError(`MCP ${method} failed with HTTP ${String(response.status)}: ${text.slice(0, 500)}`, response.status, errorBodyOf(text));
28435
+ }
28436
+ const contentType = response.headers.get("content-type") ?? "";
28437
+ const payload = contentType.includes("text/event-stream") ? parseEventStream(text) : JSON.parse(text);
28438
+ const envelope = Array.isArray(payload) ? payload[0] : payload;
28439
+ if (!isJsonObject(envelope)) {
28440
+ throw new McpCallError(`MCP ${method} returned a non-object response.`);
28441
+ }
28442
+ const error2 = envelope["error"];
28443
+ if (isJsonObject(error2)) {
28444
+ const code = error2["code"];
28445
+ const message = error2["message"];
28446
+ const data = error2["data"];
28447
+ throw new McpCallError(`MCP ${method} error ${typeof code === "number" ? String(code) : "unknown"}: ${typeof message === "string" ? message : "no message"}`, undefined, isJsonObject(data) ? data : null);
28448
+ }
28449
+ return envelope["result"] ?? null;
28450
+ }
28451
+ async function callConnectorTool(options, call) {
28452
+ return mcpRequest(options, "tools/call", {
28453
+ name: call.wrapperToolName,
28454
+ arguments: {
28455
+ tool_name: call.providerNativeToolName,
28456
+ parameters: call.parameters
28457
+ }
28458
+ });
28459
+ }
28460
+
28461
+ // src/tools-commands.ts
28462
+ var TOOLS_SUBCOMMANDS = new Set([
28463
+ "search",
28464
+ "ls",
28465
+ "list",
28466
+ "schema",
28467
+ "inspect",
28468
+ "run",
28469
+ "connections",
28470
+ "approve"
28471
+ ]);
28472
+ function parseParkedCallEnvelope(value) {
28473
+ if (!isJsonObject(value)) {
28474
+ return null;
28475
+ }
28476
+ const nested = value["parked"];
28477
+ if (isJsonObject(nested)) {
28478
+ return parseParkedCallEnvelope(nested);
28479
+ }
28480
+ const read = readToolRefusalEnvelope(value);
28481
+ return read.outcome === "envelope" ? read.envelope : null;
28482
+ }
28483
+ function parkedLines(envelope) {
28484
+ return [
28485
+ `Status ${envelope.status}`,
28486
+ `Question id ${envelope.questionId ?? "(none — nothing to approve)"}`,
28487
+ ...envelope.toolName === undefined ? [] : [`Tool ${envelope.toolName}`],
28488
+ `Write class ${envelope.writeClass}`,
28489
+ ...envelope.questionId === null ? [] : [
28490
+ `Answer it uru tools approve ${envelope.questionId} --accept`,
28491
+ ` uru tools approve ${envelope.questionId} --decline`
28492
+ ]
28493
+ ];
28494
+ }
28495
+ function isToolsAddressDrillDown(command) {
28496
+ const [first, second] = command;
28497
+ return first === "tools" && second !== undefined && !second.startsWith("-") && !TOOLS_SUBCOMMANDS.has(second);
28498
+ }
28499
+ function limitFlag(flags, fallback) {
28500
+ const raw = flags.values["--limit"];
28501
+ if (raw === undefined) {
28502
+ return fallback;
28503
+ }
28504
+ const parsed = Number.parseInt(raw, 10);
28505
+ if (!Number.isInteger(parsed) || parsed < 1) {
28506
+ throw new CliError("--limit must be a positive whole number.");
28507
+ }
28508
+ return parsed;
28509
+ }
28510
+ function offsetFlag(flags) {
28511
+ const raw = flags.values["--offset"];
28512
+ if (raw === undefined) {
28513
+ return 0;
28514
+ }
28515
+ const parsed = Number.parseInt(raw, 10);
28516
+ if (!Number.isInteger(parsed) || parsed < 0) {
28517
+ throw new CliError("--offset must be a whole number of 0 or more.");
28518
+ }
28519
+ return parsed;
28520
+ }
28521
+ function ownerFlag(flags) {
28522
+ const owner = flags.values["--owner"];
28523
+ if (owner === undefined) {
28524
+ return;
28525
+ }
28526
+ if (!TOOL_OWNER_CLASSES.includes(owner)) {
28527
+ throw new CliError(`--owner must be one of ${TOOL_OWNER_CLASSES.join(", ")}. ` + "It says whose connection a tool runs on; use --type for what kind it is.");
28528
+ }
28529
+ return owner;
28530
+ }
28531
+ function typeFlag(flags) {
28532
+ const type = flags.values["--type"];
28533
+ if (type === undefined) {
28534
+ return;
28535
+ }
28536
+ if (!TOOL_CONNECTION_TYPES.includes(type)) {
28537
+ throw new CliError(`--type must be one of ${TOOL_CONNECTION_TYPES.join(", ")}.`);
28538
+ }
28539
+ return type;
28540
+ }
28541
+ function catalogFilters(flags) {
28542
+ return {
28543
+ namespace: flags.values["--namespace"],
28544
+ owner: ownerFlag(flags),
28545
+ type: typeFlag(flags),
28546
+ category: flags.values["--category"]
28547
+ };
28548
+ }
28549
+ async function toolParameters(ctx, flags) {
28550
+ const raw = flags.values["--params-json"];
28551
+ if (raw === undefined) {
28552
+ return {};
28553
+ }
28554
+ const text = await readParamsJsonSource(raw, {
28555
+ cwd: ctx.cwd,
28556
+ ...ctx.stdin === undefined ? {} : { stdin: ctx.stdin }
28557
+ });
28558
+ return parseParamsJson(text, "--params-json");
28559
+ }
28560
+ function toolRow(tool) {
28561
+ const write = tool.writeClass === "unknown" ? "?" : tool.writeClass;
28562
+ const summary = tool.description.split(`
28563
+ `)[0]?.trim() ?? "";
28564
+ const kind = tool.category === null ? tool.typeLabel : `${tool.typeLabel}/${tool.category}`;
28565
+ return ` ${tool.address} [${write}] ${kind} ${summary}`;
28566
+ }
28567
+ function toolJson(tool) {
28568
+ return {
28569
+ address: tool.address,
28570
+ owner: tool.owner,
28571
+ type: tool.type,
28572
+ typeLabel: tool.typeLabel,
28573
+ provider: tool.provider,
28574
+ connection: tool.connection,
28575
+ connectionLabel: tool.connectionLabel,
28576
+ tool: tool.tool,
28577
+ flatName: tool.flatName,
28578
+ writeClass: tool.writeClass,
28579
+ enabled: tool.enabled,
28580
+ description: tool.description,
28581
+ ...tool.title === null ? {} : { title: tool.title },
28582
+ ...tool.category === null ? {} : { category: tool.category }
28583
+ };
28584
+ }
28585
+ function groupedListing(tools) {
28586
+ if (tools.length === 0) {
28587
+ return "No tools. Connect an app in Uru, or ask for access to a workspace offer.";
28588
+ }
28589
+ const lines = [];
28590
+ let currentConnection = "";
28591
+ for (const tool of tools) {
28592
+ const connection = `${tool.provider}.${tool.owner}.${tool.connection}`;
28593
+ if (connection !== currentConnection) {
28594
+ currentConnection = connection;
28595
+ lines.push(`${connection} ${tool.typeLabel} (${tool.connectionLabel})`);
28596
+ }
28597
+ lines.push(toolRow(tool));
28598
+ }
28599
+ lines.push("");
28600
+ lines.push(`${String(tools.length)} tools.`);
28601
+ return lines.join(`
28602
+ `);
28603
+ }
28604
+ async function toolsSearch(ctx, args) {
28605
+ const flags = parseLocalFlags(args, ["tools", "search"]);
28606
+ const query = flags.positionals.join(" ").trim();
28607
+ if (query === "") {
28608
+ throw new CliError('Usage: uru tools search "<task>" [--limit N] [--offset N]');
28609
+ }
28610
+ const catalog = filterTools(await fetchToolCatalog(ctx), catalogFilters(flags));
28611
+ const limit = limitFlag(flags, 20);
28612
+ const offset = offsetFlag(flags);
28613
+ const { hits, total } = searchTools(catalog, query, limit, offset);
28614
+ if (total === 0) {
28615
+ const offers = offersMatching(await fetchUnadoptedOffers(ctx), query);
28616
+ if (offers.length > 0) {
28617
+ throw notAdoptedError(query, offers);
28618
+ }
28619
+ throw noMatchError(query, []);
28620
+ }
28621
+ const nextOffset = offset + hits.length;
28622
+ const hasMore = nextOffset < total;
28623
+ emitValue(ctx, {
28624
+ hits: hits.map((hit) => ({ ...toolJson(hit.tool), score: hit.score })),
28625
+ total,
28626
+ hasMore,
28627
+ nextOffset
28628
+ }, [
28629
+ ...hits.map((hit) => `${toolRow(hit.tool)} (score ${String(hit.score)})`),
28630
+ "",
28631
+ `${String(hits.length)} of ${String(total)} matches${offset > 0 ? `, from ${String(offset)}` : ""}.${hasMore ? ` Next page: --offset ${String(nextOffset)}.` : ""}`
28632
+ ].join(`
28633
+ `));
28634
+ }
28635
+ async function toolsList(ctx, args) {
28636
+ const flags = parseLocalFlags(args, ["tools", "ls"]);
28637
+ const tools = filterTools(await fetchToolCatalog(ctx), catalogFilters(flags));
28638
+ emitValue(ctx, tools.map(toolJson), groupedListing(tools));
28639
+ }
28640
+ async function toolsConnections(ctx, args) {
28641
+ const flags = parseLocalFlags(args, ["tools", "connections"]);
28642
+ const tools = filterTools(await fetchToolCatalog(ctx), {
28643
+ owner: ownerFlag(flags),
28644
+ type: typeFlag(flags)
28645
+ });
28646
+ const connections = summarizeConnections(tools);
28647
+ emitValue(ctx, connections.map((connection) => ({ ...connection })), connections.length === 0 ? "No connections." : connections.map((connection) => ` ${connection.connection} ${connection.typeLabel} ${connection.label} ${String(connection.toolCount)} tools${connection.disabledToolCount > 0 ? ` (${String(connection.disabledToolCount)} disabled)` : ""}`).join(`
28648
+ `));
28649
+ }
28650
+ async function requireTool(ctx, query) {
28651
+ const catalog = await fetchToolCatalog(ctx);
28652
+ const resolution = resolveTool(catalog, query);
28653
+ if (resolution.kind === "no_match") {
28654
+ throw noMatchError(query, resolution.near);
28655
+ }
28656
+ if (resolution.kind === "ambiguous") {
28657
+ throw ambiguousError(query, resolution.matches);
28658
+ }
28659
+ return { tool: resolution.tool, catalog };
28660
+ }
28661
+ async function toolSchemaAnswer(ctx, tool) {
28662
+ if (isPlatformTool(tool)) {
28663
+ return client(ctx).getToolSchema(tool.tool);
28664
+ }
28665
+ return {
28666
+ address: tool.address,
28667
+ inputSchema: tool.inputSchema,
28668
+ outputSchema: tool.outputSchema
28669
+ };
28670
+ }
28671
+ async function toolsSchema(ctx, args) {
28672
+ const flags = parseLocalFlags(args, ["tools", "schema"]);
28673
+ const query = flags.positionals[0];
28674
+ if (query === undefined) {
28675
+ throw new CliError("Usage: uru tools schema <address>");
28676
+ }
28677
+ const { tool } = await requireTool(ctx, query);
28678
+ emitValue(ctx, await toolSchemaAnswer(ctx, tool));
28679
+ }
28680
+ function policyAnswer(tool) {
28681
+ return {
28682
+ owner: tool.owner,
28683
+ connection: `${tool.provider}.${tool.owner}.${tool.connection}`,
28684
+ connectionLabel: tool.connectionLabel,
28685
+ type: tool.type,
28686
+ typeLabel: tool.typeLabel,
28687
+ category: tool.category,
28688
+ writeClass: tool.writeClass,
28689
+ namespaceServing: tool.enabled,
28690
+ execution: isPlatformTool(tool) ? "platform_execute_route" : "mcp_proxy_tools_call"
28691
+ };
28692
+ }
28693
+ async function toolsInspect(ctx, args) {
28694
+ const flags = parseLocalFlags(args, ["tools", "inspect"]);
28695
+ const query = flags.positionals[0];
28696
+ if (query === undefined) {
28697
+ throw new CliError("Usage: uru tools inspect <address>");
28698
+ }
28699
+ const { tool } = await requireTool(ctx, query);
28700
+ const answer = policyAnswer(tool);
28701
+ emitValue(ctx, { ...toolJson(tool), policy: answer }, [
28702
+ `Address ${tool.address}`,
28703
+ `Owner ${tool.owner}`,
28704
+ `Connection ${tool.connectionLabel} (${tool.connection})`,
28705
+ `Type ${tool.typeLabel} (${tool.type})`,
28706
+ `Category ${tool.category ?? "-"}`,
28707
+ `Write class ${tool.writeClass}`,
28708
+ `Serving ${tool.enabled ? "yes" : "no"}`,
28709
+ `Runs through ${String(answer["execution"])}`,
28710
+ `Flat name ${tool.flatName}`,
28711
+ "",
28712
+ tool.description
28713
+ ].join(`
28714
+ `));
28715
+ }
28716
+ function assertServing(tool) {
28717
+ if (tool.enabled) {
28718
+ return;
28719
+ }
28720
+ throw refuse(TOOL_REFUSALS.namespaceUnavailable, `${tool.address} is in your catalog but its connection is not serving tools right now.`, `uru tools inspect ${tool.address}`);
28721
+ }
28722
+ function connectorRefusal(tool, error2) {
28723
+ const message = error2.message;
28724
+ const lower = message.toLowerCase();
28725
+ const envelope = parseParkedCallEnvelope(error2.data);
28726
+ if (envelope !== null && envelope.questionId !== null) {
28727
+ return refuse(envelope.status === "approval_required" ? TOOL_REFUSALS.approvalRequired : TOOL_REFUSALS.refusedByPolicy, [
28728
+ `${tool.address} did not run: ${envelope.status}.`,
28729
+ ...parkedLines(envelope)
28730
+ ].join(`
28731
+ `), `uru tools approve ${envelope.questionId} --accept`);
28732
+ }
28733
+ if (lower.includes("approval")) {
28734
+ return refuse(TOOL_REFUSALS.approvalRequired, `${tool.address} needs an approval before it runs: ${message}`, "uru tools inspect " + tool.address);
28735
+ }
28736
+ if (lower.includes("not connected") || lower.includes("missing exact connection") || lower.includes("connected_account")) {
28737
+ return refuse(TOOL_REFUSALS.notConnected, `${tool.address} has no live connection: ${message}. Connect ${tool.provider} in Uru, then retry.`, `uru tools connections`);
28738
+ }
28739
+ if (error2.status === 403 || lower.includes("policy") || lower.includes("denied")) {
28740
+ return refuse(TOOL_REFUSALS.refusedByPolicy, `${tool.address} was refused by workspace policy: ${message}`, `uru tools inspect ${tool.address}`);
28741
+ }
28742
+ if (error2.status === 404) {
28743
+ return refuse(TOOL_REFUSALS.namespaceUnavailable, `${tool.address} is in your catalog but the proxy does not serve it right now: ${message}`);
28744
+ }
28745
+ return refuse(TOOL_REFUSALS.refusedByPolicy, message);
28746
+ }
28747
+ async function runOneCall(ctx, tool, parameters, mcpUrl) {
28748
+ if (isPlatformTool(tool)) {
28749
+ const operation = getPlatformOperationForToolCall(tool.tool, parameters);
28750
+ if (operation !== undefined) {
28751
+ requireOperationConfirmation(ctx, operation, parameters);
28752
+ } else if (isRegisteredPlatformToolName(tool.tool)) {
28753
+ throw new CliError(unmappedPlatformToolCallMessage(tool.tool, parameters));
28754
+ }
28755
+ return client(ctx).runTool(tool.tool, parameters);
28756
+ }
28757
+ if (ctx.token === undefined) {
28758
+ throw new CliError("Authentication required. Run `uru login`, pass --token, or set URU_TOKEN.");
28759
+ }
28760
+ try {
28761
+ return await callConnectorTool({
28762
+ url: resolveMcpUrl({ apiUrl: ctx.apiUrl, explicit: mcpUrl }),
28763
+ token: ctx.token,
28764
+ workspaceId: ctx.workspace,
28765
+ fetchImpl: ctx.fetch ?? fetch,
28766
+ signal: ctx.abortSignal
28767
+ }, {
28768
+ wrapperToolName: tool.wrapperToolName,
28769
+ providerNativeToolName: tool.providerNativeToolName,
28770
+ parameters
28771
+ });
28772
+ } catch (error2) {
28773
+ if (error2 instanceof McpCallError) {
28774
+ throw connectorRefusal(tool, error2);
28775
+ }
28776
+ throw error2;
28777
+ }
28778
+ }
28779
+ async function toolsRun(ctx, args) {
28780
+ const flags = parseLocalFlags(args, ["tools", "run"]);
28781
+ const query = flags.positionals[0];
28782
+ if (query === undefined) {
28783
+ throw new CliError("Usage: uru tools run <address> -d '{...}'");
28784
+ }
28785
+ const { tool } = await requireTool(ctx, query);
28786
+ if (flags.booleans.has("--get-schema")) {
28787
+ emitValue(ctx, await toolSchemaAnswer(ctx, tool));
28788
+ return;
28789
+ }
28790
+ const parameters = await toolParameters(ctx, flags);
28791
+ const parallel = flags.booleans.has("--parallel");
28792
+ if (parallel && !Array.isArray(parameters)) {
28793
+ throw new CliError("--parallel needs a JSON array of input objects.");
28794
+ }
28795
+ if (!parallel && !isJsonObject(parameters)) {
28796
+ throw new CliError("-d must be a JSON object. Use --parallel for an array.");
28797
+ }
28798
+ if (flags.booleans.has("--dry-run")) {
28799
+ emitValue(ctx, {
28800
+ dryRun: true,
28801
+ address: tool.address,
28802
+ wrapperToolName: tool.wrapperToolName,
28803
+ providerNativeToolName: tool.providerNativeToolName,
28804
+ parameters,
28805
+ policy: policyAnswer(tool)
28806
+ }, [
28807
+ `Dry run — nothing was executed.`,
28808
+ `Address ${tool.address}`,
28809
+ `Connection ${tool.connectionLabel} (${tool.connection})`,
28810
+ `Write class ${tool.writeClass}`,
28811
+ `Serving ${tool.enabled ? "yes" : "no"}`,
28812
+ `Runs through ${String(policyAnswer(tool)["execution"])}`
28813
+ ].join(`
28814
+ `));
28815
+ return;
28816
+ }
28817
+ assertServing(tool);
28818
+ const mcpUrl = flags.values["--mcp-url"];
28819
+ if (parallel) {
28820
+ const inputs = parameters;
28821
+ const results = [];
28822
+ for (const input of inputs) {
28823
+ if (!isJsonObject(input)) {
28824
+ throw new CliError("--parallel needs every array entry to be an object.");
28825
+ }
28826
+ results.push(await runOneCall(ctx, tool, input, mcpUrl));
28827
+ }
28828
+ emitValue(ctx, { address: tool.address, results });
28829
+ return;
28830
+ }
28831
+ const result = await runOneCall(ctx, tool, parameters, mcpUrl);
28832
+ const parked = parseParkedCallEnvelope(result);
28833
+ if (parked !== null) {
28834
+ emitValue(ctx, result, [
28835
+ `${tool.address} did not run yet.`,
28836
+ ...parkedLines(parked)
28837
+ ].join(`
28838
+ `));
28839
+ return;
28840
+ }
28841
+ emitValue(ctx, result);
28842
+ }
28843
+ async function toolsApprove(ctx, args) {
28844
+ const flags = parseLocalFlags(args, ["tools", "approve"]);
28845
+ const questionId = flags.positionals[0]?.trim();
28846
+ if (questionId === undefined || questionId === "") {
28847
+ throw new CliError("Usage: uru tools approve <question-id> --accept | --decline");
28848
+ }
28849
+ const accept = flags.booleans.has("--accept");
28850
+ const decline = flags.booleans.has("--decline");
28851
+ if (accept === decline) {
28852
+ throw new CliError("Choose exactly one of --accept or --decline for `uru tools approve`.");
28853
+ }
28854
+ const decision = accept ? "accept" : "decline";
28855
+ const answer = await client(ctx).api(`/api/tool-approvals/${encodeURIComponent(questionId)}`, { method: "POST", body: { decision } });
28856
+ if (!isJsonObject(answer)) {
28857
+ throw new CliError("The approval answer route returned a non-object response.");
28858
+ }
28859
+ const alreadyAnswered = answer["alreadyResolved"] === true;
28860
+ const settled = typeof answer["decision"] === "string" ? answer["decision"] : "unknown";
28861
+ emitValue(ctx, answer, [
28862
+ alreadyAnswered ? `This call was already answered: ${settled}.` : `Answered: ${settled}.`,
28863
+ `Question id ${questionId}`
28864
+ ].join(`
28865
+ `));
28866
+ }
28867
+ async function toolsDrillDown(ctx, command) {
28868
+ const flags = parseLocalFlags(command.slice(1), ["tools"]);
28869
+ const prefix = flags.positionals.join(".").trim();
28870
+ const catalog = await fetchToolCatalog(ctx);
28871
+ const match = flags.values["--match"]?.trim().toLowerCase();
28872
+ const resolution = resolveTool(catalog, prefix);
28873
+ if (resolution.kind === "exact") {
28874
+ const tool = resolution.tool;
28875
+ emitValue(ctx, {
28876
+ ...toolJson(tool),
28877
+ inputSchema: tool.inputSchema,
28878
+ outputSchema: tool.outputSchema
28879
+ }, [
28880
+ `${tool.address} [${tool.writeClass}]`,
28881
+ tool.description,
28882
+ "",
28883
+ "Input",
28884
+ JSON.stringify(tool.inputSchema, null, 2),
28885
+ "",
28886
+ "Output",
28887
+ JSON.stringify(tool.outputSchema ?? {}, null, 2)
28888
+ ].join(`
28889
+ `));
28890
+ return;
28891
+ }
28892
+ const branches = addressBranches(catalog, prefix).filter((branch) => match === undefined || branch.segment.toLowerCase().includes(match));
28893
+ if (branches.length === 0) {
28894
+ throw noMatchError(prefix, resolution.kind === "no_match" ? resolution.near : []);
28895
+ }
28896
+ const limited = branches.slice(0, limitFlag(flags, 50));
28897
+ emitValue(ctx, { prefix, branches: limited.map((branch) => ({ ...branch })), total: branches.length }, [
28898
+ `${prefix}.*`,
28899
+ ...limited.map((branch) => ` ${branch.prefix}${branch.leaf ? "" : ".*"} ${String(branch.toolCount)} tools`),
28900
+ "",
28901
+ `${String(limited.length)} of ${String(branches.length)} branches.`
28902
+ ].join(`
28903
+ `));
28904
+ }
28905
+ async function dispatchTools(ctx, command) {
28906
+ const sub = command[1];
28907
+ const args = command.slice(2);
28908
+ await withWorkspace(ctx);
28909
+ if (sub === "search") {
28910
+ await toolsSearch(ctx, args);
28911
+ return;
28912
+ }
28913
+ if (sub === "ls" || sub === "list") {
28914
+ await toolsList(ctx, args);
28915
+ return;
28916
+ }
28917
+ if (sub === "connections") {
28918
+ await toolsConnections(ctx, args);
28919
+ return;
28920
+ }
28921
+ if (sub === "schema") {
28922
+ await toolsSchema(ctx, args);
28923
+ return;
28924
+ }
28925
+ if (sub === "inspect") {
28926
+ await toolsInspect(ctx, args);
28927
+ return;
28928
+ }
28929
+ if (sub === "run") {
28930
+ await toolsRun(ctx, args);
28931
+ return;
28932
+ }
28933
+ if (sub === "approve") {
28934
+ await toolsApprove(ctx, args);
28935
+ return;
28936
+ }
28937
+ if (isToolsAddressDrillDown(command)) {
28938
+ await toolsDrillDown(ctx, command);
28939
+ return;
28940
+ }
28941
+ throw new CliError("Usage: uru tools search|ls|schema|inspect|run|approve|connections, or `uru tools <partial address> --help`.");
28942
+ }
28943
+ function unmappedPlatformToolCallMessage(toolName, params) {
28944
+ const op2 = typeof params["op"] === "string" ? params["op"] : undefined;
28945
+ const mistakenOperation = typeof params["operation"] === "string" ? params["operation"] : undefined;
28946
+ const normalizedTool = toolName.trim().toLowerCase();
28947
+ const normalizedOp = op2?.trim().toLowerCase();
28948
+ if (mistakenOperation !== undefined && mistakenOperation.trim() !== "" && (op2 === undefined || op2.trim() === "")) {
28949
+ const suggested = mistakenOperation.trim();
28950
+ return `Platform tools take \`op\`, not \`operation\` (got operation=${suggested}). ` + `Retry with op=${suggested}.`;
28951
+ }
28952
+ if (normalizedTool === "library_fs" && (normalizedOp === "ls" || normalizedOp === "stat" || normalizedOp === "read" || normalizedOp === "search")) {
28953
+ return `library_fs does not serve read op=${normalizedOp}. ` + "Use library_query (or `uru library ls|stat|search|read` / " + "`uru operations run library.<op>`).";
28954
+ }
28955
+ if (normalizedOp !== undefined) {
28956
+ return `No registered safety policy for ${toolName} op=${normalizedOp}. ` + "Refuse to execute. Check `uru operations search` for the supported operation id.";
28957
+ }
28958
+ return `No registered safety policy for ${toolName}. ` + "Refuse to execute without a mapped platform operation.";
28959
+ }
28960
+
27650
28961
  // src/token-env-secret-commands.ts
27651
28962
  import { spawn } from "node:child_process";
27652
28963
  import { chmod as chmod2, mkdir as mkdir6, readFile as readFile9, writeFile as writeFile5 } from "node:fs/promises";
@@ -28349,7 +29660,7 @@ var cliCommandRuntimeBindings = {
28349
29660
  await dispatchUpdate(ctx, command.slice(1));
28350
29661
  },
28351
29662
  tools: async ({ ctx, command }) => {
28352
- await dispatchTools(ctx, command[1], command[2], command);
29663
+ await dispatchTools(ctx, command);
28353
29664
  },
28354
29665
  init: async ({ ctx, command }) => {
28355
29666
  await withWorkspace(ctx);
@@ -28509,7 +29820,7 @@ async function dispatchRegistryFamilyCommand({
28509
29820
  // src/config.ts
28510
29821
  import { chmod as chmod3, mkdir as mkdir7, open as open2, readFile as readFile10, rename as rename3, rm as rm4 } from "node:fs/promises";
28511
29822
  import { constants as constants2 } from "node:fs";
28512
- import { randomUUID as randomUUID2 } from "node:crypto";
29823
+ import { randomUUID as randomUUID3 } from "node:crypto";
28513
29824
  import { homedir as homedir2 } from "node:os";
28514
29825
  import { dirname as dirname7, join as join5 } from "node:path";
28515
29826
  function resolveConfigHome(env = process.env) {
@@ -28545,7 +29856,7 @@ function createConfigStore(path = configPath()) {
28545
29856
  await mkdir7(dirname7(path), { recursive: true });
28546
29857
  const body = `${JSON.stringify(sanitizeConfig(config2), null, 2)}
28547
29858
  `;
28548
- const temporaryPath = `${path}.${process.pid}.${randomUUID2()}.tmp`;
29859
+ const temporaryPath = `${path}.${process.pid}.${randomUUID3()}.tmp`;
28549
29860
  let handle = null;
28550
29861
  try {
28551
29862
  handle = await open2(temporaryPath, constants2.O_CREAT | constants2.O_EXCL | constants2.O_WRONLY, constants2.S_IRUSR | constants2.S_IWUSR);
@@ -29153,7 +30464,8 @@ async function runCli(options) {
29153
30464
  writeText(io, CLI_VERSION);
29154
30465
  return ExitCode.Ok;
29155
30466
  }
29156
- if (parsed.flags.help || parsed.command.length === 0) {
30467
+ const helpIsAddressDrillDown = parsed.flags.help && isToolsAddressDrillDown(parsed.command);
30468
+ if (parsed.flags.help && !helpIsAddressDrillDown || parsed.command.length === 0) {
29157
30469
  const helpTerminal = detectTerminalCapabilities({
29158
30470
  flags: parsed.flags,
29159
30471
  ...options.env === undefined ? {} : { env: options.env },