@gravity-rail/cli 0.33.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/lib.js CHANGED
@@ -153,7 +153,6 @@ function findDef(defs, arg) {
153
153
  }
154
154
 
155
155
  // src/lib/global-options.ts
156
- var MAX_COMMAND_WORDS = 4;
157
156
  function parseGlobalOptions(argv, spec) {
158
157
  let env = spec.defaultEnv;
159
158
  let profile;
@@ -220,9 +219,6 @@ function parseGlobalOptions(argv, spec) {
220
219
  rest.push(arg);
221
220
  }
222
221
  }
223
- while (commandWords.length > MAX_COMMAND_WORDS) {
224
- rest.unshift(commandWords.pop());
225
- }
226
222
  return {
227
223
  opts: { env, profile, apiUrl, outputFormat, jq, verbose, allowWrite },
228
224
  commandWords,
@@ -958,7 +954,7 @@ var CommandRegistry = class {
958
954
  /** Resolve command words to a CommandDef using longest-match.
959
955
  * Supports {param} path segments that match any word and extract as pathParams. */
960
956
  resolve(words) {
961
- for (let len = Math.min(words.length, 4); len >= 1; len--) {
957
+ for (let len = words.length; len >= 1; len--) {
962
958
  const key = words.slice(0, len).join(" ");
963
959
  const cmd = this.commands.get(key);
964
960
  if (cmd) {
@@ -3840,8 +3836,8 @@ function parseBindingsFile(payload) {
3840
3836
  if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
3841
3837
  throw new CLIError(`${where}: each binding must be a JSON object.`);
3842
3838
  }
3843
- const record = entry;
3844
- const value = record.value;
3839
+ const record2 = entry;
3840
+ const value = record2.value;
3845
3841
  if (typeof value !== "string" && typeof value !== "boolean") {
3846
3842
  throw new CLIError(`${where}: "value" must be a string or a boolean.`);
3847
3843
  }
@@ -3849,14 +3845,14 @@ function parseBindingsFile(payload) {
3849
3845
  throw new CLIError(`${where}: "value" must not be empty.`);
3850
3846
  }
3851
3847
  const binding = {
3852
- entityType: requireString2(record.entityType, "entityType", where),
3853
- sourceUuid: requireString2(record.sourceUuid, "sourceUuid", where),
3854
- fieldPath: requireString2(record.fieldPath, "fieldPath", where),
3848
+ entityType: requireString2(record2.entityType, "entityType", where),
3849
+ sourceUuid: requireString2(record2.sourceUuid, "sourceUuid", where),
3850
+ fieldPath: requireString2(record2.fieldPath, "fieldPath", where),
3855
3851
  value
3856
3852
  };
3857
- const parentKind = optionalString2(record.parentKind, "parentKind", where);
3853
+ const parentKind = optionalString2(record2.parentKind, "parentKind", where);
3858
3854
  if (parentKind !== void 0) binding.parentKind = parentKind;
3859
- const parentUuid = optionalString2(record.parentUuid, "parentUuid", where);
3855
+ const parentUuid = optionalString2(record2.parentUuid, "parentUuid", where);
3860
3856
  if (parentUuid !== void 0) binding.parentUuid = parentUuid;
3861
3857
  return binding;
3862
3858
  });
@@ -12572,6 +12568,7 @@ var operatorGroupsDomain = {
12572
12568
  };
12573
12569
 
12574
12570
  // src/domains/org.ts
12571
+ import { ApiError as ApiError2 } from "@gravity-rail/sdk";
12575
12572
  var ORG_COLUMNS = [
12576
12573
  { header: "ID", getValue: (item) => String(item.organizationId ?? "") },
12577
12574
  { header: "NAME", getValue: (item) => item.organizationName || "" },
@@ -12619,6 +12616,95 @@ var DOMAIN_UUID_ARG = {
12619
12616
  function pluralize(count, singular, plural = `${singular}s`) {
12620
12617
  return count === 1 ? singular : plural;
12621
12618
  }
12619
+ var CHILD_HOST_COLUMNS = [
12620
+ { header: "HOST", getValue: (item) => item.domain },
12621
+ { header: "STATUS", getValue: (item) => item.status },
12622
+ { header: "UUID", getValue: (item) => item.orgDomainUuid },
12623
+ { header: "CREATED", getValue: (item) => String(item.createdAt) }
12624
+ ];
12625
+ var CHILD_HOST_ROW_COLUMNS = [
12626
+ { header: "HOST", getValue: (row) => row.host },
12627
+ { header: "STATUS", getValue: (row) => row.status },
12628
+ { header: "DETAIL", getValue: (row) => row.detail ?? "", maxWidth: 80 }
12629
+ ];
12630
+ var CHILD_HOSTING_ARG = {
12631
+ name: "child-hosting",
12632
+ description: "Child-hosting mode: the domain hosts children under its *.<domain> wildcard (see `site children`)",
12633
+ isFlag: true
12634
+ };
12635
+ var PARENT_DOMAIN_UUID_ARG = {
12636
+ ...DOMAIN_UUID_ARG,
12637
+ description: "OrgDomain UUID of the child-hosting parent"
12638
+ };
12639
+ var LABEL_ARGS = [
12640
+ { name: "label", description: "Host label, e.g. clinic-a (repeatable)", repeatable: true },
12641
+ {
12642
+ name: "labels-file",
12643
+ description: "File with one label per line; blank lines and # comments are skipped"
12644
+ }
12645
+ ];
12646
+ async function readLabelsFile(filePath) {
12647
+ const { readFile } = await import("fs/promises");
12648
+ let raw;
12649
+ try {
12650
+ raw = await readFile(filePath, "utf8");
12651
+ } catch (err) {
12652
+ throw new CLIError(
12653
+ `Could not read --labels-file ${filePath}: ${err?.code ?? "unknown error"}`
12654
+ );
12655
+ }
12656
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
12657
+ }
12658
+ async function collectLabels(args) {
12659
+ const raw = [...repeatedValues(args, "label")];
12660
+ const labelsFile = args["labels-file"];
12661
+ if (typeof labelsFile === "string" && labelsFile) {
12662
+ raw.push(...await readLabelsFile(labelsFile));
12663
+ }
12664
+ const labels = [...new Set(raw.map((label) => label.trim().toLowerCase()).filter(Boolean))];
12665
+ if (labels.length === 0) {
12666
+ throw new CLIError("Give at least one --label, or a --labels-file with one label per line.");
12667
+ }
12668
+ return labels;
12669
+ }
12670
+ var detailOf = (err) => err instanceof Error ? err.message : String(err);
12671
+ function record(rows, row) {
12672
+ rows.push(row);
12673
+ stderr(`${row.host}: ${row.status}${row.detail ? ` (${row.detail})` : ""}`);
12674
+ }
12675
+ function reportRows(rows, opts, verb) {
12676
+ outputData(rows, opts, CHILD_HOST_ROW_COLUMNS);
12677
+ if (rows.some((row) => row.status === "pending")) {
12678
+ hint(opts, [
12679
+ "New hosts verify on their own once their certificates issue, usually within minutes.",
12680
+ "Check with `gr org domains site children list`."
12681
+ ]);
12682
+ }
12683
+ const failed = rows.filter((row) => row.status === "error").length;
12684
+ if (failed > 0) {
12685
+ throw new CLIError(
12686
+ `${failed} of ${rows.length} ${pluralize(rows.length, "host")} could not be ${verb}.`
12687
+ );
12688
+ }
12689
+ }
12690
+ async function removeChildHost(client, orgRef, host, confirm) {
12691
+ try {
12692
+ const result2 = await client.disableOrgDomainSite(orgRef, host.orgDomainUuid, confirm);
12693
+ if (!result2.ok) {
12694
+ const { siteCount } = result2.impact;
12695
+ const sites = `${siteCount} ${pluralize(siteCount, "site")}`;
12696
+ return {
12697
+ host: host.domain,
12698
+ status: "error",
12699
+ detail: confirm ? `Still routes ${sites} after a confirmed disable; it was not removed.` : `Still routes ${sites}; re-run with --confirm to remove it anyway.`
12700
+ };
12701
+ }
12702
+ } catch (err) {
12703
+ if (!(err instanceof ApiError2 && err.statusCode === 404)) throw err;
12704
+ }
12705
+ await client.deleteOrgDomain(orgRef, host.orgDomainUuid);
12706
+ return { host: host.domain, status: "removed" };
12707
+ }
12622
12708
  function emailVerifyCommand(path4, description) {
12623
12709
  return {
12624
12710
  path: path4,
@@ -12822,19 +12908,42 @@ var domainCommands = [
12822
12908
  `);
12823
12909
  }
12824
12910
  },
12911
+ {
12912
+ path: ["org", "domains", "site", "preflight"],
12913
+ description: "Check the DNS that enabling sites needs, without enabling; exits non-zero until it resolves",
12914
+ requiresWorkspace: false,
12915
+ args: [ORG_UUID_ARG2, DOMAIN_UUID_ARG, CHILD_HOSTING_ARG],
12916
+ examples: [
12917
+ "yarn gr org domains site preflight --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID",
12918
+ "yarn gr org domains site preflight --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --child-hosting"
12919
+ ],
12920
+ handler: async (hctx) => {
12921
+ const result2 = await requireAuth(hctx.ctx).apiClient.preflightOrgDomainSite(
12922
+ requireOrgRef(hctx.args),
12923
+ requireDomainUuid(hctx.args),
12924
+ { childHosting: hctx.args["child-hosting"] === true }
12925
+ );
12926
+ outputSingle(result2, hctx.ctx);
12927
+ if (!result2.dnsReady) {
12928
+ throw new CLIError("DNS is not ready yet. Publish the records above, then re-run.");
12929
+ }
12930
+ }
12931
+ },
12825
12932
  {
12826
12933
  path: ["org", "domains", "site", "enable"],
12827
- description: "Enable custom site CNAME verification on a verified organization domain",
12934
+ description: "Enable custom site routing on a verified organization domain, or with --child-hosting as a parent for hosts under its wildcard",
12828
12935
  mutates: true,
12829
12936
  requiresWorkspace: false,
12830
- args: [ORG_UUID_ARG2, DOMAIN_UUID_ARG],
12937
+ args: [ORG_UUID_ARG2, DOMAIN_UUID_ARG, CHILD_HOSTING_ARG],
12831
12938
  examples: [
12832
- "yarn gr org domains site enable --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write"
12939
+ "yarn gr org domains site enable --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --allow-write",
12940
+ "yarn gr org domains site enable --org-uuid $ORG_UUID --domain-uuid $DOMAIN_UUID --child-hosting --allow-write"
12833
12941
  ],
12834
12942
  handler: async (hctx) => {
12835
12943
  const result2 = await requireAuth(hctx.ctx).apiClient.enableOrgDomainSite(
12836
12944
  requireOrgRef(hctx.args),
12837
- requireDomainUuid(hctx.args)
12945
+ requireDomainUuid(hctx.args),
12946
+ { childHosting: hctx.args["child-hosting"] === true }
12838
12947
  );
12839
12948
  outputSingle(result2, hctx.ctx);
12840
12949
  }
@@ -12897,6 +13006,103 @@ var domainCommands = [
12897
13006
  process.stdout.write(`Disabled site routing for org domain ${domainUuid}
12898
13007
  `);
12899
13008
  }
13009
+ },
13010
+ {
13011
+ path: ["org", "domains", "site", "children", "list"],
13012
+ description: "List the hosts beneath a child-hosting parent domain, with their status",
13013
+ requiresWorkspace: false,
13014
+ args: [ORG_UUID_ARG2, PARENT_DOMAIN_UUID_ARG],
13015
+ examples: [
13016
+ "yarn gr org domains site children list --org-uuid $ORG_UUID --domain-uuid $PARENT_UUID"
13017
+ ],
13018
+ handler: async (hctx) => {
13019
+ const result2 = await requireAuth(hctx.ctx).apiClient.listOrgDomainSiteChildHosts(
13020
+ requireOrgRef(hctx.args),
13021
+ requireDomainUuid(hctx.args)
13022
+ );
13023
+ outputData(result2.items, hctx.ctx, CHILD_HOST_COLUMNS);
13024
+ }
13025
+ },
13026
+ {
13027
+ path: ["org", "domains", "site", "children", "add"],
13028
+ description: "Add hosts beneath a child-hosting parent domain, one at a time; exits non-zero if any fail",
13029
+ mutates: true,
13030
+ requiresWorkspace: false,
13031
+ args: [ORG_UUID_ARG2, PARENT_DOMAIN_UUID_ARG, ...LABEL_ARGS],
13032
+ examples: [
13033
+ "yarn gr org domains site children add --org-uuid $ORG_UUID --domain-uuid $PARENT_UUID --label clinic-a --label clinic-b --allow-write",
13034
+ "yarn gr org domains site children add --org-uuid $ORG_UUID --domain-uuid $PARENT_UUID --labels-file labels.txt --allow-write"
13035
+ ],
13036
+ handler: async (hctx) => {
13037
+ const client = requireAuth(hctx.ctx).apiClient;
13038
+ const orgRef = requireOrgRef(hctx.args);
13039
+ const parentUuid = requireDomainUuid(hctx.args);
13040
+ const labels = await collectLabels(hctx.args);
13041
+ const parent = await client.getOrgDomain(orgRef, parentUuid);
13042
+ const rows = [];
13043
+ for (const label of labels) {
13044
+ try {
13045
+ const created = await client.createOrgDomainSiteChildHost(orgRef, parentUuid, {
13046
+ label
13047
+ });
13048
+ record(rows, { host: created.domain, status: created.status });
13049
+ } catch (err) {
13050
+ record(rows, {
13051
+ host: `${label}.${parent.domain}`,
13052
+ status: "error",
13053
+ detail: detailOf(err)
13054
+ });
13055
+ }
13056
+ }
13057
+ reportRows(rows, hctx.ctx, "added");
13058
+ }
13059
+ },
13060
+ {
13061
+ path: ["org", "domains", "site", "children", "remove"],
13062
+ description: "Remove hosts from a child-hosting parent domain (certificate, routing and registration); exits non-zero if any fail",
13063
+ mutates: true,
13064
+ requiresWorkspace: false,
13065
+ args: [
13066
+ ORG_UUID_ARG2,
13067
+ PARENT_DOMAIN_UUID_ARG,
13068
+ ...LABEL_ARGS,
13069
+ {
13070
+ name: "confirm",
13071
+ description: "Also remove hosts that still route sites",
13072
+ isFlag: true
13073
+ }
13074
+ ],
13075
+ examples: [
13076
+ "yarn gr org domains site children remove --org-uuid $ORG_UUID --domain-uuid $PARENT_UUID --label clinic-a --allow-write",
13077
+ "yarn gr org domains site children remove --org-uuid $ORG_UUID --domain-uuid $PARENT_UUID --labels-file labels.txt --confirm --allow-write"
13078
+ ],
13079
+ handler: async (hctx) => {
13080
+ const client = requireAuth(hctx.ctx).apiClient;
13081
+ const orgRef = requireOrgRef(hctx.args);
13082
+ const parentUuid = requireDomainUuid(hctx.args);
13083
+ const labels = await collectLabels(hctx.args);
13084
+ const confirm = hctx.args.confirm === true;
13085
+ const { items } = await client.listOrgDomainSiteChildHosts(orgRef, parentUuid);
13086
+ const hostsByLabel = new Map(items.map((host) => [host.label, host]));
13087
+ const rows = [];
13088
+ for (const label of labels) {
13089
+ const host = hostsByLabel.get(label);
13090
+ if (!host) {
13091
+ record(rows, {
13092
+ host: label,
13093
+ status: "error",
13094
+ detail: "No host with this label under this domain."
13095
+ });
13096
+ continue;
13097
+ }
13098
+ try {
13099
+ record(rows, await removeChildHost(client, orgRef, host, confirm));
13100
+ } catch (err) {
13101
+ record(rows, { host: host.domain, status: "error", detail: detailOf(err) });
13102
+ }
13103
+ }
13104
+ reportRows(rows, hctx.ctx, "removed");
13105
+ }
12900
13106
  }
12901
13107
  ];
12902
13108
  var orgDomain = {
@@ -16447,9 +16653,9 @@ ${PHONE_NUMBER_FIELD_REFERENCE}`,
16447
16653
  workspace,
16448
16654
  payload
16449
16655
  );
16450
- const record = asPhoneRecord(result2);
16451
- const warnings = collectPhoneNumberWarnings(record);
16452
- outputData([record], ctx, COLUMNS26);
16656
+ const record2 = asPhoneRecord(result2);
16657
+ const warnings = collectPhoneNumberWarnings(record2);
16658
+ outputData([record2], ctx, COLUMNS26);
16453
16659
  if (warnings.length > 0) {
16454
16660
  process.stderr.write("\nWarnings after create:\n");
16455
16661
  for (const w of warnings) {
@@ -20295,17 +20501,17 @@ function parseUiPromptResponse(content) {
20295
20501
  return null;
20296
20502
  }
20297
20503
  if (!parsed || typeof parsed !== "object") return null;
20298
- const record = parsed;
20299
- const hasChoice = record.chosenValue !== void 0;
20300
- const hasValues = record.values !== void 0;
20301
- if (record.status === "declined") {
20504
+ const record2 = parsed;
20505
+ const hasChoice = record2.chosenValue !== void 0;
20506
+ const hasValues = record2.values !== void 0;
20507
+ if (record2.status === "declined") {
20302
20508
  return hasChoice || hasValues ? null : { promptId, kind, body: { status: "declined" } };
20303
20509
  }
20304
- if (record.status !== "submitted" || hasChoice === hasValues) return null;
20510
+ if (record2.status !== "submitted" || hasChoice === hasValues) return null;
20305
20511
  if (hasChoice) {
20306
- return typeof record.chosenValue === "string" ? { promptId, kind, body: { status: "submitted", chosenValue: record.chosenValue } } : null;
20512
+ return typeof record2.chosenValue === "string" ? { promptId, kind, body: { status: "submitted", chosenValue: record2.chosenValue } } : null;
20307
20513
  }
20308
- const values = parseFormValues(record.values);
20514
+ const values = parseFormValues(record2.values);
20309
20515
  return values ? { promptId, kind, body: { status: "submitted", values } } : null;
20310
20516
  }
20311
20517
  function parseFormValues(raw) {
@@ -21320,8 +21526,8 @@ async function handleUIChoiceTerminal(payload) {
21320
21526
  }
21321
21527
 
21322
21528
  // src/lib/ui-prompt-presented.ts
21323
- function optionalString3(record, key) {
21324
- const value = record[key];
21529
+ function optionalString3(record2, key) {
21530
+ const value = record2[key];
21325
21531
  return typeof value === "string" && value.length > 0 ? value : void 0;
21326
21532
  }
21327
21533
  var CONNECT_TOOL_NAME = /^connect_[a-z0-9_]+_account$/;
@@ -21331,14 +21537,14 @@ function isUiPromptToolName(name) {
21331
21537
  }
21332
21538
  function parseConnect(value) {
21333
21539
  if (!value || typeof value !== "object") return void 0;
21334
- const record = value;
21335
- const appSlug = optionalString3(record, "appSlug");
21540
+ const record2 = value;
21541
+ const appSlug = optionalString3(record2, "appSlug");
21336
21542
  if (!appSlug) return void 0;
21337
21543
  return {
21338
21544
  appSlug,
21339
- connectionUuid: optionalString3(record, "connectionUuid") ?? "",
21340
- label: optionalString3(record, "label") ?? appSlug,
21341
- expiresInMinutes: typeof record.expiresInMinutes === "number" ? record.expiresInMinutes : 0
21545
+ connectionUuid: optionalString3(record2, "connectionUuid") ?? "",
21546
+ label: optionalString3(record2, "label") ?? appSlug,
21547
+ expiresInMinutes: typeof record2.expiresInMinutes === "number" ? record2.expiresInMinutes : 0
21342
21548
  };
21343
21549
  }
21344
21550
  function parseUiPromptPresented(content) {
@@ -21353,24 +21559,24 @@ function parseUiPromptPresented(content) {
21353
21559
  }
21354
21560
  }
21355
21561
  if (!value || typeof value !== "object") return null;
21356
- const record = value;
21357
- if (record.status !== "presented") return null;
21358
- const promptId = optionalString3(record, "promptId");
21359
- const kind = record.kind;
21562
+ const record2 = value;
21563
+ if (record2.status !== "presented") return null;
21564
+ const promptId = optionalString3(record2, "promptId");
21565
+ const kind = record2.kind;
21360
21566
  if (!promptId || !isUiPromptKind(kind)) return null;
21361
- const connect = parseConnect(record.connect);
21567
+ const connect = parseConnect(record2.connect);
21362
21568
  if (kind === "inline_connect" && !connect) return null;
21363
21569
  return {
21364
21570
  status: "presented",
21365
21571
  promptId,
21366
21572
  kind,
21367
- userPrompt: optionalString3(record, "userPrompt"),
21368
- openuiMarkup: optionalString3(record, "openuiMarkup"),
21369
- description: optionalString3(record, "description"),
21370
- multi: record.multi === true,
21573
+ userPrompt: optionalString3(record2, "userPrompt"),
21574
+ openuiMarkup: optionalString3(record2, "openuiMarkup"),
21575
+ description: optionalString3(record2, "description"),
21576
+ multi: record2.multi === true,
21371
21577
  // LLM-facing guidance. The CLI never renders it, but it is part of the
21372
21578
  // result, so it travels rather than being dropped on the floor.
21373
- instructions: typeof record.instructions === "string" ? record.instructions : "",
21579
+ instructions: typeof record2.instructions === "string" ? record2.instructions : "",
21374
21580
  ...connect ? { connect } : {}
21375
21581
  };
21376
21582
  }
@@ -21461,9 +21667,9 @@ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
21461
21667
  const type = event.type?.toLowerCase() ?? "";
21462
21668
  if (type === "tool_call_start") {
21463
21669
  hasToolCalls = true;
21464
- const record = event;
21465
- const callId = record.toolCallId ?? record.tool_call_id;
21466
- const name = record.toolCallName ?? record.tool_call_name ?? record.toolName;
21670
+ const record2 = event;
21671
+ const callId = record2.toolCallId ?? record2.tool_call_id;
21672
+ const name = record2.toolCallName ?? record2.tool_call_name ?? record2.toolName;
21467
21673
  if (typeof callId === "string" && typeof name === "string") {
21468
21674
  toolCallNames.set(callId, name);
21469
21675
  }