sim 2.0.0-preview.10.1 → 2.0.0-preview.13.1

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 +13 -4
  2. package/dist/index.js +674 -279
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2403,7 +2403,7 @@ function removeSection(doc, name) {
2403
2403
 
2404
2404
  // src/config/profile.ts
2405
2405
  var DEFAULT_PROFILE = "default";
2406
- var DEFAULT_ENDPOINT = "https://sim.ai";
2406
+ var DEFAULT_ENDPOINT = "https://www.sim.ai";
2407
2407
  var OUTPUT_FORMATS = ["table", "json", "yaml", "text"];
2408
2408
 
2409
2409
  class ProfileConfigError extends Error {
@@ -2466,8 +2466,18 @@ function deleteProfile(profile) {
2466
2466
  writeIni(credentialsPath(), credentialsDoc, true);
2467
2467
  return { config, credentials };
2468
2468
  }
2469
- function normalizeEndpoint(endpoint) {
2470
- return endpoint.replace(/\/+$/, "");
2469
+ function normalizeEndpoint(endpoint, source) {
2470
+ const trimmed = endpoint.replace(/\/+$/, "");
2471
+ let parsed;
2472
+ try {
2473
+ parsed = new URL(trimmed);
2474
+ } catch {
2475
+ throw new ProfileConfigError(`Invalid endpoint "${endpoint}" from ${source}. Use an absolute URL, e.g. ${DEFAULT_ENDPOINT} or http://localhost:3000`);
2476
+ }
2477
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2478
+ throw new ProfileConfigError(`Unsupported endpoint scheme "${parsed.protocol.replace(/:$/, "")}" from ${source}. Use http or https, e.g. ${DEFAULT_ENDPOINT}`);
2479
+ }
2480
+ return trimmed;
2471
2481
  }
2472
2482
  function resolve(candidates, fallback, fallbackSource) {
2473
2483
  for (const [source, value] of candidates) {
@@ -2505,7 +2515,7 @@ function resolveProfile(overrides = {}) {
2505
2515
  }
2506
2516
  return {
2507
2517
  name,
2508
- endpoint: normalizeEndpoint(endpoint.value),
2518
+ endpoint: normalizeEndpoint(endpoint.value, endpoint.source),
2509
2519
  apiKey: apiKey.value,
2510
2520
  workspaceId: workspaceId.value,
2511
2521
  output: output.value,
@@ -2539,13 +2549,25 @@ function buildUrl(endpoint, path, query) {
2539
2549
  }
2540
2550
  return url.toString();
2541
2551
  }
2542
- function toApiError(status, raw) {
2552
+ var REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
2553
+ var MARKUP_PREFIX = /^\s*<(?:!doctype|html|\?xml)/i;
2554
+ var WORKSPACE_KEY_REFUSAL = "WORKSPACE_KEY_OPERATION_NOT_PERMITTED";
2555
+ function toNonJsonError(url, status, contentType, raw) {
2556
+ const type = contentType?.split(";")[0]?.trim().toLowerCase();
2557
+ const isMarkup = type === "text/html" || type === "application/xhtml+xml" || MARKUP_PREFIX.test(raw);
2558
+ const kind = isMarkup ? "HTML" : type && type !== "application/json" ? type : "a non-JSON response";
2559
+ const text = raw.trim();
2560
+ const keepSnippet = !isMarkup && text.length > 0 && text.length <= 200;
2561
+ return new SimApiError(`${url} returned ${kind}, not JSON (HTTP ${status}) — check your endpoint.${keepSnippet ? ` Response: ${truncate(text, 200)}` : ""}`, status);
2562
+ }
2563
+ function toApiError(url, status, contentType, raw) {
2543
2564
  let parsed;
2544
2565
  try {
2545
2566
  parsed = JSON.parse(raw);
2546
2567
  } catch {
2547
- const text = raw.trim();
2548
- return new SimApiError(text ? truncate(text, 300) : `Request failed with status ${status}`, status);
2568
+ if (!raw.trim())
2569
+ return new SimApiError(`Request failed with status ${status}`, status);
2570
+ return toNonJsonError(url, status, contentType, raw);
2549
2571
  }
2550
2572
  const body = parsed;
2551
2573
  if (body.error && typeof body.error === "object") {
@@ -2561,8 +2583,33 @@ function toApiError(status, raw) {
2561
2583
  function truncate(value, max) {
2562
2584
  return value.length <= max ? value : `${value.slice(0, max)}…`;
2563
2585
  }
2586
+ function namesWorkspaceKeyRefusal(error) {
2587
+ if (error.code === WORKSPACE_KEY_REFUSAL)
2588
+ return true;
2589
+ const details = error.details;
2590
+ if (!details || typeof details !== "object")
2591
+ return false;
2592
+ return details.code === WORKSPACE_KEY_REFUSAL;
2593
+ }
2594
+ function isStrictPrefix(path, other) {
2595
+ if (path.length >= other.length)
2596
+ return false;
2597
+ return path.every((segment, index) => segment === other[index]);
2598
+ }
2599
+ function rejectsAKeyThatValidated(issue, other) {
2600
+ if (!issue.unrecognizedKeys || !isStrictPrefix(issue.path, other.path))
2601
+ return false;
2602
+ return issue.unrecognizedKeys.includes(other.path[issue.path.length]);
2603
+ }
2604
+ function dropUnionBranchNoise(issues) {
2605
+ if (issues.length < 2)
2606
+ return issues;
2607
+ const kept = issues.filter((issue) => !issues.some((other) => rejectsAKeyThatValidated(issue, other)));
2608
+ return kept.length > 0 ? kept : issues;
2609
+ }
2564
2610
  function formatApiErrorDetails(details) {
2565
- const issues = new Set;
2611
+ const issues = [];
2612
+ const seen = new Set;
2566
2613
  const visit = (value, parentPath = []) => {
2567
2614
  if (Array.isArray(value)) {
2568
2615
  value.forEach((item) => visit(item, parentPath));
@@ -2580,15 +2627,27 @@ function formatApiErrorDetails(details) {
2580
2627
  }
2581
2628
  if (typeof issue.message !== "string" || issue.message === "Invalid input")
2582
2629
  return;
2583
- issues.add(`${path.length > 0 ? path.join(".") : "request"}: ${issue.message}`);
2630
+ const line = `${path.join(".")}: ${issue.message}`;
2631
+ if (seen.has(line))
2632
+ return;
2633
+ seen.add(line);
2634
+ issues.push({
2635
+ path,
2636
+ message: issue.message,
2637
+ unrecognizedKeys: issue.code === "unrecognized_keys" && Array.isArray(issue.keys) ? issue.keys.map(String) : null
2638
+ });
2584
2639
  };
2585
2640
  visit(details);
2586
- if (issues.size === 0)
2641
+ if (issues.length === 0)
2587
2642
  return [` details: ${truncate(JSON.stringify(details), 1000)}`];
2588
- const visible = [...issues].slice(0, 8);
2589
- const lines = [" details:", ...visible.map((issue) => ` ${issue}`)];
2590
- if (issues.size > visible.length)
2591
- lines.push(` … ${issues.size - visible.length} more issues`);
2643
+ const kept = dropUnionBranchNoise(issues);
2644
+ const visible = kept.slice(0, 8);
2645
+ const lines = [
2646
+ " details:",
2647
+ ...visible.map((issue) => ` ${issue.path.length > 0 ? issue.path.join(".") : "request"}: ${issue.message}`)
2648
+ ];
2649
+ if (kept.length > visible.length)
2650
+ lines.push(` … ${kept.length - visible.length} more issues`);
2592
2651
  return lines;
2593
2652
  }
2594
2653
 
@@ -2614,6 +2673,20 @@ class SimClient {
2614
2673
  return workspaceId;
2615
2674
  }
2616
2675
  async requestRaw(path, options = {}) {
2676
+ return (await this.send(path, options)).response;
2677
+ }
2678
+ async request(path, options = {}) {
2679
+ const { response, url } = await this.send(path, options);
2680
+ const raw = await response.text();
2681
+ if (!raw)
2682
+ return;
2683
+ try {
2684
+ return JSON.parse(raw);
2685
+ } catch {
2686
+ throw toNonJsonError(url, response.status, response.headers.get("content-type"), raw);
2687
+ }
2688
+ }
2689
+ async send(path, options) {
2617
2690
  const apiKey = this.resolveApiKey(options.auth);
2618
2691
  const url = buildUrl(this.profile.endpoint, path, options.query);
2619
2692
  const hasBody = options.body !== undefined;
@@ -2628,7 +2701,8 @@ class SimClient {
2628
2701
  ...options.headers
2629
2702
  },
2630
2703
  body: hasBody ? JSON.stringify(options.body) : undefined,
2631
- signal: options.signal
2704
+ signal: options.signal,
2705
+ redirect: "manual"
2632
2706
  });
2633
2707
  } catch (cause) {
2634
2708
  if (options.signal?.aborted) {
@@ -2636,43 +2710,87 @@ class SimClient {
2636
2710
  }
2637
2711
  throw new SimApiError(`Could not reach ${this.profile.endpoint}: ${cause.message}`, 0);
2638
2712
  }
2713
+ if (REDIRECT_STATUSES.has(response.status))
2714
+ throw this.toRedirectError(url, path, response);
2639
2715
  if (!response.ok) {
2640
2716
  const raw = await response.text();
2641
- const error = toApiError(response.status, raw);
2717
+ const error = toApiError(url, response.status, response.headers.get("content-type"), raw);
2642
2718
  if (response.status === 401) {
2643
2719
  error.message = `${error.message} — run: sim login --profile ${this.profile.name}`;
2644
2720
  }
2721
+ if (namesWorkspaceKeyRefusal(error)) {
2722
+ error.message = `${error.message} — this operation needs a personal API key: sim login --profile ${this.profile.name}`;
2723
+ }
2645
2724
  throw error;
2646
2725
  }
2647
- return response;
2726
+ return { response, url };
2648
2727
  }
2649
- async request(path, options = {}) {
2650
- const response = await this.requestRaw(path, options);
2651
- const raw = await response.text();
2652
- if (!raw)
2653
- return;
2654
- return JSON.parse(raw);
2728
+ toRedirectError(url, path, response) {
2729
+ const location = response.headers.get("location")?.trim();
2730
+ let target = null;
2731
+ if (location) {
2732
+ try {
2733
+ target = new URL(location, url);
2734
+ } catch {
2735
+ target = null;
2736
+ }
2737
+ }
2738
+ if (!target) {
2739
+ return new SimApiError(`${url} answered HTTP ${response.status} with no usable redirect target. Check the endpoint for profile "${this.profile.name}".`, response.status);
2740
+ }
2741
+ const suggested = redirectEndpoint(this.profile.endpoint, path, target);
2742
+ if (!suggested) {
2743
+ return new SimApiError(`${url} redirected to ${target.href}. The CLI does not follow redirects, because a redirect can drop the request body and turn a write into a silent no-op.`, response.status);
2744
+ }
2745
+ return new SimApiError(`Endpoint redirected to ${suggested}. Run: sim configure --profile ${this.profile.name} --set-endpoint ${suggested}`, response.status);
2655
2746
  }
2656
2747
  }
2748
+ function redirectEndpoint(endpoint, requestPath, target) {
2749
+ const prefix = target.pathname.endsWith(requestPath) ? target.pathname.slice(0, target.pathname.length - requestPath.length) : "";
2750
+ const suggested = `${target.origin}${prefix}`.replace(/\/+$/, "");
2751
+ return suggested === endpoint.replace(/\/+$/, "") ? null : suggested;
2752
+ }
2753
+ function pageProgress() {
2754
+ let reported = false;
2755
+ return {
2756
+ advance: (fetched) => {
2757
+ if (!process.stderr.isTTY)
2758
+ return;
2759
+ reported = true;
2760
+ process.stderr.write(`\r${source_default.dim(`fetched ${fetched}…`)}\x1B[K`);
2761
+ },
2762
+ finish: () => {
2763
+ if (reported)
2764
+ process.stderr.write("\r\x1B[K");
2765
+ }
2766
+ };
2767
+ }
2657
2768
  async function requestAllPages(client, path, options) {
2658
2769
  const { query, pageSize, limit: requestedLimit, ...requestOptions } = options;
2659
2770
  const limit = requestedLimit ?? Number.POSITIVE_INFINITY;
2660
2771
  if (limit <= 0)
2661
2772
  return [];
2662
2773
  const items = [];
2774
+ const progress = pageProgress();
2663
2775
  let cursor = null;
2664
- do {
2665
- const page = await client.request(path, {
2666
- ...requestOptions,
2667
- query: {
2668
- ...query,
2669
- limit: Math.min(pageSize, limit - items.length),
2670
- cursor
2671
- }
2672
- });
2673
- items.push(...page.data);
2674
- cursor = page.nextCursor;
2675
- } while (cursor && items.length < limit);
2776
+ try {
2777
+ do {
2778
+ const page = await client.request(path, {
2779
+ ...requestOptions,
2780
+ query: {
2781
+ ...query,
2782
+ limit: Math.min(pageSize, limit - items.length),
2783
+ cursor
2784
+ }
2785
+ });
2786
+ items.push(...page.data);
2787
+ cursor = page.nextCursor;
2788
+ if (cursor && items.length < limit)
2789
+ progress.advance(items.length);
2790
+ } while (cursor && items.length < limit);
2791
+ } finally {
2792
+ progress.finish();
2793
+ }
2676
2794
  return items.slice(0, limit);
2677
2795
  }
2678
2796
  function resolvePath(template, params = {}) {
@@ -5894,7 +6012,7 @@ function duration(ms) {
5894
6012
  if (ms === null || ms === undefined)
5895
6013
  return EMPTY;
5896
6014
  if (ms < 1000)
5897
- return `${ms}ms`;
6015
+ return `${Math.round(ms)}ms`;
5898
6016
  if (ms < 60000)
5899
6017
  return `${(ms / 1000).toFixed(1)}s`;
5900
6018
  return `${Math.floor(ms / 60000)}m${Math.round(ms % 60000 / 1000)}s`;
@@ -5914,17 +6032,18 @@ function oneLine(value) {
5914
6032
  return value.replace(/\s*[\r\n\t]+\s*/g, " ");
5915
6033
  }
5916
6034
  var MAX_CELL_WIDTH = 60;
5917
- function clampCell(value) {
5918
- if (visibleWidth(value) <= MAX_CELL_WIDTH || value !== value.replace(ANSI_PATTERN, "")) {
6035
+ var MAX_RECORD_WIDTH = 160;
6036
+ function clamp(value, width) {
6037
+ if (visibleWidth(value) <= width || value !== value.replace(ANSI_PATTERN, "")) {
5919
6038
  return value;
5920
6039
  }
5921
- return `${value.slice(0, MAX_CELL_WIDTH - 1)}…`;
6040
+ return `${value.slice(0, width - 1)}…`;
5922
6041
  }
5923
6042
  function renderTable(rows, columns) {
5924
6043
  if (rows.length === 0)
5925
6044
  return source_default.dim("No results.");
5926
6045
  const headers = columns.map((column) => sanitize(column.header));
5927
- const cells = rows.map((row) => columns.map((column) => clampCell(oneLine(column.value(row)))));
6046
+ const cells = rows.map((row) => columns.map((column) => clamp(oneLine(column.value(row)), MAX_CELL_WIDTH)));
5928
6047
  const widths = columns.map((_column, index) => Math.max(visibleWidth(headers[index]), ...cells.map((line) => visibleWidth(line[index]))));
5929
6048
  const header = headers.map((label, index) => source_default.dim(pad(label.toUpperCase(), widths[index]))).join(" ").trimEnd();
5930
6049
  const body = cells.map((line) => line.map((cell, index) => pad(cell, widths[index])).join(" ").trimEnd());
@@ -5970,7 +6089,7 @@ function printRecord(format, fields, raw) {
5970
6089
  }
5971
6090
  const width = Math.max(...safeFields.map(([label]) => visibleWidth(label)));
5972
6091
  for (const [label, value] of safeFields) {
5973
- console.log(`${source_default.dim(pad(`${label}:`, width + 1))} ${oneLine(value)}`);
6092
+ console.log(`${source_default.dim(pad(`${label}:`, width + 1))} ${clamp(oneLine(value), MAX_RECORD_WIDTH)}`);
5974
6093
  }
5975
6094
  }
5976
6095
 
@@ -6036,6 +6155,24 @@ function buildApprovalUrl(endpoint, auth, scope, workspaceId) {
6036
6155
  url.searchParams.set("workspace", workspaceId);
6037
6156
  return url.toString();
6038
6157
  }
6158
+ var POLL_PATH = "/api/cli/auth/poll";
6159
+ function toRedirectError(endpoint, response) {
6160
+ const location = response.headers.get("location")?.trim();
6161
+ let target = null;
6162
+ if (location) {
6163
+ try {
6164
+ target = new URL(location, endpoint);
6165
+ } catch {
6166
+ target = null;
6167
+ }
6168
+ }
6169
+ if (!target) {
6170
+ return new SimApiError(`${endpoint} answered the login poll with HTTP ${response.status} and no usable redirect target. Check the endpoint.`, response.status);
6171
+ }
6172
+ const refusal = `${endpoint} redirected the login poll to ${target.href}. The CLI does not follow redirects, because a redirect drops the request body and would carry the login secret to another origin.`;
6173
+ const suggested = redirectEndpoint(endpoint, POLL_PATH, target);
6174
+ return new SimApiError(suggested ? `${refusal} Re-run with --endpoint ${suggested}, or run: sim configure --set-endpoint ${suggested}` : refusal, response.status);
6175
+ }
6039
6176
  async function pollForKey(endpoint, auth, signal) {
6040
6177
  const deadline = Date.now() + POLL_TIMEOUT_MS;
6041
6178
  while (Date.now() < deadline) {
@@ -6043,16 +6180,19 @@ async function pollForKey(endpoint, auth, signal) {
6043
6180
  throw new SimApiError("Login cancelled.", 0);
6044
6181
  let response = null;
6045
6182
  try {
6046
- response = await fetch(new URL("/api/cli/auth/poll", endpoint), {
6183
+ response = await fetch(new URL(POLL_PATH, endpoint), {
6047
6184
  method: "POST",
6048
6185
  headers: { "content-type": "application/json", accept: "application/json" },
6049
6186
  body: JSON.stringify({ request: auth.request, verifier: auth.pollSecret }),
6050
- signal
6187
+ signal,
6188
+ redirect: "manual"
6051
6189
  });
6052
6190
  } catch {
6053
6191
  response = null;
6054
6192
  }
6055
6193
  if (response) {
6194
+ if (REDIRECT_STATUSES.has(response.status))
6195
+ throw toRedirectError(endpoint, response);
6056
6196
  const raw = await response.text();
6057
6197
  if (!response.ok) {
6058
6198
  if (!RETRYABLE_POLL_STATUSES.has(response.status)) {
@@ -6106,193 +6246,6 @@ function clientFrom(command) {
6106
6246
  return { client: new SimClient(profile), profile };
6107
6247
  }
6108
6248
 
6109
- // src/commands/auth.ts
6110
- function openBrowser(url) {
6111
- const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
6112
- try {
6113
- const child = spawn(command, args, { stdio: "ignore", detached: true });
6114
- child.on("error", () => {});
6115
- child.unref();
6116
- } catch {}
6117
- }
6118
- function presentAuthentication(source) {
6119
- switch (source) {
6120
- case "flag":
6121
- return { authenticated: true, source: "flag" };
6122
- case "env":
6123
- return { authenticated: true, source: "env" };
6124
- case "credentials":
6125
- return { authenticated: true, source: "credentials" };
6126
- case "unset":
6127
- return { authenticated: false, source: "unset" };
6128
- case "config":
6129
- case "default":
6130
- throw new SimApiError(`Unexpected API key source "${source}".`, 0);
6131
- }
6132
- }
6133
- async function confirmProfileOverwrite(profileName) {
6134
- if (!process.stdin.isTTY) {
6135
- throw new SimApiError(`Profile "${profileName}" already exists. Re-run with --yes to overwrite it.`, 0);
6136
- }
6137
- const prompt = createInterface({ input: process.stdin, output: process.stderr });
6138
- try {
6139
- const answer = await prompt.question(`Profile "${profileName}" already exists. Replace its API key and login defaults? (y/N) `);
6140
- return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
6141
- } finally {
6142
- prompt.close();
6143
- }
6144
- }
6145
- function loginCommand() {
6146
- return new Command("login").description("Authorize this terminal and store an API key for the profile").option("--scope <scope>", "Key space to mint from: platform or copilot", "platform").option("--no-browser", "Print the URL instead of opening a browser").option("-y, --yes", "Overwrite an existing profile without prompting").action(async (options, command) => {
6147
- const profile = profileFrom(command);
6148
- if (options.scope !== "platform" && options.scope !== "copilot") {
6149
- throw new SimApiError(`Unknown scope "${options.scope}". Use platform or copilot.`, 0);
6150
- }
6151
- const scope = options.scope;
6152
- if (readCredentialsProfile(profile.name).api_key && !options.yes) {
6153
- const confirmed = await confirmProfileOverwrite(profile.name);
6154
- if (!confirmed) {
6155
- console.log(source_default.dim("Login cancelled; the existing profile was not changed."));
6156
- return;
6157
- }
6158
- }
6159
- const auth = createAuthRequest();
6160
- const url = buildApprovalUrl(profile.endpoint, auth, scope, profile.workspaceId ?? undefined);
6161
- console.log(`Signing in to ${source_default.bold(profile.endpoint)} as profile ${source_default.bold(profile.name)}`);
6162
- console.log(`
6163
- Pairing code: ${source_default.bold(auth.pairing)}`);
6164
- console.log(source_default.dim(`Confirm this code matches what the browser shows before approving.
6165
- `));
6166
- console.log(url);
6167
- if (options.browser)
6168
- openBrowser(url);
6169
- console.log(source_default.dim(`
6170
- Waiting for approval…`));
6171
- const key = await pollForKey(profile.endpoint, auth);
6172
- if (key.scope !== scope) {
6173
- throw new SimApiError(`Server issued a ${key.scope} key but this profile needs a ${scope} key. Update the Sim deployment, or run: sim login --scope ${key.scope}`, 0);
6174
- }
6175
- writeCredentialsProfile(profile.name, key.apiKey);
6176
- const settings = {
6177
- endpoint: profile.endpoint,
6178
- workspace: key.workspaceId ?? null
6179
- };
6180
- writeConfigProfile(profile.name, settings);
6181
- console.log(source_default.green(`
6182
- ✓ Logged in. Key stored in ${credentialsPath()}`));
6183
- if (key.workspaceBound && key.workspaceId) {
6184
- console.log(source_default.dim(` Workspace-scoped key — it can only reach ${key.workspaceId}.`));
6185
- } else if (key.workspaceId) {
6186
- console.log(source_default.dim(` Personal key, defaulting to ${key.workspaceId}. Override per command with --workspace.`));
6187
- } else {
6188
- console.log(source_default.dim(" Personal key with no default workspace. Set one with: sim configure --set-workspace <id>"));
6189
- }
6190
- });
6191
- }
6192
- function logoutCommand() {
6193
- return new Command("logout").description("Remove the profile's stored API key").option("--all", "Remove the profile entirely, including its settings").action((options, command) => {
6194
- const profile = profileFrom(command);
6195
- if (options.all) {
6196
- const removed = deleteProfile(profile.name);
6197
- if (!removed.config && !removed.credentials) {
6198
- console.log(source_default.dim(`Nothing stored for profile "${profile.name}".`));
6199
- return;
6200
- }
6201
- console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
6202
- return;
6203
- }
6204
- if (!readCredentialsProfile(profile.name).api_key) {
6205
- console.log(source_default.dim(`No stored key for profile "${profile.name}".`));
6206
- return;
6207
- }
6208
- writeCredentialsProfile(profile.name, null);
6209
- console.log(source_default.green(`✓ Removed the stored key for profile "${profile.name}".`));
6210
- console.log(source_default.dim(" The key itself is still active — revoke it in Settings → API keys."));
6211
- });
6212
- }
6213
- function whoamiCommand() {
6214
- return new Command("whoami").description("Show the resolved profile and where each setting came from").action((_options, command) => {
6215
- const profile = profileFrom(command);
6216
- const { sources } = profile;
6217
- const authentication = presentAuthentication(sources.apiKey);
6218
- const annotate = (value, source) => source === "unset" ? source_default.dim("not set") : `${value} ${source_default.dim(`(${source})`)}`;
6219
- printRecord(profile.output, [
6220
- ["Profile", profile.name],
6221
- ["Endpoint", annotate(profile.endpoint, sources.endpoint)],
6222
- [
6223
- "API key",
6224
- authentication.authenticated ? annotate("configured", authentication.source) : source_default.yellow("not logged in")
6225
- ],
6226
- ["Workspace", annotate(profile.workspaceId ?? "", sources.workspaceId)],
6227
- ["Output", annotate(profile.output, sources.output)]
6228
- ], {
6229
- profile: profile.name,
6230
- endpoint: profile.endpoint,
6231
- workspaceId: profile.workspaceId,
6232
- output: profile.output,
6233
- authenticated: authentication.authenticated,
6234
- sources: {
6235
- endpoint: sources.endpoint,
6236
- authentication: authentication.source,
6237
- workspaceId: sources.workspaceId,
6238
- output: sources.output
6239
- }
6240
- });
6241
- });
6242
- }
6243
- function profilesCommand() {
6244
- return new Command("profiles").alias("profile").description("List the profiles defined in the config and credentials files").action((_options, command) => {
6245
- const profiles = listProfiles();
6246
- if (profiles.length === 0) {
6247
- console.log(source_default.dim("No profiles yet. Run: sim login"));
6248
- return;
6249
- }
6250
- const active = profileFrom(command).name;
6251
- for (const name of profiles) {
6252
- const marker = name === active ? source_default.green("*") : " ";
6253
- const hasKey = Boolean(readCredentialsProfile(name).api_key);
6254
- console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}`);
6255
- }
6256
- });
6257
- }
6258
-
6259
- // src/commands/configure.ts
6260
- function configureCommand() {
6261
- return new Command("configure").description("Set a profile's endpoint, default workspace, or output format").option("--set-endpoint <url>", "Sim deployment to talk to").option("--set-workspace <id>", "Default workspace for workspace-scoped commands").option("--set-output <format>", `Default output format (${OUTPUT_FORMATS.join(" | ")})`).option("--unset <key...>", "Remove settings (endpoint, workspace, output)").action((options, command) => {
6262
- const profile = profileFrom(command);
6263
- const updates = {};
6264
- if (options.setEndpoint)
6265
- updates.endpoint = options.setEndpoint.replace(/\/+$/, "");
6266
- if (options.setWorkspace)
6267
- updates.workspace = options.setWorkspace;
6268
- if (options.setOutput) {
6269
- if (!OUTPUT_FORMATS.includes(options.setOutput)) {
6270
- throw new SimApiError(`Unknown output format "${options.setOutput}". Use one of: ${OUTPUT_FORMATS.join(", ")}`, 0);
6271
- }
6272
- updates.output = options.setOutput;
6273
- }
6274
- for (const key of options.unset ?? []) {
6275
- if (!["endpoint", "workspace", "output"].includes(key)) {
6276
- throw new SimApiError(`Cannot unset "${key}". Use endpoint, workspace, or output.`, 0);
6277
- }
6278
- updates[key] = null;
6279
- }
6280
- if (Object.keys(updates).length === 0) {
6281
- const current = readConfigProfile(profile.name);
6282
- if (Object.keys(current).length === 0) {
6283
- console.log(source_default.dim(`No settings stored for profile "${profile.name}".`));
6284
- return;
6285
- }
6286
- for (const [key, value] of Object.entries(current)) {
6287
- console.log(`${source_default.dim(`${key}:`)} ${value}`);
6288
- }
6289
- return;
6290
- }
6291
- writeConfigProfile(profile.name, updates);
6292
- console.log(source_default.green(`✓ Updated profile "${profile.name}" in ${configPath()}`));
6293
- });
6294
- }
6295
-
6296
6249
  // src/generated/v2-api.ts
6297
6250
  var V2_OPERATIONS = {
6298
6251
  abortFileUpload: {
@@ -9380,6 +9333,261 @@ var V2_OPERATIONS = {
9380
9333
  }
9381
9334
  };
9382
9335
 
9336
+ // src/commands/auth.ts
9337
+ function openBrowser(url) {
9338
+ const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
9339
+ try {
9340
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
9341
+ child.on("error", () => {});
9342
+ child.unref();
9343
+ } catch {}
9344
+ }
9345
+ function presentAuthentication(source) {
9346
+ switch (source) {
9347
+ case "flag":
9348
+ return { authenticated: true, source: "flag" };
9349
+ case "env":
9350
+ return { authenticated: true, source: "env" };
9351
+ case "credentials":
9352
+ return { authenticated: true, source: "credentials" };
9353
+ case "unset":
9354
+ return { authenticated: false, source: "unset" };
9355
+ case "config":
9356
+ case "default":
9357
+ throw new SimApiError(`Unexpected API key source "${source}".`, 0);
9358
+ }
9359
+ }
9360
+ async function confirmProfileOverwrite(profileName) {
9361
+ if (!process.stdin.isTTY) {
9362
+ throw new SimApiError(`Profile "${profileName}" already exists. Re-run with --yes to overwrite it.`, 0);
9363
+ }
9364
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9365
+ try {
9366
+ const answer = await prompt.question(`Profile "${profileName}" already exists. Replace its API key and login defaults? (y/N) `);
9367
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
9368
+ } finally {
9369
+ prompt.close();
9370
+ }
9371
+ }
9372
+ function loginCommand() {
9373
+ return new Command("login").description("Authorize this terminal and store an API key for the profile").option("--scope <scope>", "Key space to mint from: platform or copilot", "platform").option("--no-browser", "Print the URL instead of opening a browser").option("-y, --yes", "Overwrite an existing profile without prompting").action(async (options, command) => {
9374
+ const profile = profileFrom(command);
9375
+ if (options.scope !== "platform" && options.scope !== "copilot") {
9376
+ throw new SimApiError(`Unknown scope "${options.scope}". Use platform or copilot.`, 0);
9377
+ }
9378
+ const scope = options.scope;
9379
+ if (readCredentialsProfile(profile.name).api_key && !options.yes) {
9380
+ const confirmed = await confirmProfileOverwrite(profile.name);
9381
+ if (!confirmed) {
9382
+ console.log(source_default.dim("Login cancelled; the existing profile was not changed."));
9383
+ return;
9384
+ }
9385
+ }
9386
+ const auth = createAuthRequest();
9387
+ const url = buildApprovalUrl(profile.endpoint, auth, scope, profile.workspaceId ?? undefined);
9388
+ console.log(`Signing in to ${source_default.bold(profile.endpoint)} as profile ${source_default.bold(profile.name)}`);
9389
+ console.log(`
9390
+ Pairing code: ${source_default.bold(auth.pairing)}`);
9391
+ console.log(source_default.dim(`Confirm this code matches what the browser shows before approving.
9392
+ `));
9393
+ console.log(url);
9394
+ if (options.browser)
9395
+ openBrowser(url);
9396
+ console.log(source_default.dim(`
9397
+ Waiting for approval…`));
9398
+ const key = await pollForKey(profile.endpoint, auth);
9399
+ if (key.scope !== scope) {
9400
+ throw new SimApiError(`Server issued a ${key.scope} key but this profile needs a ${scope} key. Update the Sim deployment, or run: sim login --scope ${key.scope}`, 0);
9401
+ }
9402
+ writeCredentialsProfile(profile.name, key.apiKey);
9403
+ const settings = {
9404
+ endpoint: profile.endpoint,
9405
+ workspace: key.workspaceId ?? null
9406
+ };
9407
+ writeConfigProfile(profile.name, settings);
9408
+ console.log(source_default.green(`
9409
+ ✓ Logged in. Key stored in ${credentialsPath()}`));
9410
+ if (key.workspaceBound && key.workspaceId) {
9411
+ console.log(source_default.dim(` Workspace-scoped key — it can only reach ${key.workspaceId}.`));
9412
+ } else if (key.workspaceId) {
9413
+ console.log(source_default.dim(` Personal key, defaulting to ${key.workspaceId}. Override per command with --workspace.`));
9414
+ } else {
9415
+ console.log(source_default.dim(" Personal key with no default workspace. Set one with: sim configure --set-workspace <id>"));
9416
+ }
9417
+ });
9418
+ }
9419
+ function logoutCommand() {
9420
+ return new Command("logout").description("Remove the profile's stored API key").option("--all", "Remove the profile entirely, including its settings").action((options, command) => {
9421
+ const profile = profileFrom(command);
9422
+ if (options.all) {
9423
+ const removed = deleteProfile(profile.name);
9424
+ if (!removed.config && !removed.credentials) {
9425
+ console.log(source_default.dim(`Nothing stored for profile "${profile.name}".`));
9426
+ return;
9427
+ }
9428
+ console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
9429
+ return;
9430
+ }
9431
+ if (!readCredentialsProfile(profile.name).api_key) {
9432
+ console.log(source_default.dim(`No stored key for profile "${profile.name}".`));
9433
+ return;
9434
+ }
9435
+ writeCredentialsProfile(profile.name, null);
9436
+ console.log(source_default.green(`✓ Removed the stored key for profile "${profile.name}".`));
9437
+ console.log(source_default.dim(" The key itself is still active — revoke it in Settings → API keys."));
9438
+ });
9439
+ }
9440
+ var CREDENTIAL_VERDICT_STATUSES = new Set([401, 403, 404]);
9441
+ var WHOAMI_EXIT_CODES = {
9442
+ verified: 0,
9443
+ disabled: 0,
9444
+ unauthenticated: 1,
9445
+ rejected: 1,
9446
+ unreachable: 2,
9447
+ "no-workspace": 2
9448
+ };
9449
+ async function verifyProfile(client, profile) {
9450
+ if (!profile.apiKey) {
9451
+ return {
9452
+ status: "unauthenticated",
9453
+ workspace: null,
9454
+ detail: `no API key — run: sim login --profile ${profile.name}`
9455
+ };
9456
+ }
9457
+ if (!profile.workspaceId) {
9458
+ return {
9459
+ status: "no-workspace",
9460
+ workspace: null,
9461
+ detail: `no workspace to check against — run: sim configure --profile ${profile.name} --set-workspace <id>`
9462
+ };
9463
+ }
9464
+ const operation = V2_OPERATIONS.getWorkspace;
9465
+ try {
9466
+ const response = await client.request(resolvePath(operation.path, { workspaceId: profile.workspaceId }), { method: operation.method });
9467
+ const { id, name, memberCount } = response.data;
9468
+ return { status: "verified", workspace: { id, name, memberCount }, detail: null };
9469
+ } catch (error) {
9470
+ if (!(error instanceof SimApiError))
9471
+ throw error;
9472
+ return {
9473
+ status: CREDENTIAL_VERDICT_STATUSES.has(error.status) ? "rejected" : "unreachable",
9474
+ workspace: null,
9475
+ detail: error.message
9476
+ };
9477
+ }
9478
+ }
9479
+ function presentVerification(verification) {
9480
+ if (verification.status === "verified") {
9481
+ const { name, memberCount } = verification.workspace;
9482
+ const members = `${memberCount} ${memberCount === 1 ? "member" : "members"}`;
9483
+ return `${source_default.green("✓")} ${safeOneLine(name)} · ${members}`;
9484
+ }
9485
+ const detail = safeOneLine(verification.detail);
9486
+ switch (verification.status) {
9487
+ case "rejected":
9488
+ return `${source_default.red("✗")} ${detail}`;
9489
+ case "unauthenticated":
9490
+ return source_default.yellow(`not logged in — ${detail}`);
9491
+ case "disabled":
9492
+ return source_default.dim(detail);
9493
+ default:
9494
+ return source_default.yellow(`could not check — ${detail}`);
9495
+ }
9496
+ }
9497
+ function whoamiCommand() {
9498
+ return new Command("whoami").description("Show the resolved profile, where each setting came from, and whether it works").option("--no-verify", "Skip the API check and only print the resolved settings").action(async (options, command) => {
9499
+ const { client, profile } = clientFrom(command);
9500
+ const { sources } = profile;
9501
+ const authentication = presentAuthentication(sources.apiKey);
9502
+ const verification = options.verify ? await verifyProfile(client, profile) : { status: "disabled", workspace: null, detail: "not checked (--no-verify)" };
9503
+ const annotate = (value, source) => source === "unset" ? source_default.dim("not set") : `${value} ${source_default.dim(`(${source})`)}`;
9504
+ printRecord(profile.output, [
9505
+ ["Profile", profile.name],
9506
+ ["Endpoint", annotate(profile.endpoint, sources.endpoint)],
9507
+ [
9508
+ "API key",
9509
+ authentication.authenticated ? annotate("configured", authentication.source) : source_default.yellow("not logged in")
9510
+ ],
9511
+ ["Workspace", annotate(profile.workspaceId ?? "", sources.workspaceId)],
9512
+ ["Output", annotate(profile.output, sources.output)],
9513
+ ["Verified", presentVerification(verification)]
9514
+ ], {
9515
+ profile: profile.name,
9516
+ endpoint: profile.endpoint,
9517
+ workspaceId: profile.workspaceId,
9518
+ output: profile.output,
9519
+ authenticated: authentication.authenticated,
9520
+ sources: {
9521
+ endpoint: sources.endpoint,
9522
+ authentication: authentication.source,
9523
+ workspaceId: sources.workspaceId,
9524
+ output: sources.output
9525
+ },
9526
+ verification: {
9527
+ status: verification.status,
9528
+ workspace: verification.workspace,
9529
+ detail: verification.detail
9530
+ }
9531
+ });
9532
+ const exitCode = WHOAMI_EXIT_CODES[verification.status];
9533
+ if (exitCode !== 0)
9534
+ process.exitCode = exitCode;
9535
+ });
9536
+ }
9537
+ function profilesCommand() {
9538
+ return new Command("profiles").alias("profile").description("List the profiles defined in the config and credentials files").action((_options, command) => {
9539
+ const profiles = listProfiles();
9540
+ if (profiles.length === 0) {
9541
+ console.log(source_default.dim("No profiles yet. Run: sim login"));
9542
+ return;
9543
+ }
9544
+ const active = profileFrom(command).name;
9545
+ for (const name of profiles) {
9546
+ const marker = name === active ? source_default.green("*") : " ";
9547
+ const hasKey = Boolean(readCredentialsProfile(name).api_key);
9548
+ console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}`);
9549
+ }
9550
+ });
9551
+ }
9552
+
9553
+ // src/commands/configure.ts
9554
+ function configureCommand() {
9555
+ return new Command("configure").description("Set a profile's endpoint, default workspace, or output format").option("--set-endpoint <url>", "Sim deployment to talk to").option("--set-workspace <id>", "Default workspace for workspace-scoped commands").option("--set-output <format>", `Default output format (${OUTPUT_FORMATS.join(" | ")})`).option("--unset <key...>", "Remove settings (endpoint, workspace, output)").action((options, command) => {
9556
+ const profile = profileFrom(command);
9557
+ const updates = {};
9558
+ if (options.setEndpoint) {
9559
+ updates.endpoint = normalizeEndpoint(options.setEndpoint, "--set-endpoint");
9560
+ }
9561
+ if (options.setWorkspace)
9562
+ updates.workspace = options.setWorkspace;
9563
+ if (options.setOutput) {
9564
+ if (!OUTPUT_FORMATS.includes(options.setOutput)) {
9565
+ throw new SimApiError(`Unknown output format "${options.setOutput}". Use one of: ${OUTPUT_FORMATS.join(", ")}`, 0);
9566
+ }
9567
+ updates.output = options.setOutput;
9568
+ }
9569
+ for (const key of options.unset ?? []) {
9570
+ if (!["endpoint", "workspace", "output"].includes(key)) {
9571
+ throw new SimApiError(`Cannot unset "${key}". Use endpoint, workspace, or output.`, 0);
9572
+ }
9573
+ updates[key] = null;
9574
+ }
9575
+ if (Object.keys(updates).length === 0) {
9576
+ const current = readConfigProfile(profile.name);
9577
+ if (Object.keys(current).length === 0) {
9578
+ console.log(source_default.dim(`No settings stored for profile "${profile.name}".`));
9579
+ return;
9580
+ }
9581
+ for (const [key, value] of Object.entries(current)) {
9582
+ console.log(`${source_default.dim(`${key}:`)} ${value}`);
9583
+ }
9584
+ return;
9585
+ }
9586
+ writeConfigProfile(profile.name, updates);
9587
+ console.log(source_default.green(`✓ Updated profile "${profile.name}" in ${configPath()}`));
9588
+ });
9589
+ }
9590
+
9383
9591
  // src/runtime/request.ts
9384
9592
  import { existsSync as existsSync2, readFileSync as readFileSync2, readSync } from "node:fs";
9385
9593
 
@@ -9389,7 +9597,8 @@ var TABLE_FILTER_HELP = 'Predicate: {"all":[{"field":"status","op":"eq","value":
9389
9597
  var TABLE_SORT_HELP = 'Ordered sort keys: [{"field":"createdAt","direction":"desc"}] (direction: asc or desc)';
9390
9598
  var CUSTOM_TOOL_SCHEMA_HELP = 'OpenAI function schema: {"type":"function","function":{"name":"...","parameters":{"type":"object","properties":{}}}}';
9391
9599
  var FOLDER_PATH_INPUT = {
9392
- describe: "Folder path; the leading / is optional"
9600
+ describe: "Folder path as shown in the app; the leading / is optional",
9601
+ folderPath: true
9393
9602
  };
9394
9603
  var FOLDER_PATH_FLAG = {
9395
9604
  ...FOLDER_PATH_INPUT,
@@ -9399,7 +9608,7 @@ var FOLDER_DELETE_FLAGS = {
9399
9608
  path: FOLDER_PATH_INPUT,
9400
9609
  recursive: { boolean: true, describe: "Delete the folder and its descendants" }
9401
9610
  };
9402
- var KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS = { id: "knowledgeBaseId" };
9611
+ var KNOWLEDGE_BASE_PATH_ARGUMENT = { id: "knowledgeBaseId" };
9403
9612
  var WORKFLOW_RUN_SCOPE = {
9404
9613
  id: {
9405
9614
  name: "workflow",
@@ -9407,10 +9616,11 @@ var WORKFLOW_RUN_SCOPE = {
9407
9616
  describe: "Workflow ID"
9408
9617
  }
9409
9618
  };
9619
+ var FOLDER_COLUMN = { header: "folder", path: "folderPath", format: "folder-path" };
9410
9620
  var FOLDER_LIST_COLUMNS = [
9411
- { header: "path" },
9621
+ { header: "path", format: "folder-path" },
9412
9622
  { header: "name" },
9413
- { header: "parent", path: "parentPath" },
9623
+ { header: "parent", path: "parentPath", format: "folder-path" },
9414
9624
  { header: "updated", path: "updatedAt", format: "timestamp" }
9415
9625
  ];
9416
9626
  function moveResource(command, resource) {
@@ -9480,7 +9690,7 @@ var CLI_CONTRACT = {
9480
9690
  bulkUpdateKnowledgeDocuments: {
9481
9691
  command: "knowledge documents batch-update",
9482
9692
  describe: "Enable or disable every matching document",
9483
- pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
9693
+ pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9484
9694
  flags: {
9485
9695
  documentIds: { name: "document", list: true },
9486
9696
  selectAll: { boolean: true, describe: "Apply to every document in the knowledge base" }
@@ -9490,6 +9700,11 @@ var CLI_CONTRACT = {
9490
9700
  command: "workflows undeploy",
9491
9701
  describe: "Take a workflow out of deployment"
9492
9702
  },
9703
+ getWorkflowDeployment: {
9704
+ command: "workflows deployment status",
9705
+ renamedFrom: ["workflows deployment list"],
9706
+ describe: "Show a workflow’s current deployment"
9707
+ },
9493
9708
  setSecret: { hidden: true },
9494
9709
  deleteTable: { confirm: "This deletes the table and all of its rows." },
9495
9710
  deleteTableRow: { confirm: "This deletes the row." },
@@ -9499,7 +9714,7 @@ var CLI_CONTRACT = {
9499
9714
  },
9500
9715
  deleteKnowledgeBase: { confirm: "This deletes the knowledge base and every document in it." },
9501
9716
  deleteKnowledgeDocument: {
9502
- pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
9717
+ pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9503
9718
  confirm: "This deletes the document and its embeddings."
9504
9719
  },
9505
9720
  deleteFile: { confirm: "This archives the file." },
@@ -9529,7 +9744,10 @@ var CLI_CONTRACT = {
9529
9744
  workflowIds: { name: "workflow", list: true },
9530
9745
  folderPaths: { ...FOLDER_PATH_FLAG, list: true },
9531
9746
  triggers: { name: "trigger", list: true },
9532
- details: { describe: "Response detail level" },
9747
+ details: {
9748
+ requestDefault: "full",
9749
+ describe: "Response detail level; full is requested by default to name each run’s workflow"
9750
+ },
9533
9751
  includeTraceSpans: {
9534
9752
  boolean: true,
9535
9753
  describe: "Include trace spans in JSON or YAML output (implies full detail)"
@@ -9656,7 +9874,7 @@ var CLI_CONTRACT = {
9656
9874
  columns: [
9657
9875
  { header: "id" },
9658
9876
  { header: "name" },
9659
- { header: "folder", path: "folderPath" },
9877
+ FOLDER_COLUMN,
9660
9878
  { header: "rows", path: "rowCount" },
9661
9879
  { header: "updated", path: "updatedAt", format: "timestamp" }
9662
9880
  ]
@@ -9666,7 +9884,7 @@ var CLI_CONTRACT = {
9666
9884
  columns: [
9667
9885
  { header: "id" },
9668
9886
  { header: "name" },
9669
- { header: "folder", path: "folderPath" },
9887
+ FOLDER_COLUMN,
9670
9888
  { header: "deployed", path: "isDeployed", format: "bool" },
9671
9889
  { header: "runs", path: "runCount" },
9672
9890
  { header: "last run", path: "lastRunAt", format: "timestamp" }
@@ -9677,7 +9895,7 @@ var CLI_CONTRACT = {
9677
9895
  columns: [
9678
9896
  { header: "id" },
9679
9897
  { header: "name" },
9680
- { header: "folder", path: "folderPath" },
9898
+ FOLDER_COLUMN,
9681
9899
  { header: "size", format: "bytes" },
9682
9900
  { header: "type" },
9683
9901
  { header: "uploaded by", path: "uploadedByEmail" },
@@ -9690,15 +9908,17 @@ var CLI_CONTRACT = {
9690
9908
  columns: [
9691
9909
  { header: "id" },
9692
9910
  { header: "name" },
9693
- { header: "folder", path: "folderPath" },
9911
+ FOLDER_COLUMN,
9694
9912
  { header: "docs", path: "docCount" },
9695
9913
  { header: "tokens", path: "tokenCount" },
9696
9914
  { header: "model", path: "embeddingModel" }
9697
9915
  ]
9698
9916
  },
9699
- getKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS },
9917
+ getKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
9918
+ updateKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
9919
+ listKnowledgeTags: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
9700
9920
  listKnowledgeDocuments: {
9701
- pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
9921
+ pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9702
9922
  columns: [
9703
9923
  { header: "id" },
9704
9924
  { header: "filename" },
@@ -9742,6 +9962,17 @@ var CLI_CONTRACT = {
9742
9962
  { header: "updated", path: "updatedAt", format: "timestamp" }
9743
9963
  ]
9744
9964
  },
9965
+ listCredentialProviders: {
9966
+ columns: [
9967
+ { header: "type" },
9968
+ { header: "service", path: "serviceId" },
9969
+ { header: "provider", path: "providerId" },
9970
+ { header: "name" },
9971
+ { header: "family", path: "providerFamily" },
9972
+ { header: "available", format: "bool" },
9973
+ { header: "description" }
9974
+ ]
9975
+ },
9745
9976
  listSecrets: {
9746
9977
  columns: [
9747
9978
  { header: "name" },
@@ -9813,7 +10044,7 @@ var CLI_CONTRACT = {
9813
10044
  { header: "name" },
9814
10045
  { header: "size", format: "bytes" },
9815
10046
  { header: "type" },
9816
- { header: "folder", path: "folderPath" },
10047
+ FOLDER_COLUMN,
9817
10048
  { header: "uploaded by", path: "uploadedByEmail" },
9818
10049
  { header: "uploaded", path: "uploadedAt", format: "timestamp" },
9819
10050
  { header: "updated", path: "updatedAt", format: "timestamp" },
@@ -9840,6 +10071,11 @@ var CLI_CONTRACT = {
9840
10071
  command: "files rename",
9841
10072
  describe: "Rename a file"
9842
10073
  },
10074
+ restoreFile: {
10075
+ command: "files restore",
10076
+ renamedFrom: ["files restore create"],
10077
+ describe: "Restore an archived file"
10078
+ },
9843
10079
  updateFileContent: {
9844
10080
  command: "files set-content",
9845
10081
  describe: "Replace a file’s contents",
@@ -9992,13 +10228,26 @@ var CLI_CONTRACT = {
9992
10228
  command: "tables rows find",
9993
10229
  describe: "Find rows matching a predicate",
9994
10230
  flags: {
9995
- q: { describe: "Value to find" },
10231
+ q: { name: "query", renamedFrom: ["q"], describe: "Value to find" },
9996
10232
  predicate: { name: "filter", json: true, describe: TABLE_FILTER_HELP },
9997
10233
  sort: { json: true, describe: TABLE_SORT_HELP }
9998
10234
  },
9999
10235
  itemsPath: "matches",
10000
10236
  columns: [{ header: "ordinal" }, { header: "row", path: "rowId" }, { header: "column" }]
10001
10237
  },
10238
+ queryRowsCount: {
10239
+ command: "tables rows count",
10240
+ renamedFrom: ["tables count create"],
10241
+ describe: "Count rows matching a filter",
10242
+ flags: {
10243
+ predicate: {
10244
+ name: "filter",
10245
+ renamedFrom: ["predicate"],
10246
+ json: true,
10247
+ describe: TABLE_FILTER_HELP
10248
+ }
10249
+ }
10250
+ },
10002
10251
  runTableColumn: {
10003
10252
  command: "tables columns run",
10004
10253
  describe: "Run a column’s workflow",
@@ -10251,6 +10500,26 @@ function readListValues(raw, flagName) {
10251
10500
  return trimmed;
10252
10501
  });
10253
10502
  }
10503
+ var PERCENT_ESCAPE = /%[0-9A-Fa-f]{2}/;
10504
+ var SUB_DELIMITERS = /[!'()*]/g;
10505
+ function encodeFolderPathSegment(name) {
10506
+ if (name === ".")
10507
+ return "%2E";
10508
+ if (name === "..")
10509
+ return "%2E%2E";
10510
+ return encodeURIComponent(name).replace(SUB_DELIMITERS, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
10511
+ }
10512
+ function encodeFolderPath(value) {
10513
+ return value.split("/").map((segment) => {
10514
+ if (!PERCENT_ESCAPE.test(segment))
10515
+ return encodeFolderPathSegment(segment);
10516
+ try {
10517
+ return encodeFolderPathSegment(decodeURIComponent(segment));
10518
+ } catch {
10519
+ return encodeFolderPathSegment(segment);
10520
+ }
10521
+ }).join("/");
10522
+ }
10254
10523
  function pathHint(raw) {
10255
10524
  if (raw.startsWith("@") || /^\s*[[{"\-\d]|^\s*(true|false|null)/.test(raw))
10256
10525
  return "";
@@ -10260,7 +10529,7 @@ function coerce(raw, field, flag, flagName) {
10260
10529
  if (raw === undefined)
10261
10530
  return;
10262
10531
  if (flag.list) {
10263
- const values = readListValues(raw, flagName);
10532
+ const values = readListValues(raw, flagName).map((value) => flag.folderPath ? encodeFolderPath(value) : value);
10264
10533
  return field.kind === "string" ? values.join(",") : values;
10265
10534
  }
10266
10535
  if (takesJson(field, flag)) {
@@ -10285,6 +10554,8 @@ function coerce(raw, field, flag, flagName) {
10285
10554
  if (choices && !choices.includes(String(raw))) {
10286
10555
  throw new SimApiError(`--${flagName} must be one of: ${choices.join(", ")}`, 0);
10287
10556
  }
10557
+ if (flag.folderPath && typeof raw === "string")
10558
+ return encodeFolderPath(raw);
10288
10559
  return raw;
10289
10560
  }
10290
10561
  function asQueryValue(value) {
@@ -10325,7 +10596,8 @@ function buildRequest(operation, positional, flags, workspaceId) {
10325
10596
  continue;
10326
10597
  const flagName = flagNameFor(operation, field);
10327
10598
  const omitProfileWorkspace = commandSpec.allWorkspaces && flags.allWorkspaces === true;
10328
- const raw = field === PROFILE_INJECTED_FIELD ? omitProfileWorkspace ? undefined : workspaceId : flags[camel(flagName)];
10599
+ const provided = field === PROFILE_INJECTED_FIELD ? omitProfileWorkspace ? undefined : workspaceId : flags[camel(flagName)];
10600
+ const raw = provided ?? flag.requestDefault;
10329
10601
  const value = coerce(raw ?? undefined, descriptor, flag, flagName);
10330
10602
  if (value === undefined) {
10331
10603
  if (descriptor.required) {
@@ -10498,6 +10770,15 @@ function countTraceSpans(value) {
10498
10770
  function at(row, path) {
10499
10771
  return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
10500
10772
  }
10773
+ function decodeFolderPath(value) {
10774
+ return value.split("/").map((segment) => {
10775
+ try {
10776
+ return decodeURIComponent(segment);
10777
+ } catch {
10778
+ return segment;
10779
+ }
10780
+ }).join("/");
10781
+ }
10501
10782
  function renderCell(value, format, options = {}) {
10502
10783
  switch (format) {
10503
10784
  case "timestamp":
@@ -10512,6 +10793,8 @@ function renderCell(value, format, options = {}) {
10512
10793
  return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
10513
10794
  case "count":
10514
10795
  return Array.isArray(value) ? String(value.length) : text(null);
10796
+ case "folder-path":
10797
+ return typeof value === "string" ? text(decodeFolderPath(value)) : text(value);
10515
10798
  case "trace-count": {
10516
10799
  const count = countTraceSpans(value);
10517
10800
  return `${count} ${count === 1 ? "span" : "spans"}${options.expandedTrace ? "" : " (use --trace)"}`;
@@ -10522,10 +10805,42 @@ function renderCell(value, format, options = {}) {
10522
10805
  return sanitize(typeof value === "object" ? JSON.stringify(value) : String(value));
10523
10806
  }
10524
10807
  }
10525
- var NESTED_CELL_WIDTH = 160;
10526
- function recordCell(value) {
10527
- const rendered = renderCell(value, "auto");
10528
- return rendered.length > NESTED_CELL_WIDTH ? `${rendered.slice(0, NESTED_CELL_WIDTH)}…` : rendered;
10808
+ var TIMESTAMP_KEY = /(?:At|Date)$/;
10809
+ var DURATION_KEY = /Ms$|^duration/;
10810
+ var BYTES_KEY = /^size$|(?:Size|Bytes)$/;
10811
+ var BOOL_KEY = /^(?:is|has)[A-Z]/;
10812
+ var RATIO_KEY = /^(?:similarity|score)$|(?:Similarity|Score)$/;
10813
+ var FOLDER_PATH_KEY = /^(?:path|parentPath|folderPath)$/;
10814
+ var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/;
10815
+ var RATIO_PRECISION = 4;
10816
+ function inferFormat(key, value) {
10817
+ if (typeof value === "boolean")
10818
+ return BOOL_KEY.test(key) ? "bool" : null;
10819
+ if (typeof value === "string") {
10820
+ if (FOLDER_PATH_KEY.test(key))
10821
+ return "folder-path";
10822
+ return TIMESTAMP_KEY.test(key) && ISO_TIMESTAMP.test(value) && !Number.isNaN(Date.parse(value)) ? "timestamp" : null;
10823
+ }
10824
+ if (typeof value !== "number" || !Number.isFinite(value))
10825
+ return null;
10826
+ if (DURATION_KEY.test(key))
10827
+ return "duration";
10828
+ if (BYTES_KEY.test(key))
10829
+ return "bytes";
10830
+ return null;
10831
+ }
10832
+ function inferredCell(key, value) {
10833
+ if (typeof value === "number" && Number.isFinite(value) && RATIO_KEY.test(key)) {
10834
+ return value.toFixed(RATIO_PRECISION);
10835
+ }
10836
+ return renderCell(value, inferFormat(key, value) ?? "auto");
10837
+ }
10838
+ function humanizeKey(key) {
10839
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
10840
+ }
10841
+ function inferHeader(key, format) {
10842
+ const trimmed = format === "duration" || format === "bytes" ? key.replace(/(?:Ms|Bytes)$/, "") : format === "bool" ? key.replace(/^is(?=[A-Z])/, "") : key;
10843
+ return humanizeKey(trimmed || key);
10529
10844
  }
10530
10845
  function columnsFrom(specs) {
10531
10846
  return specs.map((spec) => ({
@@ -10534,9 +10849,9 @@ function columnsFrom(specs) {
10534
10849
  }));
10535
10850
  }
10536
10851
  function fieldsFrom(data, specs, options = {}) {
10537
- return specs.flatMap((spec) => {
10852
+ return specs.map((spec) => {
10538
10853
  const value = at(data, spec.path ?? spec.header);
10539
- return value === undefined ? [] : [[spec.header, renderCell(value, spec.format, options)]];
10854
+ return [spec.header, value === undefined ? text(null) : renderCell(value, spec.format, options)];
10540
10855
  });
10541
10856
  }
10542
10857
  function inferColumns(rows, expand) {
@@ -10551,7 +10866,7 @@ function inferColumns(rows, expand) {
10551
10866
  if (value !== null && typeof value === "object")
10552
10867
  continue;
10553
10868
  seen.add(key);
10554
- paths.push({ path: key, header: key });
10869
+ paths.push({ path: key, key, header: inferHeader(key, inferFormat(key, value)), owned: true });
10555
10870
  }
10556
10871
  }
10557
10872
  if (expand) {
@@ -10564,13 +10879,18 @@ function inferColumns(rows, expand) {
10564
10879
  if (nested.has(key))
10565
10880
  continue;
10566
10881
  nested.add(key);
10567
- paths.push({ path: `${expand}.${key}`, header: seen.has(key) ? `${expand}.${key}` : key });
10882
+ paths.push({
10883
+ path: `${expand}.${key}`,
10884
+ key,
10885
+ header: seen.has(key) ? `${expand}.${key}` : key,
10886
+ owned: false
10887
+ });
10568
10888
  }
10569
10889
  }
10570
10890
  }
10571
- return paths.map(({ path, header }) => ({
10891
+ return paths.map(({ path, key, header, owned }) => ({
10572
10892
  header: sanitize(header),
10573
- value: (row) => renderCell(at(row, path), "auto")
10893
+ value: (row) => owned ? inferredCell(key, at(row, path)) : renderCell(at(row, path), "auto")
10574
10894
  }));
10575
10895
  }
10576
10896
  function unwrapResource(data) {
@@ -10603,7 +10923,10 @@ function renderResult(operation, format, raw, spec, options = {}) {
10603
10923
  printList(format, data, spec.columns ? columnsFrom(spec.columns) : inferColumns(data, spec.expand));
10604
10924
  return;
10605
10925
  }
10606
- const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [key, recordCell(value)]) : [];
10926
+ const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [
10927
+ inferHeader(key, inferFormat(key, value)),
10928
+ inferredCell(key, value)
10929
+ ]) : [];
10607
10930
  printRecord(format, fields, data);
10608
10931
  if (spec.expandedTrace && options.expandedTrace) {
10609
10932
  const traceSpans = at(data, "traceSpans");
@@ -11001,7 +11324,7 @@ async function finishUploadSession(client, workspaceId, session, path) {
11001
11324
 
11002
11325
  // src/commands/protocol/files-upload.ts
11003
11326
  function attachFileUpload(files) {
11004
- files.command("upload").argument("<path>", "Local file to upload").description("Upload a file to the workspace").option("--folder <path>", "Destination folder path (defaults to /)").option("--name <name>", "Store it under a different name").action(async (path, options, command) => {
11327
+ files.command("upload").argument("<path>", "Local file to upload").description("Upload a file to the workspace").option("--folder <path>", "Folder path as shown in the app; defaults to the root folder").option("--name <name>", "Store it under a different name").action(async (path, options, command) => {
11005
11328
  const { client, profile } = clientFrom(command);
11006
11329
  const workspaceId = client.requireWorkspace();
11007
11330
  const { name, size } = await localFile(path, options.name);
@@ -11012,7 +11335,7 @@ function attachFileUpload(files) {
11012
11335
  name,
11013
11336
  contentType: contentTypeFor(name),
11014
11337
  size,
11015
- ...options.folder !== undefined ? { folderPath: options.folder } : {}
11338
+ ...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
11016
11339
  }
11017
11340
  });
11018
11341
  const { session, uploadToken, transfer } = created.data;
@@ -11114,15 +11437,22 @@ function addFieldOption(command, operation, field, descriptor) {
11114
11437
  const placeholder = takesList ? "<value...>" : wantsJson ? "<json|@file>" : "<value>";
11115
11438
  const choices = flag.choices ?? descriptor.values;
11116
11439
  const describe = `${documented}${takesList ? " (space-separated, or @path / @- with one value per line)" : wantsJson ? " (JSON, or @path / @- to read a file or stdin)" : ""}${descriptor.required ? " (required)" : ""}`;
11440
+ const renamedFrom = flag.renamedFrom ?? [];
11117
11441
  const option = new Option(`${short}--${name} ${placeholder}`, describe);
11118
11442
  if (choices && !takesList)
11119
11443
  option.choices([...choices]);
11120
11444
  if (descriptor.default !== undefined && field !== "limit") {
11121
11445
  option.default(undefined, String(descriptor.default));
11122
11446
  }
11123
- if (descriptor.required)
11447
+ if (descriptor.required && renamedFrom.length === 0)
11124
11448
  option.makeOptionMandatory();
11125
11449
  command.addOption(option);
11450
+ for (const previous of renamedFrom) {
11451
+ const retired = new Option(`--${previous} ${placeholder}`).hideHelp();
11452
+ if (choices && !takesList)
11453
+ retired.choices([...choices]);
11454
+ command.addOption(retired);
11455
+ }
11126
11456
  }
11127
11457
  function addOperationOptions(command, operation, commandSpec, operationSpec) {
11128
11458
  for (const param of operationSpec.pathParams) {
@@ -11158,7 +11488,7 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
11158
11488
  }
11159
11489
  }
11160
11490
  if (commandSpec.confirm) {
11161
- command.option("-y, --yes", "Skip the confirmation");
11491
+ command.option("-y, --yes", "Confirm this destructive operation (required)");
11162
11492
  }
11163
11493
  }
11164
11494
 
@@ -11166,8 +11496,11 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
11166
11496
  var COLUMNS = [
11167
11497
  { header: "kind", value: (entry) => text(entry.kind) },
11168
11498
  { header: "name", value: (entry) => text(entry.name) },
11169
- { header: "ref", value: (entry) => text(entry.ref) },
11170
- { header: "folder", value: (entry) => text(entry.folderPath) },
11499
+ {
11500
+ header: "ref",
11501
+ value: (entry) => text(entry.kind === "folder" ? decodeFolderPath(entry.ref) : entry.ref)
11502
+ },
11503
+ { header: "folder", value: (entry) => text(decodeFolderPath(entry.folderPath)) },
11171
11504
  { header: "updated", value: (entry) => timestamp2(entry.updatedAt) }
11172
11505
  ];
11173
11506
  function operationPath(operation) {
@@ -11218,7 +11551,7 @@ function attachResourceDirectoryCommands(group, config) {
11218
11551
  throw new SimApiError("--limit must be a non-negative integer", 0);
11219
11552
  }
11220
11553
  const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
11221
- const folderPath = path ?? "/";
11554
+ const folderPath = encodeFolderPath(path ?? "/");
11222
11555
  const { client, profile } = clientFrom(command);
11223
11556
  const workspaceId = client.requireWorkspace();
11224
11557
  const [folders, resources] = await Promise.all([
@@ -11233,7 +11566,7 @@ function attachResourceDirectoryCommands(group, config) {
11233
11566
  const operation = V2_OPERATIONS[config.createFolder];
11234
11567
  const result = await client.request(operation.path, {
11235
11568
  method: operation.method,
11236
- body: { workspaceId: client.requireWorkspace(), path }
11569
+ body: { workspaceId: client.requireWorkspace(), path: encodeFolderPath(path) }
11237
11570
  });
11238
11571
  renderResult(config.createFolder, profile.output, result.data ?? result, {});
11239
11572
  });
@@ -11287,7 +11620,7 @@ function validateTargetOptions(options) {
11287
11620
  return intoExisting;
11288
11621
  }
11289
11622
  function attachTableImport(tables) {
11290
- tables.command("import").argument("[path]", "Local CSV file to import; omit when using --file-id").description("Import a CSV, into a new table by default").option("--name <name>", "Identifier for the new table: letters, numbers, and underscores; defaults to the sanitized file name").option("--table-id <id>", "Import into this existing table instead of creating one").addOption(new Option("--mode <append|replace>", "How to write into --table-id (default: append)").choices(["append", "replace"])).option("--folder <path>", "Folder path for the new table").option("--file-id <id>", "Import a file already in the workspace instead of a local path").option("--mapping <json|@file>", "Column mapping (--table-id only)").option("--create-columns <json|@file>", "Columns to create (--table-id only)").option("--timezone <iana>", "Timezone for date parsing, e.g. America/New_York").option("--no-wait", "Return once the import is queued instead of watching it").action(async (path, options, command) => {
11623
+ tables.command("import").argument("[path]", "Local CSV file to import; omit when using --file-id").description("Import a CSV, into a new table by default").option("--name <name>", "Identifier for the new table: letters, numbers, and underscores; defaults to the sanitized file name").option("--table-id <id>", "Import into this existing table instead of creating one").addOption(new Option("--mode <append|replace>", "How to write into --table-id (default: append)").choices(["append", "replace"])).option("--folder <path>", "Folder path for the new table, as shown in the app").option("--file-id <id>", "Import a file already in the workspace instead of a local path").option("--mapping <json|@file>", "Column mapping (--table-id only)").option("--create-columns <json|@file>", "Columns to create (--table-id only)").option("--timezone <iana>", "Timezone for date parsing, e.g. America/New_York").option("--no-wait", "Return once the import is queued instead of watching it").action(async (path, options, command) => {
11291
11624
  const { client, profile } = clientFrom(command);
11292
11625
  const workspaceId = client.requireWorkspace();
11293
11626
  if (Boolean(path) === Boolean(options.fileId)) {
@@ -11312,7 +11645,7 @@ function attachTableImport(tables) {
11312
11645
  target = {
11313
11646
  type: "new",
11314
11647
  name,
11315
- ...options.folder !== undefined ? { folderPath: options.folder } : {}
11648
+ ...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
11316
11649
  };
11317
11650
  }
11318
11651
  const started = await client.request(V2_OPERATIONS.createTableImport.path, {
@@ -11513,6 +11846,23 @@ function attachSecretCommands(program2) {
11513
11846
  secrets.command("set").argument("<name>", "Secret name, as referenced in workflows").description("Create or replace a named secret").addOption(new Option("--scope <scope>", "Secret ownership scope").choices([...SECRET_SCOPES]).makeOptionMandatory()).option("--value <value>", "Secret value; visible to shell history when supplied directly").action((name, options, command) => setSecret(name, options, command));
11514
11847
  }
11515
11848
 
11849
+ // src/runtime/renamed.ts
11850
+ var warned = new Set;
11851
+ function warn(kind, from, to) {
11852
+ const key = `${kind}:${from}`;
11853
+ if (warned.has(key))
11854
+ return;
11855
+ warned.add(key);
11856
+ process.stderr.write(`warning: ${kind} "${from}" has been renamed to "${to}". The old name still works.
11857
+ `);
11858
+ }
11859
+ function warnRenamedCommand(from, to) {
11860
+ warn("command", `sim ${from}`, `sim ${to}`);
11861
+ }
11862
+ function warnRenamedFlag(from, to) {
11863
+ warn("flag", `--${from}`, `--${to}`);
11864
+ }
11865
+
11516
11866
  // src/runtime/execute.ts
11517
11867
  function cursorSlot(operationSpec) {
11518
11868
  if (operationSpec.query && "cursor" in operationSpec.query)
@@ -11521,6 +11871,23 @@ function cursorSlot(operationSpec) {
11521
11871
  return "body";
11522
11872
  return null;
11523
11873
  }
11874
+ function foldRenamedFlags(operation, commandSpec, flags) {
11875
+ for (const [field, flag] of Object.entries(commandSpec.flags ?? {})) {
11876
+ if (!flag.renamedFrom?.length)
11877
+ continue;
11878
+ const current = flagNameFor(operation, field);
11879
+ for (const previous of flag.renamedFrom) {
11880
+ const supplied = flags[camel(previous)];
11881
+ if (supplied === undefined)
11882
+ continue;
11883
+ if (flags[camel(current)] !== undefined) {
11884
+ throw new SimApiError(`--${previous} is the former name of --${current}; pass one, not both`, 0);
11885
+ }
11886
+ warnRenamedFlag(previous, current);
11887
+ flags[camel(current)] = supplied;
11888
+ }
11889
+ }
11890
+ }
11524
11891
  async function executeOperation(operation, commandSpec, operationSpec, invocation) {
11525
11892
  const host = invocation[invocation.length - 1];
11526
11893
  const inheritedFlags = host.optsWithGlobals();
@@ -11535,6 +11902,7 @@ async function executeOperation(operation, commandSpec, operationSpec, invocatio
11535
11902
  for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
11536
11903
  requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
11537
11904
  }
11905
+ foldRenamedFlags(operation, commandSpec, requestFlags);
11538
11906
  if (commandSpec.confirm && !requestFlags.yes) {
11539
11907
  throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
11540
11908
  }
@@ -11555,16 +11923,23 @@ async function executeOperation(operation, commandSpec, operationSpec, invocatio
11555
11923
  const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
11556
11924
  const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
11557
11925
  const rows = [];
11926
+ const progress = pageProgress();
11558
11927
  let cursor = null;
11559
- do {
11560
- const page = await client.request(request.path, {
11561
- method: operationSpec.method,
11562
- query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
11563
- body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
11564
- });
11565
- rows.push(...page.data);
11566
- cursor = page.nextCursor;
11567
- } while (cursor && rows.length < limit);
11928
+ try {
11929
+ do {
11930
+ const page = await client.request(request.path, {
11931
+ method: operationSpec.method,
11932
+ query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
11933
+ body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
11934
+ });
11935
+ rows.push(...page.data);
11936
+ cursor = page.nextCursor;
11937
+ if (cursor && rows.length < limit)
11938
+ progress.advance(rows.length);
11939
+ } while (cursor && rows.length < limit);
11940
+ } finally {
11941
+ progress.finish();
11942
+ }
11568
11943
  renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
11569
11944
  return;
11570
11945
  }
@@ -11693,6 +12068,19 @@ function configureOperation(command, operation, spec) {
11693
12068
  function buildLeaf(operation, spec, leafName) {
11694
12069
  return addMissingArgumentExample(configureOperation(new Command(leafName), operation, spec));
11695
12070
  }
12071
+ function addRenamedCommand(groups, operation, spec, from, to) {
12072
+ const segments = from.split(" ");
12073
+ const [groupName, ...rest] = segments;
12074
+ if (rest.length === 0)
12075
+ throw new Error(`${operation}.renamedFrom "${from}" must include a verb`);
12076
+ let parent = groupFor(groups, groupName);
12077
+ for (const segment of rest.slice(0, -1)) {
12078
+ parent = nestedGroup(parent, segment, { hidden: true });
12079
+ }
12080
+ const leaf = buildLeaf(operation, spec, rest[rest.length - 1]);
12081
+ leaf.hook("preAction", () => warnRenamedCommand(from, to));
12082
+ parent.addCommand(leaf, { hidden: true });
12083
+ }
11696
12084
  function groupFor(groups, name) {
11697
12085
  const existing = groups.get(name);
11698
12086
  if (existing)
@@ -11708,12 +12096,12 @@ function resourceLabel(name) {
11708
12096
  const label = name.endsWith("s") ? name.slice(0, -1) : name;
11709
12097
  return label.replaceAll("-", " ");
11710
12098
  }
11711
- function nestedGroup(parent, name) {
12099
+ function nestedGroup(parent, name, options = {}) {
11712
12100
  const existing = parent.commands.find((candidate) => candidate.name() === name);
11713
12101
  if (existing)
11714
12102
  return existing;
11715
12103
  const created = new Command(name).description(`Manage ${resourceLabel(parent.name())} ${name.replaceAll("-", " ")}`);
11716
- parent.addCommand(created);
12104
+ parent.addCommand(created, { hidden: options.hidden });
11717
12105
  return created;
11718
12106
  }
11719
12107
  function addLeafCommand(groups, operation, spec, segments) {
@@ -11742,6 +12130,7 @@ function variantCommandSpec(spec, variant) {
11742
12130
  }
11743
12131
  function buildGeneratedCommands() {
11744
12132
  const groups = new Map;
12133
+ const renamed = [];
11745
12134
  for (const operation of Object.keys(V2_OPERATIONS)) {
11746
12135
  const spec = CLI_CONTRACT[operation] ?? {};
11747
12136
  const operationSpec = V2_OPERATIONS[operation];
@@ -11764,6 +12153,12 @@ function buildGeneratedCommands() {
11764
12153
  for (const variant of spec.variants ?? []) {
11765
12154
  addLeafCommand(groups, operation, variantCommandSpec(spec, variant), variant.command.split(" "));
11766
12155
  }
12156
+ for (const from of spec.renamedFrom ?? []) {
12157
+ renamed.push({ operation, spec, from, to: segments.join(" ") });
12158
+ }
12159
+ }
12160
+ for (const { operation, spec, from, to } of renamed) {
12161
+ addRenamedCommand(groups, operation, spec, from, to);
11767
12162
  }
11768
12163
  return [...groups.values()].sort((a, b) => a.name().localeCompare(b.name()));
11769
12164
  }