@uru-intelligence/cli 0.3.55 → 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 (2) hide show
  1. package/dist/uru.mjs +342 -240
  2. package/package.json +4 -4
package/dist/uru.mjs CHANGED
@@ -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)}
@@ -1581,7 +1416,14 @@ async function executeOperationStream(ctx, operationId, params, onEvent) {
1581
1416
  if (operation.backendOp !== undefined) {
1582
1417
  toolParams["op"] = operation.backendOp;
1583
1418
  }
1584
- 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;
1585
1427
  }
1586
1428
  async function executeOperation(ctx, operationId, params) {
1587
1429
  const operation = getPlatformOperation(operationId);
@@ -1592,7 +1434,9 @@ async function executeOperation(ctx, operationId, params) {
1592
1434
  if (operation.backendOp !== undefined) {
1593
1435
  toolParams["op"] = operation.backendOp;
1594
1436
  }
1595
- return client(ctx).runTool(operation.backendToolName, toolParams);
1437
+ const result = await client(ctx).runTool(operation.backendToolName, toolParams);
1438
+ assertToolResultSuccess(result);
1439
+ return result;
1596
1440
  }
1597
1441
  function emit(ctx, value, text) {
1598
1442
  if (ctx.outputFormat === "stream-json") {
@@ -1604,19 +1448,51 @@ function emit(ctx, value, text) {
1604
1448
  }
1605
1449
  }
1606
1450
  function unwrapToolResult(value) {
1607
- if (isJsonObject2(value) && isJsonObject2(value["result"])) {
1608
- 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
+ }
1609
1457
  }
1610
1458
  return value;
1611
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
+ }
1612
1489
  function delegatedPayload(value) {
1613
1490
  if (isJsonObject2(value) && isJsonObject2(value["payload"])) {
1614
1491
  return value["payload"];
1615
1492
  }
1616
1493
  return value;
1617
1494
  }
1618
- function gemPathFromArgs(args, linkedProject) {
1619
- const flags = parseLocalFlags(args);
1495
+ function gemPathFromArgs(flags, linkedProject) {
1620
1496
  return flags.values["--path"] ?? flags.values["--gem"] ?? flags.positionals.find((value) => value.startsWith("library/")) ?? linkedProject.libraryPath ?? linkedProject.gemId ?? (() => {
1621
1497
  throw new CliError("Gem path required. Pass library/<name>.gem or run `uru link --path library/<name>.gem`.");
1622
1498
  })();
@@ -1840,16 +1716,9 @@ function createMacosKeychainCredentialStore(runCommand) {
1840
1716
  },
1841
1717
  async writeToken(apiUrl, token) {
1842
1718
  try {
1843
- await runCommand("security", [
1844
- "add-generic-password",
1845
- "-a",
1846
- accountForApiUrl(apiUrl),
1847
- "-s",
1848
- KEYCHAIN_SERVICE,
1849
- "-w",
1850
- token,
1851
- "-U"
1852
- ]);
1719
+ await runCommand("security", ["-i"], {
1720
+ input: macosKeychainWriteCommand(apiUrl, token)
1721
+ });
1853
1722
  } catch (error) {
1854
1723
  throw credentialStoreError("macOS Keychain", error);
1855
1724
  }
@@ -1871,6 +1740,11 @@ function createMacosKeychainCredentialStore(runCommand) {
1871
1740
  }
1872
1741
  };
1873
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
+ }
1874
1748
  function createLinuxSecretToolCredentialStore(runCommand) {
1875
1749
  return {
1876
1750
  async readToken(apiUrl) {
@@ -2369,7 +2243,8 @@ var SKIPPED_SOURCE_PATH_SEGMENTS = new Set([
2369
2243
  "build",
2370
2244
  "coverage",
2371
2245
  "tmp",
2372
- "temp"
2246
+ "temp",
2247
+ "__MACOSX"
2373
2248
  ]);
2374
2249
  var SKIPPED_SOURCE_FILE_NAMES = new Set([
2375
2250
  ".DS_Store",
@@ -2382,7 +2257,8 @@ var SKIPPED_SOURCE_FILE_NAMES = new Set([
2382
2257
  ]);
2383
2258
  function shouldSkipProjectPath(path) {
2384
2259
  const segments = path.split("/");
2385
- 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("._");
2386
2262
  }
2387
2263
  function stableStringRecord(record) {
2388
2264
  return Object.fromEntries(Object.entries(record).filter(([, value]) => typeof value === "string" && value.trim() !== "").sort(([left], [right]) => compareStrings(left, right)));
@@ -2437,11 +2313,12 @@ async function maybeNotifyCliUpdate(ctx, options = {}) {
2437
2313
  if (!shouldCheckForUpdate(ctx, options.env ?? process.env)) {
2438
2314
  return;
2439
2315
  }
2316
+ const currentConfig = await readConfigBestEffort(ctx);
2440
2317
  const now = options.now?.() ?? Date.now();
2441
- if (!isUpdateCheckDue(ctx.config, now)) {
2318
+ if (!isUpdateCheckDue(currentConfig, now)) {
2442
2319
  return;
2443
2320
  }
2444
- const checkedConfig = withUpdateCheckTimestamp(ctx.config, now);
2321
+ const checkedConfig = withUpdateCheckTimestamp(currentConfig, now);
2445
2322
  await writeConfigBestEffort(ctx, checkedConfig);
2446
2323
  const latestVersion = await fetchLatestVersion({
2447
2324
  fetchImpl: ctx.fetch ?? fetch,
@@ -2463,6 +2340,13 @@ async function maybeNotifyCliUpdate(ctx, options = {}) {
2463
2340
  ctx.io.stderr.write(`A newer Uru CLI is available: ${CLI_VERSION} → ${latestVersion}. ` + `Run npm install -g ${PACKAGE_NAME}@latest
2464
2341
  `);
2465
2342
  }
2343
+ async function readConfigBestEffort(ctx) {
2344
+ try {
2345
+ return await ctx.store.read();
2346
+ } catch {
2347
+ return ctx.config;
2348
+ }
2349
+ }
2466
2350
  function shouldCheckForUpdate(ctx, env) {
2467
2351
  return ctx.outputFormat === "text" && ctx.terminal.stderrIsTty && !ctx.terminal.ci && env["NO_UPDATE_NOTIFIER"] === undefined && env["URU_NO_UPDATE_NOTIFIER"] === undefined;
2468
2352
  }
@@ -2542,6 +2426,194 @@ function isRecord2(value) {
2542
2426
  return value !== null && typeof value === "object" && !Array.isArray(value);
2543
2427
  }
2544
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
+
2545
2617
  // src/auth-commands.ts
2546
2618
  import { readFile as readFile3 } from "node:fs/promises";
2547
2619
  import { createServer } from "node:http";
@@ -3747,7 +3819,8 @@ import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from
3747
3819
  import { setTimeout as sleep } from "node:timers/promises";
3748
3820
  import { join as join3, resolve as resolve3 } from "node:path";
3749
3821
  async function dispatchGems(ctx, sub, args, linkedProject) {
3750
- const gemPath = () => gemPathFromArgs(args, linkedProject);
3822
+ const flags = parseLocalFlags(args);
3823
+ const gemPath = () => gemPathFromArgs(flags, linkedProject);
3751
3824
  if (sub === "inspect") {
3752
3825
  await emitGemTool(ctx, "gem.inspect", { path: gemPath() });
3753
3826
  return;
@@ -3823,6 +3896,12 @@ async function gemDeploy(ctx, args, linkedProject) {
3823
3896
  }
3824
3897
  if (flags.booleans.has("--dry-run")) {
3825
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
+ }
3826
3905
  if (flags.booleans.has("--force-build")) {
3827
3906
  buildArgs.push("--force");
3828
3907
  }
@@ -3833,7 +3912,7 @@ async function gemDeploy(ctx, args, linkedProject) {
3833
3912
  await gemBuild(ctx, buildArgs, linkedProject);
3834
3913
  return;
3835
3914
  }
3836
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
3915
+ const gemPath = gemPathFromArgs(flags, linkedProject);
3837
3916
  const publish = !flags.booleans.has("--no-publish") && !flags.booleans.has("--skip-promote");
3838
3917
  const params = {
3839
3918
  path: gemPath,
@@ -3862,7 +3941,7 @@ async function gemDeploy(ctx, args, linkedProject) {
3862
3941
  async function gemBuild(ctx, args, linkedProject) {
3863
3942
  const flags = parseLocalFlags(args);
3864
3943
  assertAllowedLocalFlags(flags, ["--dry-run", "--force", "--gem", "--outdir", "--path", "--source-version-id"], "uru build");
3865
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
3944
+ const gemPath = gemPathFromArgs(flags, linkedProject);
3866
3945
  const params = {
3867
3946
  path: gemPath
3868
3947
  };
@@ -3911,9 +3990,9 @@ async function gemChecks(ctx, args, linkedProject) {
3911
3990
  }
3912
3991
  assertAllowedLocalFlags(flags, ["--deployment-id", "--gem", "--path"], "uru gems checks");
3913
3992
  const explicitDeploymentId = flags.values["--deployment-id"];
3914
- const gemPathArgs = explicitDeploymentId === undefined ? flags.positionals.slice(1) : flags.positionals;
3993
+ const gemPathFlags = explicitDeploymentId === undefined ? shiftPositionals(flags, 1) : flags;
3915
3994
  const params = {
3916
- path: gemPathFromArgs(gemPathArgs, linkedProject)
3995
+ path: gemPathFromArgs(gemPathFlags, linkedProject)
3917
3996
  };
3918
3997
  if (explicitDeploymentId !== undefined) {
3919
3998
  params["deployment_id"] = explicitDeploymentId;
@@ -3926,7 +4005,7 @@ async function gemRunCheck(ctx, flags, linkedProject) {
3926
4005
  assertAllowedLocalFlags(flags, ["--blocking", "--deep", "--deployment-id", "--gem", "--path"], "uru gems checks run");
3927
4006
  const kind = requireCommandValue(flags.positionals[1], "Usage: uru gems checks run browser|security [library/name.gem] [--deployment-id <id>] [--blocking]");
3928
4007
  const params = {
3929
- path: gemPathFromArgs(flags.positionals.slice(2), linkedProject)
4008
+ path: gemPathFromArgs(shiftPositionals(flags, 2), linkedProject)
3930
4009
  };
3931
4010
  copyStringFlag(flags, params, "--deployment-id", "deployment_id");
3932
4011
  if (flags.booleans.has("--blocking")) {
@@ -3962,7 +4041,7 @@ async function gemLogs(ctx, args, linkedProject) {
3962
4041
  "--request-id"
3963
4042
  ], "uru logs");
3964
4043
  const params = {
3965
- path: gemPathFromArgs(flags.positionals, linkedProject),
4044
+ path: gemPathFromArgs(flags, linkedProject),
3966
4045
  limit: integerFlag(flags, "--limit", 100)
3967
4046
  };
3968
4047
  copyStringFlag(flags, params, "--level", "level");
@@ -4110,8 +4189,9 @@ function boundedIntegerFlag(flags, name, fallback, maximum) {
4110
4189
  return Math.min(value, maximum);
4111
4190
  }
4112
4191
  async function gemOpen(ctx, args, linkedProject) {
4192
+ const flags = parseLocalFlags(args);
4113
4193
  const result = await executeOperation(ctx, "gem.links_list", {
4114
- path: gemPathFromArgs(args, linkedProject)
4194
+ path: gemPathFromArgs(flags, linkedProject)
4115
4195
  });
4116
4196
  const url = firstUrl(unwrapToolResult(result));
4117
4197
  if (url === undefined) {
@@ -4169,7 +4249,7 @@ async function dispatchGemVersions(ctx, args, linkedProject) {
4169
4249
  assertAllowedLocalFlags(flags, ["--gem", "--link-id", "--no-wait", "--path", "--pct", "--skip-promote"], "uru gems versions deploy");
4170
4250
  const version = requireCommandValue(flags.positionals[0], "Usage: uru gems versions deploy <version-id> [library/name.gem] [--pct 100] [--skip-promote]");
4171
4251
  const [versionId, trafficPercent] = version.split("@", 2);
4172
- const gemPath = gemPathFromArgs(flags.positionals.slice(1), linkedProject);
4252
+ const gemPath = gemPathFromArgs(shiftPositionals(flags, 1), linkedProject);
4173
4253
  const pct = flags.values["--pct"] ?? trafficPercent;
4174
4254
  const trafficPercentValue = pct === undefined || pct === "" ? 100 : parseTrafficPercent(pct);
4175
4255
  const publish = !flags.booleans.has("--skip-promote");
@@ -4206,7 +4286,7 @@ async function gemVersionUpload(ctx, args, linkedProject) {
4206
4286
  "--tag",
4207
4287
  "--tags"
4208
4288
  ], "uru gems versions upload");
4209
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4289
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4210
4290
  const params = {
4211
4291
  path: gemPath
4212
4292
  };
@@ -4242,7 +4322,7 @@ async function deployVersionToPublishedLink(ctx, flags, linkedProject, options)
4242
4322
  if (deploymentId === undefined && versionId === undefined) {
4243
4323
  throw new CliError(options.usage);
4244
4324
  }
4245
- const gemPath = gemPathFromArgs(versionId === undefined ? flags.positionals : flags.positionals.slice(1), linkedProject);
4325
+ const gemPath = gemPathFromArgs(versionId === undefined ? flags : shiftPositionals(flags, 1), linkedProject);
4246
4326
  const trafficPercent = parseTrafficPercent(flags.values["--pct"] ?? "100");
4247
4327
  if (trafficPercent !== 100) {
4248
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.`);
@@ -4501,7 +4581,7 @@ import { mkdir as mkdir5, rm as rm3, writeFile as writeFile5 } from "node:fs/pro
4501
4581
  import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
4502
4582
  async function gemSourcePull(ctx, args, linkedProject) {
4503
4583
  const flags = parseLocalFlags(args);
4504
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4584
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4505
4585
  const force = flags.booleans.has("--force");
4506
4586
  const previousLock = await readSourceLock(ctx.cwd);
4507
4587
  const localFilesBeforePull = await collectLocalSourceFiles(ctx.cwd);
@@ -4521,15 +4601,19 @@ async function gemSourcePull(ctx, args, linkedProject) {
4521
4601
  }
4522
4602
  let sourceVersionId = findSourceVersionId(tree);
4523
4603
  const files = {};
4604
+ const pulledFiles = [];
4524
4605
  for (const filePath of filePaths) {
4525
4606
  const readResult = unwrapToolResult(await executeOperation(ctx, "gem.fs_read", {
4526
4607
  path: gemFilePath(gemPath, filePath)
4527
4608
  }));
4528
4609
  sourceVersionId = sourceVersionId ?? findSourceVersionId(readResult);
4529
4610
  const content = extractRemoteFileContent(readResult, filePath);
4530
- await writePulledSourceFile(ctx.cwd, filePath, content);
4611
+ pulledFiles.push({ path: filePath, content });
4531
4612
  files[filePath] = sha256Hex(content);
4532
4613
  }
4614
+ for (const file of pulledFiles) {
4615
+ await writePulledSourceFile(ctx.cwd, file.path, file.content);
4616
+ }
4533
4617
  const pruned = await pruneRemoteDeletedSourceFiles(ctx.cwd, previousLock, localFilesBeforePull, new Set(filePaths));
4534
4618
  const lock = {
4535
4619
  ...sourceVersionId === undefined ? {} : { sourceVersionId },
@@ -4548,11 +4632,11 @@ async function gemSourcePull(ctx, args, linkedProject) {
4548
4632
  }
4549
4633
  async function gemSourceStatus(ctx, args, linkedProject) {
4550
4634
  const flags = parseLocalFlags(args);
4551
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4635
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4552
4636
  const lock = await readSourceLock(ctx.cwd);
4553
4637
  const localFiles = await collectLocalSourceFiles(ctx.cwd);
4554
4638
  const localStatus = compareSourceLock(lock, localFiles);
4555
- const remote = await tryRemoteSourceOperation(ctx, "gem.fs_status", {
4639
+ const remote = await remoteSourceOperation(ctx, "gem.fs_status", {
4556
4640
  path: gemPath,
4557
4641
  ...lock.sourceVersionId === undefined ? {} : { base: lock.sourceVersionId }
4558
4642
  });
@@ -4567,12 +4651,12 @@ async function gemSourceStatus(ctx, args, linkedProject) {
4567
4651
  }
4568
4652
  async function gemSourceDiff(ctx, args, linkedProject) {
4569
4653
  const flags = parseLocalFlags(args);
4570
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4654
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4571
4655
  const lock = await readSourceLock(ctx.cwd);
4572
4656
  const localFiles = await collectLocalSourceFiles(ctx.cwd);
4573
4657
  const localStatus = compareSourceLock(lock, localFiles);
4574
4658
  const mode = flags.values["--mode"] ?? "name_status";
4575
- const remote = await tryRemoteSourceOperation(ctx, "gem.fs_diff", {
4659
+ const remote = await remoteSourceOperation(ctx, "gem.fs_diff", {
4576
4660
  path: gemPath,
4577
4661
  mode,
4578
4662
  ...lock.sourceVersionId === undefined ? {} : { base: lock.sourceVersionId }
@@ -4588,7 +4672,7 @@ async function gemSourceDiff(ctx, args, linkedProject) {
4588
4672
  }
4589
4673
  async function gemSourcePush(ctx, args, linkedProject) {
4590
4674
  const flags = parseLocalFlags(args);
4591
- const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
4675
+ const gemPath = gemPathFromArgs(flags, linkedProject);
4592
4676
  const lock = await readSourceLock(ctx.cwd);
4593
4677
  const force = flags.booleans.has("--force");
4594
4678
  const localFiles = await collectLocalSourceFiles(ctx.cwd);
@@ -4615,6 +4699,9 @@ async function gemSourcePush(ctx, args, linkedProject) {
4615
4699
  }));
4616
4700
  sourceCas = nextSourceMutationCas(sourceCas, syncResult);
4617
4701
  } catch (error) {
4702
+ if (error instanceof CliError && error.exitCode === ExitCode.Conflict) {
4703
+ throw error;
4704
+ }
4618
4705
  if (isConflictLikeError(error)) {
4619
4706
  throw new CliError(error instanceof Error ? error.message : "Gem source conflict", { code: ExitCode.Conflict, cause: error });
4620
4707
  }
@@ -4780,18 +4867,11 @@ function dirtyPullErrorMessage(status) {
4780
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.
4781
4868
  ${preview}${moreLine}`;
4782
4869
  }
4783
- async function tryRemoteSourceOperation(ctx, operationId, params) {
4784
- try {
4785
- return {
4786
- available: true,
4787
- result: unwrapToolResult(await executeOperation(ctx, operationId, params))
4788
- };
4789
- } catch (error) {
4790
- return {
4791
- available: false,
4792
- error: error instanceof Error ? error.message : "Remote operation failed"
4793
- };
4794
- }
4870
+ async function remoteSourceOperation(ctx, operationId, params) {
4871
+ return {
4872
+ available: true,
4873
+ result: unwrapToolResult(await executeOperation(ctx, operationId, params))
4874
+ };
4795
4875
  }
4796
4876
  function formatRemoteSummary(remote) {
4797
4877
  if (!remote.available) {
@@ -4937,6 +5017,7 @@ function isConflictLikeError(error) {
4937
5017
 
4938
5018
  // src/token-env-secret-commands.ts
4939
5019
  import { spawn as spawn2 } from "node:child_process";
5020
+ import { StringDecoder } from "node:string_decoder";
4940
5021
  import { chmod, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
4941
5022
  import { dirname as dirname5, resolve as resolve5 } from "node:path";
4942
5023
  async function dispatchTokens(ctx, sub, args, linkedProject) {
@@ -5188,48 +5269,69 @@ function unquoteEnvValue(value) {
5188
5269
  }
5189
5270
  async function runEnvCommand(ctx, command, envVars) {
5190
5271
  const executable = requireCommandValue(command[0], "env run command is required");
5191
- 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), {
5192
5274
  cwd: ctx.cwd,
5193
- env: { ...process.env, ...envVars }
5275
+ env: { ...process.env, ...envVars },
5276
+ redactions,
5277
+ stdout: ctx.io.stdout,
5278
+ stderr: ctx.io.stderr
5194
5279
  });
5195
- const redactions = Object.values(envVars).filter((value) => value.length > 0);
5196
- const stdout = redactValues2(result.stdout, redactions);
5197
- const stderr = redactValues2(result.stderr, redactions);
5198
- if (stdout.length > 0) {
5199
- ctx.io.stdout.write(stdout);
5200
- }
5201
- if (stderr.length > 0) {
5202
- ctx.io.stderr.write(stderr);
5203
- }
5204
- if (result.code !== 0) {
5205
- 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}`);
5206
5282
  }
5207
5283
  }
5208
- async function spawnBuffered(command, args, options) {
5284
+ async function spawnStreaming(command, args, options) {
5209
5285
  return new Promise((resolvePromise, reject) => {
5210
5286
  const child = spawn2(command, [...args], {
5211
5287
  cwd: options.cwd,
5212
5288
  env: options.env,
5213
- stdio: ["ignore", "pipe", "pipe"]
5289
+ stdio: ["inherit", "pipe", "pipe"]
5214
5290
  });
5215
- const stdout = [];
5216
- const stderr = [];
5291
+ const stdout = redactingLineWriter(options.stdout, options.redactions);
5292
+ const stderr = redactingLineWriter(options.stderr, options.redactions);
5217
5293
  child.stdout?.on("data", (chunk) => {
5218
- stdout.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
5294
+ stdout.write(chunk);
5219
5295
  });
5220
5296
  child.stderr?.on("data", (chunk) => {
5221
- stderr.push(Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk));
5297
+ stderr.write(chunk);
5222
5298
  });
5223
5299
  child.on("error", reject);
5224
5300
  child.on("close", (code) => {
5225
- resolvePromise({
5226
- code: code ?? 1,
5227
- stdout: stdout.join(""),
5228
- stderr: stderr.join("")
5229
- });
5301
+ stdout.end();
5302
+ stderr.end();
5303
+ resolvePromise(code ?? 1);
5230
5304
  });
5231
5305
  });
5232
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
+ }
5233
5335
  function redactValues2(content, values) {
5234
5336
  let redacted = content;
5235
5337
  const ordered = [...new Set(values)].filter((value) => value.length > 0).sort((left, right) => right.length - left.length);
@@ -5377,7 +5479,7 @@ async function runCli(options) {
5377
5479
  const explicitGemRef = parsed.flags.gem ?? envGemId;
5378
5480
  const apiUrl = parsed.flags.apiUrl ?? envApiUrl ?? config.apiUrl ?? DEFAULT_API_URL;
5379
5481
  const token = flagToken ?? envToken ?? await resolveStoredToken(credentialStore, apiUrl, config);
5380
- const cwd = parsed.flags.cwd ?? options.env?.["PWD"] ?? process.cwd();
5482
+ const cwd = parsed.flags.cwd ?? process.cwd();
5381
5483
  const linkedProjectFromDisk = await readLinkedProject(cwd);
5382
5484
  const linkedProject = explicitGemRef === undefined ? linkedProjectFromDisk : linkedProjectWithExplicitGemRef(linkedProjectFromDisk, explicitGemRef);
5383
5485
  const workspace = parsed.flags.workspace ?? envWorkspace ?? linkedProject.workspaceId ?? config.workspace;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uru-intelligence/cli",
3
- "version": "0.3.55",
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",