sim 2.0.0-dev.12.1 → 2.0.0-dev.19.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 +808 -383
  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,
@@ -2517,6 +2527,18 @@ function resolveProfile(overrides = {}) {
2517
2527
  }
2518
2528
  };
2519
2529
  }
2530
+ // src/version.ts
2531
+ import { readFileSync as readFileSync2 } from "node:fs";
2532
+ function readPackageVersion() {
2533
+ const metadata = JSON.parse(readFileSync2(new URL("../package.json", import.meta.url), "utf8"));
2534
+ if (typeof metadata !== "object" || metadata === null || !("version" in metadata) || typeof metadata.version !== "string") {
2535
+ throw new Error("CLI package metadata is missing a valid version");
2536
+ }
2537
+ return metadata.version;
2538
+ }
2539
+ var CLI_VERSION = readPackageVersion();
2540
+ var USER_AGENT = `sim-cli/${CLI_VERSION} node/${process.versions.node} (${process.platform}; ${process.arch})`;
2541
+
2520
2542
  // src/http/client.ts
2521
2543
  class SimApiError extends Error {
2522
2544
  status;
@@ -2539,13 +2561,28 @@ function buildUrl(endpoint, path, query) {
2539
2561
  }
2540
2562
  return url.toString();
2541
2563
  }
2542
- function toApiError(status, raw) {
2564
+ var REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
2565
+ var MARKUP_PREFIX = /^\s*<(?:!doctype|html|\?xml)/i;
2566
+ var KEY_SCOPE_REFUSALS = new Set([
2567
+ "WORKSPACE_KEY_OPERATION_NOT_PERMITTED",
2568
+ "PRINCIPAL_KIND_NOT_PERMITTED"
2569
+ ]);
2570
+ function toNonJsonError(url, status, contentType, raw) {
2571
+ const type = contentType?.split(";")[0]?.trim().toLowerCase();
2572
+ const isMarkup = type === "text/html" || type === "application/xhtml+xml" || MARKUP_PREFIX.test(raw);
2573
+ const kind = isMarkup ? "HTML" : type && type !== "application/json" ? type : "a non-JSON response";
2574
+ const text = raw.trim();
2575
+ const keepSnippet = !isMarkup && text.length > 0 && text.length <= 200;
2576
+ return new SimApiError(`${url} returned ${kind}, not JSON (HTTP ${status}) — check your endpoint.${keepSnippet ? ` Response: ${truncate(text, 200)}` : ""}`, status);
2577
+ }
2578
+ function toApiError(url, status, contentType, raw) {
2543
2579
  let parsed;
2544
2580
  try {
2545
2581
  parsed = JSON.parse(raw);
2546
2582
  } catch {
2547
- const text = raw.trim();
2548
- return new SimApiError(text ? truncate(text, 300) : `Request failed with status ${status}`, status);
2583
+ if (!raw.trim())
2584
+ return new SimApiError(`Request failed with status ${status}`, status);
2585
+ return toNonJsonError(url, status, contentType, raw);
2549
2586
  }
2550
2587
  const body = parsed;
2551
2588
  if (body.error && typeof body.error === "object") {
@@ -2561,8 +2598,34 @@ function toApiError(status, raw) {
2561
2598
  function truncate(value, max) {
2562
2599
  return value.length <= max ? value : `${value.slice(0, max)}…`;
2563
2600
  }
2601
+ function namesKeyScopeRefusal(error) {
2602
+ if (typeof error.code === "string" && KEY_SCOPE_REFUSALS.has(error.code))
2603
+ return true;
2604
+ const details = error.details;
2605
+ if (!details || typeof details !== "object")
2606
+ return false;
2607
+ const code = details.code;
2608
+ return typeof code === "string" && KEY_SCOPE_REFUSALS.has(code);
2609
+ }
2610
+ function isStrictPrefix(path, other) {
2611
+ if (path.length >= other.length)
2612
+ return false;
2613
+ return path.every((segment, index) => segment === other[index]);
2614
+ }
2615
+ function rejectsAKeyThatValidated(issue, other) {
2616
+ if (!issue.unrecognizedKeys || !isStrictPrefix(issue.path, other.path))
2617
+ return false;
2618
+ return issue.unrecognizedKeys.includes(other.path[issue.path.length]);
2619
+ }
2620
+ function dropUnionBranchNoise(issues) {
2621
+ if (issues.length < 2)
2622
+ return issues;
2623
+ const kept = issues.filter((issue) => !issues.some((other) => rejectsAKeyThatValidated(issue, other)));
2624
+ return kept.length > 0 ? kept : issues;
2625
+ }
2564
2626
  function formatApiErrorDetails(details) {
2565
- const issues = new Set;
2627
+ const issues = [];
2628
+ const seen = new Set;
2566
2629
  const visit = (value, parentPath = []) => {
2567
2630
  if (Array.isArray(value)) {
2568
2631
  value.forEach((item) => visit(item, parentPath));
@@ -2580,15 +2643,27 @@ function formatApiErrorDetails(details) {
2580
2643
  }
2581
2644
  if (typeof issue.message !== "string" || issue.message === "Invalid input")
2582
2645
  return;
2583
- issues.add(`${path.length > 0 ? path.join(".") : "request"}: ${issue.message}`);
2646
+ const line = `${path.join(".")}: ${issue.message}`;
2647
+ if (seen.has(line))
2648
+ return;
2649
+ seen.add(line);
2650
+ issues.push({
2651
+ path,
2652
+ message: issue.message,
2653
+ unrecognizedKeys: issue.code === "unrecognized_keys" && Array.isArray(issue.keys) ? issue.keys.map(String) : null
2654
+ });
2584
2655
  };
2585
2656
  visit(details);
2586
- if (issues.size === 0)
2657
+ if (issues.length === 0)
2587
2658
  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`);
2659
+ const kept = dropUnionBranchNoise(issues);
2660
+ const visible = kept.slice(0, 8);
2661
+ const lines = [
2662
+ " details:",
2663
+ ...visible.map((issue) => ` ${issue.path.length > 0 ? issue.path.join(".") : "request"}: ${issue.message}`)
2664
+ ];
2665
+ if (kept.length > visible.length)
2666
+ lines.push(` … ${kept.length - visible.length} more issues`);
2592
2667
  return lines;
2593
2668
  }
2594
2669
 
@@ -2614,6 +2689,20 @@ class SimClient {
2614
2689
  return workspaceId;
2615
2690
  }
2616
2691
  async requestRaw(path, options = {}) {
2692
+ return (await this.send(path, options)).response;
2693
+ }
2694
+ async request(path, options = {}) {
2695
+ const { response, url } = await this.send(path, options);
2696
+ const raw = await response.text();
2697
+ if (!raw)
2698
+ return;
2699
+ try {
2700
+ return JSON.parse(raw);
2701
+ } catch {
2702
+ throw toNonJsonError(url, response.status, response.headers.get("content-type"), raw);
2703
+ }
2704
+ }
2705
+ async send(path, options) {
2617
2706
  const apiKey = this.resolveApiKey(options.auth);
2618
2707
  const url = buildUrl(this.profile.endpoint, path, options.query);
2619
2708
  const hasBody = options.body !== undefined;
@@ -2624,11 +2713,13 @@ class SimClient {
2624
2713
  headers: {
2625
2714
  ...apiKey ? { "x-api-key": apiKey } : {},
2626
2715
  accept: "application/json",
2716
+ "user-agent": USER_AGENT,
2627
2717
  ...hasBody ? { "content-type": "application/json" } : {},
2628
2718
  ...options.headers
2629
2719
  },
2630
2720
  body: hasBody ? JSON.stringify(options.body) : undefined,
2631
- signal: options.signal
2721
+ signal: options.signal,
2722
+ redirect: "manual"
2632
2723
  });
2633
2724
  } catch (cause) {
2634
2725
  if (options.signal?.aborted) {
@@ -2636,43 +2727,87 @@ class SimClient {
2636
2727
  }
2637
2728
  throw new SimApiError(`Could not reach ${this.profile.endpoint}: ${cause.message}`, 0);
2638
2729
  }
2730
+ if (REDIRECT_STATUSES.has(response.status))
2731
+ throw this.toRedirectError(url, path, response);
2639
2732
  if (!response.ok) {
2640
2733
  const raw = await response.text();
2641
- const error = toApiError(response.status, raw);
2734
+ const error = toApiError(url, response.status, response.headers.get("content-type"), raw);
2642
2735
  if (response.status === 401) {
2643
2736
  error.message = `${error.message} — run: sim login --profile ${this.profile.name}`;
2644
2737
  }
2738
+ if (namesKeyScopeRefusal(error)) {
2739
+ error.message = `${error.message} — this operation needs a personal API key: sim login --profile ${this.profile.name}`;
2740
+ }
2645
2741
  throw error;
2646
2742
  }
2647
- return response;
2743
+ return { response, url };
2648
2744
  }
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);
2745
+ toRedirectError(url, path, response) {
2746
+ const location = response.headers.get("location")?.trim();
2747
+ let target = null;
2748
+ if (location) {
2749
+ try {
2750
+ target = new URL(location, url);
2751
+ } catch {
2752
+ target = null;
2753
+ }
2754
+ }
2755
+ if (!target) {
2756
+ return new SimApiError(`${url} answered HTTP ${response.status} with no usable redirect target. Check the endpoint for profile "${this.profile.name}".`, response.status);
2757
+ }
2758
+ const suggested = redirectEndpoint(this.profile.endpoint, path, target);
2759
+ if (!suggested) {
2760
+ 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);
2761
+ }
2762
+ return new SimApiError(`Endpoint redirected to ${suggested}. Run: sim configure --profile ${this.profile.name} --set-endpoint ${suggested}`, response.status);
2655
2763
  }
2656
2764
  }
2765
+ function redirectEndpoint(endpoint, requestPath, target) {
2766
+ const prefix = target.pathname.endsWith(requestPath) ? target.pathname.slice(0, target.pathname.length - requestPath.length) : "";
2767
+ const suggested = `${target.origin}${prefix}`.replace(/\/+$/, "");
2768
+ return suggested === endpoint.replace(/\/+$/, "") ? null : suggested;
2769
+ }
2770
+ function pageProgress() {
2771
+ let reported = false;
2772
+ return {
2773
+ advance: (fetched) => {
2774
+ if (!process.stderr.isTTY)
2775
+ return;
2776
+ reported = true;
2777
+ process.stderr.write(`\r${source_default.dim(`fetched ${fetched}…`)}\x1B[K`);
2778
+ },
2779
+ finish: () => {
2780
+ if (reported)
2781
+ process.stderr.write("\r\x1B[K");
2782
+ }
2783
+ };
2784
+ }
2657
2785
  async function requestAllPages(client, path, options) {
2658
2786
  const { query, pageSize, limit: requestedLimit, ...requestOptions } = options;
2659
2787
  const limit = requestedLimit ?? Number.POSITIVE_INFINITY;
2660
2788
  if (limit <= 0)
2661
2789
  return [];
2662
2790
  const items = [];
2791
+ const progress = pageProgress();
2663
2792
  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);
2793
+ try {
2794
+ do {
2795
+ const page = await client.request(path, {
2796
+ ...requestOptions,
2797
+ query: {
2798
+ ...query,
2799
+ limit: Math.min(pageSize, limit - items.length),
2800
+ cursor
2801
+ }
2802
+ });
2803
+ items.push(...page.data);
2804
+ cursor = page.nextCursor;
2805
+ if (cursor && items.length < limit)
2806
+ progress.advance(items.length);
2807
+ } while (cursor && items.length < limit);
2808
+ } finally {
2809
+ progress.finish();
2810
+ }
2676
2811
  return items.slice(0, limit);
2677
2812
  }
2678
2813
  function resolvePath(template, params = {}) {
@@ -5894,7 +6029,7 @@ function duration(ms) {
5894
6029
  if (ms === null || ms === undefined)
5895
6030
  return EMPTY;
5896
6031
  if (ms < 1000)
5897
- return `${ms}ms`;
6032
+ return `${Math.round(ms)}ms`;
5898
6033
  if (ms < 60000)
5899
6034
  return `${(ms / 1000).toFixed(1)}s`;
5900
6035
  return `${Math.floor(ms / 60000)}m${Math.round(ms % 60000 / 1000)}s`;
@@ -5914,17 +6049,18 @@ function oneLine(value) {
5914
6049
  return value.replace(/\s*[\r\n\t]+\s*/g, " ");
5915
6050
  }
5916
6051
  var MAX_CELL_WIDTH = 60;
5917
- function clampCell(value) {
5918
- if (visibleWidth(value) <= MAX_CELL_WIDTH || value !== value.replace(ANSI_PATTERN, "")) {
6052
+ var MAX_RECORD_WIDTH = 160;
6053
+ function clamp(value, width) {
6054
+ if (visibleWidth(value) <= width || value !== value.replace(ANSI_PATTERN, "")) {
5919
6055
  return value;
5920
6056
  }
5921
- return `${value.slice(0, MAX_CELL_WIDTH - 1)}…`;
6057
+ return `${value.slice(0, width - 1)}…`;
5922
6058
  }
5923
6059
  function renderTable(rows, columns) {
5924
6060
  if (rows.length === 0)
5925
6061
  return source_default.dim("No results.");
5926
6062
  const headers = columns.map((column) => sanitize(column.header));
5927
- const cells = rows.map((row) => columns.map((column) => clampCell(oneLine(column.value(row)))));
6063
+ const cells = rows.map((row) => columns.map((column) => clamp(oneLine(column.value(row)), MAX_CELL_WIDTH)));
5928
6064
  const widths = columns.map((_column, index) => Math.max(visibleWidth(headers[index]), ...cells.map((line) => visibleWidth(line[index]))));
5929
6065
  const header = headers.map((label, index) => source_default.dim(pad(label.toUpperCase(), widths[index]))).join(" ").trimEnd();
5930
6066
  const body = cells.map((line) => line.map((cell, index) => pad(cell, widths[index])).join(" ").trimEnd());
@@ -5970,13 +6106,10 @@ function printRecord(format, fields, raw) {
5970
6106
  }
5971
6107
  const width = Math.max(...safeFields.map(([label]) => visibleWidth(label)));
5972
6108
  for (const [label, value] of safeFields) {
5973
- console.log(`${source_default.dim(pad(`${label}:`, width + 1))} ${oneLine(value)}`);
6109
+ console.log(`${source_default.dim(pad(`${label}:`, width + 1))} ${clamp(oneLine(value), MAX_RECORD_WIDTH)}`);
5974
6110
  }
5975
6111
  }
5976
6112
 
5977
- // src/program.ts
5978
- import { readFileSync as readFileSync3 } from "node:fs";
5979
-
5980
6113
  // ../../node_modules/commander/esm.mjs
5981
6114
  var import__ = __toESM(require_commander(), 1);
5982
6115
  var {
@@ -6009,6 +6142,8 @@ function sleep(ms) {
6009
6142
  var PAIRING_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
6010
6143
  var POLL_INTERVAL_MS = 2000;
6011
6144
  var POLL_TIMEOUT_MS = 15 * 60 * 1000;
6145
+ var APPROVAL_PATH = "/cli/auth";
6146
+ var POLL_PATH = "/api/cli/auth/poll";
6012
6147
  var RETRYABLE_POLL_STATUSES = new Set([409, 429, 500, 502, 503, 504]);
6013
6148
  function token() {
6014
6149
  return randomBytes(32).toString("base64url");
@@ -6027,14 +6162,30 @@ function createAuthRequest() {
6027
6162
  };
6028
6163
  }
6029
6164
  function buildApprovalUrl(endpoint, auth, scope, workspaceId) {
6030
- const url = new URL("/cli/auth", endpoint);
6031
- url.searchParams.set("request", auth.request);
6032
- url.searchParams.set("challenge", auth.challenge);
6033
- url.searchParams.set("pairing", auth.pairing);
6034
- url.searchParams.set("scope", scope);
6035
- if (workspaceId)
6036
- url.searchParams.set("workspace", workspaceId);
6037
- return url.toString();
6165
+ return buildUrl(endpoint, APPROVAL_PATH, {
6166
+ request: auth.request,
6167
+ challenge: auth.challenge,
6168
+ pairing: auth.pairing,
6169
+ scope,
6170
+ workspace: workspaceId
6171
+ });
6172
+ }
6173
+ function toRedirectError(endpoint, response) {
6174
+ const location = response.headers.get("location")?.trim();
6175
+ let target = null;
6176
+ if (location) {
6177
+ try {
6178
+ target = new URL(location, endpoint);
6179
+ } catch {
6180
+ target = null;
6181
+ }
6182
+ }
6183
+ if (!target) {
6184
+ return new SimApiError(`${endpoint} answered the login poll with HTTP ${response.status} and no usable redirect target. Check the endpoint.`, response.status);
6185
+ }
6186
+ 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.`;
6187
+ const suggested = redirectEndpoint(endpoint, POLL_PATH, target);
6188
+ return new SimApiError(suggested ? `${refusal} Re-run with --endpoint ${suggested}, or run: sim configure --set-endpoint ${suggested}` : refusal, response.status);
6038
6189
  }
6039
6190
  async function pollForKey(endpoint, auth, signal) {
6040
6191
  const deadline = Date.now() + POLL_TIMEOUT_MS;
@@ -6043,16 +6194,23 @@ async function pollForKey(endpoint, auth, signal) {
6043
6194
  throw new SimApiError("Login cancelled.", 0);
6044
6195
  let response = null;
6045
6196
  try {
6046
- response = await fetch(new URL("/api/cli/auth/poll", endpoint), {
6197
+ response = await fetch(buildUrl(endpoint, POLL_PATH), {
6047
6198
  method: "POST",
6048
- headers: { "content-type": "application/json", accept: "application/json" },
6199
+ headers: {
6200
+ "content-type": "application/json",
6201
+ accept: "application/json",
6202
+ "user-agent": USER_AGENT
6203
+ },
6049
6204
  body: JSON.stringify({ request: auth.request, verifier: auth.pollSecret }),
6050
- signal
6205
+ signal,
6206
+ redirect: "manual"
6051
6207
  });
6052
6208
  } catch {
6053
6209
  response = null;
6054
6210
  }
6055
6211
  if (response) {
6212
+ if (REDIRECT_STATUSES.has(response.status))
6213
+ throw toRedirectError(endpoint, response);
6056
6214
  const raw = await response.text();
6057
6215
  if (!response.ok) {
6058
6216
  if (!RETRYABLE_POLL_STATUSES.has(response.status)) {
@@ -6106,275 +6264,88 @@ function clientFrom(command) {
6106
6264
  return { client: new SimClient(profile), profile };
6107
6265
  }
6108
6266
 
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;
6267
+ // src/generated/v2-api.ts
6268
+ var V2_OPERATIONS = {
6269
+ abortFileUpload: {
6270
+ method: "DELETE",
6271
+ path: "/api/v2/files/uploads/[uploadId]",
6272
+ pathParams: ["uploadId"],
6273
+ pathParamDocs: { uploadId: "Upload session identifier." },
6274
+ responseMode: "json",
6275
+ summary: "Abort File Upload",
6276
+ query: {
6277
+ workspaceId: {
6278
+ kind: "string",
6279
+ required: true,
6280
+ describe: "Workspace that owns the upload session."
6157
6281
  }
6158
6282
  }
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);
6283
+ },
6284
+ abortKnowledgeDocumentUpload: {
6285
+ method: "DELETE",
6286
+ path: "/api/v2/knowledge/[id]/documents/uploads/[uploadId]",
6287
+ pathParams: ["id", "uploadId"],
6288
+ pathParamDocs: {
6289
+ id: "Unique knowledge base identifier.",
6290
+ uploadId: "Upload session identifier returned when the upload was created."
6291
+ },
6292
+ responseMode: "json",
6293
+ summary: "Abort Document Upload",
6294
+ query: {
6295
+ workspaceId: {
6296
+ kind: "string",
6297
+ required: true,
6298
+ describe: "Workspace that owns the knowledge base."
6299
+ }
6174
6300
  }
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>"));
6301
+ },
6302
+ addTableColumn: {
6303
+ method: "POST",
6304
+ path: "/api/v2/tables/[tableId]/columns",
6305
+ pathParams: ["tableId"],
6306
+ pathParamDocs: { tableId: "Unique table identifier." },
6307
+ responseMode: "json",
6308
+ summary: "Add Column",
6309
+ body: {
6310
+ workspaceId: { kind: "string", required: true, describe: "Workspace that owns the table." },
6311
+ column: { kind: "object", required: true, describe: "Column definition to add." }
6189
6312
  }
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;
6313
+ },
6314
+ addWorkflowGroup: {
6315
+ method: "POST",
6316
+ path: "/api/v2/tables/[tableId]/groups",
6317
+ pathParams: ["tableId"],
6318
+ pathParamDocs: { tableId: "Unique table identifier." },
6319
+ responseMode: "json",
6320
+ summary: "Add Workflow Group",
6321
+ body: {
6322
+ workspaceId: { kind: "string", required: true, describe: "Unique workspace identifier." },
6323
+ group: {
6324
+ kind: "object",
6325
+ required: true,
6326
+ describe: "Workflow or enrichment producer definition."
6327
+ },
6328
+ outputColumns: {
6329
+ kind: "array",
6330
+ required: true,
6331
+ describe: "Columns created for producer outputs."
6332
+ },
6333
+ autoRun: {
6334
+ kind: "boolean",
6335
+ default: false,
6336
+ describe: "Whether to schedule existing rows after group creation."
6200
6337
  }
6201
- console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
6202
- return;
6203
6338
  }
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
- // src/generated/v2-api.ts
6297
- var V2_OPERATIONS = {
6298
- abortFileUpload: {
6299
- method: "DELETE",
6300
- path: "/api/v2/files/uploads/[uploadId]",
6301
- pathParams: ["uploadId"],
6302
- pathParamDocs: { uploadId: "Upload session identifier." },
6303
- responseMode: "json",
6304
- summary: "Abort File Upload",
6305
- query: {
6306
- workspaceId: {
6307
- kind: "string",
6308
- required: true,
6309
- describe: "Workspace that owns the upload session."
6310
- }
6311
- }
6312
- },
6313
- abortKnowledgeDocumentUpload: {
6314
- method: "DELETE",
6315
- path: "/api/v2/knowledge/[id]/documents/uploads/[uploadId]",
6316
- pathParams: ["id", "uploadId"],
6317
- pathParamDocs: {
6318
- id: "Unique knowledge base identifier.",
6319
- uploadId: "Upload session identifier returned when the upload was created."
6320
- },
6321
- responseMode: "json",
6322
- summary: "Abort Document Upload",
6323
- query: {
6324
- workspaceId: {
6325
- kind: "string",
6326
- required: true,
6327
- describe: "Workspace that owns the knowledge base."
6328
- }
6329
- }
6330
- },
6331
- addTableColumn: {
6332
- method: "POST",
6333
- path: "/api/v2/tables/[tableId]/columns",
6334
- pathParams: ["tableId"],
6335
- pathParamDocs: { tableId: "Unique table identifier." },
6336
- responseMode: "json",
6337
- summary: "Add Column",
6338
- body: {
6339
- workspaceId: { kind: "string", required: true, describe: "Workspace that owns the table." },
6340
- column: { kind: "object", required: true, describe: "Column definition to add." }
6341
- }
6342
- },
6343
- addWorkflowGroup: {
6344
- method: "POST",
6345
- path: "/api/v2/tables/[tableId]/groups",
6346
- pathParams: ["tableId"],
6347
- pathParamDocs: { tableId: "Unique table identifier." },
6348
- responseMode: "json",
6349
- summary: "Add Workflow Group",
6350
- body: {
6351
- workspaceId: { kind: "string", required: true, describe: "Unique workspace identifier." },
6352
- group: {
6353
- kind: "object",
6354
- required: true,
6355
- describe: "Workflow or enrichment producer definition."
6356
- },
6357
- outputColumns: {
6358
- kind: "array",
6359
- required: true,
6360
- describe: "Columns created for producer outputs."
6361
- },
6362
- autoRun: {
6363
- kind: "boolean",
6364
- default: false,
6365
- describe: "Whether to schedule existing rows after group creation."
6366
- }
6367
- }
6368
- },
6369
- bulkDeleteFiles: {
6370
- method: "POST",
6371
- path: "/api/v2/files/bulk-delete",
6372
- pathParams: [],
6373
- responseMode: "json",
6374
- summary: "Delete Files",
6375
- body: {
6376
- workspaceId: { kind: "string", required: true, describe: "Workspace containing the files." },
6377
- fileIds: { kind: "array", required: true, describe: "File identifiers to update." }
6339
+ },
6340
+ bulkDeleteFiles: {
6341
+ method: "POST",
6342
+ path: "/api/v2/files/bulk-delete",
6343
+ pathParams: [],
6344
+ responseMode: "json",
6345
+ summary: "Delete Files",
6346
+ body: {
6347
+ workspaceId: { kind: "string", required: true, describe: "Workspace containing the files." },
6348
+ fileIds: { kind: "array", required: true, describe: "File identifiers to update." }
6378
6349
  }
6379
6350
  },
6380
6351
  bulkUpdateKnowledgeDocuments: {
@@ -8968,6 +8939,10 @@ var V2_OPERATIONS = {
8968
8939
  kind: "string",
8969
8940
  required: true,
8970
8941
  describe: "Write-only secret value. It is never returned."
8942
+ },
8943
+ description: {
8944
+ kind: "string",
8945
+ describe: "What the secret is for, shown to teammates. Workspace scope only — sending it for a personal secret is rejected. Omit it to leave an existing description untouched; send null or an empty string to clear one."
8971
8946
  }
8972
8947
  }
8973
8948
  },
@@ -9380,8 +9355,263 @@ var V2_OPERATIONS = {
9380
9355
  }
9381
9356
  };
9382
9357
 
9358
+ // src/commands/auth.ts
9359
+ function openBrowser(url) {
9360
+ const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
9361
+ try {
9362
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
9363
+ child.on("error", () => {});
9364
+ child.unref();
9365
+ } catch {}
9366
+ }
9367
+ function presentAuthentication(source) {
9368
+ switch (source) {
9369
+ case "flag":
9370
+ return { authenticated: true, source: "flag" };
9371
+ case "env":
9372
+ return { authenticated: true, source: "env" };
9373
+ case "credentials":
9374
+ return { authenticated: true, source: "credentials" };
9375
+ case "unset":
9376
+ return { authenticated: false, source: "unset" };
9377
+ case "config":
9378
+ case "default":
9379
+ throw new SimApiError(`Unexpected API key source "${source}".`, 0);
9380
+ }
9381
+ }
9382
+ async function confirmProfileOverwrite(profileName) {
9383
+ if (!process.stdin.isTTY) {
9384
+ throw new SimApiError(`Profile "${profileName}" already exists. Re-run with --yes to overwrite it.`, 0);
9385
+ }
9386
+ const prompt = createInterface({ input: process.stdin, output: process.stderr });
9387
+ try {
9388
+ const answer = await prompt.question(`Profile "${profileName}" already exists. Replace its API key and login defaults? (y/N) `);
9389
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
9390
+ } finally {
9391
+ prompt.close();
9392
+ }
9393
+ }
9394
+ function loginCommand() {
9395
+ 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) => {
9396
+ const profile = profileFrom(command);
9397
+ if (options.scope !== "platform" && options.scope !== "copilot") {
9398
+ throw new SimApiError(`Unknown scope "${options.scope}". Use platform or copilot.`, 0);
9399
+ }
9400
+ const scope = options.scope;
9401
+ if (readCredentialsProfile(profile.name).api_key && !options.yes) {
9402
+ const confirmed = await confirmProfileOverwrite(profile.name);
9403
+ if (!confirmed) {
9404
+ console.log(source_default.dim("Login cancelled; the existing profile was not changed."));
9405
+ return;
9406
+ }
9407
+ }
9408
+ const auth = createAuthRequest();
9409
+ const url = buildApprovalUrl(profile.endpoint, auth, scope, profile.workspaceId ?? undefined);
9410
+ console.log(`Signing in to ${source_default.bold(profile.endpoint)} as profile ${source_default.bold(profile.name)}`);
9411
+ console.log(`
9412
+ Pairing code: ${source_default.bold(auth.pairing)}`);
9413
+ console.log(source_default.dim(`Confirm this code matches what the browser shows before approving.
9414
+ `));
9415
+ console.log(url);
9416
+ if (options.browser)
9417
+ openBrowser(url);
9418
+ console.log(source_default.dim(`
9419
+ Waiting for approval…`));
9420
+ const key = await pollForKey(profile.endpoint, auth);
9421
+ if (key.scope !== scope) {
9422
+ 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);
9423
+ }
9424
+ writeCredentialsProfile(profile.name, key.apiKey);
9425
+ const settings = {
9426
+ endpoint: profile.endpoint,
9427
+ workspace: key.workspaceId ?? null
9428
+ };
9429
+ writeConfigProfile(profile.name, settings);
9430
+ console.log(source_default.green(`
9431
+ ✓ Logged in. Key stored in ${credentialsPath()}`));
9432
+ if (key.workspaceBound && key.workspaceId) {
9433
+ console.log(source_default.dim(` Workspace-scoped key — it can only reach ${key.workspaceId}.`));
9434
+ } else if (key.workspaceId) {
9435
+ console.log(source_default.dim(` Personal key, defaulting to ${key.workspaceId}. Override per command with --workspace.`));
9436
+ } else {
9437
+ console.log(source_default.dim(" Personal key with no default workspace. Set one with: sim configure --set-workspace <id>"));
9438
+ }
9439
+ });
9440
+ }
9441
+ function logoutCommand() {
9442
+ return new Command("logout").description("Remove the profile's stored API key").option("--all", "Remove the profile entirely, including its settings").action((options, command) => {
9443
+ const profile = profileFrom(command);
9444
+ if (options.all) {
9445
+ const removed = deleteProfile(profile.name);
9446
+ if (!removed.config && !removed.credentials) {
9447
+ console.log(source_default.dim(`Nothing stored for profile "${profile.name}".`));
9448
+ return;
9449
+ }
9450
+ console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
9451
+ return;
9452
+ }
9453
+ if (!readCredentialsProfile(profile.name).api_key) {
9454
+ console.log(source_default.dim(`No stored key for profile "${profile.name}".`));
9455
+ return;
9456
+ }
9457
+ writeCredentialsProfile(profile.name, null);
9458
+ console.log(source_default.green(`✓ Removed the stored key for profile "${profile.name}".`));
9459
+ console.log(source_default.dim(" The key itself is still active — revoke it in Settings → API keys."));
9460
+ });
9461
+ }
9462
+ var CREDENTIAL_VERDICT_STATUSES = new Set([401, 403, 404]);
9463
+ var WHOAMI_EXIT_CODES = {
9464
+ verified: 0,
9465
+ disabled: 0,
9466
+ unauthenticated: 1,
9467
+ rejected: 1,
9468
+ unreachable: 2,
9469
+ "no-workspace": 2
9470
+ };
9471
+ async function verifyProfile(client, profile) {
9472
+ if (!profile.apiKey) {
9473
+ return {
9474
+ status: "unauthenticated",
9475
+ workspace: null,
9476
+ detail: `no API key — run: sim login --profile ${profile.name}`
9477
+ };
9478
+ }
9479
+ if (!profile.workspaceId) {
9480
+ return {
9481
+ status: "no-workspace",
9482
+ workspace: null,
9483
+ detail: `no workspace to check against — run: sim configure --profile ${profile.name} --set-workspace <id>`
9484
+ };
9485
+ }
9486
+ const operation = V2_OPERATIONS.getWorkspace;
9487
+ try {
9488
+ const response = await client.request(resolvePath(operation.path, { workspaceId: profile.workspaceId }), { method: operation.method });
9489
+ const { id, name, memberCount } = response.data;
9490
+ return { status: "verified", workspace: { id, name, memberCount }, detail: null };
9491
+ } catch (error) {
9492
+ if (!(error instanceof SimApiError))
9493
+ throw error;
9494
+ return {
9495
+ status: CREDENTIAL_VERDICT_STATUSES.has(error.status) ? "rejected" : "unreachable",
9496
+ workspace: null,
9497
+ detail: error.message
9498
+ };
9499
+ }
9500
+ }
9501
+ function presentVerification(verification) {
9502
+ if (verification.status === "verified") {
9503
+ const { name, memberCount } = verification.workspace;
9504
+ const members = `${memberCount} ${memberCount === 1 ? "member" : "members"}`;
9505
+ return `${source_default.green("✓")} ${safeOneLine(name)} · ${members}`;
9506
+ }
9507
+ const detail = safeOneLine(verification.detail);
9508
+ switch (verification.status) {
9509
+ case "rejected":
9510
+ return `${source_default.red("✗")} ${detail}`;
9511
+ case "unauthenticated":
9512
+ return source_default.yellow(`not logged in — ${detail}`);
9513
+ case "disabled":
9514
+ return source_default.dim(detail);
9515
+ default:
9516
+ return source_default.yellow(`could not check — ${detail}`);
9517
+ }
9518
+ }
9519
+ function whoamiCommand() {
9520
+ 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) => {
9521
+ const { client, profile } = clientFrom(command);
9522
+ const { sources } = profile;
9523
+ const authentication = presentAuthentication(sources.apiKey);
9524
+ const verification = options.verify ? await verifyProfile(client, profile) : { status: "disabled", workspace: null, detail: "not checked (--no-verify)" };
9525
+ const annotate = (value, source) => source === "unset" ? source_default.dim("not set") : `${value} ${source_default.dim(`(${source})`)}`;
9526
+ printRecord(profile.output, [
9527
+ ["Profile", profile.name],
9528
+ ["Endpoint", annotate(profile.endpoint, sources.endpoint)],
9529
+ [
9530
+ "API key",
9531
+ authentication.authenticated ? annotate("configured", authentication.source) : source_default.yellow("not logged in")
9532
+ ],
9533
+ ["Workspace", annotate(profile.workspaceId ?? "", sources.workspaceId)],
9534
+ ["Output", annotate(profile.output, sources.output)],
9535
+ ["Verified", presentVerification(verification)]
9536
+ ], {
9537
+ profile: profile.name,
9538
+ endpoint: profile.endpoint,
9539
+ workspaceId: profile.workspaceId,
9540
+ output: profile.output,
9541
+ authenticated: authentication.authenticated,
9542
+ sources: {
9543
+ endpoint: sources.endpoint,
9544
+ authentication: authentication.source,
9545
+ workspaceId: sources.workspaceId,
9546
+ output: sources.output
9547
+ },
9548
+ verification: {
9549
+ status: verification.status,
9550
+ workspace: verification.workspace,
9551
+ detail: verification.detail
9552
+ }
9553
+ });
9554
+ const exitCode = WHOAMI_EXIT_CODES[verification.status];
9555
+ if (exitCode !== 0)
9556
+ process.exitCode = exitCode;
9557
+ });
9558
+ }
9559
+ function profilesCommand() {
9560
+ return new Command("profiles").alias("profile").description("List the profiles defined in the config and credentials files").action((_options, command) => {
9561
+ const profiles = listProfiles();
9562
+ if (profiles.length === 0) {
9563
+ console.log(source_default.dim("No profiles yet. Run: sim login"));
9564
+ return;
9565
+ }
9566
+ const active = profileFrom(command).name;
9567
+ for (const name of profiles) {
9568
+ const marker = name === active ? source_default.green("*") : " ";
9569
+ const hasKey = Boolean(readCredentialsProfile(name).api_key);
9570
+ console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}`);
9571
+ }
9572
+ });
9573
+ }
9574
+
9575
+ // src/commands/configure.ts
9576
+ function configureCommand() {
9577
+ 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) => {
9578
+ const profile = profileFrom(command);
9579
+ const updates = {};
9580
+ if (options.setEndpoint) {
9581
+ updates.endpoint = normalizeEndpoint(options.setEndpoint, "--set-endpoint");
9582
+ }
9583
+ if (options.setWorkspace)
9584
+ updates.workspace = options.setWorkspace;
9585
+ if (options.setOutput) {
9586
+ if (!OUTPUT_FORMATS.includes(options.setOutput)) {
9587
+ throw new SimApiError(`Unknown output format "${options.setOutput}". Use one of: ${OUTPUT_FORMATS.join(", ")}`, 0);
9588
+ }
9589
+ updates.output = options.setOutput;
9590
+ }
9591
+ for (const key of options.unset ?? []) {
9592
+ if (!["endpoint", "workspace", "output"].includes(key)) {
9593
+ throw new SimApiError(`Cannot unset "${key}". Use endpoint, workspace, or output.`, 0);
9594
+ }
9595
+ updates[key] = null;
9596
+ }
9597
+ if (Object.keys(updates).length === 0) {
9598
+ const current = readConfigProfile(profile.name);
9599
+ if (Object.keys(current).length === 0) {
9600
+ console.log(source_default.dim(`No settings stored for profile "${profile.name}".`));
9601
+ return;
9602
+ }
9603
+ for (const [key, value] of Object.entries(current)) {
9604
+ console.log(`${source_default.dim(`${key}:`)} ${value}`);
9605
+ }
9606
+ return;
9607
+ }
9608
+ writeConfigProfile(profile.name, updates);
9609
+ console.log(source_default.green(`✓ Updated profile "${profile.name}" in ${configPath()}`));
9610
+ });
9611
+ }
9612
+
9383
9613
  // src/runtime/request.ts
9384
- import { existsSync as existsSync2, readFileSync as readFileSync2, readSync } from "node:fs";
9614
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readSync } from "node:fs";
9385
9615
 
9386
9616
  // src/contract/commands.ts
9387
9617
  var TABLE_NAME_HELP = "Identifier: letters, numbers, and underscores; cannot start with a number";
@@ -9389,7 +9619,8 @@ var TABLE_FILTER_HELP = 'Predicate: {"all":[{"field":"status","op":"eq","value":
9389
9619
  var TABLE_SORT_HELP = 'Ordered sort keys: [{"field":"createdAt","direction":"desc"}] (direction: asc or desc)';
9390
9620
  var CUSTOM_TOOL_SCHEMA_HELP = 'OpenAI function schema: {"type":"function","function":{"name":"...","parameters":{"type":"object","properties":{}}}}';
9391
9621
  var FOLDER_PATH_INPUT = {
9392
- describe: "Folder path; the leading / is optional"
9622
+ describe: "Folder path as shown in the app; the leading / is optional",
9623
+ folderPath: true
9393
9624
  };
9394
9625
  var FOLDER_PATH_FLAG = {
9395
9626
  ...FOLDER_PATH_INPUT,
@@ -9399,7 +9630,7 @@ var FOLDER_DELETE_FLAGS = {
9399
9630
  path: FOLDER_PATH_INPUT,
9400
9631
  recursive: { boolean: true, describe: "Delete the folder and its descendants" }
9401
9632
  };
9402
- var KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS = { id: "knowledgeBaseId" };
9633
+ var KNOWLEDGE_BASE_PATH_ARGUMENT = { id: "knowledgeBaseId" };
9403
9634
  var WORKFLOW_RUN_SCOPE = {
9404
9635
  id: {
9405
9636
  name: "workflow",
@@ -9407,10 +9638,11 @@ var WORKFLOW_RUN_SCOPE = {
9407
9638
  describe: "Workflow ID"
9408
9639
  }
9409
9640
  };
9641
+ var FOLDER_COLUMN = { header: "folder", path: "folderPath", format: "folder-path" };
9410
9642
  var FOLDER_LIST_COLUMNS = [
9411
- { header: "path" },
9643
+ { header: "path", format: "folder-path" },
9412
9644
  { header: "name" },
9413
- { header: "parent", path: "parentPath" },
9645
+ { header: "parent", path: "parentPath", format: "folder-path" },
9414
9646
  { header: "updated", path: "updatedAt", format: "timestamp" }
9415
9647
  ];
9416
9648
  function moveResource(command, resource) {
@@ -9480,7 +9712,7 @@ var CLI_CONTRACT = {
9480
9712
  bulkUpdateKnowledgeDocuments: {
9481
9713
  command: "knowledge documents batch-update",
9482
9714
  describe: "Enable or disable every matching document",
9483
- pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
9715
+ pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9484
9716
  flags: {
9485
9717
  documentIds: { name: "document", list: true },
9486
9718
  selectAll: { boolean: true, describe: "Apply to every document in the knowledge base" }
@@ -9490,6 +9722,11 @@ var CLI_CONTRACT = {
9490
9722
  command: "workflows undeploy",
9491
9723
  describe: "Take a workflow out of deployment"
9492
9724
  },
9725
+ getWorkflowDeployment: {
9726
+ command: "workflows deployment status",
9727
+ renamedFrom: ["workflows deployment list"],
9728
+ describe: "Show a workflow’s current deployment"
9729
+ },
9493
9730
  setSecret: { hidden: true },
9494
9731
  deleteTable: { confirm: "This deletes the table and all of its rows." },
9495
9732
  deleteTableRow: { confirm: "This deletes the row." },
@@ -9499,7 +9736,7 @@ var CLI_CONTRACT = {
9499
9736
  },
9500
9737
  deleteKnowledgeBase: { confirm: "This deletes the knowledge base and every document in it." },
9501
9738
  deleteKnowledgeDocument: {
9502
- pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
9739
+ pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9503
9740
  confirm: "This deletes the document and its embeddings."
9504
9741
  },
9505
9742
  deleteFile: { confirm: "This archives the file." },
@@ -9529,7 +9766,10 @@ var CLI_CONTRACT = {
9529
9766
  workflowIds: { name: "workflow", list: true },
9530
9767
  folderPaths: { ...FOLDER_PATH_FLAG, list: true },
9531
9768
  triggers: { name: "trigger", list: true },
9532
- details: { describe: "Response detail level" },
9769
+ details: {
9770
+ requestDefault: "full",
9771
+ describe: "Response detail level; full is requested by default to name each run’s workflow"
9772
+ },
9533
9773
  includeTraceSpans: {
9534
9774
  boolean: true,
9535
9775
  describe: "Include trace spans in JSON or YAML output (implies full detail)"
@@ -9582,7 +9822,7 @@ var CLI_CONTRACT = {
9582
9822
  },
9583
9823
  itemsPath: "results",
9584
9824
  columns: [
9585
- { header: "score", path: "similarity" },
9825
+ { header: "score", path: "similarity", format: "score" },
9586
9826
  { header: "document", path: "documentName" },
9587
9827
  { header: "chunk", path: "chunkIndex" },
9588
9828
  { header: "content" }
@@ -9656,7 +9896,7 @@ var CLI_CONTRACT = {
9656
9896
  columns: [
9657
9897
  { header: "id" },
9658
9898
  { header: "name" },
9659
- { header: "folder", path: "folderPath" },
9899
+ FOLDER_COLUMN,
9660
9900
  { header: "rows", path: "rowCount" },
9661
9901
  { header: "updated", path: "updatedAt", format: "timestamp" }
9662
9902
  ]
@@ -9666,7 +9906,7 @@ var CLI_CONTRACT = {
9666
9906
  columns: [
9667
9907
  { header: "id" },
9668
9908
  { header: "name" },
9669
- { header: "folder", path: "folderPath" },
9909
+ FOLDER_COLUMN,
9670
9910
  { header: "deployed", path: "isDeployed", format: "bool" },
9671
9911
  { header: "runs", path: "runCount" },
9672
9912
  { header: "last run", path: "lastRunAt", format: "timestamp" }
@@ -9677,7 +9917,7 @@ var CLI_CONTRACT = {
9677
9917
  columns: [
9678
9918
  { header: "id" },
9679
9919
  { header: "name" },
9680
- { header: "folder", path: "folderPath" },
9920
+ FOLDER_COLUMN,
9681
9921
  { header: "size", format: "bytes" },
9682
9922
  { header: "type" },
9683
9923
  { header: "uploaded by", path: "uploadedByEmail" },
@@ -9690,15 +9930,17 @@ var CLI_CONTRACT = {
9690
9930
  columns: [
9691
9931
  { header: "id" },
9692
9932
  { header: "name" },
9693
- { header: "folder", path: "folderPath" },
9933
+ FOLDER_COLUMN,
9694
9934
  { header: "docs", path: "docCount" },
9695
9935
  { header: "tokens", path: "tokenCount" },
9696
9936
  { header: "model", path: "embeddingModel" }
9697
9937
  ]
9698
9938
  },
9699
- getKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS },
9939
+ getKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
9940
+ updateKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
9941
+ listKnowledgeTags: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
9700
9942
  listKnowledgeDocuments: {
9701
- pathArgumentNames: KNOWLEDGE_DOCUMENT_PATH_ARGUMENTS,
9943
+ pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
9702
9944
  columns: [
9703
9945
  { header: "id" },
9704
9946
  { header: "filename" },
@@ -9742,12 +9984,24 @@ var CLI_CONTRACT = {
9742
9984
  { header: "updated", path: "updatedAt", format: "timestamp" }
9743
9985
  ]
9744
9986
  },
9987
+ listCredentialProviders: {
9988
+ columns: [
9989
+ { header: "type" },
9990
+ { header: "service", path: "serviceId" },
9991
+ { header: "provider", path: "providerId" },
9992
+ { header: "name" },
9993
+ { header: "family", path: "providerFamily" },
9994
+ { header: "available", format: "bool" },
9995
+ { header: "description" }
9996
+ ]
9997
+ },
9745
9998
  listSecrets: {
9746
9999
  columns: [
9747
10000
  { header: "name" },
9748
10001
  { header: "scope" },
9749
10002
  { header: "role" },
9750
- { header: "updated", path: "updatedAt", format: "timestamp" }
10003
+ { header: "updated", path: "updatedAt", format: "timestamp" },
10004
+ { header: "description" }
9751
10005
  ]
9752
10006
  },
9753
10007
  getWorkspace: {
@@ -9813,7 +10067,7 @@ var CLI_CONTRACT = {
9813
10067
  { header: "name" },
9814
10068
  { header: "size", format: "bytes" },
9815
10069
  { header: "type" },
9816
- { header: "folder", path: "folderPath" },
10070
+ FOLDER_COLUMN,
9817
10071
  { header: "uploaded by", path: "uploadedByEmail" },
9818
10072
  { header: "uploaded", path: "uploadedAt", format: "timestamp" },
9819
10073
  { header: "updated", path: "updatedAt", format: "timestamp" },
@@ -9840,6 +10094,11 @@ var CLI_CONTRACT = {
9840
10094
  command: "files rename",
9841
10095
  describe: "Rename a file"
9842
10096
  },
10097
+ restoreFile: {
10098
+ command: "files restore",
10099
+ renamedFrom: ["files restore create"],
10100
+ describe: "Restore an archived file"
10101
+ },
9843
10102
  updateFileContent: {
9844
10103
  command: "files set-content",
9845
10104
  describe: "Replace a file’s contents",
@@ -9992,13 +10251,26 @@ var CLI_CONTRACT = {
9992
10251
  command: "tables rows find",
9993
10252
  describe: "Find rows matching a predicate",
9994
10253
  flags: {
9995
- q: { describe: "Value to find" },
10254
+ q: { name: "query", renamedFrom: ["q"], describe: "Value to find" },
9996
10255
  predicate: { name: "filter", json: true, describe: TABLE_FILTER_HELP },
9997
10256
  sort: { json: true, describe: TABLE_SORT_HELP }
9998
10257
  },
9999
10258
  itemsPath: "matches",
10000
10259
  columns: [{ header: "ordinal" }, { header: "row", path: "rowId" }, { header: "column" }]
10001
10260
  },
10261
+ queryRowsCount: {
10262
+ command: "tables rows count",
10263
+ renamedFrom: ["tables count create"],
10264
+ describe: "Count rows matching a filter",
10265
+ flags: {
10266
+ predicate: {
10267
+ name: "filter",
10268
+ renamedFrom: ["predicate"],
10269
+ json: true,
10270
+ describe: TABLE_FILTER_HELP
10271
+ }
10272
+ }
10273
+ },
10002
10274
  runTableColumn: {
10003
10275
  command: "tables columns run",
10004
10276
  describe: "Run a column’s workflow",
@@ -10216,7 +10488,7 @@ function readArgumentSource(raw, flagName) {
10216
10488
  }
10217
10489
  }
10218
10490
  try {
10219
- return { text: readFileSync2(path, "utf8"), from: ` (read from ${path})` };
10491
+ return { text: readFileSync3(path, "utf8"), from: ` (read from ${path})` };
10220
10492
  } catch (error) {
10221
10493
  throw new SimApiError(`--${flagName} cannot read ${path}: ${error.message}`, 0);
10222
10494
  }
@@ -10251,6 +10523,26 @@ function readListValues(raw, flagName) {
10251
10523
  return trimmed;
10252
10524
  });
10253
10525
  }
10526
+ var PERCENT_ESCAPE = /%[0-9A-Fa-f]{2}/;
10527
+ var SUB_DELIMITERS = /[!'()*]/g;
10528
+ function encodeFolderPathSegment(name) {
10529
+ if (name === ".")
10530
+ return "%2E";
10531
+ if (name === "..")
10532
+ return "%2E%2E";
10533
+ return encodeURIComponent(name).replace(SUB_DELIMITERS, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
10534
+ }
10535
+ function encodeFolderPath(value) {
10536
+ return value.split("/").map((segment) => {
10537
+ if (!PERCENT_ESCAPE.test(segment))
10538
+ return encodeFolderPathSegment(segment);
10539
+ try {
10540
+ return encodeFolderPathSegment(decodeURIComponent(segment));
10541
+ } catch {
10542
+ return encodeFolderPathSegment(segment);
10543
+ }
10544
+ }).join("/");
10545
+ }
10254
10546
  function pathHint(raw) {
10255
10547
  if (raw.startsWith("@") || /^\s*[[{"\-\d]|^\s*(true|false|null)/.test(raw))
10256
10548
  return "";
@@ -10260,7 +10552,7 @@ function coerce(raw, field, flag, flagName) {
10260
10552
  if (raw === undefined)
10261
10553
  return;
10262
10554
  if (flag.list) {
10263
- const values = readListValues(raw, flagName);
10555
+ const values = readListValues(raw, flagName).map((value) => flag.folderPath ? encodeFolderPath(value) : value);
10264
10556
  return field.kind === "string" ? values.join(",") : values;
10265
10557
  }
10266
10558
  if (takesJson(field, flag)) {
@@ -10285,6 +10577,8 @@ function coerce(raw, field, flag, flagName) {
10285
10577
  if (choices && !choices.includes(String(raw))) {
10286
10578
  throw new SimApiError(`--${flagName} must be one of: ${choices.join(", ")}`, 0);
10287
10579
  }
10580
+ if (flag.folderPath && typeof raw === "string")
10581
+ return encodeFolderPath(raw);
10288
10582
  return raw;
10289
10583
  }
10290
10584
  function asQueryValue(value) {
@@ -10325,7 +10619,8 @@ function buildRequest(operation, positional, flags, workspaceId) {
10325
10619
  continue;
10326
10620
  const flagName = flagNameFor(operation, field);
10327
10621
  const omitProfileWorkspace = commandSpec.allWorkspaces && flags.allWorkspaces === true;
10328
- const raw = field === PROFILE_INJECTED_FIELD ? omitProfileWorkspace ? undefined : workspaceId : flags[camel(flagName)];
10622
+ const provided = field === PROFILE_INJECTED_FIELD ? omitProfileWorkspace ? undefined : workspaceId : flags[camel(flagName)];
10623
+ const raw = provided ?? flag.requestDefault;
10329
10624
  const value = coerce(raw ?? undefined, descriptor, flag, flagName);
10330
10625
  if (value === undefined) {
10331
10626
  if (descriptor.required) {
@@ -10498,6 +10793,15 @@ function countTraceSpans(value) {
10498
10793
  function at(row, path) {
10499
10794
  return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
10500
10795
  }
10796
+ function decodeFolderPath(value) {
10797
+ return value.split("/").map((segment) => {
10798
+ try {
10799
+ return decodeURIComponent(segment);
10800
+ } catch {
10801
+ return segment;
10802
+ }
10803
+ }).join("/");
10804
+ }
10501
10805
  function renderCell(value, format, options = {}) {
10502
10806
  switch (format) {
10503
10807
  case "timestamp":
@@ -10510,8 +10814,12 @@ function renderCell(value, format, options = {}) {
10510
10814
  return bool2(value);
10511
10815
  case "cost":
10512
10816
  return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
10817
+ case "score":
10818
+ return typeof value === "number" ? value.toFixed(4) : text(null);
10513
10819
  case "count":
10514
10820
  return Array.isArray(value) ? String(value.length) : text(null);
10821
+ case "folder-path":
10822
+ return typeof value === "string" ? text(decodeFolderPath(value)) : text(value);
10515
10823
  case "trace-count": {
10516
10824
  const count = countTraceSpans(value);
10517
10825
  return `${count} ${count === 1 ? "span" : "spans"}${options.expandedTrace ? "" : " (use --trace)"}`;
@@ -10522,10 +10830,42 @@ function renderCell(value, format, options = {}) {
10522
10830
  return sanitize(typeof value === "object" ? JSON.stringify(value) : String(value));
10523
10831
  }
10524
10832
  }
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;
10833
+ var TIMESTAMP_KEY = /(?:At|Date)$/;
10834
+ var DURATION_KEY = /Ms$|^duration/;
10835
+ var BYTES_KEY = /^size$|(?:Size|Bytes)$/;
10836
+ var BOOL_KEY = /^(?:is|has)[A-Z]/;
10837
+ var RATIO_KEY = /^(?:similarity|score)$|(?:Similarity|Score)$/;
10838
+ var FOLDER_PATH_KEY = /^(?:path|parentPath|folderPath)$/;
10839
+ var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/;
10840
+ var RATIO_PRECISION = 4;
10841
+ function inferFormat(key, value) {
10842
+ if (typeof value === "boolean")
10843
+ return BOOL_KEY.test(key) ? "bool" : null;
10844
+ if (typeof value === "string") {
10845
+ if (FOLDER_PATH_KEY.test(key))
10846
+ return "folder-path";
10847
+ return TIMESTAMP_KEY.test(key) && ISO_TIMESTAMP.test(value) && !Number.isNaN(Date.parse(value)) ? "timestamp" : null;
10848
+ }
10849
+ if (typeof value !== "number" || !Number.isFinite(value))
10850
+ return null;
10851
+ if (DURATION_KEY.test(key))
10852
+ return "duration";
10853
+ if (BYTES_KEY.test(key))
10854
+ return "bytes";
10855
+ return null;
10856
+ }
10857
+ function inferredCell(key, value) {
10858
+ if (typeof value === "number" && Number.isFinite(value) && RATIO_KEY.test(key)) {
10859
+ return value.toFixed(RATIO_PRECISION);
10860
+ }
10861
+ return renderCell(value, inferFormat(key, value) ?? "auto");
10862
+ }
10863
+ function humanizeKey(key) {
10864
+ 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();
10865
+ }
10866
+ function inferHeader(key, format) {
10867
+ const trimmed = format === "duration" || format === "bytes" ? key.replace(/(?:Ms|Bytes)$/, "") : format === "bool" ? key.replace(/^is(?=[A-Z])/, "") : key;
10868
+ return humanizeKey(trimmed || key);
10529
10869
  }
10530
10870
  function columnsFrom(specs) {
10531
10871
  return specs.map((spec) => ({
@@ -10534,9 +10874,9 @@ function columnsFrom(specs) {
10534
10874
  }));
10535
10875
  }
10536
10876
  function fieldsFrom(data, specs, options = {}) {
10537
- return specs.flatMap((spec) => {
10877
+ return specs.map((spec) => {
10538
10878
  const value = at(data, spec.path ?? spec.header);
10539
- return value === undefined ? [] : [[spec.header, renderCell(value, spec.format, options)]];
10879
+ return [spec.header, value === undefined ? text(null) : renderCell(value, spec.format, options)];
10540
10880
  });
10541
10881
  }
10542
10882
  function inferColumns(rows, expand) {
@@ -10551,7 +10891,7 @@ function inferColumns(rows, expand) {
10551
10891
  if (value !== null && typeof value === "object")
10552
10892
  continue;
10553
10893
  seen.add(key);
10554
- paths.push({ path: key, header: key });
10894
+ paths.push({ path: key, key, header: inferHeader(key, inferFormat(key, value)), owned: true });
10555
10895
  }
10556
10896
  }
10557
10897
  if (expand) {
@@ -10564,13 +10904,18 @@ function inferColumns(rows, expand) {
10564
10904
  if (nested.has(key))
10565
10905
  continue;
10566
10906
  nested.add(key);
10567
- paths.push({ path: `${expand}.${key}`, header: seen.has(key) ? `${expand}.${key}` : key });
10907
+ paths.push({
10908
+ path: `${expand}.${key}`,
10909
+ key,
10910
+ header: seen.has(key) ? `${expand}.${key}` : key,
10911
+ owned: false
10912
+ });
10568
10913
  }
10569
10914
  }
10570
10915
  }
10571
- return paths.map(({ path, header }) => ({
10916
+ return paths.map(({ path, key, header, owned }) => ({
10572
10917
  header: sanitize(header),
10573
- value: (row) => renderCell(at(row, path), "auto")
10918
+ value: (row) => owned ? inferredCell(key, at(row, path)) : renderCell(at(row, path), "auto")
10574
10919
  }));
10575
10920
  }
10576
10921
  function unwrapResource(data) {
@@ -10603,7 +10948,10 @@ function renderResult(operation, format, raw, spec, options = {}) {
10603
10948
  printList(format, data, spec.columns ? columnsFrom(spec.columns) : inferColumns(data, spec.expand));
10604
10949
  return;
10605
10950
  }
10606
- const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [key, recordCell(value)]) : [];
10951
+ const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [
10952
+ inferHeader(key, inferFormat(key, value)),
10953
+ inferredCell(key, value)
10954
+ ]) : [];
10607
10955
  printRecord(format, fields, data);
10608
10956
  if (spec.expandedTrace && options.expandedTrace) {
10609
10957
  const traceSpans = at(data, "traceSpans");
@@ -11001,7 +11349,7 @@ async function finishUploadSession(client, workspaceId, session, path) {
11001
11349
 
11002
11350
  // src/commands/protocol/files-upload.ts
11003
11351
  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) => {
11352
+ 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
11353
  const { client, profile } = clientFrom(command);
11006
11354
  const workspaceId = client.requireWorkspace();
11007
11355
  const { name, size } = await localFile(path, options.name);
@@ -11012,7 +11360,7 @@ function attachFileUpload(files) {
11012
11360
  name,
11013
11361
  contentType: contentTypeFor(name),
11014
11362
  size,
11015
- ...options.folder !== undefined ? { folderPath: options.folder } : {}
11363
+ ...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
11016
11364
  }
11017
11365
  });
11018
11366
  const { session, uploadToken, transfer } = created.data;
@@ -11114,15 +11462,22 @@ function addFieldOption(command, operation, field, descriptor) {
11114
11462
  const placeholder = takesList ? "<value...>" : wantsJson ? "<json|@file>" : "<value>";
11115
11463
  const choices = flag.choices ?? descriptor.values;
11116
11464
  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)" : ""}`;
11465
+ const renamedFrom = flag.renamedFrom ?? [];
11117
11466
  const option = new Option(`${short}--${name} ${placeholder}`, describe);
11118
11467
  if (choices && !takesList)
11119
11468
  option.choices([...choices]);
11120
11469
  if (descriptor.default !== undefined && field !== "limit") {
11121
11470
  option.default(undefined, String(descriptor.default));
11122
11471
  }
11123
- if (descriptor.required)
11472
+ if (descriptor.required && renamedFrom.length === 0)
11124
11473
  option.makeOptionMandatory();
11125
11474
  command.addOption(option);
11475
+ for (const previous of renamedFrom) {
11476
+ const retired = new Option(`--${previous} ${placeholder}`).hideHelp();
11477
+ if (choices && !takesList)
11478
+ retired.choices([...choices]);
11479
+ command.addOption(retired);
11480
+ }
11126
11481
  }
11127
11482
  function addOperationOptions(command, operation, commandSpec, operationSpec) {
11128
11483
  for (const param of operationSpec.pathParams) {
@@ -11158,7 +11513,7 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
11158
11513
  }
11159
11514
  }
11160
11515
  if (commandSpec.confirm) {
11161
- command.option("-y, --yes", "Skip the confirmation");
11516
+ command.option("-y, --yes", "Confirm this destructive operation (required)");
11162
11517
  }
11163
11518
  }
11164
11519
 
@@ -11166,8 +11521,11 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
11166
11521
  var COLUMNS = [
11167
11522
  { header: "kind", value: (entry) => text(entry.kind) },
11168
11523
  { header: "name", value: (entry) => text(entry.name) },
11169
- { header: "ref", value: (entry) => text(entry.ref) },
11170
- { header: "folder", value: (entry) => text(entry.folderPath) },
11524
+ {
11525
+ header: "ref",
11526
+ value: (entry) => text(entry.kind === "folder" ? decodeFolderPath(entry.ref) : entry.ref)
11527
+ },
11528
+ { header: "folder", value: (entry) => text(decodeFolderPath(entry.folderPath)) },
11171
11529
  { header: "updated", value: (entry) => timestamp2(entry.updatedAt) }
11172
11530
  ];
11173
11531
  function operationPath(operation) {
@@ -11218,7 +11576,7 @@ function attachResourceDirectoryCommands(group, config) {
11218
11576
  throw new SimApiError("--limit must be a non-negative integer", 0);
11219
11577
  }
11220
11578
  const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
11221
- const folderPath = path ?? "/";
11579
+ const folderPath = encodeFolderPath(path ?? "/");
11222
11580
  const { client, profile } = clientFrom(command);
11223
11581
  const workspaceId = client.requireWorkspace();
11224
11582
  const [folders, resources] = await Promise.all([
@@ -11233,7 +11591,7 @@ function attachResourceDirectoryCommands(group, config) {
11233
11591
  const operation = V2_OPERATIONS[config.createFolder];
11234
11592
  const result = await client.request(operation.path, {
11235
11593
  method: operation.method,
11236
- body: { workspaceId: client.requireWorkspace(), path }
11594
+ body: { workspaceId: client.requireWorkspace(), path: encodeFolderPath(path) }
11237
11595
  });
11238
11596
  renderResult(config.createFolder, profile.output, result.data ?? result, {});
11239
11597
  });
@@ -11287,7 +11645,7 @@ function validateTargetOptions(options) {
11287
11645
  return intoExisting;
11288
11646
  }
11289
11647
  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) => {
11648
+ 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
11649
  const { client, profile } = clientFrom(command);
11292
11650
  const workspaceId = client.requireWorkspace();
11293
11651
  if (Boolean(path) === Boolean(options.fileId)) {
@@ -11312,7 +11670,7 @@ function attachTableImport(tables) {
11312
11670
  target = {
11313
11671
  type: "new",
11314
11672
  name,
11315
- ...options.folder !== undefined ? { folderPath: options.folder } : {}
11673
+ ...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
11316
11674
  };
11317
11675
  }
11318
11676
  const started = await client.request(V2_OPERATIONS.createTableImport.path, {
@@ -11481,7 +11839,8 @@ var SECRET_RESULT = {
11481
11839
  { header: "name" },
11482
11840
  { header: "scope" },
11483
11841
  { header: "role" },
11484
- { header: "updated", path: "updatedAt", format: "timestamp" }
11842
+ { header: "updated", path: "updatedAt", format: "timestamp" },
11843
+ { header: "description" }
11485
11844
  ]
11486
11845
  };
11487
11846
  function validateSecretValue(value) {
@@ -11492,7 +11851,16 @@ function validateSecretValue(value) {
11492
11851
  }
11493
11852
  return value;
11494
11853
  }
11854
+ function validateDescriptionScope(description, scope) {
11855
+ if (description === undefined)
11856
+ return;
11857
+ if (scope === "personal") {
11858
+ throw new SimApiError("--description is only supported for a workspace secret.", 0);
11859
+ }
11860
+ return description;
11861
+ }
11495
11862
  async function setSecret(name, options, command) {
11863
+ const description = validateDescriptionScope(options.description, options.scope);
11496
11864
  const value = validateSecretValue(options.value ?? await promptSecret());
11497
11865
  const { client, profile } = clientFrom(command);
11498
11866
  const operation = V2_OPERATIONS.setSecret;
@@ -11501,7 +11869,8 @@ async function setSecret(name, options, command) {
11501
11869
  body: {
11502
11870
  workspaceId: client.requireWorkspace(),
11503
11871
  scope: options.scope,
11504
- value
11872
+ value,
11873
+ description
11505
11874
  }
11506
11875
  });
11507
11876
  renderResult("setSecret", profile.output, response.data, SECRET_RESULT);
@@ -11510,7 +11879,24 @@ function attachSecretCommands(program2) {
11510
11879
  const secrets = program2.commands.find((command) => command.name() === "secrets");
11511
11880
  if (!secrets)
11512
11881
  throw new Error("The generated secrets command group is missing");
11513
- 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));
11882
+ 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").option("--description <description>", "What the secret is for, shown to teammates; workspace scope only. Omit to leave an existing description unchanged").action((name, options, command) => setSecret(name, options, command));
11883
+ }
11884
+
11885
+ // src/runtime/renamed.ts
11886
+ var warned = new Set;
11887
+ function warn(kind, from, to) {
11888
+ const key = `${kind}:${from}`;
11889
+ if (warned.has(key))
11890
+ return;
11891
+ warned.add(key);
11892
+ process.stderr.write(`warning: ${kind} "${from}" has been renamed to "${to}". The old name still works.
11893
+ `);
11894
+ }
11895
+ function warnRenamedCommand(from, to) {
11896
+ warn("command", `sim ${from}`, `sim ${to}`);
11897
+ }
11898
+ function warnRenamedFlag(from, to) {
11899
+ warn("flag", `--${from}`, `--${to}`);
11514
11900
  }
11515
11901
 
11516
11902
  // src/runtime/execute.ts
@@ -11521,6 +11907,23 @@ function cursorSlot(operationSpec) {
11521
11907
  return "body";
11522
11908
  return null;
11523
11909
  }
11910
+ function foldRenamedFlags(operation, commandSpec, flags) {
11911
+ for (const [field, flag] of Object.entries(commandSpec.flags ?? {})) {
11912
+ if (!flag.renamedFrom?.length)
11913
+ continue;
11914
+ const current = flagNameFor(operation, field);
11915
+ for (const previous of flag.renamedFrom) {
11916
+ const supplied = flags[camel(previous)];
11917
+ if (supplied === undefined)
11918
+ continue;
11919
+ if (flags[camel(current)] !== undefined) {
11920
+ throw new SimApiError(`--${previous} is the former name of --${current}; pass one, not both`, 0);
11921
+ }
11922
+ warnRenamedFlag(previous, current);
11923
+ flags[camel(current)] = supplied;
11924
+ }
11925
+ }
11926
+ }
11524
11927
  async function executeOperation(operation, commandSpec, operationSpec, invocation) {
11525
11928
  const host = invocation[invocation.length - 1];
11526
11929
  const inheritedFlags = host.optsWithGlobals();
@@ -11535,6 +11938,7 @@ async function executeOperation(operation, commandSpec, operationSpec, invocatio
11535
11938
  for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
11536
11939
  requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
11537
11940
  }
11941
+ foldRenamedFlags(operation, commandSpec, requestFlags);
11538
11942
  if (commandSpec.confirm && !requestFlags.yes) {
11539
11943
  throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
11540
11944
  }
@@ -11555,16 +11959,23 @@ async function executeOperation(operation, commandSpec, operationSpec, invocatio
11555
11959
  const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
11556
11960
  const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
11557
11961
  const rows = [];
11962
+ const progress = pageProgress();
11558
11963
  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);
11964
+ try {
11965
+ do {
11966
+ const page = await client.request(request.path, {
11967
+ method: operationSpec.method,
11968
+ query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
11969
+ body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
11970
+ });
11971
+ rows.push(...page.data);
11972
+ cursor = page.nextCursor;
11973
+ if (cursor && rows.length < limit)
11974
+ progress.advance(rows.length);
11975
+ } while (cursor && rows.length < limit);
11976
+ } finally {
11977
+ progress.finish();
11978
+ }
11568
11979
  renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
11569
11980
  return;
11570
11981
  }
@@ -11693,6 +12104,19 @@ function configureOperation(command, operation, spec) {
11693
12104
  function buildLeaf(operation, spec, leafName) {
11694
12105
  return addMissingArgumentExample(configureOperation(new Command(leafName), operation, spec));
11695
12106
  }
12107
+ function addRenamedCommand(groups, operation, spec, from, to) {
12108
+ const segments = from.split(" ");
12109
+ const [groupName, ...rest] = segments;
12110
+ if (rest.length === 0)
12111
+ throw new Error(`${operation}.renamedFrom "${from}" must include a verb`);
12112
+ let parent = groupFor(groups, groupName);
12113
+ for (const segment of rest.slice(0, -1)) {
12114
+ parent = nestedGroup(parent, segment, { hidden: true });
12115
+ }
12116
+ const leaf = buildLeaf(operation, spec, rest[rest.length - 1]);
12117
+ leaf.hook("preAction", () => warnRenamedCommand(from, to));
12118
+ parent.addCommand(leaf, { hidden: true });
12119
+ }
11696
12120
  function groupFor(groups, name) {
11697
12121
  const existing = groups.get(name);
11698
12122
  if (existing)
@@ -11708,12 +12132,12 @@ function resourceLabel(name) {
11708
12132
  const label = name.endsWith("s") ? name.slice(0, -1) : name;
11709
12133
  return label.replaceAll("-", " ");
11710
12134
  }
11711
- function nestedGroup(parent, name) {
12135
+ function nestedGroup(parent, name, options = {}) {
11712
12136
  const existing = parent.commands.find((candidate) => candidate.name() === name);
11713
12137
  if (existing)
11714
12138
  return existing;
11715
12139
  const created = new Command(name).description(`Manage ${resourceLabel(parent.name())} ${name.replaceAll("-", " ")}`);
11716
- parent.addCommand(created);
12140
+ parent.addCommand(created, { hidden: options.hidden });
11717
12141
  return created;
11718
12142
  }
11719
12143
  function addLeafCommand(groups, operation, spec, segments) {
@@ -11742,6 +12166,7 @@ function variantCommandSpec(spec, variant) {
11742
12166
  }
11743
12167
  function buildGeneratedCommands() {
11744
12168
  const groups = new Map;
12169
+ const renamed = [];
11745
12170
  for (const operation of Object.keys(V2_OPERATIONS)) {
11746
12171
  const spec = CLI_CONTRACT[operation] ?? {};
11747
12172
  const operationSpec = V2_OPERATIONS[operation];
@@ -11764,6 +12189,12 @@ function buildGeneratedCommands() {
11764
12189
  for (const variant of spec.variants ?? []) {
11765
12190
  addLeafCommand(groups, operation, variantCommandSpec(spec, variant), variant.command.split(" "));
11766
12191
  }
12192
+ for (const from of spec.renamedFrom ?? []) {
12193
+ renamed.push({ operation, spec, from, to: segments.join(" ") });
12194
+ }
12195
+ }
12196
+ for (const { operation, spec, from, to } of renamed) {
12197
+ addRenamedCommand(groups, operation, spec, from, to);
11767
12198
  }
11768
12199
  return [...groups.values()].sort((a, b) => a.name().localeCompare(b.name()));
11769
12200
  }
@@ -11772,7 +12203,8 @@ function buildGeneratedCommands() {
11772
12203
  var PROGRAM_DESCRIPTION = "Talk to the Sim API from your terminal";
11773
12204
  var HELP_EPILOGUE = `
11774
12205
  Profiles work like the AWS CLI: settings live in ~/.sim/config, keys in
11775
- ~/.sim/credentials (0600). Select one with -P, --profile, or SIM_PROFILE.
12206
+ ~/.sim/credentials (0600), or under SIM_CONFIG_DIR when it is set. Select one
12207
+ with -P, --profile, or SIM_PROFILE.
11776
12208
 
11777
12209
  Examples:
11778
12210
  $ sim login Authorize the default profile
@@ -11786,18 +12218,11 @@ Examples:
11786
12218
  $ sim workflows import --workflow @wf.json
11787
12219
  $ sim whoami --profile dev
11788
12220
  `;
11789
- function readPackageVersion() {
11790
- const metadata = JSON.parse(readFileSync3(new URL("../package.json", import.meta.url), "utf8"));
11791
- if (typeof metadata !== "object" || metadata === null || !("version" in metadata) || typeof metadata.version !== "string") {
11792
- throw new Error("CLI package metadata is missing a valid version");
11793
- }
11794
- return metadata.version;
11795
- }
11796
12221
  function buildProgram(options = {}) {
11797
12222
  const program2 = new Command;
11798
12223
  program2.name("sim").description(PROGRAM_DESCRIPTION);
11799
12224
  if (options.version !== false)
11800
- program2.version(readPackageVersion());
12225
+ program2.version(CLI_VERSION);
11801
12226
  program2.option("-P, --profile <name>", "Profile to use (env: SIM_PROFILE)").option("--endpoint <url>", "Sim deployment to talk to (env: SIM_ENDPOINT)").option("-w, --workspace <id>", "Workspace to target (env: SIM_WORKSPACE)").addOption(new Option("--output <format>", "Output format for this command").choices([...OUTPUT_FORMATS]));
11802
12227
  program2.addCommand(loginCommand());
11803
12228
  program2.addCommand(logoutCommand());