@uru-intelligence/cli 0.3.54 → 0.3.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/dist/uru.mjs +491 -269
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -54,6 +54,14 @@ bun src/index.ts gems write 'library/Golden Gems/My Gem.gem' main.html --file ./
54
54
  bun src/index.ts library ls library
55
55
  bun src/index.ts library search revenue
56
56
  bun src/index.ts library mkdir 'library/Golden Gems'
57
+ bun src/index.ts library read 'library/Contexts/Client Brief.md'
58
+ bun src/index.ts library write 'library/Contexts/Client Brief.md' --file ./brief.md --create
59
+ bun src/index.ts library write 'library/Prompts/Sales Email.md' --content "Draft the follow-up." --create
60
+ bun src/index.ts library patch 'library/Prompts/Sales Email.md' --stdin
61
+ bun src/index.ts library cp 'library/Notes/Brief.md' 'library/Archive/Brief.md'
62
+ bun src/index.ts library mv 'library/Notes/Draft.md' 'library/Notes/Final.md'
63
+ bun src/index.ts library rm 'library/Notes/Old.md'
64
+ bun src/index.ts library restore 'library/Notes/Old.md'
57
65
  bun src/index.ts datasets query revenue_rows --limit 50 --response-format concise --json
58
66
  bun src/index.ts datasets get --params-json '{"dataset_id":"revenue_rows"}'
59
67
  bun src/index.ts datasets rows-upsert --params-json '{"dataset_id":"revenue_rows","rows":[{"name":"Acme"}]}'
package/dist/uru.mjs CHANGED
@@ -6,7 +6,7 @@ import { spawn as spawn3 } from "node:child_process";
6
6
 
7
7
  // src/types.ts
8
8
  var DEFAULT_API_URL = "https://api.uruintelligence.com";
9
- var CLI_VERSION = "0.3.54";
9
+ var CLI_VERSION = "0.3.55";
10
10
  var ExitCode = {
11
11
  Ok: 0,
12
12
  Failure: 1,
@@ -241,8 +241,12 @@ function buildOperationGroups(op) {
241
241
  const libraryOperations = [
242
242
  op("library.ls", "library", "List visible Library folders and resources.", "library_query", "ls", "read"),
243
243
  op("library.stat", "library", "Read metadata for a visible Library path.", "library_query", "stat", "read"),
244
+ op("library.read", "library", "Read a visible text-backed Library resource by path.", "library_query", "read", "read"),
244
245
  op("library.search", "library", "Search visible Library resources server-side.", "library_query", "search", "read"),
245
246
  op("library.mkdir", "library", "Create a Library folder.", "library_fs", "mkdir", "write"),
247
+ op("library.write", "library", "Create or replace a text-backed Library resource by path.", "library_fs", "write", "write"),
248
+ op("library.patch", "library", "Replace text content for a text-backed Library resource by path.", "library_fs", "patch", "write"),
249
+ op("library.cp", "library", "Copy a supported Library item when the backing resource supports copy semantics.", "library_fs", "cp", "write"),
246
250
  op("library.mv", "library", "Move or rename a Library item.", "library_fs", "mv", "write"),
247
251
  op("library.move_workspace", "library", "Move a Library item to another workspace.", "library_fs", "move_workspace", "destructive"),
248
252
  op("library.rm", "library", "Trash a Library item.", "library_fs", "rm", "destructive"),
@@ -250,10 +254,7 @@ function buildOperationGroups(op) {
250
254
  op("library.empty_trash", "library", "Permanently empty Library trash.", "library_fs", "empty_trash", "destructive"),
251
255
  op("library.upload_preflight", "library", "Preflight a Library upload.", "library_upload", "preflight", "read"),
252
256
  op("library.upload_create_session", "library", "Create a direct Library upload session.", "library_upload", "create_session", "write"),
253
- op("library.upload_complete_session", "library", "Complete a direct Library upload session.", "library_upload", "complete_session", "write"),
254
- op("context.manage", "library", "Manage typed Context Library metadata such as workspace-default, teams, category, and favorite state.", "context_manage", undefined, "write"),
255
- op("prompt.render", "library", "Render a typed Prompt Library resource with supplied variables.", "prompt_render", undefined, "read"),
256
- op("persona.query", "library", "Query typed Persona Library resources and linked prompt/context resources.", "persona_query", undefined, "read")
257
+ op("library.upload_complete_session", "library", "Complete a direct Library upload session.", "library_upload", "complete_session", "write")
257
258
  ];
258
259
  const sharingOperations = [
259
260
  op("sharing.share", "sharing", "Share a Library resource with a user, team, or workspace.", "library_collab", "share", "write"),
@@ -416,7 +417,7 @@ function buildOperationGroups(op) {
416
417
  op("file.download_for_user", "files", "Prepare a file download for the user.", "DOWNLOAD_FILE_FOR_USER", undefined, "read")
417
418
  ];
418
419
  const businessSqlOperations = [
419
- op("business_sql.transcript", "business_sql", "Query transcript SQL.", "TRANSCRIPT_SQL", undefined, "read"),
420
+ op("business_sql.transcript", "business_sql", "Query transcript SQL.", "transcript_sql", undefined, "read"),
420
421
  op("business_sql.maguire", "business_sql", "Query Maguire SQL.", "MAGUIRE_SQL", undefined, "read"),
421
422
  op("business_sql.tyler", "business_sql", "Query Tyler SQL.", "TYLER_SQL", undefined, "read"),
422
423
  op("business_sql.cfo", "business_sql", "Query CFO SQL.", "CFO_SQL", undefined, "read"),
@@ -473,7 +474,6 @@ var internalOnlyPlatformToolIds = new Set([
473
474
  "MAGUIRE_WRITE_SQL",
474
475
  "PEREGRINE_SQL",
475
476
  "RESTORE_DATASET_ROWS",
476
- "TRANSCRIPT_SQL",
477
477
  "TYLER_SQL",
478
478
  "UPSERT_DATASET_ROWS"
479
479
  ]);
@@ -906,171 +906,6 @@ function normalizeTool(value) {
906
906
  return [tool];
907
907
  }
908
908
 
909
- // src/cli-helpers.ts
910
- function parseApiArgs(args) {
911
- let method = "GET";
912
- let path;
913
- let body;
914
- const fields = {};
915
- for (let index = 0;index < args.length; index += 1) {
916
- const arg = args[index];
917
- if (arg === undefined) {
918
- continue;
919
- }
920
- if (arg === "-X" || arg === "--method") {
921
- method = requireCommandValue(args[index + 1], `${arg} requires a method`).toUpperCase();
922
- index += 1;
923
- continue;
924
- }
925
- if (arg.startsWith("-X") && arg.length > 2) {
926
- method = arg.slice(2).toUpperCase();
927
- continue;
928
- }
929
- if (arg === "--body-json" || arg === "--data-json") {
930
- body = parseParamsJson(requireCommandValue(args[index + 1], `${arg} requires a JSON object`), arg);
931
- index += 1;
932
- continue;
933
- }
934
- if (arg === "-F" || arg === "--field") {
935
- addApiField(fields, requireCommandValue(args[index + 1], `${arg} requires key=value`));
936
- index += 1;
937
- continue;
938
- }
939
- if (arg.startsWith("-F") && arg.length > 2) {
940
- addApiField(fields, arg.slice(2));
941
- continue;
942
- }
943
- if (path === undefined) {
944
- path = normalizeApiPath(arg);
945
- continue;
946
- }
947
- throw new CliError(`Unexpected api argument: ${arg}`);
948
- }
949
- if (Object.keys(fields).length > 0) {
950
- body = { ...body ?? {}, ...fields };
951
- if (method === "GET") {
952
- method = "POST";
953
- }
954
- }
955
- return {
956
- path: requireCommandValue(path, "Usage: uru api <path> [-X POST] [-F k=v] [--body-json {...}]"),
957
- method,
958
- ...body === undefined ? {} : { body }
959
- };
960
- }
961
- function normalizeApiPath(value) {
962
- const trimmed = value.trim();
963
- if (trimmed.startsWith("/")) {
964
- return trimmed;
965
- }
966
- return `/api/${trimmed.replace(/^api\//, "")}`;
967
- }
968
- function addApiField(target, raw) {
969
- const equals = raw.indexOf("=");
970
- if (equals <= 0) {
971
- throw new CliError("-F/--field requires key=value");
972
- }
973
- target[raw.slice(0, equals)] = raw.slice(equals + 1);
974
- }
975
- function paramsJson(args) {
976
- for (let index = 0;index < args.length; index += 1) {
977
- const arg = args[index];
978
- if (arg === "--params-json" || arg === "--args-json") {
979
- const value = args[index + 1];
980
- if (value === undefined) {
981
- throw new CliError(`${arg} requires a JSON object`);
982
- }
983
- return parseParamsJson(value, arg);
984
- }
985
- if (arg?.startsWith("--params-json=")) {
986
- return parseParamsJson(arg.slice("--params-json=".length), "--params-json");
987
- }
988
- if (arg?.startsWith("--args-json=")) {
989
- return parseParamsJson(arg.slice("--args-json=".length), "--args-json");
990
- }
991
- }
992
- return {};
993
- }
994
- function parseParamsJson(value, label) {
995
- try {
996
- return parseJsonObject(value, label);
997
- } catch (error) {
998
- throw new CliError(error instanceof Error ? error.message : `Invalid ${label}`, {
999
- cause: error
1000
- });
1001
- }
1002
- }
1003
- function parseLocalFlags(args) {
1004
- const values = {};
1005
- const booleans = new Set;
1006
- const positionals = [];
1007
- for (let index = 0;index < args.length; index += 1) {
1008
- const arg = args[index];
1009
- if (arg === undefined) {
1010
- continue;
1011
- }
1012
- if (!arg.startsWith("--")) {
1013
- positionals.push(arg);
1014
- continue;
1015
- }
1016
- const equals = arg.indexOf("=");
1017
- if (equals > -1) {
1018
- values[arg.slice(0, equals)] = arg.slice(equals + 1);
1019
- continue;
1020
- }
1021
- const next = args[index + 1];
1022
- if (next !== undefined && !next.startsWith("--")) {
1023
- values[arg] = next;
1024
- index += 1;
1025
- } else {
1026
- booleans.add(arg);
1027
- }
1028
- }
1029
- return { values, booleans, positionals };
1030
- }
1031
- function integerFlag(flags, name, fallback) {
1032
- const raw = flags.values[name];
1033
- if (raw === undefined) {
1034
- return fallback;
1035
- }
1036
- const value = Number.parseInt(raw, 10);
1037
- if (!Number.isFinite(value) || value < 0) {
1038
- throw new CliError(`${name} must be a non-negative integer`);
1039
- }
1040
- return value;
1041
- }
1042
- function copyStringFlag(flags, target, flagName, paramName) {
1043
- const value = flags.values[flagName];
1044
- if (value !== undefined) {
1045
- target[paramName] = value;
1046
- }
1047
- }
1048
- function isJsonRequested(argv) {
1049
- for (let index = 0;index < argv.length; index += 1) {
1050
- const arg = argv[index];
1051
- if (arg === "--json") {
1052
- return true;
1053
- }
1054
- if (arg === "--output-format=json" || arg === "--output-format=stream-json" || arg === "--output=json" || arg === "--output=stream-json") {
1055
- return true;
1056
- }
1057
- if (arg === "--output-format" || arg === "--output") {
1058
- const value = argv[index + 1];
1059
- if (value === "json" || value === "stream-json") {
1060
- return true;
1061
- }
1062
- }
1063
- }
1064
- return false;
1065
- }
1066
- function nonEmptyString(value) {
1067
- if (value === undefined) {
1068
- return;
1069
- }
1070
- const trimmed = value.trim();
1071
- return trimmed === "" ? undefined : trimmed;
1072
- }
1073
-
1074
909
  // src/output.ts
1075
910
  function writeJson(io, value) {
1076
911
  io.stdout.write(`${JSON.stringify(value, null, 2)}
@@ -1184,6 +1019,12 @@ LIBRARY / DATASETS / TOOLS
1184
1019
  uru library ls [path] List Library items
1185
1020
  uru library search <query> Search Library items
1186
1021
  uru library mkdir <path> Create a Library folder
1022
+ uru library read <path> Print a text-backed Library resource
1023
+ uru library write <path> --file ./note.md --create Create/replace text-backed Library content
1024
+ uru library patch <path> --content "..." Replace text-backed Library content
1025
+ uru library cp|mv <path> <target-path> Copy or move visible Library items
1026
+ uru library rm <path> [--permanent --yes] Trash or permanently delete Library items
1027
+ uru library restore <path> Restore a trashed Library item
1187
1028
  # Prompts, personas, contexts: use typed Library paths such as library/Prompts/Foo.prompt.md
1188
1029
  uru datasets query <dataset-slug> [--limit 50] Query dataset rows; add --response-format concise for compact output
1189
1030
  uru automations ls|get|create|enable|disable Manage automations without raw JSON parameters
@@ -1400,8 +1241,20 @@ the check a release gate; security also accepts --deep.
1400
1241
  library: `Usage: uru library ls [path]
1401
1242
  uru library search <query>
1402
1243
  uru library mkdir <path>
1244
+ uru library read <path>
1245
+ uru library write <path> (--file <local-path>|--content <text>|--stdin) [--create]
1246
+ uru library patch <path> (--file <local-path>|--content <text>|--stdin)
1247
+ uru library cp <path> <target-path>
1248
+ uru library mv <path> <target-path>
1249
+ uru library rm <path> [--permanent] [--yes]
1250
+ uru library restore <path>
1251
+ uru library empty-trash --yes
1403
1252
 
1404
- Operate on Library items through platform tools.
1253
+ Operate on Library items through platform tools. Use write --create to create
1254
+ text-backed Context, Prompt, Persona, note, and plain .md/.txt resources at
1255
+ visible Library paths. For binary files, use library_upload through
1256
+ \`uru operations run\` or \`uru tools run\`; for Gem child source files, use
1257
+ \`uru gems read|write\`.
1405
1258
  `,
1406
1259
  datasets: `Usage: uru datasets query <dataset-slug> [--limit 50] [--offset 0] [--response-format detailed|concise]
1407
1260
 
@@ -1563,7 +1416,14 @@ async function executeOperationStream(ctx, operationId, params, onEvent) {
1563
1416
  if (operation.backendOp !== undefined) {
1564
1417
  toolParams["op"] = operation.backendOp;
1565
1418
  }
1566
- return client(ctx).streamTool(operation.backendToolName, toolParams, onEvent);
1419
+ const response = await client(ctx).streamTool(operation.backendToolName, toolParams, (event) => {
1420
+ assertToolResultSuccess(event);
1421
+ onEvent(event);
1422
+ });
1423
+ if (response.payload !== undefined) {
1424
+ assertToolResultSuccess(response.payload);
1425
+ }
1426
+ return response;
1567
1427
  }
1568
1428
  async function executeOperation(ctx, operationId, params) {
1569
1429
  const operation = getPlatformOperation(operationId);
@@ -1574,7 +1434,9 @@ async function executeOperation(ctx, operationId, params) {
1574
1434
  if (operation.backendOp !== undefined) {
1575
1435
  toolParams["op"] = operation.backendOp;
1576
1436
  }
1577
- return client(ctx).runTool(operation.backendToolName, toolParams);
1437
+ const result = await client(ctx).runTool(operation.backendToolName, toolParams);
1438
+ assertToolResultSuccess(result);
1439
+ return result;
1578
1440
  }
1579
1441
  function emit(ctx, value, text) {
1580
1442
  if (ctx.outputFormat === "stream-json") {
@@ -1586,19 +1448,51 @@ function emit(ctx, value, text) {
1586
1448
  }
1587
1449
  }
1588
1450
  function unwrapToolResult(value) {
1589
- if (isJsonObject2(value) && isJsonObject2(value["result"])) {
1590
- return value["result"];
1451
+ assertToolResultSuccess(value);
1452
+ if (isJsonObject2(value) && Object.hasOwn(value, "result") && (isToolResultEnvelope(value) || isJsonObject2(value["result"]))) {
1453
+ const result = value["result"];
1454
+ if (result !== undefined) {
1455
+ return result;
1456
+ }
1591
1457
  }
1592
1458
  return value;
1593
1459
  }
1460
+ function assertToolResultSuccess(value) {
1461
+ if (!isJsonObject2(value) || !isToolResultEnvelope(value)) {
1462
+ return;
1463
+ }
1464
+ const httpStatus = toolResultHttpStatus(value);
1465
+ const failed = value["success"] === false || Object.hasOwn(value, "error") || Object.hasOwn(value, "errorMessage") || Object.hasOwn(value, "error_message") || Object.hasOwn(value, "errorCode") || Object.hasOwn(value, "error_code") || Object.hasOwn(value, "code") || httpStatus !== undefined && httpStatus >= 400;
1466
+ if (!failed) {
1467
+ return;
1468
+ }
1469
+ const errorCode = stringField(value, ["errorCode", "error_code", "code"]);
1470
+ const toolId = stringField(value, ["toolId", "tool_id"]);
1471
+ const message = stringField(value, ["errorMessage", "error_message", "error", "message"]) ?? `${toolId === undefined ? "Tool operation" : `Tool ${toolId}`} failed`;
1472
+ const details = [
1473
+ errorCode === undefined ? undefined : `[${errorCode}]`,
1474
+ httpStatus === undefined ? undefined : `(HTTP ${httpStatus})`
1475
+ ].filter((detail) => detail !== undefined);
1476
+ const tip = stringField(value, ["tip"]);
1477
+ throw new CliError(`${message}${details.length === 0 ? "" : ` ${details.join(" ")}`}${tip === undefined ? "" : ` ${tip}`}`, {
1478
+ code: httpStatus === 409 ? ExitCode.Conflict : httpStatus === 401 || httpStatus === 403 ? ExitCode.AuthRequired : ExitCode.Failure,
1479
+ ...errorCode === undefined ? {} : { errorCode }
1480
+ });
1481
+ }
1482
+ function isToolResultEnvelope(value) {
1483
+ return typeof value["success"] === "boolean" && (typeof value["toolId"] === "string" || typeof value["tool_id"] === "string" || typeof value["executionTimeMs"] === "number" || typeof value["execution_time_ms"] === "number");
1484
+ }
1485
+ function toolResultHttpStatus(value) {
1486
+ const status = value["httpStatus"] ?? value["http_status"];
1487
+ return typeof status === "number" && Number.isFinite(status) ? status : undefined;
1488
+ }
1594
1489
  function delegatedPayload(value) {
1595
1490
  if (isJsonObject2(value) && isJsonObject2(value["payload"])) {
1596
1491
  return value["payload"];
1597
1492
  }
1598
1493
  return value;
1599
1494
  }
1600
- function gemPathFromArgs(args, linkedProject) {
1601
- const flags = parseLocalFlags(args);
1495
+ function gemPathFromArgs(flags, linkedProject) {
1602
1496
  return flags.values["--path"] ?? flags.values["--gem"] ?? flags.positionals.find((value) => value.startsWith("library/")) ?? linkedProject.libraryPath ?? linkedProject.gemId ?? (() => {
1603
1497
  throw new CliError("Gem path required. Pass library/<name>.gem or run `uru link --path library/<name>.gem`.");
1604
1498
  })();
@@ -1822,16 +1716,9 @@ function createMacosKeychainCredentialStore(runCommand) {
1822
1716
  },
1823
1717
  async writeToken(apiUrl, token) {
1824
1718
  try {
1825
- await runCommand("security", [
1826
- "add-generic-password",
1827
- "-a",
1828
- accountForApiUrl(apiUrl),
1829
- "-s",
1830
- KEYCHAIN_SERVICE,
1831
- "-w",
1832
- token,
1833
- "-U"
1834
- ]);
1719
+ await runCommand("security", ["-i"], {
1720
+ input: macosKeychainWriteCommand(apiUrl, token)
1721
+ });
1835
1722
  } catch (error) {
1836
1723
  throw credentialStoreError("macOS Keychain", error);
1837
1724
  }
@@ -1853,6 +1740,11 @@ function createMacosKeychainCredentialStore(runCommand) {
1853
1740
  }
1854
1741
  };
1855
1742
  }
1743
+ function macosKeychainWriteCommand(apiUrl, token) {
1744
+ const tokenHex = Buffer.from(token, "utf8").toString("hex");
1745
+ return `add-generic-password -a "${accountForApiUrl(apiUrl)}" -s "${KEYCHAIN_SERVICE}" -X ${tokenHex} -U
1746
+ `;
1747
+ }
1856
1748
  function createLinuxSecretToolCredentialStore(runCommand) {
1857
1749
  return {
1858
1750
  async readToken(apiUrl) {
@@ -2351,7 +2243,8 @@ var SKIPPED_SOURCE_PATH_SEGMENTS = new Set([
2351
2243
  "build",
2352
2244
  "coverage",
2353
2245
  "tmp",
2354
- "temp"
2246
+ "temp",
2247
+ "__MACOSX"
2355
2248
  ]);
2356
2249
  var SKIPPED_SOURCE_FILE_NAMES = new Set([
2357
2250
  ".DS_Store",
@@ -2364,7 +2257,8 @@ var SKIPPED_SOURCE_FILE_NAMES = new Set([
2364
2257
  ]);
2365
2258
  function shouldSkipProjectPath(path) {
2366
2259
  const segments = path.split("/");
2367
- return segments.some((segment) => SKIPPED_SOURCE_PATH_SEGMENTS.has(segment)) || SKIPPED_SOURCE_FILE_NAMES.has(segments.at(-1) ?? "");
2260
+ const name = segments.at(-1) ?? "";
2261
+ return segments.some((segment) => SKIPPED_SOURCE_PATH_SEGMENTS.has(segment)) || SKIPPED_SOURCE_FILE_NAMES.has(name) || name.startsWith("._");
2368
2262
  }
2369
2263
  function stableStringRecord(record) {
2370
2264
  return Object.fromEntries(Object.entries(record).filter(([, value]) => typeof value === "string" && value.trim() !== "").sort(([left], [right]) => compareStrings(left, right)));
@@ -2419,11 +2313,12 @@ async function maybeNotifyCliUpdate(ctx, options = {}) {
2419
2313
  if (!shouldCheckForUpdate(ctx, options.env ?? process.env)) {
2420
2314
  return;
2421
2315
  }
2316
+ const currentConfig = await readConfigBestEffort(ctx);
2422
2317
  const now = options.now?.() ?? Date.now();
2423
- if (!isUpdateCheckDue(ctx.config, now)) {
2318
+ if (!isUpdateCheckDue(currentConfig, now)) {
2424
2319
  return;
2425
2320
  }
2426
- const checkedConfig = withUpdateCheckTimestamp(ctx.config, now);
2321
+ const checkedConfig = withUpdateCheckTimestamp(currentConfig, now);
2427
2322
  await writeConfigBestEffort(ctx, checkedConfig);
2428
2323
  const latestVersion = await fetchLatestVersion({
2429
2324
  fetchImpl: ctx.fetch ?? fetch,
@@ -2445,6 +2340,13 @@ async function maybeNotifyCliUpdate(ctx, options = {}) {
2445
2340
  ctx.io.stderr.write(`A newer Uru CLI is available: ${CLI_VERSION} → ${latestVersion}. ` + `Run npm install -g ${PACKAGE_NAME}@latest
2446
2341
  `);
2447
2342
  }
2343
+ async function readConfigBestEffort(ctx) {
2344
+ try {
2345
+ return await ctx.store.read();
2346
+ } catch {
2347
+ return ctx.config;
2348
+ }
2349
+ }
2448
2350
  function shouldCheckForUpdate(ctx, env) {
2449
2351
  return ctx.outputFormat === "text" && ctx.terminal.stderrIsTty && !ctx.terminal.ci && env["NO_UPDATE_NOTIFIER"] === undefined && env["URU_NO_UPDATE_NOTIFIER"] === undefined;
2450
2352
  }
@@ -2524,6 +2426,194 @@ function isRecord2(value) {
2524
2426
  return value !== null && typeof value === "object" && !Array.isArray(value);
2525
2427
  }
2526
2428
 
2429
+ // src/cli-helpers.ts
2430
+ var BOOLEAN_LOCAL_FLAGS = new Set([
2431
+ "--blocking",
2432
+ "--create",
2433
+ "--deep",
2434
+ "--dry-run",
2435
+ "--follow",
2436
+ "--force",
2437
+ "--force-build",
2438
+ "--hard-delete",
2439
+ "--no-create",
2440
+ "--no-publish",
2441
+ "--no-wait",
2442
+ "--permanent",
2443
+ "--prod",
2444
+ "--skip-promote",
2445
+ "--stdin",
2446
+ "--value-stdin",
2447
+ "--yes"
2448
+ ]);
2449
+ function parseApiArgs(args) {
2450
+ let method = "GET";
2451
+ let path;
2452
+ let body;
2453
+ const fields = {};
2454
+ for (let index = 0;index < args.length; index += 1) {
2455
+ const arg = args[index];
2456
+ if (arg === undefined) {
2457
+ continue;
2458
+ }
2459
+ if (arg === "-X" || arg === "--method") {
2460
+ method = requireCommandValue(args[index + 1], `${arg} requires a method`).toUpperCase();
2461
+ index += 1;
2462
+ continue;
2463
+ }
2464
+ if (arg.startsWith("-X") && arg.length > 2) {
2465
+ method = arg.slice(2).toUpperCase();
2466
+ continue;
2467
+ }
2468
+ if (arg === "--body-json" || arg === "--data-json") {
2469
+ body = parseParamsJson(requireCommandValue(args[index + 1], `${arg} requires a JSON object`), arg);
2470
+ index += 1;
2471
+ continue;
2472
+ }
2473
+ if (arg === "-F" || arg === "--field") {
2474
+ addApiField(fields, requireCommandValue(args[index + 1], `${arg} requires key=value`));
2475
+ index += 1;
2476
+ continue;
2477
+ }
2478
+ if (arg.startsWith("-F") && arg.length > 2) {
2479
+ addApiField(fields, arg.slice(2));
2480
+ continue;
2481
+ }
2482
+ if (path === undefined) {
2483
+ path = normalizeApiPath(arg);
2484
+ continue;
2485
+ }
2486
+ throw new CliError(`Unexpected api argument: ${arg}`);
2487
+ }
2488
+ if (Object.keys(fields).length > 0) {
2489
+ body = { ...body ?? {}, ...fields };
2490
+ if (method === "GET") {
2491
+ method = "POST";
2492
+ }
2493
+ }
2494
+ return {
2495
+ path: requireCommandValue(path, "Usage: uru api <path> [-X POST] [-F k=v] [--body-json {...}]"),
2496
+ method,
2497
+ ...body === undefined ? {} : { body }
2498
+ };
2499
+ }
2500
+ function normalizeApiPath(value) {
2501
+ const trimmed = value.trim();
2502
+ if (trimmed.startsWith("/")) {
2503
+ return trimmed;
2504
+ }
2505
+ return `/api/${trimmed.replace(/^api\//, "")}`;
2506
+ }
2507
+ function addApiField(target, raw) {
2508
+ const equals = raw.indexOf("=");
2509
+ if (equals <= 0) {
2510
+ throw new CliError("-F/--field requires key=value");
2511
+ }
2512
+ target[raw.slice(0, equals)] = raw.slice(equals + 1);
2513
+ }
2514
+ function paramsJson(args) {
2515
+ for (let index = 0;index < args.length; index += 1) {
2516
+ const arg = args[index];
2517
+ if (arg === "--params-json" || arg === "--args-json") {
2518
+ const value = args[index + 1];
2519
+ if (value === undefined) {
2520
+ throw new CliError(`${arg} requires a JSON object`);
2521
+ }
2522
+ return parseParamsJson(value, arg);
2523
+ }
2524
+ if (arg?.startsWith("--params-json=")) {
2525
+ return parseParamsJson(arg.slice("--params-json=".length), "--params-json");
2526
+ }
2527
+ if (arg?.startsWith("--args-json=")) {
2528
+ return parseParamsJson(arg.slice("--args-json=".length), "--args-json");
2529
+ }
2530
+ }
2531
+ return {};
2532
+ }
2533
+ function parseParamsJson(value, label) {
2534
+ try {
2535
+ return parseJsonObject(value, label);
2536
+ } catch (error) {
2537
+ throw new CliError(error instanceof Error ? error.message : `Invalid ${label}`, {
2538
+ cause: error
2539
+ });
2540
+ }
2541
+ }
2542
+ function parseLocalFlags(args) {
2543
+ const values = {};
2544
+ const booleans = new Set;
2545
+ const positionals = [];
2546
+ for (let index = 0;index < args.length; index += 1) {
2547
+ const arg = args[index];
2548
+ if (arg === undefined) {
2549
+ continue;
2550
+ }
2551
+ if (!arg.startsWith("--")) {
2552
+ positionals.push(arg);
2553
+ continue;
2554
+ }
2555
+ const equals = arg.indexOf("=");
2556
+ if (equals > -1) {
2557
+ values[arg.slice(0, equals)] = arg.slice(equals + 1);
2558
+ continue;
2559
+ }
2560
+ if (BOOLEAN_LOCAL_FLAGS.has(arg)) {
2561
+ booleans.add(arg);
2562
+ continue;
2563
+ }
2564
+ const next = args[index + 1];
2565
+ if (next !== undefined && !next.startsWith("--")) {
2566
+ values[arg] = next;
2567
+ index += 1;
2568
+ } else {
2569
+ booleans.add(arg);
2570
+ }
2571
+ }
2572
+ return { values, booleans, positionals };
2573
+ }
2574
+ function integerFlag(flags, name, fallback) {
2575
+ const raw = flags.values[name];
2576
+ if (raw === undefined) {
2577
+ return fallback;
2578
+ }
2579
+ const value = Number.parseInt(raw, 10);
2580
+ if (!Number.isFinite(value) || value < 0) {
2581
+ throw new CliError(`${name} must be a non-negative integer`);
2582
+ }
2583
+ return value;
2584
+ }
2585
+ function copyStringFlag(flags, target, flagName, paramName) {
2586
+ const value = flags.values[flagName];
2587
+ if (value !== undefined) {
2588
+ target[paramName] = value;
2589
+ }
2590
+ }
2591
+ function isJsonRequested(argv) {
2592
+ for (let index = 0;index < argv.length; index += 1) {
2593
+ const arg = argv[index];
2594
+ if (arg === "--json") {
2595
+ return true;
2596
+ }
2597
+ if (arg === "--output-format=json" || arg === "--output-format=stream-json" || arg === "--output=json" || arg === "--output=stream-json") {
2598
+ return true;
2599
+ }
2600
+ if (arg === "--output-format" || arg === "--output") {
2601
+ const value = argv[index + 1];
2602
+ if (value === "json" || value === "stream-json") {
2603
+ return true;
2604
+ }
2605
+ }
2606
+ }
2607
+ return false;
2608
+ }
2609
+ function nonEmptyString(value) {
2610
+ if (value === undefined) {
2611
+ return;
2612
+ }
2613
+ const trimmed = value.trim();
2614
+ return trimmed === "" ? undefined : trimmed;
2615
+ }
2616
+
2527
2617
  // src/auth-commands.ts
2528
2618
  import { readFile as readFile3 } from "node:fs/promises";
2529
2619
  import { createServer } from "node:http";
@@ -3184,8 +3274,8 @@ async function switchWorkspace(ctx, workspaceId) {
3184
3274
  }
3185
3275
 
3186
3276
  // src/local-commands.ts
3187
- import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
3188
- import { dirname as dirname3 } from "node:path";
3277
+ import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
3278
+ import { dirname as dirname3, resolve as resolve2 } from "node:path";
3189
3279
  var GENERIC_FAMILY_COMMANDS = {
3190
3280
  workspace: "workspace",
3191
3281
  sharing: "sharing",
@@ -3414,7 +3504,109 @@ async function dispatchLibrary(ctx, sub, args) {
3414
3504
  });
3415
3505
  return;
3416
3506
  }
3417
- throw new CliError("Usage: uru library ls|search|mkdir");
3507
+ if (sub === "read" || sub === "cat") {
3508
+ await emitTool(ctx, "library.read", {
3509
+ path: requireCommandValue(args[0], "Usage: uru library read <path>")
3510
+ }, { textExtractor: extractLibraryTextContent });
3511
+ return;
3512
+ }
3513
+ if (sub === "write") {
3514
+ const flags = parseLocalFlags(args);
3515
+ const path = requireCommandValue(flags.positionals[0], "Usage: uru library write <path> (--file <local-path>|--content <text>|--stdin) [--create]");
3516
+ await emitTool(ctx, "library.write", {
3517
+ path,
3518
+ content: await libraryTextFromFlags(ctx, flags, "write"),
3519
+ create: flags.booleans.has("--create")
3520
+ });
3521
+ return;
3522
+ }
3523
+ if (sub === "patch") {
3524
+ const flags = parseLocalFlags(args);
3525
+ const path = requireCommandValue(flags.positionals[0], "Usage: uru library patch <path> (--file <local-path>|--content <text>|--stdin)");
3526
+ await emitTool(ctx, "library.patch", {
3527
+ path,
3528
+ content: await libraryTextFromFlags(ctx, flags, "patch")
3529
+ });
3530
+ return;
3531
+ }
3532
+ if (sub === "cp" || sub === "copy") {
3533
+ const [path, targetPath] = args;
3534
+ await emitTool(ctx, "library.cp", {
3535
+ path: requireCommandValue(path, "Usage: uru library cp <path> <target-path>"),
3536
+ target_path: requireCommandValue(targetPath, "Usage: uru library cp <path> <target-path>")
3537
+ });
3538
+ return;
3539
+ }
3540
+ if (sub === "mv" || sub === "move" || sub === "rename") {
3541
+ const [path, targetPath] = args;
3542
+ await emitTool(ctx, "library.mv", {
3543
+ path: requireCommandValue(path, "Usage: uru library mv <path> <target-path>"),
3544
+ target_path: requireCommandValue(targetPath, "Usage: uru library mv <path> <target-path>")
3545
+ });
3546
+ return;
3547
+ }
3548
+ if (sub === "rm" || sub === "remove" || sub === "delete") {
3549
+ const flags = parseLocalFlags(args);
3550
+ if ((flags.booleans.has("--permanent") || flags.booleans.has("--hard-delete")) && !ctx.yes && !flags.booleans.has("--yes")) {
3551
+ throw new CliError("Permanent Library deletion cannot be undone. Re-run with --yes to confirm.");
3552
+ }
3553
+ const path = requireCommandValue(flags.positionals[0], "Usage: uru library rm <path> [--permanent] [--yes]");
3554
+ await emitTool(ctx, "library.rm", {
3555
+ path,
3556
+ permanent: flags.booleans.has("--permanent") || flags.booleans.has("--hard-delete")
3557
+ });
3558
+ return;
3559
+ }
3560
+ if (sub === "restore") {
3561
+ await emitTool(ctx, "library.restore", {
3562
+ path: requireCommandValue(args[0], "Usage: uru library restore <path>"),
3563
+ include_trashed: true
3564
+ });
3565
+ return;
3566
+ }
3567
+ if (sub === "empty-trash") {
3568
+ const flags = parseLocalFlags(args);
3569
+ if (!ctx.yes && !flags.booleans.has("--yes")) {
3570
+ throw new CliError("Emptying Library trash permanently deletes all trashed items. Re-run with --yes to confirm.");
3571
+ }
3572
+ await emitTool(ctx, "library.empty_trash", {});
3573
+ return;
3574
+ }
3575
+ throw new CliError("Usage: uru library ls|search|mkdir|read|write|patch|cp|mv|rm|restore|empty-trash");
3576
+ }
3577
+ async function libraryTextFromFlags(ctx, flags, verb) {
3578
+ const content = flags.values["--content"];
3579
+ const file = flags.values["--file"];
3580
+ const fromStdin = flags.booleans.has("--stdin");
3581
+ const sources = [content !== undefined, file !== undefined, fromStdin].filter(Boolean).length;
3582
+ if (sources !== 1) {
3583
+ throw new CliError(`Library ${verb} requires exactly one of --file <path>, --content <text>, or --stdin.`);
3584
+ }
3585
+ if (content !== undefined) {
3586
+ return content;
3587
+ }
3588
+ if (file !== undefined) {
3589
+ return await readFile4(resolve2(ctx.cwd, file), "utf8");
3590
+ }
3591
+ return await readStdinText(ctx.stdin ?? process.stdin);
3592
+ }
3593
+ async function readStdinText(stdin) {
3594
+ const chunks = [];
3595
+ for await (const chunk of stdin) {
3596
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
3597
+ }
3598
+ return Buffer.concat(chunks).toString("utf8");
3599
+ }
3600
+ function extractLibraryTextContent(value) {
3601
+ if (!isJsonObject2(value)) {
3602
+ return;
3603
+ }
3604
+ const content = value["content"];
3605
+ if (typeof content === "string") {
3606
+ return content;
3607
+ }
3608
+ const text = value["text"];
3609
+ return typeof text === "string" ? text : undefined;
3418
3610
  }
3419
3611
  async function dispatchDatasets(ctx, sub, args) {
3420
3612
  if (sub !== "query") {
@@ -3623,11 +3815,12 @@ function levenshtein(left, right) {
3623
3815
  }
3624
3816
 
3625
3817
  // src/gem-lifecycle-commands.ts
3626
- import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
3818
+ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
3627
3819
  import { setTimeout as sleep } from "node:timers/promises";
3628
- import { join as join3, resolve as resolve2 } from "node:path";
3820
+ import { join as join3, resolve as resolve3 } from "node:path";
3629
3821
  async function dispatchGems(ctx, sub, args, linkedProject) {
3630
- const gemPath = () => gemPathFromArgs(args, linkedProject);
3822
+ const flags = parseLocalFlags(args);
3823
+ const gemPath = () => gemPathFromArgs(flags, linkedProject);
3631
3824
  if (sub === "inspect") {
3632
3825
  await emitGemTool(ctx, "gem.inspect", { path: gemPath() });
3633
3826
  return;
@@ -3678,7 +3871,7 @@ async function gemInit(ctx, args) {
3678
3871
  params["content"] = flags.values["--content"];
3679
3872
  }
3680
3873
  if (flags.values["--file"] !== undefined) {
3681
- params["content"] = await readFile4(flags.values["--file"], "utf8");
3874
+ params["content"] = await readFile5(flags.values["--file"], "utf8");
3682
3875
  }
3683
3876
  await emitGemTool(ctx, "gem.init", params);
3684
3877
  }
@@ -3703,6 +3896,12 @@ async function gemDeploy(ctx, args, linkedProject) {
3703
3896
  }
3704
3897
  if (flags.booleans.has("--dry-run")) {
3705
3898
  const buildArgs = [...flags.positionals, "--dry-run"];
3899
+ for (const flag of ["--path", "--gem"]) {
3900
+ const value = flags.values[flag];
3901
+ if (value !== undefined) {
3902
+ buildArgs.push(flag, value);
3903
+ }
3904
+ }
3706
3905
  if (flags.booleans.has("--force-build")) {
3707
3906
  buildArgs.push("--force");
3708
3907
  }
@@ -3713,7 +3912,7 @@ async function gemDeploy(ctx, args, linkedProject) {
3713
3912
  await gemBuild(ctx, buildArgs, linkedProject);
3714
3913
  return;
3715
3914
  }
3716
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
3915
+ const gemPath = gemPathFromArgs(flags, linkedProject);
3717
3916
  const publish = !flags.booleans.has("--no-publish") && !flags.booleans.has("--skip-promote");
3718
3917
  const params = {
3719
3918
  path: gemPath,
@@ -3742,7 +3941,7 @@ async function gemDeploy(ctx, args, linkedProject) {
3742
3941
  async function gemBuild(ctx, args, linkedProject) {
3743
3942
  const flags = parseLocalFlags(args);
3744
3943
  assertAllowedLocalFlags(flags, ["--dry-run", "--force", "--gem", "--outdir", "--path", "--source-version-id"], "uru build");
3745
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
3944
+ const gemPath = gemPathFromArgs(flags, linkedProject);
3746
3945
  const params = {
3747
3946
  path: gemPath
3748
3947
  };
@@ -3791,9 +3990,9 @@ async function gemChecks(ctx, args, linkedProject) {
3791
3990
  }
3792
3991
  assertAllowedLocalFlags(flags, ["--deployment-id", "--gem", "--path"], "uru gems checks");
3793
3992
  const explicitDeploymentId = flags.values["--deployment-id"];
3794
- const gemPathArgs = explicitDeploymentId === undefined ? flags.positionals.slice(1) : flags.positionals;
3993
+ const gemPathFlags = explicitDeploymentId === undefined ? shiftPositionals(flags, 1) : flags;
3795
3994
  const params = {
3796
- path: gemPathFromArgs(gemPathArgs, linkedProject)
3995
+ path: gemPathFromArgs(gemPathFlags, linkedProject)
3797
3996
  };
3798
3997
  if (explicitDeploymentId !== undefined) {
3799
3998
  params["deployment_id"] = explicitDeploymentId;
@@ -3806,7 +4005,7 @@ async function gemRunCheck(ctx, flags, linkedProject) {
3806
4005
  assertAllowedLocalFlags(flags, ["--blocking", "--deep", "--deployment-id", "--gem", "--path"], "uru gems checks run");
3807
4006
  const kind = requireCommandValue(flags.positionals[1], "Usage: uru gems checks run browser|security [library/name.gem] [--deployment-id <id>] [--blocking]");
3808
4007
  const params = {
3809
- path: gemPathFromArgs(flags.positionals.slice(2), linkedProject)
4008
+ path: gemPathFromArgs(shiftPositionals(flags, 2), linkedProject)
3810
4009
  };
3811
4010
  copyStringFlag(flags, params, "--deployment-id", "deployment_id");
3812
4011
  if (flags.booleans.has("--blocking")) {
@@ -3842,7 +4041,7 @@ async function gemLogs(ctx, args, linkedProject) {
3842
4041
  "--request-id"
3843
4042
  ], "uru logs");
3844
4043
  const params = {
3845
- path: gemPathFromArgs(flags.positionals, linkedProject),
4044
+ path: gemPathFromArgs(flags, linkedProject),
3846
4045
  limit: integerFlag(flags, "--limit", 100)
3847
4046
  };
3848
4047
  copyStringFlag(flags, params, "--level", "level");
@@ -3990,8 +4189,9 @@ function boundedIntegerFlag(flags, name, fallback, maximum) {
3990
4189
  return Math.min(value, maximum);
3991
4190
  }
3992
4191
  async function gemOpen(ctx, args, linkedProject) {
4192
+ const flags = parseLocalFlags(args);
3993
4193
  const result = await executeOperation(ctx, "gem.links_list", {
3994
- path: gemPathFromArgs(args, linkedProject)
4194
+ path: gemPathFromArgs(flags, linkedProject)
3995
4195
  });
3996
4196
  const url = firstUrl(unwrapToolResult(result));
3997
4197
  if (url === undefined) {
@@ -4022,7 +4222,7 @@ async function gemWrite(ctx, args) {
4022
4222
  const flags = parseLocalFlags(args);
4023
4223
  const gemPath = requireCommandValue(flags.positionals[0], "Usage: uru gems write <gem-path> <file> --content <text>|--file <local-file>");
4024
4224
  const filePath = requireCommandValue(flags.positionals[1], "Usage: uru gems write <gem-path> <file> --content <text>|--file <local-file>");
4025
- const content = flags.values["--content"] ?? (flags.values["--file"] === undefined ? undefined : await readFile4(flags.values["--file"], "utf8"));
4225
+ const content = flags.values["--content"] ?? (flags.values["--file"] === undefined ? undefined : await readFile5(flags.values["--file"], "utf8"));
4026
4226
  if (content === undefined) {
4027
4227
  throw new CliError("Gem write requires --content or --file.");
4028
4228
  }
@@ -4049,7 +4249,7 @@ async function dispatchGemVersions(ctx, args, linkedProject) {
4049
4249
  assertAllowedLocalFlags(flags, ["--gem", "--link-id", "--no-wait", "--path", "--pct", "--skip-promote"], "uru gems versions deploy");
4050
4250
  const version = requireCommandValue(flags.positionals[0], "Usage: uru gems versions deploy <version-id> [library/name.gem] [--pct 100] [--skip-promote]");
4051
4251
  const [versionId, trafficPercent] = version.split("@", 2);
4052
- const gemPath = gemPathFromArgs(flags.positionals.slice(1), linkedProject);
4252
+ const gemPath = gemPathFromArgs(shiftPositionals(flags, 1), linkedProject);
4053
4253
  const pct = flags.values["--pct"] ?? trafficPercent;
4054
4254
  const trafficPercentValue = pct === undefined || pct === "" ? 100 : parseTrafficPercent(pct);
4055
4255
  const publish = !flags.booleans.has("--skip-promote");
@@ -4086,7 +4286,7 @@ async function gemVersionUpload(ctx, args, linkedProject) {
4086
4286
  "--tag",
4087
4287
  "--tags"
4088
4288
  ], "uru gems versions upload");
4089
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4289
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4090
4290
  const params = {
4091
4291
  path: gemPath
4092
4292
  };
@@ -4122,7 +4322,7 @@ async function deployVersionToPublishedLink(ctx, flags, linkedProject, options)
4122
4322
  if (deploymentId === undefined && versionId === undefined) {
4123
4323
  throw new CliError(options.usage);
4124
4324
  }
4125
- const gemPath = gemPathFromArgs(versionId === undefined ? flags.positionals : flags.positionals.slice(1), linkedProject);
4325
+ const gemPath = gemPathFromArgs(versionId === undefined ? flags : shiftPositionals(flags, 1), linkedProject);
4126
4326
  const trafficPercent = parseTrafficPercent(flags.values["--pct"] ?? "100");
4127
4327
  if (trafficPercent !== 100) {
4128
4328
  throw new CliError(`${options.operation} does not support partial traffic shifts yet. Deploy the version with --skip-promote for validation, or ${options.operation} with --pct 100.`);
@@ -4179,7 +4379,7 @@ function localFlagNames(flags) {
4179
4379
  async function writeBuildManifest(ctx, outdir, buildResult) {
4180
4380
  const manifest = gemBuildManifestFromBuildResult(buildResult);
4181
4381
  const providerDecision = gemProviderDecisionFromBuildResult(buildResult, manifest);
4182
- const absoluteOutdir = resolve2(ctx.cwd, outdir);
4382
+ const absoluteOutdir = resolve3(ctx.cwd, outdir);
4183
4383
  await mkdir4(absoluteOutdir, { recursive: true });
4184
4384
  await writeFile4(join3(absoluteOutdir, "gem-build-manifest.json"), `${JSON.stringify(manifest, null, 2)}
4185
4385
  `, { mode: 384 });
@@ -4378,10 +4578,10 @@ function trimLeadingSlashes(value) {
4378
4578
 
4379
4579
  // src/gem-source-commands.ts
4380
4580
  import { mkdir as mkdir5, rm as rm3, writeFile as writeFile5 } from "node:fs/promises";
4381
- import { dirname as dirname4, join as join4, resolve as resolve3 } from "node:path";
4581
+ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
4382
4582
  async function gemSourcePull(ctx, args, linkedProject) {
4383
4583
  const flags = parseLocalFlags(args);
4384
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4584
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4385
4585
  const force = flags.booleans.has("--force");
4386
4586
  const previousLock = await readSourceLock(ctx.cwd);
4387
4587
  const localFilesBeforePull = await collectLocalSourceFiles(ctx.cwd);
@@ -4401,15 +4601,19 @@ async function gemSourcePull(ctx, args, linkedProject) {
4401
4601
  }
4402
4602
  let sourceVersionId = findSourceVersionId(tree);
4403
4603
  const files = {};
4604
+ const pulledFiles = [];
4404
4605
  for (const filePath of filePaths) {
4405
4606
  const readResult = unwrapToolResult(await executeOperation(ctx, "gem.fs_read", {
4406
4607
  path: gemFilePath(gemPath, filePath)
4407
4608
  }));
4408
4609
  sourceVersionId = sourceVersionId ?? findSourceVersionId(readResult);
4409
4610
  const content = extractRemoteFileContent(readResult, filePath);
4410
- await writePulledSourceFile(ctx.cwd, filePath, content);
4611
+ pulledFiles.push({ path: filePath, content });
4411
4612
  files[filePath] = sha256Hex(content);
4412
4613
  }
4614
+ for (const file of pulledFiles) {
4615
+ await writePulledSourceFile(ctx.cwd, file.path, file.content);
4616
+ }
4413
4617
  const pruned = await pruneRemoteDeletedSourceFiles(ctx.cwd, previousLock, localFilesBeforePull, new Set(filePaths));
4414
4618
  const lock = {
4415
4619
  ...sourceVersionId === undefined ? {} : { sourceVersionId },
@@ -4428,11 +4632,11 @@ async function gemSourcePull(ctx, args, linkedProject) {
4428
4632
  }
4429
4633
  async function gemSourceStatus(ctx, args, linkedProject) {
4430
4634
  const flags = parseLocalFlags(args);
4431
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4635
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4432
4636
  const lock = await readSourceLock(ctx.cwd);
4433
4637
  const localFiles = await collectLocalSourceFiles(ctx.cwd);
4434
4638
  const localStatus = compareSourceLock(lock, localFiles);
4435
- const remote = await tryRemoteSourceOperation(ctx, "gem.fs_status", {
4639
+ const remote = await remoteSourceOperation(ctx, "gem.fs_status", {
4436
4640
  path: gemPath,
4437
4641
  ...lock.sourceVersionId === undefined ? {} : { base: lock.sourceVersionId }
4438
4642
  });
@@ -4447,12 +4651,12 @@ async function gemSourceStatus(ctx, args, linkedProject) {
4447
4651
  }
4448
4652
  async function gemSourceDiff(ctx, args, linkedProject) {
4449
4653
  const flags = parseLocalFlags(args);
4450
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4654
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4451
4655
  const lock = await readSourceLock(ctx.cwd);
4452
4656
  const localFiles = await collectLocalSourceFiles(ctx.cwd);
4453
4657
  const localStatus = compareSourceLock(lock, localFiles);
4454
4658
  const mode = flags.values["--mode"] ?? "name_status";
4455
- const remote = await tryRemoteSourceOperation(ctx, "gem.fs_diff", {
4659
+ const remote = await remoteSourceOperation(ctx, "gem.fs_diff", {
4456
4660
  path: gemPath,
4457
4661
  mode,
4458
4662
  ...lock.sourceVersionId === undefined ? {} : { base: lock.sourceVersionId }
@@ -4468,7 +4672,7 @@ async function gemSourceDiff(ctx, args, linkedProject) {
4468
4672
  }
4469
4673
  async function gemSourcePush(ctx, args, linkedProject) {
4470
4674
  const flags = parseLocalFlags(args);
4471
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4675
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4472
4676
  const lock = await readSourceLock(ctx.cwd);
4473
4677
  const force = flags.booleans.has("--force");
4474
4678
  const localFiles = await collectLocalSourceFiles(ctx.cwd);
@@ -4495,6 +4699,9 @@ async function gemSourcePush(ctx, args, linkedProject) {
4495
4699
  }));
4496
4700
  sourceCas = nextSourceMutationCas(sourceCas, syncResult);
4497
4701
  } catch (error) {
4702
+ if (error instanceof CliError && error.exitCode === ExitCode.Conflict) {
4703
+ throw error;
4704
+ }
4498
4705
  if (isConflictLikeError(error)) {
4499
4706
  throw new CliError(error instanceof Error ? error.message : "Gem source conflict", { code: ExitCode.Conflict, cause: error });
4500
4707
  }
@@ -4541,7 +4748,7 @@ async function pruneRemoteDeletedSourceFiles(cwd, previousLock, localFilesBefore
4541
4748
  preserved.push(projectPath2);
4542
4749
  continue;
4543
4750
  }
4544
- await rm3(join4(resolve3(cwd), ...safePath.split("/")), { force: true });
4751
+ await rm3(join4(resolve4(cwd), ...safePath.split("/")), { force: true });
4545
4752
  deleted.push(projectPath2);
4546
4753
  }
4547
4754
  return { deleted, preserved };
@@ -4660,18 +4867,11 @@ function dirtyPullErrorMessage(status) {
4660
4867
  Run \`uru status\` to inspect them, save or push your changes, or re-run \`uru pull --force\` to replace local files with server source.
4661
4868
  ${preview}${moreLine}`;
4662
4869
  }
4663
- async function tryRemoteSourceOperation(ctx, operationId, params) {
4664
- try {
4665
- return {
4666
- available: true,
4667
- result: unwrapToolResult(await executeOperation(ctx, operationId, params))
4668
- };
4669
- } catch (error) {
4670
- return {
4671
- available: false,
4672
- error: error instanceof Error ? error.message : "Remote operation failed"
4673
- };
4674
- }
4870
+ async function remoteSourceOperation(ctx, operationId, params) {
4871
+ return {
4872
+ available: true,
4873
+ result: unwrapToolResult(await executeOperation(ctx, operationId, params))
4874
+ };
4675
4875
  }
4676
4876
  function formatRemoteSummary(remote) {
4677
4877
  if (!remote.available) {
@@ -4762,7 +4962,7 @@ async function writePulledSourceFile(cwd, projectPath2, content) {
4762
4962
  if (safePath === null) {
4763
4963
  throw new CliError(`Refusing to write unsafe Gem source path: ${projectPath2}`);
4764
4964
  }
4765
- const localPath = join4(resolve3(cwd), ...safePath.split("/"));
4965
+ const localPath = join4(resolve4(cwd), ...safePath.split("/"));
4766
4966
  await mkdir5(dirname4(localPath), { recursive: true });
4767
4967
  await writeFile5(localPath, content);
4768
4968
  }
@@ -4817,8 +5017,9 @@ function isConflictLikeError(error) {
4817
5017
 
4818
5018
  // src/token-env-secret-commands.ts
4819
5019
  import { spawn as spawn2 } from "node:child_process";
4820
- import { chmod, mkdir as mkdir6, readFile as readFile5, writeFile as writeFile6 } from "node:fs/promises";
4821
- import { dirname as dirname5, resolve as resolve4 } from "node:path";
5020
+ import { StringDecoder } from "node:string_decoder";
5021
+ import { chmod, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
5022
+ import { dirname as dirname5, resolve as resolve5 } from "node:path";
4822
5023
  async function dispatchTokens(ctx, sub, args, linkedProject) {
4823
5024
  if (sub === "create") {
4824
5025
  const flags = parseLocalFlags(args);
@@ -4932,7 +5133,7 @@ async function dispatchEnv(ctx, sub, args, linkedProject) {
4932
5133
  })));
4933
5134
  const secrets = extractSecretRows(result);
4934
5135
  const content = formatEnvTemplate(secrets);
4935
- const absolutePath = resolve4(ctx.cwd, outputPath);
5136
+ const absolutePath = resolve5(ctx.cwd, outputPath);
4936
5137
  await mkdir6(dirname5(absolutePath), { recursive: true });
4937
5138
  await writeFile6(absolutePath, content, { mode: 384 });
4938
5139
  await chmod(absolutePath, 384);
@@ -4949,8 +5150,8 @@ async function dispatchEnv(ctx, sub, args, linkedProject) {
4949
5150
  }
4950
5151
  if (sub === "run") {
4951
5152
  const parsed = parseEnvRunArgs(args);
4952
- const envFilePath = resolve4(ctx.cwd, parsed.envFile);
4953
- const envVars = parseEnvFile(await readFile5(envFilePath, "utf8"));
5153
+ const envFilePath = resolve5(ctx.cwd, parsed.envFile);
5154
+ const envVars = parseEnvFile(await readFile6(envFilePath, "utf8"));
4954
5155
  await runEnvCommand(ctx, parsed.command, envVars);
4955
5156
  return;
4956
5157
  }
@@ -4970,10 +5171,10 @@ async function secretValueFromFlags(ctx, flags) {
4970
5171
  }
4971
5172
  const valueFile = flags.values["--value-file"];
4972
5173
  if (valueFile !== undefined) {
4973
- return (await readFile5(resolve4(ctx.cwd, valueFile), "utf8")).trimEnd();
5174
+ return (await readFile6(resolve5(ctx.cwd, valueFile), "utf8")).trimEnd();
4974
5175
  }
4975
5176
  if (flags.booleans.has("--value-stdin")) {
4976
- return await readStdinText(ctx.stdin ?? process.stdin);
5177
+ return await readStdinText2(ctx.stdin ?? process.stdin);
4977
5178
  }
4978
5179
  throw new CliError("Secret put requires --value-file <path> or --value-stdin. Values are never printed by the CLI.");
4979
5180
  }
@@ -5068,48 +5269,69 @@ function unquoteEnvValue(value) {
5068
5269
  }
5069
5270
  async function runEnvCommand(ctx, command, envVars) {
5070
5271
  const executable = requireCommandValue(command[0], "env run command is required");
5071
- const result = await spawnBuffered(executable, command.slice(1), {
5272
+ const redactions = Object.values(envVars).filter((value) => value.length > 0);
5273
+ const code = await spawnStreaming(executable, command.slice(1), {
5072
5274
  cwd: ctx.cwd,
5073
- env: { ...process.env, ...envVars }
5275
+ env: { ...process.env, ...envVars },
5276
+ redactions,
5277
+ stdout: ctx.io.stdout,
5278
+ stderr: ctx.io.stderr
5074
5279
  });
5075
- const redactions = Object.values(envVars).filter((value) => value.length > 0);
5076
- const stdout = redactValues2(result.stdout, redactions);
5077
- const stderr = redactValues2(result.stderr, redactions);
5078
- if (stdout.length > 0) {
5079
- ctx.io.stdout.write(stdout);
5080
- }
5081
- if (stderr.length > 0) {
5082
- ctx.io.stderr.write(stderr);
5083
- }
5084
- if (result.code !== 0) {
5085
- throw new CliError(`env run failed with exit code ${result.code}`);
5280
+ if (code !== 0) {
5281
+ throw new CliError(`env run failed with exit code ${code}`);
5086
5282
  }
5087
5283
  }
5088
- async function spawnBuffered(command, args, options) {
5284
+ async function spawnStreaming(command, args, options) {
5089
5285
  return new Promise((resolvePromise, reject) => {
5090
5286
  const child = spawn2(command, [...args], {
5091
5287
  cwd: options.cwd,
5092
5288
  env: options.env,
5093
- stdio: ["ignore", "pipe", "pipe"]
5289
+ stdio: ["inherit", "pipe", "pipe"]
5094
5290
  });
5095
- const stdout = [];
5096
- const stderr = [];
5291
+ const stdout = redactingLineWriter(options.stdout, options.redactions);
5292
+ const stderr = redactingLineWriter(options.stderr, options.redactions);
5097
5293
  child.stdout?.on("data", (chunk) => {
5098
- stdout.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
5294
+ stdout.write(chunk);
5099
5295
  });
5100
5296
  child.stderr?.on("data", (chunk) => {
5101
- stderr.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
5297
+ stderr.write(chunk);
5102
5298
  });
5103
5299
  child.on("error", reject);
5104
5300
  child.on("close", (code) => {
5105
- resolvePromise({
5106
- code: code ?? 1,
5107
- stdout: stdout.join(""),
5108
- stderr: stderr.join("")
5109
- });
5301
+ stdout.end();
5302
+ stderr.end();
5303
+ resolvePromise(code ?? 1);
5110
5304
  });
5111
5305
  });
5112
5306
  }
5307
+ function redactingLineWriter(destination, redactions) {
5308
+ const decoder = new StringDecoder("utf8");
5309
+ let buffered = "";
5310
+ const flushCompleteLines = () => {
5311
+ let newline = buffered.indexOf(`
5312
+ `);
5313
+ while (newline !== -1) {
5314
+ destination.write(redactValues2(buffered.slice(0, newline + 1), redactions));
5315
+ buffered = buffered.slice(newline + 1);
5316
+ newline = buffered.indexOf(`
5317
+ `);
5318
+ }
5319
+ };
5320
+ return {
5321
+ write: (chunk) => {
5322
+ buffered += decoder.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
5323
+ flushCompleteLines();
5324
+ },
5325
+ end: () => {
5326
+ buffered += decoder.end();
5327
+ flushCompleteLines();
5328
+ if (buffered.length > 0) {
5329
+ destination.write(redactValues2(buffered, redactions));
5330
+ buffered = "";
5331
+ }
5332
+ }
5333
+ };
5334
+ }
5113
5335
  function redactValues2(content, values) {
5114
5336
  let redacted = content;
5115
5337
  const ordered = [...new Set(values)].filter((value) => value.length > 0).sort((left, right) => right.length - left.length);
@@ -5118,7 +5340,7 @@ function redactValues2(content, values) {
5118
5340
  }
5119
5341
  return redacted;
5120
5342
  }
5121
- async function readStdinText(stdin) {
5343
+ async function readStdinText2(stdin) {
5122
5344
  const chunks = [];
5123
5345
  for await (const chunk of stdin) {
5124
5346
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
@@ -5257,7 +5479,7 @@ async function runCli(options) {
5257
5479
  const explicitGemRef = parsed.flags.gem ?? envGemId;
5258
5480
  const apiUrl = parsed.flags.apiUrl ?? envApiUrl ?? config.apiUrl ?? DEFAULT_API_URL;
5259
5481
  const token = flagToken ?? envToken ?? await resolveStoredToken(credentialStore, apiUrl, config);
5260
- const cwd = parsed.flags.cwd ?? options.env?.["PWD"] ?? process.cwd();
5482
+ const cwd = parsed.flags.cwd ?? process.cwd();
5261
5483
  const linkedProjectFromDisk = await readLinkedProject(cwd);
5262
5484
  const linkedProject = explicitGemRef === undefined ? linkedProjectFromDisk : linkedProjectWithExplicitGemRef(linkedProjectFromDisk, explicitGemRef);
5263
5485
  const workspace = parsed.flags.workspace ?? envWorkspace ?? linkedProject.workspaceId ?? config.workspace;
@@ -5594,14 +5816,14 @@ function writeSystemClipboard(text) {
5594
5816
  const platform = process.platform;
5595
5817
  const command = platform === "darwin" ? "pbcopy" : platform === "win32" ? "clip" : "xclip";
5596
5818
  const args = platform === "linux" ? ["-selection", "clipboard"] : [];
5597
- return new Promise((resolve5, reject) => {
5819
+ return new Promise((resolve6, reject) => {
5598
5820
  const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"] });
5599
5821
  child.on("error", (error) => {
5600
5822
  reject(new CliError("Could not copy login URL to clipboard. Re-run without --clipboard to print the URL.", { cause: error }));
5601
5823
  });
5602
5824
  child.on("close", (code) => {
5603
5825
  if (code === 0) {
5604
- resolve5();
5826
+ resolve6();
5605
5827
  return;
5606
5828
  }
5607
5829
  reject(new CliError(`Could not copy login URL to clipboard (${command} exited ${code ?? "unknown"}). Re-run without --clipboard to print the URL.`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uru-intelligence/cli",
3
- "version": "0.3.54",
3
+ "version": "0.3.56",
4
4
  "private": false,
5
5
  "description": "Uru full-platform command line interface",
6
6
  "type": "module",
@@ -31,13 +31,13 @@
31
31
  },
32
32
  "devDependencies": {
33
33
  "@uru/platform-operations": "workspace:*",
34
- "@types/node": "24.13.2",
34
+ "@types/node": "24.13.3",
35
35
  "bun-types": "1.3.13",
36
- "typescript": "7.0.1-rc"
36
+ "typescript": "7.0.2"
37
37
  },
38
38
  "packageManager": "bun@1.3.13",
39
39
  "engines": {
40
- "node": "24.13.0"
40
+ "node": ">=24"
41
41
  },
42
42
  "files": [
43
43
  "dist",