sim 2.0.0 → 2.1.0-dev.24.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.
- package/README.md +18 -4
- package/dist/index.js +1651 -522
- package/package.json +2 -2
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
|
-
|
|
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,65 @@ 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
|
+
|
|
2542
|
+
// src/http/environment.ts
|
|
2543
|
+
var reported = new Set;
|
|
2544
|
+
function once(key, message) {
|
|
2545
|
+
if (reported.has(key))
|
|
2546
|
+
return;
|
|
2547
|
+
reported.add(key);
|
|
2548
|
+
process.stderr.write(`warning: ${message}
|
|
2549
|
+
`);
|
|
2550
|
+
}
|
|
2551
|
+
var PROXY_VARIABLES = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy"];
|
|
2552
|
+
var PROXY_SUPPORT = { 22: 21, 24: 5 };
|
|
2553
|
+
var FIRST_SUPPORTED_MAJOR = 24;
|
|
2554
|
+
function runtimeCanProxy(version) {
|
|
2555
|
+
const [major, minor] = version.replace(/^v/, "").split(".").map(Number);
|
|
2556
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor))
|
|
2557
|
+
return false;
|
|
2558
|
+
const firstSupportedMinor = PROXY_SUPPORT[major];
|
|
2559
|
+
if (firstSupportedMinor !== undefined)
|
|
2560
|
+
return minor >= firstSupportedMinor;
|
|
2561
|
+
return major > FIRST_SUPPORTED_MAJOR;
|
|
2562
|
+
}
|
|
2563
|
+
function warnIfProxyIgnored(env2 = process.env, version = process.version) {
|
|
2564
|
+
const variable = PROXY_VARIABLES.find((name) => env2[name]);
|
|
2565
|
+
if (!variable)
|
|
2566
|
+
return;
|
|
2567
|
+
if (env2.NODE_USE_ENV_PROXY && runtimeCanProxy(version))
|
|
2568
|
+
return;
|
|
2569
|
+
once("proxy", runtimeCanProxy(version) ? `${variable} is set but Node only uses it when NODE_USE_ENV_PROXY=1. Re-run with NODE_USE_ENV_PROXY=1 to route through the proxy.` : `${variable} is set but Node ${version} cannot use it. Upgrade to Node 22.21 or 24.5 and set NODE_USE_ENV_PROXY=1 to route through the proxy.`);
|
|
2570
|
+
}
|
|
2571
|
+
var LOOPBACK = new Set(["localhost", "127.0.0.1", "[::1]", "::1", "0.0.0.0"]);
|
|
2572
|
+
function isLoopback(hostname) {
|
|
2573
|
+
return LOOPBACK.has(hostname) || hostname.endsWith(".localhost");
|
|
2574
|
+
}
|
|
2575
|
+
function warnIfKeyOverCleartext(endpoint, hasApiKey) {
|
|
2576
|
+
if (!hasApiKey)
|
|
2577
|
+
return;
|
|
2578
|
+
let url;
|
|
2579
|
+
try {
|
|
2580
|
+
url = new URL(endpoint);
|
|
2581
|
+
} catch {
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
if (url.protocol !== "http:" || isLoopback(url.hostname))
|
|
2585
|
+
return;
|
|
2586
|
+
once("cleartext", `sending your API key to ${url.host} over http. Anything on the path can read it — use https unless this network is trusted.`);
|
|
2587
|
+
}
|
|
2588
|
+
|
|
2520
2589
|
// src/http/client.ts
|
|
2521
2590
|
class SimApiError extends Error {
|
|
2522
2591
|
status;
|
|
@@ -2539,13 +2608,28 @@ function buildUrl(endpoint, path, query) {
|
|
|
2539
2608
|
}
|
|
2540
2609
|
return url.toString();
|
|
2541
2610
|
}
|
|
2542
|
-
|
|
2611
|
+
var REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
2612
|
+
var MARKUP_PREFIX = /^\s*<(?:!doctype|html|\?xml)/i;
|
|
2613
|
+
var KEY_SCOPE_REFUSALS = new Set([
|
|
2614
|
+
"WORKSPACE_KEY_OPERATION_NOT_PERMITTED",
|
|
2615
|
+
"PRINCIPAL_KIND_NOT_PERMITTED"
|
|
2616
|
+
]);
|
|
2617
|
+
function toNonJsonError(url, status, contentType, raw) {
|
|
2618
|
+
const type = contentType?.split(";")[0]?.trim().toLowerCase();
|
|
2619
|
+
const isMarkup = type === "text/html" || type === "application/xhtml+xml" || MARKUP_PREFIX.test(raw);
|
|
2620
|
+
const kind = isMarkup ? "HTML" : type && type !== "application/json" ? type : "a non-JSON response";
|
|
2621
|
+
const text = raw.trim();
|
|
2622
|
+
const keepSnippet = !isMarkup && text.length > 0 && text.length <= 200;
|
|
2623
|
+
return new SimApiError(`${url} returned ${kind}, not JSON (HTTP ${status}) — check your endpoint.${keepSnippet ? ` Response: ${truncate(text, 200)}` : ""}`, status);
|
|
2624
|
+
}
|
|
2625
|
+
function toApiError(url, status, contentType, raw) {
|
|
2543
2626
|
let parsed;
|
|
2544
2627
|
try {
|
|
2545
2628
|
parsed = JSON.parse(raw);
|
|
2546
2629
|
} catch {
|
|
2547
|
-
|
|
2548
|
-
|
|
2630
|
+
if (!raw.trim())
|
|
2631
|
+
return new SimApiError(`Request failed with status ${status}`, status);
|
|
2632
|
+
return toNonJsonError(url, status, contentType, raw);
|
|
2549
2633
|
}
|
|
2550
2634
|
const body = parsed;
|
|
2551
2635
|
if (body.error && typeof body.error === "object") {
|
|
@@ -2561,8 +2645,79 @@ function toApiError(status, raw) {
|
|
|
2561
2645
|
function truncate(value, max) {
|
|
2562
2646
|
return value.length <= max ? value : `${value.slice(0, max)}…`;
|
|
2563
2647
|
}
|
|
2648
|
+
function namesKeyScopeRefusal(error) {
|
|
2649
|
+
if (typeof error.code === "string" && KEY_SCOPE_REFUSALS.has(error.code))
|
|
2650
|
+
return true;
|
|
2651
|
+
const details = error.details;
|
|
2652
|
+
if (!details || typeof details !== "object")
|
|
2653
|
+
return false;
|
|
2654
|
+
const code = details.code;
|
|
2655
|
+
return typeof code === "string" && KEY_SCOPE_REFUSALS.has(code);
|
|
2656
|
+
}
|
|
2657
|
+
function isStrictPrefix(path, other) {
|
|
2658
|
+
if (path.length >= other.length)
|
|
2659
|
+
return false;
|
|
2660
|
+
return path.every((segment, index) => segment === other[index]);
|
|
2661
|
+
}
|
|
2662
|
+
function rejectsAKeyThatValidated(issue, other) {
|
|
2663
|
+
if (!issue.unrecognizedKeys || !isStrictPrefix(issue.path, other.path))
|
|
2664
|
+
return false;
|
|
2665
|
+
return issue.unrecognizedKeys.includes(other.path[issue.path.length]);
|
|
2666
|
+
}
|
|
2667
|
+
function dropUnionBranchNoise(issues) {
|
|
2668
|
+
if (issues.length < 2)
|
|
2669
|
+
return issues;
|
|
2670
|
+
const kept = issues.filter((issue) => !issues.some((other) => rejectsAKeyThatValidated(issue, other)));
|
|
2671
|
+
return kept.length > 0 ? kept : issues;
|
|
2672
|
+
}
|
|
2673
|
+
var DEFAULT_TIMEOUT_SECONDS = 3600;
|
|
2674
|
+
var MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
2675
|
+
var RAISE_TIMEOUT_HINT = "Raise SIM_TIMEOUT_SECONDS, or set it to 0 to wait indefinitely.";
|
|
2676
|
+
function isRequestTimeout(error) {
|
|
2677
|
+
return error instanceof DOMException && error.name === "TimeoutError";
|
|
2678
|
+
}
|
|
2679
|
+
function resolveTimeoutMs(env2 = process.env) {
|
|
2680
|
+
const raw = env2.SIM_TIMEOUT_SECONDS;
|
|
2681
|
+
if (raw === undefined || raw.trim() === "")
|
|
2682
|
+
return DEFAULT_TIMEOUT_SECONDS * 1000;
|
|
2683
|
+
const seconds = Number(raw);
|
|
2684
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
2685
|
+
throw new SimApiError(`Invalid SIM_TIMEOUT_SECONDS "${raw}". Use a non-negative number of seconds, or 0 to disable.`, 0);
|
|
2686
|
+
}
|
|
2687
|
+
const ms = seconds === 0 ? 0 : Math.max(1, Math.round(seconds * 1000));
|
|
2688
|
+
if (ms > MAX_TIMEOUT_MS) {
|
|
2689
|
+
throw new SimApiError(`SIM_TIMEOUT_SECONDS "${raw}" is longer than Node can wait (${Math.floor(MAX_TIMEOUT_MS / 1000)}s). Use 0 to wait indefinitely.`, 0);
|
|
2690
|
+
}
|
|
2691
|
+
return ms;
|
|
2692
|
+
}
|
|
2693
|
+
function combineSignals(caller, timeout) {
|
|
2694
|
+
if (!caller)
|
|
2695
|
+
return timeout;
|
|
2696
|
+
if (!timeout)
|
|
2697
|
+
return caller;
|
|
2698
|
+
if (typeof AbortSignal.any === "function")
|
|
2699
|
+
return AbortSignal.any([caller, timeout]);
|
|
2700
|
+
const controller = new AbortController;
|
|
2701
|
+
for (const signal of [caller, timeout]) {
|
|
2702
|
+
if (signal.aborted) {
|
|
2703
|
+
controller.abort(signal.reason);
|
|
2704
|
+
break;
|
|
2705
|
+
}
|
|
2706
|
+
signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
|
|
2707
|
+
}
|
|
2708
|
+
return controller.signal;
|
|
2709
|
+
}
|
|
2710
|
+
function debugEnabled(env2 = process.env) {
|
|
2711
|
+
const raw = env2.SIM_DEBUG;
|
|
2712
|
+
return raw !== undefined && raw !== "" && raw !== "0" && raw.toLowerCase() !== "false";
|
|
2713
|
+
}
|
|
2714
|
+
function traceRequest(method, url, status, startedAt) {
|
|
2715
|
+
process.stderr.write(`${source_default.dim(`[sim] ${method} ${url} → ${status} ${Math.round(performance.now() - startedAt)}ms`)}
|
|
2716
|
+
`);
|
|
2717
|
+
}
|
|
2564
2718
|
function formatApiErrorDetails(details) {
|
|
2565
|
-
const issues =
|
|
2719
|
+
const issues = [];
|
|
2720
|
+
const seen = new Set;
|
|
2566
2721
|
const visit = (value, parentPath = []) => {
|
|
2567
2722
|
if (Array.isArray(value)) {
|
|
2568
2723
|
value.forEach((item) => visit(item, parentPath));
|
|
@@ -2580,15 +2735,27 @@ function formatApiErrorDetails(details) {
|
|
|
2580
2735
|
}
|
|
2581
2736
|
if (typeof issue.message !== "string" || issue.message === "Invalid input")
|
|
2582
2737
|
return;
|
|
2583
|
-
|
|
2738
|
+
const line = `${path.join(".")}: ${issue.message}`;
|
|
2739
|
+
if (seen.has(line))
|
|
2740
|
+
return;
|
|
2741
|
+
seen.add(line);
|
|
2742
|
+
issues.push({
|
|
2743
|
+
path,
|
|
2744
|
+
message: issue.message,
|
|
2745
|
+
unrecognizedKeys: issue.code === "unrecognized_keys" && Array.isArray(issue.keys) ? issue.keys.map(String) : null
|
|
2746
|
+
});
|
|
2584
2747
|
};
|
|
2585
2748
|
visit(details);
|
|
2586
|
-
if (issues.
|
|
2749
|
+
if (issues.length === 0)
|
|
2587
2750
|
return [` details: ${truncate(JSON.stringify(details), 1000)}`];
|
|
2588
|
-
const
|
|
2589
|
-
const
|
|
2590
|
-
|
|
2591
|
-
|
|
2751
|
+
const kept = dropUnionBranchNoise(issues);
|
|
2752
|
+
const visible = kept.slice(0, 8);
|
|
2753
|
+
const lines = [
|
|
2754
|
+
" details:",
|
|
2755
|
+
...visible.map((issue) => ` ${issue.path.length > 0 ? issue.path.join(".") : "request"}: ${issue.message}`)
|
|
2756
|
+
];
|
|
2757
|
+
if (kept.length > visible.length)
|
|
2758
|
+
lines.push(` … ${kept.length - visible.length} more issues`);
|
|
2592
2759
|
return lines;
|
|
2593
2760
|
}
|
|
2594
2761
|
|
|
@@ -2614,65 +2781,140 @@ class SimClient {
|
|
|
2614
2781
|
return workspaceId;
|
|
2615
2782
|
}
|
|
2616
2783
|
async requestRaw(path, options = {}) {
|
|
2784
|
+
return (await this.send(path, options)).response;
|
|
2785
|
+
}
|
|
2786
|
+
async request(path, options = {}) {
|
|
2787
|
+
const { response, url } = await this.send(path, options);
|
|
2788
|
+
const raw = await response.text();
|
|
2789
|
+
if (!raw)
|
|
2790
|
+
return;
|
|
2791
|
+
try {
|
|
2792
|
+
return JSON.parse(raw);
|
|
2793
|
+
} catch {
|
|
2794
|
+
throw toNonJsonError(url, response.status, response.headers.get("content-type"), raw);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
async send(path, options) {
|
|
2617
2798
|
const apiKey = this.resolveApiKey(options.auth);
|
|
2618
2799
|
const url = buildUrl(this.profile.endpoint, path, options.query);
|
|
2619
2800
|
const hasBody = options.body !== undefined;
|
|
2801
|
+
const method = options.method ?? "GET";
|
|
2802
|
+
warnIfProxyIgnored();
|
|
2803
|
+
warnIfKeyOverCleartext(this.profile.endpoint, Boolean(apiKey));
|
|
2804
|
+
const timeoutMs = resolveTimeoutMs();
|
|
2805
|
+
const timeout = timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : undefined;
|
|
2806
|
+
const signal = combineSignals(options.signal, timeout);
|
|
2807
|
+
const trace = debugEnabled();
|
|
2808
|
+
const startedAt = performance.now();
|
|
2620
2809
|
let response;
|
|
2621
2810
|
try {
|
|
2622
2811
|
response = await fetch(url, {
|
|
2623
|
-
method
|
|
2812
|
+
method,
|
|
2624
2813
|
headers: {
|
|
2625
2814
|
...apiKey ? { "x-api-key": apiKey } : {},
|
|
2626
2815
|
accept: "application/json",
|
|
2816
|
+
"user-agent": USER_AGENT,
|
|
2627
2817
|
...hasBody ? { "content-type": "application/json" } : {},
|
|
2628
2818
|
...options.headers
|
|
2629
2819
|
},
|
|
2630
2820
|
body: hasBody ? JSON.stringify(options.body) : undefined,
|
|
2631
|
-
signal
|
|
2821
|
+
signal,
|
|
2822
|
+
redirect: "manual"
|
|
2632
2823
|
});
|
|
2633
2824
|
} catch (cause) {
|
|
2825
|
+
if (trace)
|
|
2826
|
+
traceRequest(method, url, "failed", startedAt);
|
|
2634
2827
|
if (options.signal?.aborted) {
|
|
2635
2828
|
throw new SimApiError("Request cancelled.", 0);
|
|
2636
2829
|
}
|
|
2830
|
+
if (timeout?.aborted) {
|
|
2831
|
+
throw new SimApiError(`${url} did not answer within ${timeoutMs / 1000}s. ${RAISE_TIMEOUT_HINT}`, 0);
|
|
2832
|
+
}
|
|
2637
2833
|
throw new SimApiError(`Could not reach ${this.profile.endpoint}: ${cause.message}`, 0);
|
|
2638
2834
|
}
|
|
2835
|
+
if (trace)
|
|
2836
|
+
traceRequest(method, url, response.status, startedAt);
|
|
2837
|
+
if (REDIRECT_STATUSES.has(response.status))
|
|
2838
|
+
throw this.toRedirectError(url, path, response);
|
|
2639
2839
|
if (!response.ok) {
|
|
2640
2840
|
const raw = await response.text();
|
|
2641
|
-
const error = toApiError(response.status, raw);
|
|
2841
|
+
const error = toApiError(url, response.status, response.headers.get("content-type"), raw);
|
|
2642
2842
|
if (response.status === 401) {
|
|
2643
2843
|
error.message = `${error.message} — run: sim login --profile ${this.profile.name}`;
|
|
2644
2844
|
}
|
|
2845
|
+
if (namesKeyScopeRefusal(error)) {
|
|
2846
|
+
error.message = `${error.message} — this operation needs a personal API key: sim login --profile ${this.profile.name}`;
|
|
2847
|
+
}
|
|
2645
2848
|
throw error;
|
|
2646
2849
|
}
|
|
2647
|
-
return response;
|
|
2850
|
+
return { response, url };
|
|
2648
2851
|
}
|
|
2649
|
-
|
|
2650
|
-
const
|
|
2651
|
-
|
|
2652
|
-
if (
|
|
2653
|
-
|
|
2654
|
-
|
|
2852
|
+
toRedirectError(url, path, response) {
|
|
2853
|
+
const location = response.headers.get("location")?.trim();
|
|
2854
|
+
let target = null;
|
|
2855
|
+
if (location) {
|
|
2856
|
+
try {
|
|
2857
|
+
target = new URL(location, url);
|
|
2858
|
+
} catch {
|
|
2859
|
+
target = null;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
if (!target) {
|
|
2863
|
+
return new SimApiError(`${url} answered HTTP ${response.status} with no usable redirect target. Check the endpoint for profile "${this.profile.name}".`, response.status);
|
|
2864
|
+
}
|
|
2865
|
+
const suggested = redirectEndpoint(this.profile.endpoint, path, target);
|
|
2866
|
+
if (!suggested) {
|
|
2867
|
+
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);
|
|
2868
|
+
}
|
|
2869
|
+
return new SimApiError(`Endpoint redirected to ${suggested}. Run: sim configure --profile ${this.profile.name} --set-endpoint ${suggested}`, response.status);
|
|
2655
2870
|
}
|
|
2656
2871
|
}
|
|
2872
|
+
function redirectEndpoint(endpoint, requestPath, target) {
|
|
2873
|
+
const prefix = target.pathname.endsWith(requestPath) ? target.pathname.slice(0, target.pathname.length - requestPath.length) : "";
|
|
2874
|
+
const suggested = `${target.origin}${prefix}`.replace(/\/+$/, "");
|
|
2875
|
+
return suggested === endpoint.replace(/\/+$/, "") ? null : suggested;
|
|
2876
|
+
}
|
|
2877
|
+
function pageProgress() {
|
|
2878
|
+
let reported2 = false;
|
|
2879
|
+
return {
|
|
2880
|
+
advance: (fetched) => {
|
|
2881
|
+
if (!process.stderr.isTTY)
|
|
2882
|
+
return;
|
|
2883
|
+
reported2 = true;
|
|
2884
|
+
process.stderr.write(`\r${source_default.dim(`fetched ${fetched}…`)}\x1B[K`);
|
|
2885
|
+
},
|
|
2886
|
+
finish: () => {
|
|
2887
|
+
if (reported2)
|
|
2888
|
+
process.stderr.write("\r\x1B[K");
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2657
2892
|
async function requestAllPages(client, path, options) {
|
|
2658
2893
|
const { query, pageSize, limit: requestedLimit, ...requestOptions } = options;
|
|
2659
2894
|
const limit = requestedLimit ?? Number.POSITIVE_INFINITY;
|
|
2660
2895
|
if (limit <= 0)
|
|
2661
2896
|
return [];
|
|
2662
2897
|
const items = [];
|
|
2898
|
+
const progress = pageProgress();
|
|
2663
2899
|
let cursor = null;
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2900
|
+
try {
|
|
2901
|
+
do {
|
|
2902
|
+
const page = await client.request(path, {
|
|
2903
|
+
...requestOptions,
|
|
2904
|
+
query: {
|
|
2905
|
+
...query,
|
|
2906
|
+
limit: Math.min(pageSize, limit - items.length),
|
|
2907
|
+
cursor
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
items.push(...page.data);
|
|
2911
|
+
cursor = page.nextCursor;
|
|
2912
|
+
if (cursor && items.length < limit)
|
|
2913
|
+
progress.advance(items.length);
|
|
2914
|
+
} while (cursor && items.length < limit);
|
|
2915
|
+
} finally {
|
|
2916
|
+
progress.finish();
|
|
2917
|
+
}
|
|
2676
2918
|
return items.slice(0, limit);
|
|
2677
2919
|
}
|
|
2678
2920
|
function resolvePath(template, params = {}) {
|
|
@@ -5894,7 +6136,7 @@ function duration(ms) {
|
|
|
5894
6136
|
if (ms === null || ms === undefined)
|
|
5895
6137
|
return EMPTY;
|
|
5896
6138
|
if (ms < 1000)
|
|
5897
|
-
return `${ms}ms`;
|
|
6139
|
+
return `${Math.round(ms)}ms`;
|
|
5898
6140
|
if (ms < 60000)
|
|
5899
6141
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
5900
6142
|
return `${Math.floor(ms / 60000)}m${Math.round(ms % 60000 / 1000)}s`;
|
|
@@ -5914,17 +6156,18 @@ function oneLine(value) {
|
|
|
5914
6156
|
return value.replace(/\s*[\r\n\t]+\s*/g, " ");
|
|
5915
6157
|
}
|
|
5916
6158
|
var MAX_CELL_WIDTH = 60;
|
|
5917
|
-
|
|
5918
|
-
|
|
6159
|
+
var MAX_RECORD_WIDTH = 160;
|
|
6160
|
+
function clamp(value, width) {
|
|
6161
|
+
if (visibleWidth(value) <= width || value !== value.replace(ANSI_PATTERN, "")) {
|
|
5919
6162
|
return value;
|
|
5920
6163
|
}
|
|
5921
|
-
return `${value.slice(0,
|
|
6164
|
+
return `${value.slice(0, width - 1)}…`;
|
|
5922
6165
|
}
|
|
5923
6166
|
function renderTable(rows, columns) {
|
|
5924
6167
|
if (rows.length === 0)
|
|
5925
6168
|
return source_default.dim("No results.");
|
|
5926
6169
|
const headers = columns.map((column) => sanitize(column.header));
|
|
5927
|
-
const cells = rows.map((row) => columns.map((column) =>
|
|
6170
|
+
const cells = rows.map((row) => columns.map((column) => clamp(oneLine(column.value(row)), MAX_CELL_WIDTH)));
|
|
5928
6171
|
const widths = columns.map((_column, index) => Math.max(visibleWidth(headers[index]), ...cells.map((line) => visibleWidth(line[index]))));
|
|
5929
6172
|
const header = headers.map((label, index) => source_default.dim(pad(label.toUpperCase(), widths[index]))).join(" ").trimEnd();
|
|
5930
6173
|
const body = cells.map((line) => line.map((cell, index) => pad(cell, widths[index])).join(" ").trimEnd());
|
|
@@ -5970,13 +6213,10 @@ function printRecord(format, fields, raw) {
|
|
|
5970
6213
|
}
|
|
5971
6214
|
const width = Math.max(...safeFields.map(([label]) => visibleWidth(label)));
|
|
5972
6215
|
for (const [label, value] of safeFields) {
|
|
5973
|
-
console.log(`${source_default.dim(pad(`${label}:`, width + 1))} ${oneLine(value)}`);
|
|
6216
|
+
console.log(`${source_default.dim(pad(`${label}:`, width + 1))} ${clamp(oneLine(value), MAX_RECORD_WIDTH)}`);
|
|
5974
6217
|
}
|
|
5975
6218
|
}
|
|
5976
6219
|
|
|
5977
|
-
// src/program.ts
|
|
5978
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
5979
|
-
|
|
5980
6220
|
// ../../node_modules/commander/esm.mjs
|
|
5981
6221
|
var import__ = __toESM(require_commander(), 1);
|
|
5982
6222
|
var {
|
|
@@ -6009,6 +6249,8 @@ function sleep(ms) {
|
|
|
6009
6249
|
var PAIRING_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
6010
6250
|
var POLL_INTERVAL_MS = 2000;
|
|
6011
6251
|
var POLL_TIMEOUT_MS = 15 * 60 * 1000;
|
|
6252
|
+
var APPROVAL_PATH = "/cli/auth";
|
|
6253
|
+
var POLL_PATH = "/api/cli/auth/poll";
|
|
6012
6254
|
var RETRYABLE_POLL_STATUSES = new Set([409, 429, 500, 502, 503, 504]);
|
|
6013
6255
|
function token() {
|
|
6014
6256
|
return randomBytes(32).toString("base64url");
|
|
@@ -6027,14 +6269,30 @@ function createAuthRequest() {
|
|
|
6027
6269
|
};
|
|
6028
6270
|
}
|
|
6029
6271
|
function buildApprovalUrl(endpoint, auth, scope, workspaceId) {
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6272
|
+
return buildUrl(endpoint, APPROVAL_PATH, {
|
|
6273
|
+
request: auth.request,
|
|
6274
|
+
challenge: auth.challenge,
|
|
6275
|
+
pairing: auth.pairing,
|
|
6276
|
+
scope,
|
|
6277
|
+
workspace: workspaceId
|
|
6278
|
+
});
|
|
6279
|
+
}
|
|
6280
|
+
function toRedirectError(endpoint, response) {
|
|
6281
|
+
const location = response.headers.get("location")?.trim();
|
|
6282
|
+
let target = null;
|
|
6283
|
+
if (location) {
|
|
6284
|
+
try {
|
|
6285
|
+
target = new URL(location, endpoint);
|
|
6286
|
+
} catch {
|
|
6287
|
+
target = null;
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
if (!target) {
|
|
6291
|
+
return new SimApiError(`${endpoint} answered the login poll with HTTP ${response.status} and no usable redirect target. Check the endpoint.`, response.status);
|
|
6292
|
+
}
|
|
6293
|
+
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.`;
|
|
6294
|
+
const suggested = redirectEndpoint(endpoint, POLL_PATH, target);
|
|
6295
|
+
return new SimApiError(suggested ? `${refusal} Re-run with --endpoint ${suggested}, or run: sim configure --set-endpoint ${suggested}` : refusal, response.status);
|
|
6038
6296
|
}
|
|
6039
6297
|
async function pollForKey(endpoint, auth, signal) {
|
|
6040
6298
|
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
@@ -6043,16 +6301,23 @@ async function pollForKey(endpoint, auth, signal) {
|
|
|
6043
6301
|
throw new SimApiError("Login cancelled.", 0);
|
|
6044
6302
|
let response = null;
|
|
6045
6303
|
try {
|
|
6046
|
-
response = await fetch(
|
|
6304
|
+
response = await fetch(buildUrl(endpoint, POLL_PATH), {
|
|
6047
6305
|
method: "POST",
|
|
6048
|
-
headers: {
|
|
6306
|
+
headers: {
|
|
6307
|
+
"content-type": "application/json",
|
|
6308
|
+
accept: "application/json",
|
|
6309
|
+
"user-agent": USER_AGENT
|
|
6310
|
+
},
|
|
6049
6311
|
body: JSON.stringify({ request: auth.request, verifier: auth.pollSecret }),
|
|
6050
|
-
signal
|
|
6312
|
+
signal,
|
|
6313
|
+
redirect: "manual"
|
|
6051
6314
|
});
|
|
6052
6315
|
} catch {
|
|
6053
6316
|
response = null;
|
|
6054
6317
|
}
|
|
6055
6318
|
if (response) {
|
|
6319
|
+
if (REDIRECT_STATUSES.has(response.status))
|
|
6320
|
+
throw toRedirectError(endpoint, response);
|
|
6056
6321
|
const raw = await response.text();
|
|
6057
6322
|
if (!response.ok) {
|
|
6058
6323
|
if (!RETRYABLE_POLL_STATUSES.has(response.status)) {
|
|
@@ -6106,263 +6371,76 @@ function clientFrom(command) {
|
|
|
6106
6371
|
return { client: new SimClient(profile), profile };
|
|
6107
6372
|
}
|
|
6108
6373
|
|
|
6109
|
-
// src/
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
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;
|
|
6374
|
+
// src/generated/v2-api.ts
|
|
6375
|
+
var V2_OPERATIONS = {
|
|
6376
|
+
abortFileUpload: {
|
|
6377
|
+
method: "DELETE",
|
|
6378
|
+
path: "/api/v2/files/uploads/[uploadId]",
|
|
6379
|
+
pathParams: ["uploadId"],
|
|
6380
|
+
pathParamDocs: { uploadId: "Upload session identifier." },
|
|
6381
|
+
responseMode: "json",
|
|
6382
|
+
summary: "Abort File Upload",
|
|
6383
|
+
query: {
|
|
6384
|
+
workspaceId: {
|
|
6385
|
+
kind: "string",
|
|
6386
|
+
required: true,
|
|
6387
|
+
describe: "Workspace that owns the upload session."
|
|
6157
6388
|
}
|
|
6158
6389
|
}
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6390
|
+
},
|
|
6391
|
+
abortKnowledgeDocumentUpload: {
|
|
6392
|
+
method: "DELETE",
|
|
6393
|
+
path: "/api/v2/knowledge/[id]/documents/uploads/[uploadId]",
|
|
6394
|
+
pathParams: ["id", "uploadId"],
|
|
6395
|
+
pathParamDocs: {
|
|
6396
|
+
id: "Unique knowledge base identifier.",
|
|
6397
|
+
uploadId: "Upload session identifier returned when the upload was created."
|
|
6398
|
+
},
|
|
6399
|
+
responseMode: "json",
|
|
6400
|
+
summary: "Abort Document Upload",
|
|
6401
|
+
query: {
|
|
6402
|
+
workspaceId: {
|
|
6403
|
+
kind: "string",
|
|
6404
|
+
required: true,
|
|
6405
|
+
describe: "Workspace that owns the knowledge base."
|
|
6406
|
+
}
|
|
6174
6407
|
}
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
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>"));
|
|
6408
|
+
},
|
|
6409
|
+
addTableColumn: {
|
|
6410
|
+
method: "POST",
|
|
6411
|
+
path: "/api/v2/tables/[tableId]/columns",
|
|
6412
|
+
pathParams: ["tableId"],
|
|
6413
|
+
pathParamDocs: { tableId: "Unique table identifier." },
|
|
6414
|
+
responseMode: "json",
|
|
6415
|
+
summary: "Add Column",
|
|
6416
|
+
body: {
|
|
6417
|
+
workspaceId: { kind: "string", required: true, describe: "Workspace that owns the table." },
|
|
6418
|
+
column: { kind: "object", required: true, describe: "Column definition to add." }
|
|
6189
6419
|
}
|
|
6190
|
-
}
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
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."
|
|
6420
|
+
},
|
|
6421
|
+
addWorkflowGroup: {
|
|
6422
|
+
method: "POST",
|
|
6423
|
+
path: "/api/v2/tables/[tableId]/groups",
|
|
6424
|
+
pathParams: ["tableId"],
|
|
6425
|
+
pathParamDocs: { tableId: "Unique table identifier." },
|
|
6426
|
+
responseMode: "json",
|
|
6427
|
+
summary: "Add Workflow Group",
|
|
6428
|
+
body: {
|
|
6429
|
+
workspaceId: { kind: "string", required: true, describe: "Unique workspace identifier." },
|
|
6430
|
+
group: {
|
|
6431
|
+
kind: "object",
|
|
6432
|
+
required: true,
|
|
6433
|
+
describe: "Workflow or enrichment producer definition."
|
|
6434
|
+
},
|
|
6435
|
+
outputColumns: {
|
|
6436
|
+
kind: "array",
|
|
6437
|
+
required: true,
|
|
6438
|
+
describe: "Columns created for producer outputs."
|
|
6439
|
+
},
|
|
6440
|
+
autoRun: {
|
|
6441
|
+
kind: "boolean",
|
|
6442
|
+
default: false,
|
|
6443
|
+
describe: "Whether to schedule existing rows after group creation."
|
|
6366
6444
|
}
|
|
6367
6445
|
}
|
|
6368
6446
|
},
|
|
@@ -8968,6 +9046,10 @@ var V2_OPERATIONS = {
|
|
|
8968
9046
|
kind: "string",
|
|
8969
9047
|
required: true,
|
|
8970
9048
|
describe: "Write-only secret value. It is never returned."
|
|
9049
|
+
},
|
|
9050
|
+
description: {
|
|
9051
|
+
kind: "string",
|
|
9052
|
+
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
9053
|
}
|
|
8972
9054
|
}
|
|
8973
9055
|
},
|
|
@@ -9380,88 +9462,345 @@ var V2_OPERATIONS = {
|
|
|
9380
9462
|
}
|
|
9381
9463
|
};
|
|
9382
9464
|
|
|
9383
|
-
// src/
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
};
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
};
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
name: "workflow",
|
|
9406
|
-
placeholder: "workflowId",
|
|
9407
|
-
describe: "Workflow ID"
|
|
9465
|
+
// src/commands/auth.ts
|
|
9466
|
+
function openBrowser(url) {
|
|
9467
|
+
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
|
|
9468
|
+
try {
|
|
9469
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
9470
|
+
child.on("error", () => {});
|
|
9471
|
+
child.unref();
|
|
9472
|
+
} catch {}
|
|
9473
|
+
}
|
|
9474
|
+
function presentAuthentication(source) {
|
|
9475
|
+
switch (source) {
|
|
9476
|
+
case "flag":
|
|
9477
|
+
return { authenticated: true, source: "flag" };
|
|
9478
|
+
case "env":
|
|
9479
|
+
return { authenticated: true, source: "env" };
|
|
9480
|
+
case "credentials":
|
|
9481
|
+
return { authenticated: true, source: "credentials" };
|
|
9482
|
+
case "unset":
|
|
9483
|
+
return { authenticated: false, source: "unset" };
|
|
9484
|
+
case "config":
|
|
9485
|
+
case "default":
|
|
9486
|
+
throw new SimApiError(`Unexpected API key source "${source}".`, 0);
|
|
9408
9487
|
}
|
|
9409
|
-
};
|
|
9410
|
-
var FOLDER_LIST_COLUMNS = [
|
|
9411
|
-
{ header: "path" },
|
|
9412
|
-
{ header: "name" },
|
|
9413
|
-
{ header: "parent", path: "parentPath" },
|
|
9414
|
-
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9415
|
-
];
|
|
9416
|
-
function moveResource(command, resource) {
|
|
9417
|
-
return {
|
|
9418
|
-
command,
|
|
9419
|
-
positionals: ["folderPath"],
|
|
9420
|
-
requestFields: ["folderPath"],
|
|
9421
|
-
describe: `Move a ${resource} to a folder`
|
|
9422
|
-
};
|
|
9423
9488
|
}
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9489
|
+
async function confirmProfileOverwrite(profileName) {
|
|
9490
|
+
if (!process.stdin.isTTY) {
|
|
9491
|
+
throw new SimApiError(`Profile "${profileName}" already exists. Re-run with --yes to overwrite it.`, 0);
|
|
9492
|
+
}
|
|
9493
|
+
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
9494
|
+
try {
|
|
9495
|
+
const answer = await prompt.question(`Profile "${profileName}" already exists. Replace its API key and login defaults? (y/N) `);
|
|
9496
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
9497
|
+
} finally {
|
|
9498
|
+
prompt.close();
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
function loginCommand() {
|
|
9502
|
+
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) => {
|
|
9503
|
+
const profile = profileFrom(command);
|
|
9504
|
+
if (options.scope !== "platform" && options.scope !== "copilot") {
|
|
9505
|
+
throw new SimApiError(`Unknown scope "${options.scope}". Use platform or copilot.`, 0);
|
|
9506
|
+
}
|
|
9507
|
+
const scope = options.scope;
|
|
9508
|
+
if (readCredentialsProfile(profile.name).api_key && !options.yes) {
|
|
9509
|
+
const confirmed = await confirmProfileOverwrite(profile.name);
|
|
9510
|
+
if (!confirmed) {
|
|
9511
|
+
console.log(source_default.dim("Login cancelled; the existing profile was not changed."));
|
|
9512
|
+
return;
|
|
9513
|
+
}
|
|
9514
|
+
}
|
|
9515
|
+
const auth = createAuthRequest();
|
|
9516
|
+
const url = buildApprovalUrl(profile.endpoint, auth, scope, profile.workspaceId ?? undefined);
|
|
9517
|
+
console.log(`Signing in to ${source_default.bold(profile.endpoint)} as profile ${source_default.bold(profile.name)}`);
|
|
9518
|
+
console.log(`
|
|
9519
|
+
Pairing code: ${source_default.bold(auth.pairing)}`);
|
|
9520
|
+
console.log(source_default.dim(`Confirm this code matches what the browser shows before approving.
|
|
9521
|
+
`));
|
|
9522
|
+
console.log(url);
|
|
9523
|
+
if (options.browser)
|
|
9524
|
+
openBrowser(url);
|
|
9525
|
+
console.log(source_default.dim(`
|
|
9526
|
+
Waiting for approval…`));
|
|
9527
|
+
const key = await pollForKey(profile.endpoint, auth);
|
|
9528
|
+
if (key.scope !== scope) {
|
|
9529
|
+
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);
|
|
9530
|
+
}
|
|
9531
|
+
writeCredentialsProfile(profile.name, key.apiKey);
|
|
9532
|
+
const settings = {
|
|
9533
|
+
endpoint: profile.endpoint,
|
|
9534
|
+
workspace: key.workspaceId ?? null
|
|
9535
|
+
};
|
|
9536
|
+
writeConfigProfile(profile.name, settings);
|
|
9537
|
+
console.log(source_default.green(`
|
|
9538
|
+
✓ Logged in. Key stored in ${credentialsPath()}`));
|
|
9539
|
+
if (key.workspaceBound && key.workspaceId) {
|
|
9540
|
+
console.log(source_default.dim(` Workspace-scoped key — it can only reach ${key.workspaceId}.`));
|
|
9541
|
+
} else if (key.workspaceId) {
|
|
9542
|
+
console.log(source_default.dim(` Personal key, defaulting to ${key.workspaceId}. Override per command with --workspace.`));
|
|
9543
|
+
} else {
|
|
9544
|
+
console.log(source_default.dim(" Personal key with no default workspace. Set one with: sim configure --set-workspace <id>"));
|
|
9545
|
+
}
|
|
9546
|
+
});
|
|
9547
|
+
}
|
|
9548
|
+
function logoutCommand() {
|
|
9549
|
+
return new Command("logout").description("Remove the profile's stored API key").option("--all", "Remove the profile entirely, including its settings").action((options, command) => {
|
|
9550
|
+
const profile = profileFrom(command);
|
|
9551
|
+
if (options.all) {
|
|
9552
|
+
const removed = deleteProfile(profile.name);
|
|
9553
|
+
if (!removed.config && !removed.credentials) {
|
|
9554
|
+
console.log(source_default.dim(`Nothing stored for profile "${profile.name}".`));
|
|
9555
|
+
return;
|
|
9556
|
+
}
|
|
9557
|
+
console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
|
|
9558
|
+
return;
|
|
9559
|
+
}
|
|
9560
|
+
if (!readCredentialsProfile(profile.name).api_key) {
|
|
9561
|
+
console.log(source_default.dim(`No stored key for profile "${profile.name}".`));
|
|
9562
|
+
return;
|
|
9563
|
+
}
|
|
9564
|
+
writeCredentialsProfile(profile.name, null);
|
|
9565
|
+
console.log(source_default.green(`✓ Removed the stored key for profile "${profile.name}".`));
|
|
9566
|
+
console.log(source_default.dim(" The key itself is still active — revoke it in Settings → API keys."));
|
|
9567
|
+
});
|
|
9568
|
+
}
|
|
9569
|
+
var CREDENTIAL_VERDICT_STATUSES = new Set([401, 403, 404]);
|
|
9570
|
+
var WHOAMI_EXIT_CODES = {
|
|
9571
|
+
verified: 0,
|
|
9572
|
+
disabled: 0,
|
|
9573
|
+
unauthenticated: 1,
|
|
9574
|
+
rejected: 1,
|
|
9575
|
+
unreachable: 2,
|
|
9576
|
+
"no-workspace": 2
|
|
9577
|
+
};
|
|
9578
|
+
async function verifyProfile(client, profile) {
|
|
9579
|
+
if (!profile.apiKey) {
|
|
9580
|
+
return {
|
|
9581
|
+
status: "unauthenticated",
|
|
9582
|
+
workspace: null,
|
|
9583
|
+
detail: `no API key — run: sim login --profile ${profile.name}`
|
|
9584
|
+
};
|
|
9585
|
+
}
|
|
9586
|
+
if (!profile.workspaceId) {
|
|
9587
|
+
return {
|
|
9588
|
+
status: "no-workspace",
|
|
9589
|
+
workspace: null,
|
|
9590
|
+
detail: `no workspace to check against — run: sim configure --profile ${profile.name} --set-workspace <id>`
|
|
9591
|
+
};
|
|
9592
|
+
}
|
|
9593
|
+
const operation = V2_OPERATIONS.getWorkspace;
|
|
9594
|
+
try {
|
|
9595
|
+
const response = await client.request(resolvePath(operation.path, { workspaceId: profile.workspaceId }), { method: operation.method });
|
|
9596
|
+
const { id, name, memberCount } = response.data;
|
|
9597
|
+
return { status: "verified", workspace: { id, name, memberCount }, detail: null };
|
|
9598
|
+
} catch (error) {
|
|
9599
|
+
if (!(error instanceof SimApiError))
|
|
9600
|
+
throw error;
|
|
9601
|
+
return {
|
|
9602
|
+
status: CREDENTIAL_VERDICT_STATUSES.has(error.status) ? "rejected" : "unreachable",
|
|
9603
|
+
workspace: null,
|
|
9604
|
+
detail: error.message
|
|
9605
|
+
};
|
|
9606
|
+
}
|
|
9607
|
+
}
|
|
9608
|
+
function presentVerification(verification) {
|
|
9609
|
+
if (verification.status === "verified") {
|
|
9610
|
+
const { name, memberCount } = verification.workspace;
|
|
9611
|
+
const members = `${memberCount} ${memberCount === 1 ? "member" : "members"}`;
|
|
9612
|
+
return `${source_default.green("✓")} ${safeOneLine(name)} · ${members}`;
|
|
9613
|
+
}
|
|
9614
|
+
const detail = safeOneLine(verification.detail);
|
|
9615
|
+
switch (verification.status) {
|
|
9616
|
+
case "rejected":
|
|
9617
|
+
return `${source_default.red("✗")} ${detail}`;
|
|
9618
|
+
case "unauthenticated":
|
|
9619
|
+
return source_default.yellow(`not logged in — ${detail}`);
|
|
9620
|
+
case "disabled":
|
|
9621
|
+
return source_default.dim(detail);
|
|
9622
|
+
default:
|
|
9623
|
+
return source_default.yellow(`could not check — ${detail}`);
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
function whoamiCommand() {
|
|
9627
|
+
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) => {
|
|
9628
|
+
const { client, profile } = clientFrom(command);
|
|
9629
|
+
const { sources } = profile;
|
|
9630
|
+
const authentication = presentAuthentication(sources.apiKey);
|
|
9631
|
+
const verification = options.verify ? await verifyProfile(client, profile) : { status: "disabled", workspace: null, detail: "not checked (--no-verify)" };
|
|
9632
|
+
const annotate = (value, source) => source === "unset" ? source_default.dim("not set") : `${value} ${source_default.dim(`(${source})`)}`;
|
|
9633
|
+
printRecord(profile.output, [
|
|
9634
|
+
["Profile", profile.name],
|
|
9635
|
+
["Endpoint", annotate(profile.endpoint, sources.endpoint)],
|
|
9636
|
+
[
|
|
9637
|
+
"API key",
|
|
9638
|
+
authentication.authenticated ? annotate("configured", authentication.source) : source_default.yellow("not logged in")
|
|
9639
|
+
],
|
|
9640
|
+
["Workspace", annotate(profile.workspaceId ?? "", sources.workspaceId)],
|
|
9641
|
+
["Output", annotate(profile.output, sources.output)],
|
|
9642
|
+
["Verified", presentVerification(verification)]
|
|
9643
|
+
], {
|
|
9644
|
+
profile: profile.name,
|
|
9645
|
+
endpoint: profile.endpoint,
|
|
9646
|
+
workspaceId: profile.workspaceId,
|
|
9647
|
+
output: profile.output,
|
|
9648
|
+
authenticated: authentication.authenticated,
|
|
9649
|
+
sources: {
|
|
9650
|
+
endpoint: sources.endpoint,
|
|
9651
|
+
authentication: authentication.source,
|
|
9652
|
+
workspaceId: sources.workspaceId,
|
|
9653
|
+
output: sources.output
|
|
9654
|
+
},
|
|
9655
|
+
verification: {
|
|
9656
|
+
status: verification.status,
|
|
9657
|
+
workspace: verification.workspace,
|
|
9658
|
+
detail: verification.detail
|
|
9659
|
+
}
|
|
9660
|
+
});
|
|
9661
|
+
const exitCode = WHOAMI_EXIT_CODES[verification.status];
|
|
9662
|
+
if (exitCode !== 0)
|
|
9663
|
+
process.exitCode = exitCode;
|
|
9664
|
+
});
|
|
9665
|
+
}
|
|
9666
|
+
function profilesCommand() {
|
|
9667
|
+
return new Command("profiles").alias("profile").description("List the profiles defined in the config and credentials files").action((_options, command) => {
|
|
9668
|
+
const profiles = listProfiles();
|
|
9669
|
+
if (profiles.length === 0) {
|
|
9670
|
+
console.log(source_default.dim("No profiles yet. Run: sim login"));
|
|
9671
|
+
return;
|
|
9672
|
+
}
|
|
9673
|
+
const active = profileFrom(command).name;
|
|
9674
|
+
for (const name of profiles) {
|
|
9675
|
+
const marker = name === active ? source_default.green("*") : " ";
|
|
9676
|
+
const hasKey = Boolean(readCredentialsProfile(name).api_key);
|
|
9677
|
+
console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}`);
|
|
9678
|
+
}
|
|
9679
|
+
});
|
|
9680
|
+
}
|
|
9681
|
+
|
|
9682
|
+
// src/commands/configure.ts
|
|
9683
|
+
function configureCommand() {
|
|
9684
|
+
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) => {
|
|
9685
|
+
const profile = profileFrom(command);
|
|
9686
|
+
const updates = {};
|
|
9687
|
+
if (options.setEndpoint) {
|
|
9688
|
+
updates.endpoint = normalizeEndpoint(options.setEndpoint, "--set-endpoint");
|
|
9689
|
+
}
|
|
9690
|
+
if (options.setWorkspace)
|
|
9691
|
+
updates.workspace = options.setWorkspace;
|
|
9692
|
+
if (options.setOutput) {
|
|
9693
|
+
if (!OUTPUT_FORMATS.includes(options.setOutput)) {
|
|
9694
|
+
throw new SimApiError(`Unknown output format "${options.setOutput}". Use one of: ${OUTPUT_FORMATS.join(", ")}`, 0);
|
|
9695
|
+
}
|
|
9696
|
+
updates.output = options.setOutput;
|
|
9697
|
+
}
|
|
9698
|
+
for (const key of options.unset ?? []) {
|
|
9699
|
+
if (!["endpoint", "workspace", "output"].includes(key)) {
|
|
9700
|
+
throw new SimApiError(`Cannot unset "${key}". Use endpoint, workspace, or output.`, 0);
|
|
9701
|
+
}
|
|
9702
|
+
updates[key] = null;
|
|
9703
|
+
}
|
|
9704
|
+
if (Object.keys(updates).length === 0) {
|
|
9705
|
+
const current = readConfigProfile(profile.name);
|
|
9706
|
+
if (Object.keys(current).length === 0) {
|
|
9707
|
+
console.log(source_default.dim(`No settings stored for profile "${profile.name}".`));
|
|
9708
|
+
return;
|
|
9709
|
+
}
|
|
9710
|
+
for (const [key, value] of Object.entries(current)) {
|
|
9711
|
+
console.log(`${source_default.dim(`${key}:`)} ${value}`);
|
|
9712
|
+
}
|
|
9713
|
+
return;
|
|
9714
|
+
}
|
|
9715
|
+
writeConfigProfile(profile.name, updates);
|
|
9716
|
+
console.log(source_default.green(`✓ Updated profile "${profile.name}" in ${configPath()}`));
|
|
9717
|
+
});
|
|
9718
|
+
}
|
|
9719
|
+
|
|
9720
|
+
// src/runtime/request.ts
|
|
9721
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readSync } from "node:fs";
|
|
9722
|
+
|
|
9723
|
+
// src/contract/commands.ts
|
|
9724
|
+
var TABLE_NAME_HELP = "Identifier: letters, numbers, and underscores; cannot start with a number";
|
|
9725
|
+
var TABLE_FILTER_HELP = 'Predicate: {"all":[{"field":"status","op":"eq","value":"active"}]}; groups use all/any. Operators: eq, ne, gt, gte, lt, lte, in, nin, contains, ncontains, startsWith, endsWith, like, ilike, nlike, nilike, isEmpty, isNotEmpty, isNull, isNotNull';
|
|
9726
|
+
var TABLE_SORT_HELP = 'Ordered sort keys: [{"field":"createdAt","direction":"desc"}] (direction: asc or desc)';
|
|
9727
|
+
var CUSTOM_TOOL_SCHEMA_HELP = 'OpenAI function schema: {"type":"function","function":{"name":"...","parameters":{"type":"object","properties":{}}}}';
|
|
9728
|
+
var FOLDER_PATH_INPUT = {
|
|
9729
|
+
describe: "Folder path as shown in the app; the leading / is optional",
|
|
9730
|
+
folderPath: true
|
|
9731
|
+
};
|
|
9732
|
+
var FOLDER_PATH_FLAG = {
|
|
9733
|
+
...FOLDER_PATH_INPUT,
|
|
9734
|
+
name: "folder"
|
|
9735
|
+
};
|
|
9736
|
+
var FOLDER_DELETE_FLAGS = {
|
|
9737
|
+
path: FOLDER_PATH_INPUT,
|
|
9738
|
+
recursive: { boolean: true, describe: "Delete the folder and its descendants" }
|
|
9739
|
+
};
|
|
9740
|
+
var KNOWLEDGE_BASE_PATH_ARGUMENT = { id: "knowledgeBaseId" };
|
|
9741
|
+
var WORKFLOW_RUN_SCOPE = {
|
|
9742
|
+
id: {
|
|
9743
|
+
name: "workflow",
|
|
9744
|
+
placeholder: "workflowId",
|
|
9745
|
+
describe: "Workflow ID"
|
|
9746
|
+
}
|
|
9747
|
+
};
|
|
9748
|
+
var FOLDER_COLUMN = { header: "folder", path: "folderPath", format: "folder-path" };
|
|
9749
|
+
var FOLDER_LIST_COLUMNS = [
|
|
9750
|
+
{ header: "path", format: "folder-path" },
|
|
9751
|
+
{ header: "name" },
|
|
9752
|
+
{ header: "parent", path: "parentPath", format: "folder-path" },
|
|
9753
|
+
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9754
|
+
];
|
|
9755
|
+
function moveResource(command, resource) {
|
|
9756
|
+
return {
|
|
9757
|
+
command,
|
|
9758
|
+
positionals: ["folderPath"],
|
|
9759
|
+
requestFields: ["folderPath"],
|
|
9760
|
+
describe: `Move a ${resource} to a folder`
|
|
9761
|
+
};
|
|
9762
|
+
}
|
|
9763
|
+
var CLI_CONTRACT = {
|
|
9764
|
+
createCredentialConnection: { hidden: true },
|
|
9765
|
+
createServiceAccountCredential: { hidden: true },
|
|
9766
|
+
getBillingStatus: {
|
|
9767
|
+
command: "billing status",
|
|
9768
|
+
allWorkspaces: true,
|
|
9769
|
+
describe: "Show billing status and current-period credit usage",
|
|
9770
|
+
fields: [
|
|
9771
|
+
{ header: "plan" },
|
|
9772
|
+
{ header: "status" },
|
|
9773
|
+
{ header: "workspace", path: "workspaceId" },
|
|
9774
|
+
{ header: "period start", path: "period.start", format: "timestamp" },
|
|
9775
|
+
{ header: "period end", path: "period.end", format: "timestamp" },
|
|
9776
|
+
{ header: "used credits", path: "credits.used" },
|
|
9777
|
+
{ header: "limit credits", path: "credits.limit" },
|
|
9778
|
+
{ header: "remaining credits", path: "credits.remaining" }
|
|
9779
|
+
]
|
|
9780
|
+
},
|
|
9781
|
+
listBillingLogs: {
|
|
9782
|
+
command: "billing logs",
|
|
9783
|
+
allWorkspaces: true,
|
|
9784
|
+
describe: "List credit usage events",
|
|
9785
|
+
flags: {
|
|
9786
|
+
source: { describe: "Filter by usage source; sim-chat combines Copilot and workspace chat" },
|
|
9787
|
+
period: { describe: "Billing period" },
|
|
9788
|
+
startDate: { describe: "Custom period start (ISO 8601)" },
|
|
9789
|
+
endDate: { describe: "Custom period end (ISO 8601)" }
|
|
9790
|
+
},
|
|
9791
|
+
columns: [
|
|
9792
|
+
{ header: "at", path: "createdAt", format: "timestamp" },
|
|
9793
|
+
{ header: "workspace", path: "workspaceId" },
|
|
9794
|
+
{ header: "source" },
|
|
9795
|
+
{ header: "workflow", path: "workflow.name" },
|
|
9796
|
+
{ header: "credits", path: "creditCost" },
|
|
9797
|
+
{ header: "run", path: "runId" },
|
|
9798
|
+
{ header: "id" }
|
|
9799
|
+
]
|
|
9800
|
+
},
|
|
9801
|
+
deleteTableRows: {
|
|
9802
|
+
command: "tables rows batch-delete",
|
|
9803
|
+
describe: "Delete rows matching a filter, or an explicit list of ids",
|
|
9465
9804
|
flags: {
|
|
9466
9805
|
rowIds: { name: "row", list: true },
|
|
9467
9806
|
filter: { json: true, describe: TABLE_FILTER_HELP }
|
|
@@ -9480,7 +9819,7 @@ var CLI_CONTRACT = {
|
|
|
9480
9819
|
bulkUpdateKnowledgeDocuments: {
|
|
9481
9820
|
command: "knowledge documents batch-update",
|
|
9482
9821
|
describe: "Enable or disable every matching document",
|
|
9483
|
-
pathArgumentNames:
|
|
9822
|
+
pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
|
|
9484
9823
|
flags: {
|
|
9485
9824
|
documentIds: { name: "document", list: true },
|
|
9486
9825
|
selectAll: { boolean: true, describe: "Apply to every document in the knowledge base" }
|
|
@@ -9490,6 +9829,11 @@ var CLI_CONTRACT = {
|
|
|
9490
9829
|
command: "workflows undeploy",
|
|
9491
9830
|
describe: "Take a workflow out of deployment"
|
|
9492
9831
|
},
|
|
9832
|
+
getWorkflowDeployment: {
|
|
9833
|
+
command: "workflows deployment status",
|
|
9834
|
+
renamedFrom: ["workflows deployment list"],
|
|
9835
|
+
describe: "Show a workflow’s current deployment"
|
|
9836
|
+
},
|
|
9493
9837
|
setSecret: { hidden: true },
|
|
9494
9838
|
deleteTable: { confirm: "This deletes the table and all of its rows." },
|
|
9495
9839
|
deleteTableRow: { confirm: "This deletes the row." },
|
|
@@ -9499,7 +9843,7 @@ var CLI_CONTRACT = {
|
|
|
9499
9843
|
},
|
|
9500
9844
|
deleteKnowledgeBase: { confirm: "This deletes the knowledge base and every document in it." },
|
|
9501
9845
|
deleteKnowledgeDocument: {
|
|
9502
|
-
pathArgumentNames:
|
|
9846
|
+
pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
|
|
9503
9847
|
confirm: "This deletes the document and its embeddings."
|
|
9504
9848
|
},
|
|
9505
9849
|
deleteFile: { confirm: "This archives the file." },
|
|
@@ -9529,7 +9873,10 @@ var CLI_CONTRACT = {
|
|
|
9529
9873
|
workflowIds: { name: "workflow", list: true },
|
|
9530
9874
|
folderPaths: { ...FOLDER_PATH_FLAG, list: true },
|
|
9531
9875
|
triggers: { name: "trigger", list: true },
|
|
9532
|
-
details: {
|
|
9876
|
+
details: {
|
|
9877
|
+
requestDefault: "full",
|
|
9878
|
+
describe: "Response detail level; full is requested by default to name each run’s workflow"
|
|
9879
|
+
},
|
|
9533
9880
|
includeTraceSpans: {
|
|
9534
9881
|
boolean: true,
|
|
9535
9882
|
describe: "Include trace spans in JSON or YAML output (implies full detail)"
|
|
@@ -9582,7 +9929,7 @@ var CLI_CONTRACT = {
|
|
|
9582
9929
|
},
|
|
9583
9930
|
itemsPath: "results",
|
|
9584
9931
|
columns: [
|
|
9585
|
-
{ header: "score", path: "similarity" },
|
|
9932
|
+
{ header: "score", path: "similarity", format: "score" },
|
|
9586
9933
|
{ header: "document", path: "documentName" },
|
|
9587
9934
|
{ header: "chunk", path: "chunkIndex" },
|
|
9588
9935
|
{ header: "content" }
|
|
@@ -9656,7 +10003,7 @@ var CLI_CONTRACT = {
|
|
|
9656
10003
|
columns: [
|
|
9657
10004
|
{ header: "id" },
|
|
9658
10005
|
{ header: "name" },
|
|
9659
|
-
|
|
10006
|
+
FOLDER_COLUMN,
|
|
9660
10007
|
{ header: "rows", path: "rowCount" },
|
|
9661
10008
|
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9662
10009
|
]
|
|
@@ -9666,7 +10013,7 @@ var CLI_CONTRACT = {
|
|
|
9666
10013
|
columns: [
|
|
9667
10014
|
{ header: "id" },
|
|
9668
10015
|
{ header: "name" },
|
|
9669
|
-
|
|
10016
|
+
FOLDER_COLUMN,
|
|
9670
10017
|
{ header: "deployed", path: "isDeployed", format: "bool" },
|
|
9671
10018
|
{ header: "runs", path: "runCount" },
|
|
9672
10019
|
{ header: "last run", path: "lastRunAt", format: "timestamp" }
|
|
@@ -9677,7 +10024,7 @@ var CLI_CONTRACT = {
|
|
|
9677
10024
|
columns: [
|
|
9678
10025
|
{ header: "id" },
|
|
9679
10026
|
{ header: "name" },
|
|
9680
|
-
|
|
10027
|
+
FOLDER_COLUMN,
|
|
9681
10028
|
{ header: "size", format: "bytes" },
|
|
9682
10029
|
{ header: "type" },
|
|
9683
10030
|
{ header: "uploaded by", path: "uploadedByEmail" },
|
|
@@ -9690,15 +10037,17 @@ var CLI_CONTRACT = {
|
|
|
9690
10037
|
columns: [
|
|
9691
10038
|
{ header: "id" },
|
|
9692
10039
|
{ header: "name" },
|
|
9693
|
-
|
|
10040
|
+
FOLDER_COLUMN,
|
|
9694
10041
|
{ header: "docs", path: "docCount" },
|
|
9695
10042
|
{ header: "tokens", path: "tokenCount" },
|
|
9696
10043
|
{ header: "model", path: "embeddingModel" }
|
|
9697
10044
|
]
|
|
9698
10045
|
},
|
|
9699
|
-
getKnowledgeDocument: { pathArgumentNames:
|
|
10046
|
+
getKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
|
|
10047
|
+
updateKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
|
|
10048
|
+
listKnowledgeTags: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
|
|
9700
10049
|
listKnowledgeDocuments: {
|
|
9701
|
-
pathArgumentNames:
|
|
10050
|
+
pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
|
|
9702
10051
|
columns: [
|
|
9703
10052
|
{ header: "id" },
|
|
9704
10053
|
{ header: "filename" },
|
|
@@ -9742,12 +10091,24 @@ var CLI_CONTRACT = {
|
|
|
9742
10091
|
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9743
10092
|
]
|
|
9744
10093
|
},
|
|
10094
|
+
listCredentialProviders: {
|
|
10095
|
+
columns: [
|
|
10096
|
+
{ header: "type" },
|
|
10097
|
+
{ header: "service", path: "serviceId" },
|
|
10098
|
+
{ header: "provider", path: "providerId" },
|
|
10099
|
+
{ header: "name" },
|
|
10100
|
+
{ header: "family", path: "providerFamily" },
|
|
10101
|
+
{ header: "available", format: "bool" },
|
|
10102
|
+
{ header: "description" }
|
|
10103
|
+
]
|
|
10104
|
+
},
|
|
9745
10105
|
listSecrets: {
|
|
9746
10106
|
columns: [
|
|
9747
10107
|
{ header: "name" },
|
|
9748
10108
|
{ header: "scope" },
|
|
9749
10109
|
{ header: "role" },
|
|
9750
|
-
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
10110
|
+
{ header: "updated", path: "updatedAt", format: "timestamp" },
|
|
10111
|
+
{ header: "description" }
|
|
9751
10112
|
]
|
|
9752
10113
|
},
|
|
9753
10114
|
getWorkspace: {
|
|
@@ -9813,7 +10174,7 @@ var CLI_CONTRACT = {
|
|
|
9813
10174
|
{ header: "name" },
|
|
9814
10175
|
{ header: "size", format: "bytes" },
|
|
9815
10176
|
{ header: "type" },
|
|
9816
|
-
|
|
10177
|
+
FOLDER_COLUMN,
|
|
9817
10178
|
{ header: "uploaded by", path: "uploadedByEmail" },
|
|
9818
10179
|
{ header: "uploaded", path: "uploadedAt", format: "timestamp" },
|
|
9819
10180
|
{ header: "updated", path: "updatedAt", format: "timestamp" },
|
|
@@ -9840,6 +10201,11 @@ var CLI_CONTRACT = {
|
|
|
9840
10201
|
command: "files rename",
|
|
9841
10202
|
describe: "Rename a file"
|
|
9842
10203
|
},
|
|
10204
|
+
restoreFile: {
|
|
10205
|
+
command: "files restore",
|
|
10206
|
+
renamedFrom: ["files restore create"],
|
|
10207
|
+
describe: "Restore an archived file"
|
|
10208
|
+
},
|
|
9843
10209
|
updateFileContent: {
|
|
9844
10210
|
command: "files set-content",
|
|
9845
10211
|
describe: "Replace a file’s contents",
|
|
@@ -9992,13 +10358,26 @@ var CLI_CONTRACT = {
|
|
|
9992
10358
|
command: "tables rows find",
|
|
9993
10359
|
describe: "Find rows matching a predicate",
|
|
9994
10360
|
flags: {
|
|
9995
|
-
q: { describe: "Value to find" },
|
|
10361
|
+
q: { name: "query", renamedFrom: ["q"], describe: "Value to find" },
|
|
9996
10362
|
predicate: { name: "filter", json: true, describe: TABLE_FILTER_HELP },
|
|
9997
10363
|
sort: { json: true, describe: TABLE_SORT_HELP }
|
|
9998
10364
|
},
|
|
9999
10365
|
itemsPath: "matches",
|
|
10000
10366
|
columns: [{ header: "ordinal" }, { header: "row", path: "rowId" }, { header: "column" }]
|
|
10001
10367
|
},
|
|
10368
|
+
queryRowsCount: {
|
|
10369
|
+
command: "tables rows count",
|
|
10370
|
+
renamedFrom: ["tables count create"],
|
|
10371
|
+
describe: "Count rows matching a filter",
|
|
10372
|
+
flags: {
|
|
10373
|
+
predicate: {
|
|
10374
|
+
name: "filter",
|
|
10375
|
+
renamedFrom: ["predicate"],
|
|
10376
|
+
json: true,
|
|
10377
|
+
describe: TABLE_FILTER_HELP
|
|
10378
|
+
}
|
|
10379
|
+
}
|
|
10380
|
+
},
|
|
10002
10381
|
runTableColumn: {
|
|
10003
10382
|
command: "tables columns run",
|
|
10004
10383
|
describe: "Run a column’s workflow",
|
|
@@ -10216,7 +10595,7 @@ function readArgumentSource(raw, flagName) {
|
|
|
10216
10595
|
}
|
|
10217
10596
|
}
|
|
10218
10597
|
try {
|
|
10219
|
-
return { text:
|
|
10598
|
+
return { text: readFileSync3(path, "utf8"), from: ` (read from ${path})` };
|
|
10220
10599
|
} catch (error) {
|
|
10221
10600
|
throw new SimApiError(`--${flagName} cannot read ${path}: ${error.message}`, 0);
|
|
10222
10601
|
}
|
|
@@ -10251,6 +10630,26 @@ function readListValues(raw, flagName) {
|
|
|
10251
10630
|
return trimmed;
|
|
10252
10631
|
});
|
|
10253
10632
|
}
|
|
10633
|
+
var PERCENT_ESCAPE = /%[0-9A-Fa-f]{2}/;
|
|
10634
|
+
var SUB_DELIMITERS = /[!'()*]/g;
|
|
10635
|
+
function encodeFolderPathSegment(name) {
|
|
10636
|
+
if (name === ".")
|
|
10637
|
+
return "%2E";
|
|
10638
|
+
if (name === "..")
|
|
10639
|
+
return "%2E%2E";
|
|
10640
|
+
return encodeURIComponent(name).replace(SUB_DELIMITERS, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
10641
|
+
}
|
|
10642
|
+
function encodeFolderPath(value) {
|
|
10643
|
+
return value.split("/").map((segment) => {
|
|
10644
|
+
if (!PERCENT_ESCAPE.test(segment))
|
|
10645
|
+
return encodeFolderPathSegment(segment);
|
|
10646
|
+
try {
|
|
10647
|
+
return encodeFolderPathSegment(decodeURIComponent(segment));
|
|
10648
|
+
} catch {
|
|
10649
|
+
return encodeFolderPathSegment(segment);
|
|
10650
|
+
}
|
|
10651
|
+
}).join("/");
|
|
10652
|
+
}
|
|
10254
10653
|
function pathHint(raw) {
|
|
10255
10654
|
if (raw.startsWith("@") || /^\s*[[{"\-\d]|^\s*(true|false|null)/.test(raw))
|
|
10256
10655
|
return "";
|
|
@@ -10260,7 +10659,7 @@ function coerce(raw, field, flag, flagName) {
|
|
|
10260
10659
|
if (raw === undefined)
|
|
10261
10660
|
return;
|
|
10262
10661
|
if (flag.list) {
|
|
10263
|
-
const values = readListValues(raw, flagName);
|
|
10662
|
+
const values = readListValues(raw, flagName).map((value) => flag.folderPath ? encodeFolderPath(value) : value);
|
|
10264
10663
|
return field.kind === "string" ? values.join(",") : values;
|
|
10265
10664
|
}
|
|
10266
10665
|
if (takesJson(field, flag)) {
|
|
@@ -10285,6 +10684,8 @@ function coerce(raw, field, flag, flagName) {
|
|
|
10285
10684
|
if (choices && !choices.includes(String(raw))) {
|
|
10286
10685
|
throw new SimApiError(`--${flagName} must be one of: ${choices.join(", ")}`, 0);
|
|
10287
10686
|
}
|
|
10687
|
+
if (flag.folderPath && typeof raw === "string")
|
|
10688
|
+
return encodeFolderPath(raw);
|
|
10288
10689
|
return raw;
|
|
10289
10690
|
}
|
|
10290
10691
|
function asQueryValue(value) {
|
|
@@ -10325,7 +10726,8 @@ function buildRequest(operation, positional, flags, workspaceId) {
|
|
|
10325
10726
|
continue;
|
|
10326
10727
|
const flagName = flagNameFor(operation, field);
|
|
10327
10728
|
const omitProfileWorkspace = commandSpec.allWorkspaces && flags.allWorkspaces === true;
|
|
10328
|
-
const
|
|
10729
|
+
const provided = field === PROFILE_INJECTED_FIELD ? omitProfileWorkspace ? undefined : workspaceId : flags[camel(flagName)];
|
|
10730
|
+
const raw = provided ?? flag.requestDefault;
|
|
10329
10731
|
const value = coerce(raw ?? undefined, descriptor, flag, flagName);
|
|
10330
10732
|
if (value === undefined) {
|
|
10331
10733
|
if (descriptor.required) {
|
|
@@ -10498,6 +10900,15 @@ function countTraceSpans(value) {
|
|
|
10498
10900
|
function at(row, path) {
|
|
10499
10901
|
return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
|
|
10500
10902
|
}
|
|
10903
|
+
function decodeFolderPath(value) {
|
|
10904
|
+
return value.split("/").map((segment) => {
|
|
10905
|
+
try {
|
|
10906
|
+
return decodeURIComponent(segment);
|
|
10907
|
+
} catch {
|
|
10908
|
+
return segment;
|
|
10909
|
+
}
|
|
10910
|
+
}).join("/");
|
|
10911
|
+
}
|
|
10501
10912
|
function renderCell(value, format, options = {}) {
|
|
10502
10913
|
switch (format) {
|
|
10503
10914
|
case "timestamp":
|
|
@@ -10510,8 +10921,12 @@ function renderCell(value, format, options = {}) {
|
|
|
10510
10921
|
return bool2(value);
|
|
10511
10922
|
case "cost":
|
|
10512
10923
|
return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
|
|
10924
|
+
case "score":
|
|
10925
|
+
return typeof value === "number" ? value.toFixed(4) : text(null);
|
|
10513
10926
|
case "count":
|
|
10514
10927
|
return Array.isArray(value) ? String(value.length) : text(null);
|
|
10928
|
+
case "folder-path":
|
|
10929
|
+
return typeof value === "string" ? text(decodeFolderPath(value)) : text(value);
|
|
10515
10930
|
case "trace-count": {
|
|
10516
10931
|
const count = countTraceSpans(value);
|
|
10517
10932
|
return `${count} ${count === 1 ? "span" : "spans"}${options.expandedTrace ? "" : " (use --trace)"}`;
|
|
@@ -10522,10 +10937,42 @@ function renderCell(value, format, options = {}) {
|
|
|
10522
10937
|
return sanitize(typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
10523
10938
|
}
|
|
10524
10939
|
}
|
|
10525
|
-
var
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10940
|
+
var TIMESTAMP_KEY = /(?:At|Date)$/;
|
|
10941
|
+
var DURATION_KEY = /Ms$|^duration/;
|
|
10942
|
+
var BYTES_KEY = /^size$|(?:Size|Bytes)$/;
|
|
10943
|
+
var BOOL_KEY = /^(?:is|has)[A-Z]/;
|
|
10944
|
+
var RATIO_KEY = /^(?:similarity|score)$|(?:Similarity|Score)$/;
|
|
10945
|
+
var FOLDER_PATH_KEY = /^(?:path|parentPath|folderPath)$/;
|
|
10946
|
+
var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/;
|
|
10947
|
+
var RATIO_PRECISION = 4;
|
|
10948
|
+
function inferFormat(key, value) {
|
|
10949
|
+
if (typeof value === "boolean")
|
|
10950
|
+
return BOOL_KEY.test(key) ? "bool" : null;
|
|
10951
|
+
if (typeof value === "string") {
|
|
10952
|
+
if (FOLDER_PATH_KEY.test(key))
|
|
10953
|
+
return "folder-path";
|
|
10954
|
+
return TIMESTAMP_KEY.test(key) && ISO_TIMESTAMP.test(value) && !Number.isNaN(Date.parse(value)) ? "timestamp" : null;
|
|
10955
|
+
}
|
|
10956
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
10957
|
+
return null;
|
|
10958
|
+
if (DURATION_KEY.test(key))
|
|
10959
|
+
return "duration";
|
|
10960
|
+
if (BYTES_KEY.test(key))
|
|
10961
|
+
return "bytes";
|
|
10962
|
+
return null;
|
|
10963
|
+
}
|
|
10964
|
+
function inferredCell(key, value) {
|
|
10965
|
+
if (typeof value === "number" && Number.isFinite(value) && RATIO_KEY.test(key)) {
|
|
10966
|
+
return value.toFixed(RATIO_PRECISION);
|
|
10967
|
+
}
|
|
10968
|
+
return renderCell(value, inferFormat(key, value) ?? "auto");
|
|
10969
|
+
}
|
|
10970
|
+
function humanizeKey(key) {
|
|
10971
|
+
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();
|
|
10972
|
+
}
|
|
10973
|
+
function inferHeader(key, format) {
|
|
10974
|
+
const trimmed = format === "duration" || format === "bytes" ? key.replace(/(?:Ms|Bytes)$/, "") : format === "bool" ? key.replace(/^is(?=[A-Z])/, "") : key;
|
|
10975
|
+
return humanizeKey(trimmed || key);
|
|
10529
10976
|
}
|
|
10530
10977
|
function columnsFrom(specs) {
|
|
10531
10978
|
return specs.map((spec) => ({
|
|
@@ -10534,9 +10981,9 @@ function columnsFrom(specs) {
|
|
|
10534
10981
|
}));
|
|
10535
10982
|
}
|
|
10536
10983
|
function fieldsFrom(data, specs, options = {}) {
|
|
10537
|
-
return specs.
|
|
10984
|
+
return specs.map((spec) => {
|
|
10538
10985
|
const value = at(data, spec.path ?? spec.header);
|
|
10539
|
-
return value === undefined ?
|
|
10986
|
+
return [spec.header, value === undefined ? text(null) : renderCell(value, spec.format, options)];
|
|
10540
10987
|
});
|
|
10541
10988
|
}
|
|
10542
10989
|
function inferColumns(rows, expand) {
|
|
@@ -10551,7 +10998,7 @@ function inferColumns(rows, expand) {
|
|
|
10551
10998
|
if (value !== null && typeof value === "object")
|
|
10552
10999
|
continue;
|
|
10553
11000
|
seen.add(key);
|
|
10554
|
-
paths.push({ path: key, header: key });
|
|
11001
|
+
paths.push({ path: key, key, header: inferHeader(key, inferFormat(key, value)), owned: true });
|
|
10555
11002
|
}
|
|
10556
11003
|
}
|
|
10557
11004
|
if (expand) {
|
|
@@ -10564,13 +11011,18 @@ function inferColumns(rows, expand) {
|
|
|
10564
11011
|
if (nested.has(key))
|
|
10565
11012
|
continue;
|
|
10566
11013
|
nested.add(key);
|
|
10567
|
-
paths.push({
|
|
11014
|
+
paths.push({
|
|
11015
|
+
path: `${expand}.${key}`,
|
|
11016
|
+
key,
|
|
11017
|
+
header: seen.has(key) ? `${expand}.${key}` : key,
|
|
11018
|
+
owned: false
|
|
11019
|
+
});
|
|
10568
11020
|
}
|
|
10569
11021
|
}
|
|
10570
11022
|
}
|
|
10571
|
-
return paths.map(({ path, header }) => ({
|
|
11023
|
+
return paths.map(({ path, key, header, owned }) => ({
|
|
10572
11024
|
header: sanitize(header),
|
|
10573
|
-
value: (row) => renderCell(at(row, path), "auto")
|
|
11025
|
+
value: (row) => owned ? inferredCell(key, at(row, path)) : renderCell(at(row, path), "auto")
|
|
10574
11026
|
}));
|
|
10575
11027
|
}
|
|
10576
11028
|
function unwrapResource(data) {
|
|
@@ -10603,7 +11055,10 @@ function renderResult(operation, format, raw, spec, options = {}) {
|
|
|
10603
11055
|
printList(format, data, spec.columns ? columnsFrom(spec.columns) : inferColumns(data, spec.expand));
|
|
10604
11056
|
return;
|
|
10605
11057
|
}
|
|
10606
|
-
const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [
|
|
11058
|
+
const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [
|
|
11059
|
+
inferHeader(key, inferFormat(key, value)),
|
|
11060
|
+
inferredCell(key, value)
|
|
11061
|
+
]) : [];
|
|
10607
11062
|
printRecord(format, fields, data);
|
|
10608
11063
|
if (spec.expandedTrace && options.expandedTrace) {
|
|
10609
11064
|
const traceSpans = at(data, "traceSpans");
|
|
@@ -10716,7 +11171,7 @@ function attachCredentialCommands(program2) {
|
|
|
10716
11171
|
}
|
|
10717
11172
|
|
|
10718
11173
|
// src/commands/protocol/files-get.ts
|
|
10719
|
-
import { once } from "node:events";
|
|
11174
|
+
import { once as once2 } from "node:events";
|
|
10720
11175
|
import { createWriteStream } from "node:fs";
|
|
10721
11176
|
import { link, lstat, mkdtemp, readlink, rename, rm } from "node:fs/promises";
|
|
10722
11177
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
@@ -10731,6 +11186,9 @@ function printProtocolResult(format, result) {
|
|
|
10731
11186
|
|
|
10732
11187
|
// src/commands/protocol/files-get.ts
|
|
10733
11188
|
function writeFailure(path, error) {
|
|
11189
|
+
if (isRequestTimeout(error)) {
|
|
11190
|
+
return new SimApiError(`Downloading ${path} timed out. ${RAISE_TIMEOUT_HINT}`, 0);
|
|
11191
|
+
}
|
|
10734
11192
|
const code = error.code;
|
|
10735
11193
|
if (code === "EEXIST") {
|
|
10736
11194
|
return new SimApiError(`${path} already exists. Pass --force to overwrite it, or choose another output path.`, 0);
|
|
@@ -10821,7 +11279,7 @@ async function streamToStdout(body, output = process.stdout) {
|
|
|
10821
11279
|
if (done)
|
|
10822
11280
|
return;
|
|
10823
11281
|
if (!output.write(value))
|
|
10824
|
-
await
|
|
11282
|
+
await once2(output, "drain");
|
|
10825
11283
|
}
|
|
10826
11284
|
} finally {
|
|
10827
11285
|
reader.releaseLock();
|
|
@@ -11001,7 +11459,7 @@ async function finishUploadSession(client, workspaceId, session, path) {
|
|
|
11001
11459
|
|
|
11002
11460
|
// src/commands/protocol/files-upload.ts
|
|
11003
11461
|
function attachFileUpload(files) {
|
|
11004
|
-
files.command("upload").argument("<path>", "Local file to upload").description("Upload a file to the workspace").option("--folder <path>", "
|
|
11462
|
+
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
11463
|
const { client, profile } = clientFrom(command);
|
|
11006
11464
|
const workspaceId = client.requireWorkspace();
|
|
11007
11465
|
const { name, size } = await localFile(path, options.name);
|
|
@@ -11012,7 +11470,7 @@ function attachFileUpload(files) {
|
|
|
11012
11470
|
name,
|
|
11013
11471
|
contentType: contentTypeFor(name),
|
|
11014
11472
|
size,
|
|
11015
|
-
...options.folder !== undefined ? { folderPath: options.folder } : {}
|
|
11473
|
+
...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
|
|
11016
11474
|
}
|
|
11017
11475
|
});
|
|
11018
11476
|
const { session, uploadToken, transfer } = created.data;
|
|
@@ -11071,13 +11529,285 @@ function attachKnowledgeDocumentUpload(documents) {
|
|
|
11071
11529
|
if (!completed.document) {
|
|
11072
11530
|
throw new Error(`Knowledge upload ${session.id} completed without a document`);
|
|
11073
11531
|
}
|
|
11074
|
-
printProtocolResult(profile.output, {
|
|
11075
|
-
id: completed.document.id,
|
|
11076
|
-
knowledgeBaseId: completed.document.knowledgeBaseId,
|
|
11077
|
-
name: completed.document.filename,
|
|
11078
|
-
size: completed.document.fileSize,
|
|
11079
|
-
status: completed.document.processingStatus
|
|
11080
|
-
});
|
|
11532
|
+
printProtocolResult(profile.output, {
|
|
11533
|
+
id: completed.document.id,
|
|
11534
|
+
knowledgeBaseId: completed.document.knowledgeBaseId,
|
|
11535
|
+
name: completed.document.filename,
|
|
11536
|
+
size: completed.document.fileSize,
|
|
11537
|
+
status: completed.document.processingStatus
|
|
11538
|
+
});
|
|
11539
|
+
});
|
|
11540
|
+
}
|
|
11541
|
+
|
|
11542
|
+
// src/commands/protocol/logs-follow.ts
|
|
11543
|
+
var DEFAULT_BACKLOG = 10;
|
|
11544
|
+
var DEFAULT_INTERVAL_SECONDS = 3;
|
|
11545
|
+
var MIN_INTERVAL_SECONDS = 0.1;
|
|
11546
|
+
var MAX_BACKOFF_MS = 30000;
|
|
11547
|
+
var POLL_PAGE_SIZE = 100;
|
|
11548
|
+
var MAX_PAGES_PER_POLL = 10;
|
|
11549
|
+
var MAX_REMEMBERED_RUNS = 5000;
|
|
11550
|
+
var MAX_CELL_WIDTH2 = 60;
|
|
11551
|
+
var WAIT_SLICE_MS = 250;
|
|
11552
|
+
var RETRYABLE_CLIENT_STATUSES = new Set([408, 425, 429]);
|
|
11553
|
+
var ERASE_LINE = `${String.fromCharCode(27)}[K`;
|
|
11554
|
+
function collect(value, previous) {
|
|
11555
|
+
return [...previous, value];
|
|
11556
|
+
}
|
|
11557
|
+
function at2(row, path) {
|
|
11558
|
+
return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
|
|
11559
|
+
}
|
|
11560
|
+
function renderCell2(value, format) {
|
|
11561
|
+
switch (format) {
|
|
11562
|
+
case "timestamp":
|
|
11563
|
+
return timestamp2(value);
|
|
11564
|
+
case "duration":
|
|
11565
|
+
return duration(value);
|
|
11566
|
+
case "bytes":
|
|
11567
|
+
return bytes(value);
|
|
11568
|
+
case "bool":
|
|
11569
|
+
return bool2(value);
|
|
11570
|
+
case "cost":
|
|
11571
|
+
return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
|
|
11572
|
+
default:
|
|
11573
|
+
return text(typeof value === "object" && value !== null ? JSON.stringify(value) : value);
|
|
11574
|
+
}
|
|
11575
|
+
}
|
|
11576
|
+
var COLUMNS = (CLI_CONTRACT.listLogs?.columns ?? []).map((spec) => ({
|
|
11577
|
+
header: spec.header,
|
|
11578
|
+
value: (row) => renderCell2(at2(row, spec.path ?? spec.header), spec.format)
|
|
11579
|
+
}));
|
|
11580
|
+
function oneLine2(value) {
|
|
11581
|
+
return value.replace(/\s*[\r\n\t]+\s*/g, " ");
|
|
11582
|
+
}
|
|
11583
|
+
function pad2(value, width) {
|
|
11584
|
+
return value + " ".repeat(Math.max(0, width - visibleWidth(value)));
|
|
11585
|
+
}
|
|
11586
|
+
function clamp2(value, width) {
|
|
11587
|
+
if (visibleWidth(value) <= width || visibleWidth(value) !== value.length)
|
|
11588
|
+
return value;
|
|
11589
|
+
return `${value.slice(0, Math.max(1, width - 1))}…`;
|
|
11590
|
+
}
|
|
11591
|
+
function createTableWriter() {
|
|
11592
|
+
let widths = null;
|
|
11593
|
+
return (rows) => {
|
|
11594
|
+
const lines = rows.map((row) => COLUMNS.map((column) => oneLine2(column.value(row))));
|
|
11595
|
+
if (!widths) {
|
|
11596
|
+
widths = COLUMNS.map((column, index) => Math.min(MAX_CELL_WIDTH2, Math.max(visibleWidth(column.header), ...lines.map((line) => visibleWidth(line[index])))));
|
|
11597
|
+
const header = widths;
|
|
11598
|
+
console.log(source_default.dim(COLUMNS.map((column, index) => pad2(column.header.toUpperCase(), header[index])).join(" ").trimEnd()));
|
|
11599
|
+
}
|
|
11600
|
+
const locked = widths;
|
|
11601
|
+
for (const line of lines) {
|
|
11602
|
+
console.log(line.map((cell, index) => pad2(clamp2(cell, locked[index]), locked[index])).join(" ").trimEnd());
|
|
11603
|
+
}
|
|
11604
|
+
};
|
|
11605
|
+
}
|
|
11606
|
+
function createWriter(format) {
|
|
11607
|
+
if (format === "json") {
|
|
11608
|
+
return (rows) => {
|
|
11609
|
+
for (const row of rows)
|
|
11610
|
+
console.log(JSON.stringify(row));
|
|
11611
|
+
};
|
|
11612
|
+
}
|
|
11613
|
+
if (format === "yaml") {
|
|
11614
|
+
return (rows) => {
|
|
11615
|
+
for (const row of rows) {
|
|
11616
|
+
console.log(`---
|
|
11617
|
+
${dump(row, { lineWidth: 0, noRefs: true }).trimEnd()}`);
|
|
11618
|
+
}
|
|
11619
|
+
};
|
|
11620
|
+
}
|
|
11621
|
+
if (format === "text") {
|
|
11622
|
+
return (rows) => {
|
|
11623
|
+
if (rows.length > 0)
|
|
11624
|
+
printList("text", rows, COLUMNS);
|
|
11625
|
+
};
|
|
11626
|
+
}
|
|
11627
|
+
return createTableWriter();
|
|
11628
|
+
}
|
|
11629
|
+
function followStatus() {
|
|
11630
|
+
let reported2 = false;
|
|
11631
|
+
return {
|
|
11632
|
+
note: (message) => {
|
|
11633
|
+
if (!process.stderr.isTTY)
|
|
11634
|
+
return;
|
|
11635
|
+
reported2 = true;
|
|
11636
|
+
process.stderr.write(`\r${source_default.dim(message)}${ERASE_LINE}`);
|
|
11637
|
+
},
|
|
11638
|
+
warn: (message) => {
|
|
11639
|
+
if (reported2) {
|
|
11640
|
+
reported2 = false;
|
|
11641
|
+
process.stderr.write(`\r${ERASE_LINE}`);
|
|
11642
|
+
}
|
|
11643
|
+
process.stderr.write(`warning: ${message}
|
|
11644
|
+
`);
|
|
11645
|
+
},
|
|
11646
|
+
clear: () => {
|
|
11647
|
+
if (!reported2)
|
|
11648
|
+
return;
|
|
11649
|
+
reported2 = false;
|
|
11650
|
+
process.stderr.write(`\r${ERASE_LINE}`);
|
|
11651
|
+
}
|
|
11652
|
+
};
|
|
11653
|
+
}
|
|
11654
|
+
function watchForInterrupt() {
|
|
11655
|
+
let stopped = false;
|
|
11656
|
+
const stop = () => {
|
|
11657
|
+
stopped = true;
|
|
11658
|
+
};
|
|
11659
|
+
process.on("SIGINT", stop);
|
|
11660
|
+
process.on("SIGTERM", stop);
|
|
11661
|
+
return {
|
|
11662
|
+
interrupted: () => stopped,
|
|
11663
|
+
dispose: () => {
|
|
11664
|
+
process.off("SIGINT", stop);
|
|
11665
|
+
process.off("SIGTERM", stop);
|
|
11666
|
+
}
|
|
11667
|
+
};
|
|
11668
|
+
}
|
|
11669
|
+
async function waitFor(ms, interrupted) {
|
|
11670
|
+
let remaining = ms;
|
|
11671
|
+
while (remaining > 0 && !interrupted()) {
|
|
11672
|
+
const step = Math.min(WAIT_SLICE_MS, remaining);
|
|
11673
|
+
await sleep(step);
|
|
11674
|
+
remaining -= step;
|
|
11675
|
+
}
|
|
11676
|
+
}
|
|
11677
|
+
function isUnprinted(state, row) {
|
|
11678
|
+
if (state.seen.has(row.runId))
|
|
11679
|
+
return false;
|
|
11680
|
+
return state.floor === null || row.startedAt >= state.floor;
|
|
11681
|
+
}
|
|
11682
|
+
function remember(state, rows) {
|
|
11683
|
+
for (const row of rows)
|
|
11684
|
+
state.seen.set(row.runId, row.startedAt);
|
|
11685
|
+
let excess = state.seen.size - MAX_REMEMBERED_RUNS;
|
|
11686
|
+
if (excess <= 0)
|
|
11687
|
+
return;
|
|
11688
|
+
for (const [runId, startedAt] of state.seen) {
|
|
11689
|
+
if (excess <= 0)
|
|
11690
|
+
break;
|
|
11691
|
+
if (state.floor === null || startedAt > state.floor)
|
|
11692
|
+
state.floor = startedAt;
|
|
11693
|
+
state.seen.delete(runId);
|
|
11694
|
+
excess -= 1;
|
|
11695
|
+
}
|
|
11696
|
+
}
|
|
11697
|
+
async function collectUnprinted(client, path, query, state, pageSize, maxPages) {
|
|
11698
|
+
const rows = [];
|
|
11699
|
+
let cursor = null;
|
|
11700
|
+
let truncated = false;
|
|
11701
|
+
for (let page = 0;page < maxPages; page += 1) {
|
|
11702
|
+
const response = await client.request(path, {
|
|
11703
|
+
query: { ...query, limit: pageSize, cursor }
|
|
11704
|
+
});
|
|
11705
|
+
const page_rows = response?.data ?? [];
|
|
11706
|
+
const unprinted = page_rows.filter((row) => isUnprinted(state, row));
|
|
11707
|
+
rows.push(...unprinted);
|
|
11708
|
+
cursor = response?.nextCursor ?? null;
|
|
11709
|
+
if (!cursor || page_rows.length === 0 || unprinted.length < page_rows.length)
|
|
11710
|
+
break;
|
|
11711
|
+
if (page === maxPages - 1)
|
|
11712
|
+
truncated = true;
|
|
11713
|
+
}
|
|
11714
|
+
return { rows, truncated };
|
|
11715
|
+
}
|
|
11716
|
+
function isTransient(error) {
|
|
11717
|
+
if (!(error instanceof SimApiError))
|
|
11718
|
+
return false;
|
|
11719
|
+
if (error.status === 0 || error.status >= 500)
|
|
11720
|
+
return true;
|
|
11721
|
+
return RETRYABLE_CLIENT_STATUSES.has(error.status);
|
|
11722
|
+
}
|
|
11723
|
+
function nonNegativeInteger(raw, flag) {
|
|
11724
|
+
const value = Number(raw);
|
|
11725
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
11726
|
+
throw new SimApiError(`${flag} must be a non-negative integer`, 0);
|
|
11727
|
+
}
|
|
11728
|
+
return value;
|
|
11729
|
+
}
|
|
11730
|
+
function intervalMs(raw) {
|
|
11731
|
+
const seconds = Number(raw);
|
|
11732
|
+
if (!Number.isFinite(seconds) || seconds < MIN_INTERVAL_SECONDS) {
|
|
11733
|
+
throw new SimApiError(`--interval must be at least ${MIN_INTERVAL_SECONDS} seconds`, 0);
|
|
11734
|
+
}
|
|
11735
|
+
return Math.round(seconds * 1000);
|
|
11736
|
+
}
|
|
11737
|
+
function inSeconds(ms) {
|
|
11738
|
+
return Math.round(ms / 100) / 10;
|
|
11739
|
+
}
|
|
11740
|
+
function attachLogsFollow(logs) {
|
|
11741
|
+
logs.command("follow").description("Watch runs as they arrive, printing each new run once").option("--workflow <id>", "Only follow runs of this workflow (repeatable)", collect, []).option("--folder <path>", "Only follow runs of workflows in this folder (repeatable)", collect, []).option("--trigger <type>", "Only follow runs with this trigger type (repeatable)", collect, []).addOption(new Option("--level <level>", "Only follow runs at this severity").choices([
|
|
11742
|
+
...V2_OPERATIONS.listLogs.query.level.values
|
|
11743
|
+
])).addOption(new Option("--details <level>", "Response detail level; full names each run’s workflow").choices([...V2_OPERATIONS.listLogs.query.details.values]).default("full")).option("-n, --lines <count>", "Recent runs to print before watching", String(DEFAULT_BACKLOG)).option("--interval <seconds>", "Seconds between polls", String(DEFAULT_INTERVAL_SECONDS)).addHelpText("after", `
|
|
11744
|
+
Each run prints once, when it is first seen, so its status is the status it had
|
|
11745
|
+
at that moment. With --output json every run is a JSON object on its own line
|
|
11746
|
+
(JSONL) rather than a member of an array, because a follow never ends and so can
|
|
11747
|
+
never close one; --output yaml emits a --- separated document stream. Progress
|
|
11748
|
+
and retries go to stderr, leaving stdout a clean stream of rows. Ctrl-C stops the
|
|
11749
|
+
follow.
|
|
11750
|
+
|
|
11751
|
+
Examples:
|
|
11752
|
+
$ sim logs follow --level error
|
|
11753
|
+
$ sim logs follow --workflow wf_123 -n 0
|
|
11754
|
+
$ sim --output json logs follow | jq -r '.runId'
|
|
11755
|
+
`).action(async (options, command) => {
|
|
11756
|
+
const lines = nonNegativeInteger(options.lines, "--lines");
|
|
11757
|
+
const delay = intervalMs(options.interval);
|
|
11758
|
+
const { client, profile } = clientFrom(command);
|
|
11759
|
+
const path = V2_OPERATIONS.listLogs.path;
|
|
11760
|
+
const query = {
|
|
11761
|
+
workspaceId: client.requireWorkspace(),
|
|
11762
|
+
workflowIds: options.workflow?.length ? options.workflow.join(",") : undefined,
|
|
11763
|
+
folderPaths: options.folder?.length ? options.folder.map(encodeFolderPath).join(",") : undefined,
|
|
11764
|
+
triggers: options.trigger?.length ? options.trigger.join(",") : undefined,
|
|
11765
|
+
level: options.level,
|
|
11766
|
+
details: options.details,
|
|
11767
|
+
order: "desc"
|
|
11768
|
+
};
|
|
11769
|
+
const write = createWriter(profile.output);
|
|
11770
|
+
const status = followStatus();
|
|
11771
|
+
const interrupt = watchForInterrupt();
|
|
11772
|
+
const state = { seen: new Map, floor: null };
|
|
11773
|
+
try {
|
|
11774
|
+
const seed = await collectUnprinted(client, path, query, state, Math.max(lines, 1), 1);
|
|
11775
|
+
remember(state, seed.rows);
|
|
11776
|
+
state.floor = seed.rows.at(-1)?.startedAt ?? null;
|
|
11777
|
+
if (seed.truncated && seed.rows.length < lines) {
|
|
11778
|
+
status.warn(`asked for ${lines} earlier runs but a page holds ${seed.rows.length}; following from there — see sim logs list for more`);
|
|
11779
|
+
}
|
|
11780
|
+
write(lines > 0 ? seed.rows.slice(0, lines).reverse() : []);
|
|
11781
|
+
let failures = 0;
|
|
11782
|
+
while (!interrupt.interrupted()) {
|
|
11783
|
+
await waitFor(failures === 0 ? delay : Math.min(delay * 2 ** failures, MAX_BACKOFF_MS), interrupt.interrupted);
|
|
11784
|
+
if (interrupt.interrupted())
|
|
11785
|
+
break;
|
|
11786
|
+
let fresh;
|
|
11787
|
+
try {
|
|
11788
|
+
fresh = await collectUnprinted(client, path, query, state, POLL_PAGE_SIZE, MAX_PAGES_PER_POLL);
|
|
11789
|
+
} catch (error) {
|
|
11790
|
+
if (!isTransient(error))
|
|
11791
|
+
throw error;
|
|
11792
|
+
failures += 1;
|
|
11793
|
+
const next = Math.min(delay * 2 ** failures, MAX_BACKOFF_MS);
|
|
11794
|
+
status.note(`poll failed (${error.message}); retrying in ${inSeconds(next)}s…`);
|
|
11795
|
+
continue;
|
|
11796
|
+
}
|
|
11797
|
+
failures = 0;
|
|
11798
|
+
status.clear();
|
|
11799
|
+
if (fresh.truncated) {
|
|
11800
|
+
status.warn(`more than ${MAX_PAGES_PER_POLL * POLL_PAGE_SIZE} runs arrived at once; older ones were skipped — see sim logs list`);
|
|
11801
|
+
}
|
|
11802
|
+
if (fresh.rows.length === 0)
|
|
11803
|
+
continue;
|
|
11804
|
+
remember(state, fresh.rows);
|
|
11805
|
+
write(fresh.rows.reverse());
|
|
11806
|
+
}
|
|
11807
|
+
} finally {
|
|
11808
|
+
status.clear();
|
|
11809
|
+
interrupt.dispose();
|
|
11810
|
+
}
|
|
11081
11811
|
});
|
|
11082
11812
|
}
|
|
11083
11813
|
|
|
@@ -11114,15 +11844,22 @@ function addFieldOption(command, operation, field, descriptor) {
|
|
|
11114
11844
|
const placeholder = takesList ? "<value...>" : wantsJson ? "<json|@file>" : "<value>";
|
|
11115
11845
|
const choices = flag.choices ?? descriptor.values;
|
|
11116
11846
|
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)" : ""}`;
|
|
11847
|
+
const renamedFrom = flag.renamedFrom ?? [];
|
|
11117
11848
|
const option = new Option(`${short}--${name} ${placeholder}`, describe);
|
|
11118
11849
|
if (choices && !takesList)
|
|
11119
11850
|
option.choices([...choices]);
|
|
11120
11851
|
if (descriptor.default !== undefined && field !== "limit") {
|
|
11121
11852
|
option.default(undefined, String(descriptor.default));
|
|
11122
11853
|
}
|
|
11123
|
-
if (descriptor.required)
|
|
11854
|
+
if (descriptor.required && renamedFrom.length === 0)
|
|
11124
11855
|
option.makeOptionMandatory();
|
|
11125
11856
|
command.addOption(option);
|
|
11857
|
+
for (const previous of renamedFrom) {
|
|
11858
|
+
const retired = new Option(`--${previous} ${placeholder}`).hideHelp();
|
|
11859
|
+
if (choices && !takesList)
|
|
11860
|
+
retired.choices([...choices]);
|
|
11861
|
+
command.addOption(retired);
|
|
11862
|
+
}
|
|
11126
11863
|
}
|
|
11127
11864
|
function addOperationOptions(command, operation, commandSpec, operationSpec) {
|
|
11128
11865
|
for (const param of operationSpec.pathParams) {
|
|
@@ -11158,16 +11895,19 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
|
|
|
11158
11895
|
}
|
|
11159
11896
|
}
|
|
11160
11897
|
if (commandSpec.confirm) {
|
|
11161
|
-
command.option("-y, --yes", "
|
|
11898
|
+
command.option("-y, --yes", "Confirm this destructive operation (required)");
|
|
11162
11899
|
}
|
|
11163
11900
|
}
|
|
11164
11901
|
|
|
11165
11902
|
// src/commands/protocol/resource-directory.ts
|
|
11166
|
-
var
|
|
11903
|
+
var COLUMNS2 = [
|
|
11167
11904
|
{ header: "kind", value: (entry) => text(entry.kind) },
|
|
11168
11905
|
{ header: "name", value: (entry) => text(entry.name) },
|
|
11169
|
-
{
|
|
11170
|
-
|
|
11906
|
+
{
|
|
11907
|
+
header: "ref",
|
|
11908
|
+
value: (entry) => text(entry.kind === "folder" ? decodeFolderPath(entry.ref) : entry.ref)
|
|
11909
|
+
},
|
|
11910
|
+
{ header: "folder", value: (entry) => text(decodeFolderPath(entry.folderPath)) },
|
|
11171
11911
|
{ header: "updated", value: (entry) => timestamp2(entry.updatedAt) }
|
|
11172
11912
|
];
|
|
11173
11913
|
function operationPath(operation) {
|
|
@@ -11218,7 +11958,7 @@ function attachResourceDirectoryCommands(group, config) {
|
|
|
11218
11958
|
throw new SimApiError("--limit must be a non-negative integer", 0);
|
|
11219
11959
|
}
|
|
11220
11960
|
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
11221
|
-
const folderPath = path ?? "/";
|
|
11961
|
+
const folderPath = encodeFolderPath(path ?? "/");
|
|
11222
11962
|
const { client, profile } = clientFrom(command);
|
|
11223
11963
|
const workspaceId = client.requireWorkspace();
|
|
11224
11964
|
const [folders, resources] = await Promise.all([
|
|
@@ -11226,14 +11966,14 @@ function attachResourceDirectoryCommands(group, config) {
|
|
|
11226
11966
|
listResources(client, config, workspaceId, folderPath, options.search, limit)
|
|
11227
11967
|
]);
|
|
11228
11968
|
const entries = entriesFor(config, folders, resources);
|
|
11229
|
-
printList(profile.output, entries.slice(0, limit),
|
|
11969
|
+
printList(profile.output, entries.slice(0, limit), COLUMNS2);
|
|
11230
11970
|
});
|
|
11231
11971
|
group.command("mkdir").argument("<path>", "Folder path to create; the leading / is optional").allowExcessArguments(false).description(`Create a ${config.kind} directory at a path`).action(async (path, _options, command) => {
|
|
11232
11972
|
const { client, profile } = clientFrom(command);
|
|
11233
11973
|
const operation = V2_OPERATIONS[config.createFolder];
|
|
11234
11974
|
const result = await client.request(operation.path, {
|
|
11235
11975
|
method: operation.method,
|
|
11236
|
-
body: { workspaceId: client.requireWorkspace(), path }
|
|
11976
|
+
body: { workspaceId: client.requireWorkspace(), path: encodeFolderPath(path) }
|
|
11237
11977
|
});
|
|
11238
11978
|
renderResult(config.createFolder, profile.output, result.data ?? result, {});
|
|
11239
11979
|
});
|
|
@@ -11255,17 +11995,17 @@ function jsonFlag(raw, flagName, kind) {
|
|
|
11255
11995
|
}
|
|
11256
11996
|
async function watchImport(client, workspaceId, job) {
|
|
11257
11997
|
let current = job;
|
|
11258
|
-
let
|
|
11998
|
+
let reported2 = -1;
|
|
11259
11999
|
while (!IMPORT_SETTLED.has(current.status)) {
|
|
11260
12000
|
await sleep2(IMPORT_POLL_MS);
|
|
11261
12001
|
const next = await client.request(`/api/v2/tables/imports/${encodeURIComponent(current.id)}`, { query: { workspaceId } });
|
|
11262
12002
|
current = next.data;
|
|
11263
|
-
if (process.stderr.isTTY && current.rowsProcessed !==
|
|
11264
|
-
|
|
11265
|
-
process.stderr.write(`\r${source_default.dim(`${current.status}… ${
|
|
12003
|
+
if (process.stderr.isTTY && current.rowsProcessed !== reported2) {
|
|
12004
|
+
reported2 = current.rowsProcessed;
|
|
12005
|
+
process.stderr.write(`\r${source_default.dim(`${current.status}… ${reported2} rows`)}\x1B[K`);
|
|
11266
12006
|
}
|
|
11267
12007
|
}
|
|
11268
|
-
if (process.stderr.isTTY &&
|
|
12008
|
+
if (process.stderr.isTTY && reported2 >= 0)
|
|
11269
12009
|
process.stderr.write("\r\x1B[K");
|
|
11270
12010
|
return current;
|
|
11271
12011
|
}
|
|
@@ -11287,7 +12027,7 @@ function validateTargetOptions(options) {
|
|
|
11287
12027
|
return intoExisting;
|
|
11288
12028
|
}
|
|
11289
12029
|
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) => {
|
|
12030
|
+
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
12031
|
const { client, profile } = clientFrom(command);
|
|
11292
12032
|
const workspaceId = client.requireWorkspace();
|
|
11293
12033
|
if (Boolean(path) === Boolean(options.fileId)) {
|
|
@@ -11312,7 +12052,7 @@ function attachTableImport(tables) {
|
|
|
11312
12052
|
target = {
|
|
11313
12053
|
type: "new",
|
|
11314
12054
|
name,
|
|
11315
|
-
...options.folder !== undefined ? { folderPath: options.folder } : {}
|
|
12055
|
+
...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
|
|
11316
12056
|
};
|
|
11317
12057
|
}
|
|
11318
12058
|
const started = await client.request(V2_OPERATIONS.createTableImport.path, {
|
|
@@ -11360,6 +12100,427 @@ function attachTableImport(tables) {
|
|
|
11360
12100
|
});
|
|
11361
12101
|
}
|
|
11362
12102
|
|
|
12103
|
+
// src/runtime/renamed.ts
|
|
12104
|
+
var warned = new Set;
|
|
12105
|
+
function warn(kind, from, to) {
|
|
12106
|
+
const key = `${kind}:${from}`;
|
|
12107
|
+
if (warned.has(key))
|
|
12108
|
+
return;
|
|
12109
|
+
warned.add(key);
|
|
12110
|
+
process.stderr.write(`warning: ${kind} "${from}" has been renamed to "${to}". The old name still works.
|
|
12111
|
+
`);
|
|
12112
|
+
}
|
|
12113
|
+
function warnRenamedCommand(from, to) {
|
|
12114
|
+
warn("command", `sim ${from}`, `sim ${to}`);
|
|
12115
|
+
}
|
|
12116
|
+
function warnRenamedFlag(from, to) {
|
|
12117
|
+
warn("flag", `--${from}`, `--${to}`);
|
|
12118
|
+
}
|
|
12119
|
+
|
|
12120
|
+
// src/runtime/execute.ts
|
|
12121
|
+
function cursorSlot(operationSpec) {
|
|
12122
|
+
if (operationSpec.query && "cursor" in operationSpec.query)
|
|
12123
|
+
return "query";
|
|
12124
|
+
if (operationSpec.body && "cursor" in operationSpec.body)
|
|
12125
|
+
return "body";
|
|
12126
|
+
return null;
|
|
12127
|
+
}
|
|
12128
|
+
function foldRenamedFlags(operation, commandSpec, flags) {
|
|
12129
|
+
for (const [field, flag] of Object.entries(commandSpec.flags ?? {})) {
|
|
12130
|
+
if (!flag.renamedFrom?.length)
|
|
12131
|
+
continue;
|
|
12132
|
+
const current = flagNameFor(operation, field);
|
|
12133
|
+
for (const previous of flag.renamedFrom) {
|
|
12134
|
+
const supplied = flags[camel(previous)];
|
|
12135
|
+
if (supplied === undefined)
|
|
12136
|
+
continue;
|
|
12137
|
+
if (flags[camel(current)] !== undefined) {
|
|
12138
|
+
throw new SimApiError(`--${previous} is the former name of --${current}; pass one, not both`, 0);
|
|
12139
|
+
}
|
|
12140
|
+
warnRenamedFlag(previous, current);
|
|
12141
|
+
flags[camel(current)] = supplied;
|
|
12142
|
+
}
|
|
12143
|
+
}
|
|
12144
|
+
}
|
|
12145
|
+
async function executeOperation(operation, commandSpec, operationSpec, invocation) {
|
|
12146
|
+
const host = invocation[invocation.length - 1];
|
|
12147
|
+
const inheritedFlags = host.optsWithGlobals();
|
|
12148
|
+
const flags = {
|
|
12149
|
+
...inheritedFlags.workspace === undefined ? {} : { workspace: inheritedFlags.workspace },
|
|
12150
|
+
...inheritedFlags.allWorkspaces === undefined ? {} : { allWorkspaces: inheritedFlags.allWorkspaces },
|
|
12151
|
+
...invocation[invocation.length - 2]
|
|
12152
|
+
};
|
|
12153
|
+
const pathPositionalCount = operationSpec.pathParams.filter((param) => !commandSpec.pathFlags?.[param] && !isProfileWorkspacePath(commandSpec, param)).length;
|
|
12154
|
+
const positional = invocation.slice(0, pathPositionalCount);
|
|
12155
|
+
const requestFlags = { ...flags };
|
|
12156
|
+
for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
|
|
12157
|
+
requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
|
|
12158
|
+
}
|
|
12159
|
+
foldRenamedFlags(operation, commandSpec, requestFlags);
|
|
12160
|
+
if (commandSpec.confirm && !requestFlags.yes) {
|
|
12161
|
+
throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
|
|
12162
|
+
}
|
|
12163
|
+
if (commandSpec.allWorkspaces && requestFlags.allWorkspaces && requestFlags.workspace) {
|
|
12164
|
+
throw new SimApiError("--all-workspaces cannot be combined with --workspace", 0);
|
|
12165
|
+
}
|
|
12166
|
+
const { client, profile } = clientFrom(host);
|
|
12167
|
+
const hasWorkspaceField = Boolean(operationSpec.query && PROFILE_INJECTED_FIELD in operationSpec.query || operationSpec.body && PROFILE_INJECTED_FIELD in operationSpec.body);
|
|
12168
|
+
const omitsWorkspace = commandSpec.allWorkspaces && requestFlags.allWorkspaces === true;
|
|
12169
|
+
const request = buildRequest(operation, positional, requestFlags, hasWorkspaceField && !omitsWorkspace ? client.requireWorkspace() : profile.workspaceId);
|
|
12170
|
+
const paging = cursorSlot(operationSpec);
|
|
12171
|
+
if (paging) {
|
|
12172
|
+
const rawLimit = Number.parseInt(String(requestFlags.limit ?? DEFAULT_LIMIT), 10);
|
|
12173
|
+
if (Number.isNaN(rawLimit) || rawLimit < 0) {
|
|
12174
|
+
throw new SimApiError("--limit must be a non-negative number", 0);
|
|
12175
|
+
}
|
|
12176
|
+
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
12177
|
+
const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
12178
|
+
const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
|
|
12179
|
+
const rows = [];
|
|
12180
|
+
const progress = pageProgress();
|
|
12181
|
+
let cursor = null;
|
|
12182
|
+
try {
|
|
12183
|
+
do {
|
|
12184
|
+
const page = await client.request(request.path, {
|
|
12185
|
+
method: operationSpec.method,
|
|
12186
|
+
query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
|
|
12187
|
+
body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
|
|
12188
|
+
});
|
|
12189
|
+
rows.push(...page.data);
|
|
12190
|
+
cursor = page.nextCursor;
|
|
12191
|
+
if (cursor && rows.length < limit)
|
|
12192
|
+
progress.advance(rows.length);
|
|
12193
|
+
} while (cursor && rows.length < limit);
|
|
12194
|
+
} finally {
|
|
12195
|
+
progress.finish();
|
|
12196
|
+
}
|
|
12197
|
+
renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
|
|
12198
|
+
return;
|
|
12199
|
+
}
|
|
12200
|
+
const result = await client.request(request.path, {
|
|
12201
|
+
method: operationSpec.method,
|
|
12202
|
+
query: request.query,
|
|
12203
|
+
body: request.body
|
|
12204
|
+
});
|
|
12205
|
+
renderResult(operation, profile.output, result?.data ?? result, commandSpec, {
|
|
12206
|
+
expandedTrace: requestFlags.trace === true
|
|
12207
|
+
});
|
|
12208
|
+
}
|
|
12209
|
+
|
|
12210
|
+
// src/commands/protocol/workflow-run-follow.ts
|
|
12211
|
+
var AGENT_STREAM_PROTOCOL_HEADER = "x-sim-stream-protocol";
|
|
12212
|
+
var AGENT_STREAM_PROTOCOL_V1 = "agent-events-v1";
|
|
12213
|
+
var DONE_SENTINEL = "[DONE]";
|
|
12214
|
+
function isRecord(value) {
|
|
12215
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12216
|
+
}
|
|
12217
|
+
function stringField(frame, key) {
|
|
12218
|
+
const value = frame[key];
|
|
12219
|
+
return typeof value === "string" ? value : null;
|
|
12220
|
+
}
|
|
12221
|
+
async function* sseData(body) {
|
|
12222
|
+
const reader = body.getReader();
|
|
12223
|
+
const decoder = new TextDecoder;
|
|
12224
|
+
let buffer = "";
|
|
12225
|
+
try {
|
|
12226
|
+
while (true) {
|
|
12227
|
+
const { done, value } = await reader.read();
|
|
12228
|
+
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
12229
|
+
const lines = buffer.split(`
|
|
12230
|
+
`);
|
|
12231
|
+
buffer = done ? "" : lines.pop() ?? "";
|
|
12232
|
+
for (const rawLine of lines) {
|
|
12233
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
12234
|
+
if (!line.startsWith("data:"))
|
|
12235
|
+
continue;
|
|
12236
|
+
const payload = line.slice(5).startsWith(" ") ? line.slice(6) : line.slice(5);
|
|
12237
|
+
if (payload.length > 0)
|
|
12238
|
+
yield payload;
|
|
12239
|
+
}
|
|
12240
|
+
if (done)
|
|
12241
|
+
return;
|
|
12242
|
+
}
|
|
12243
|
+
} finally {
|
|
12244
|
+
reader.releaseLock();
|
|
12245
|
+
}
|
|
12246
|
+
}
|
|
12247
|
+
|
|
12248
|
+
class Commentary {
|
|
12249
|
+
sink;
|
|
12250
|
+
atLineStart = true;
|
|
12251
|
+
constructor(sink) {
|
|
12252
|
+
this.sink = sink;
|
|
12253
|
+
}
|
|
12254
|
+
inline(text2) {
|
|
12255
|
+
if (text2.length === 0)
|
|
12256
|
+
return;
|
|
12257
|
+
this.sink.write(text2);
|
|
12258
|
+
this.atLineStart = text2.endsWith(`
|
|
12259
|
+
`);
|
|
12260
|
+
}
|
|
12261
|
+
line(text2) {
|
|
12262
|
+
this.sink.write(`${this.atLineStart ? "" : `
|
|
12263
|
+
`}${text2}
|
|
12264
|
+
`);
|
|
12265
|
+
this.atLineStart = true;
|
|
12266
|
+
}
|
|
12267
|
+
endLine() {
|
|
12268
|
+
if (this.atLineStart)
|
|
12269
|
+
return;
|
|
12270
|
+
this.sink.write(`
|
|
12271
|
+
`);
|
|
12272
|
+
this.atLineStart = true;
|
|
12273
|
+
}
|
|
12274
|
+
}
|
|
12275
|
+
function toolNotice(frame) {
|
|
12276
|
+
const name = safeOneLine(stringField(frame, "name") ?? "tool");
|
|
12277
|
+
if (frame.phase === "start")
|
|
12278
|
+
return source_default.dim(`→ ${name}`);
|
|
12279
|
+
const status = stringField(frame, "status");
|
|
12280
|
+
if (status && status !== "success")
|
|
12281
|
+
return source_default.yellow(`✗ ${name} (${safeOneLine(status)})`);
|
|
12282
|
+
return source_default.dim(`✓ ${name}`);
|
|
12283
|
+
}
|
|
12284
|
+
async function renderRunStream(body, options) {
|
|
12285
|
+
const commentary = new Commentary(options.stderr);
|
|
12286
|
+
let final = null;
|
|
12287
|
+
for await (const payload of sseData(body)) {
|
|
12288
|
+
let frame;
|
|
12289
|
+
try {
|
|
12290
|
+
frame = JSON.parse(payload);
|
|
12291
|
+
} catch {
|
|
12292
|
+
continue;
|
|
12293
|
+
}
|
|
12294
|
+
if (frame === DONE_SENTINEL)
|
|
12295
|
+
break;
|
|
12296
|
+
if (!isRecord(frame))
|
|
12297
|
+
continue;
|
|
12298
|
+
if (frame.event === undefined && typeof frame.chunk === "string") {
|
|
12299
|
+
commentary.inline(sanitize(frame.chunk));
|
|
12300
|
+
continue;
|
|
12301
|
+
}
|
|
12302
|
+
switch (frame.event) {
|
|
12303
|
+
case "chunk_reset":
|
|
12304
|
+
commentary.line(source_default.dim("… retracted; that turn resolved to tool calls"));
|
|
12305
|
+
break;
|
|
12306
|
+
case "thinking":
|
|
12307
|
+
if (options.includeThinking && typeof frame.data === "string") {
|
|
12308
|
+
commentary.inline(source_default.dim(sanitize(frame.data)));
|
|
12309
|
+
}
|
|
12310
|
+
break;
|
|
12311
|
+
case "tool":
|
|
12312
|
+
if (options.includeToolCalls)
|
|
12313
|
+
commentary.line(toolNotice(frame));
|
|
12314
|
+
break;
|
|
12315
|
+
case "stream_error":
|
|
12316
|
+
commentary.line(source_default.yellow(`warning: ${safeOneLine(stringField(frame, "error") ?? "stream read failed")}`));
|
|
12317
|
+
break;
|
|
12318
|
+
case "error":
|
|
12319
|
+
commentary.endLine();
|
|
12320
|
+
throw new SimApiError(safeOneLine(stringField(frame, "error") ?? "The workflow run failed."), 0);
|
|
12321
|
+
case "final":
|
|
12322
|
+
if (isRecord(frame.data))
|
|
12323
|
+
final = frame.data;
|
|
12324
|
+
break;
|
|
12325
|
+
default:
|
|
12326
|
+
break;
|
|
12327
|
+
}
|
|
12328
|
+
}
|
|
12329
|
+
commentary.endLine();
|
|
12330
|
+
if (!final) {
|
|
12331
|
+
throw new SimApiError("The run stream ended before the workflow reported a result. The run may still be in progress — check: sim workflows runs list", 0);
|
|
12332
|
+
}
|
|
12333
|
+
return final;
|
|
12334
|
+
}
|
|
12335
|
+
async function followRun(workflowId, command) {
|
|
12336
|
+
const flags = command.optsWithGlobals();
|
|
12337
|
+
if (flags.async === true) {
|
|
12338
|
+
throw new SimApiError("--follow streams a run as it happens and --async returns before it starts; pass one, not both", 0);
|
|
12339
|
+
}
|
|
12340
|
+
const includeThinking = flags.includeThinking === true;
|
|
12341
|
+
const includeToolCalls = flags.includeToolCalls === true;
|
|
12342
|
+
const negotiates = includeThinking || includeToolCalls;
|
|
12343
|
+
const { client, profile } = clientFrom(command);
|
|
12344
|
+
const operation = V2_OPERATIONS.executeWorkflow;
|
|
12345
|
+
const request = buildRequest("executeWorkflow", [workflowId], flags, profile.workspaceId);
|
|
12346
|
+
const response = await client.requestRaw(request.path, {
|
|
12347
|
+
method: "POST",
|
|
12348
|
+
query: request.query,
|
|
12349
|
+
body: {
|
|
12350
|
+
...request.body ?? {},
|
|
12351
|
+
stream: true,
|
|
12352
|
+
...includeThinking ? { includeThinking: true } : {},
|
|
12353
|
+
...includeToolCalls ? { includeToolCalls: true } : {}
|
|
12354
|
+
},
|
|
12355
|
+
headers: {
|
|
12356
|
+
accept: "text/event-stream",
|
|
12357
|
+
...negotiates ? { [AGENT_STREAM_PROTOCOL_HEADER]: AGENT_STREAM_PROTOCOL_V1 } : {}
|
|
12358
|
+
}
|
|
12359
|
+
});
|
|
12360
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
12361
|
+
if (!contentType.toLowerCase().includes("text/event-stream")) {
|
|
12362
|
+
await response.body?.cancel();
|
|
12363
|
+
throw new SimApiError(`${operation.path} answered ${contentType || "an unknown content type"} instead of an event stream. This deployment may predate streaming runs — re-run without --follow.`, response.status);
|
|
12364
|
+
}
|
|
12365
|
+
if (!response.body) {
|
|
12366
|
+
throw new SimApiError("The run stream had no body.", response.status);
|
|
12367
|
+
}
|
|
12368
|
+
const final = await renderRunStream(response.body, {
|
|
12369
|
+
includeThinking,
|
|
12370
|
+
includeToolCalls,
|
|
12371
|
+
stderr: process.stderr
|
|
12372
|
+
});
|
|
12373
|
+
renderResult("executeWorkflow", profile.output, final, CLI_CONTRACT.executeWorkflow ?? {});
|
|
12374
|
+
if (final.success === false) {
|
|
12375
|
+
throw new SimApiError(safeOneLine(typeof final.error === "string" ? final.error : "The workflow run failed."), 0);
|
|
12376
|
+
}
|
|
12377
|
+
}
|
|
12378
|
+
function followOrDelegate(previous) {
|
|
12379
|
+
return async (workflowId, _options, command) => {
|
|
12380
|
+
const flags = command.optsWithGlobals();
|
|
12381
|
+
if (flags.follow !== true) {
|
|
12382
|
+
if (flags.includeThinking === true || flags.includeToolCalls === true) {
|
|
12383
|
+
throw new SimApiError("--include-thinking and --include-tool-calls describe a stream; add --follow", 0);
|
|
12384
|
+
}
|
|
12385
|
+
if (previous) {
|
|
12386
|
+
await previous(command.processedArgs);
|
|
12387
|
+
return;
|
|
12388
|
+
}
|
|
12389
|
+
await executeOperation("executeWorkflow", CLI_CONTRACT.executeWorkflow ?? {}, V2_OPERATIONS.executeWorkflow, [workflowId, command.opts(), command]);
|
|
12390
|
+
return;
|
|
12391
|
+
}
|
|
12392
|
+
await followRun(workflowId, command);
|
|
12393
|
+
};
|
|
12394
|
+
}
|
|
12395
|
+
function attachWorkflowRunFollow(workflows) {
|
|
12396
|
+
const run = workflows.commands.find((command) => command.name() === "run");
|
|
12397
|
+
if (!run) {
|
|
12398
|
+
throw new Error("workflows run must be registered before --follow can be attached to it");
|
|
12399
|
+
}
|
|
12400
|
+
const held = run._actionHandler;
|
|
12401
|
+
const previous = typeof held === "function" ? held : null;
|
|
12402
|
+
run.option("--follow", "Stream the run as it happens; progress on stderr, result on stdout. The stream reports only success and output, so the result omits the run id and timings a non-streaming run returns").option("--include-thinking", "Show model reasoning while following (requires --follow)").option("--include-tool-calls", "Show tool calls while following (requires --follow)").action(followOrDelegate(previous));
|
|
12403
|
+
}
|
|
12404
|
+
|
|
12405
|
+
// src/commands/protocol/workflow-run-wait.ts
|
|
12406
|
+
var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
12407
|
+
var WAIT_EXIT_CODES = {
|
|
12408
|
+
completed: 0,
|
|
12409
|
+
failed: 1,
|
|
12410
|
+
cancelled: 2,
|
|
12411
|
+
paused: 3,
|
|
12412
|
+
timeout: 4
|
|
12413
|
+
};
|
|
12414
|
+
var FIRST_POLL_DELAY_MS = 2000;
|
|
12415
|
+
var MAX_POLL_DELAY_MS = 15000;
|
|
12416
|
+
var POLL_BACKOFF_FACTOR = 2;
|
|
12417
|
+
var DEFAULT_WAIT_TIMEOUT_SECONDS = 3600;
|
|
12418
|
+
var WAIT_TIMEOUT_FLAG = "--wait-timeout <seconds>";
|
|
12419
|
+
function isRecord2(value) {
|
|
12420
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12421
|
+
}
|
|
12422
|
+
function optionalString(value) {
|
|
12423
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
12424
|
+
}
|
|
12425
|
+
function readRun(raw) {
|
|
12426
|
+
const run = isRecord2(raw) && isRecord2(raw.data) ? raw.data : raw;
|
|
12427
|
+
if (!isRecord2(run) || typeof run.status !== "string") {
|
|
12428
|
+
throw new SimApiError("Run status response carried no status.", 0);
|
|
12429
|
+
}
|
|
12430
|
+
const paused = isRecord2(run.paused) ? run.paused : null;
|
|
12431
|
+
return {
|
|
12432
|
+
status: run.status,
|
|
12433
|
+
pauseKind: paused ? optionalString(paused.pauseKind) : null,
|
|
12434
|
+
resumeAt: paused ? optionalString(paused.resumeAt) : null,
|
|
12435
|
+
contextId: paused ? optionalString(paused.contextId) : null
|
|
12436
|
+
};
|
|
12437
|
+
}
|
|
12438
|
+
function classify(snapshot) {
|
|
12439
|
+
if (snapshot.status === "paused")
|
|
12440
|
+
return snapshot.pauseKind === "time" ? null : "paused";
|
|
12441
|
+
if (!TERMINAL_STATUSES.has(snapshot.status))
|
|
12442
|
+
return null;
|
|
12443
|
+
return snapshot.status === "completed" ? "completed" : snapshot.status === "cancelled" ? "cancelled" : "failed";
|
|
12444
|
+
}
|
|
12445
|
+
function waitProgress() {
|
|
12446
|
+
let reported2 = false;
|
|
12447
|
+
return {
|
|
12448
|
+
advance: (status, elapsedMs) => {
|
|
12449
|
+
if (!process.stderr.isTTY)
|
|
12450
|
+
return;
|
|
12451
|
+
reported2 = true;
|
|
12452
|
+
process.stderr.write(`\r${source_default.dim(`${status} — waiting ${Math.round(elapsedMs / 1000)}s…`)}\x1B[K`);
|
|
12453
|
+
},
|
|
12454
|
+
finish: () => {
|
|
12455
|
+
if (!reported2)
|
|
12456
|
+
return;
|
|
12457
|
+
reported2 = false;
|
|
12458
|
+
process.stderr.write("\r\x1B[K");
|
|
12459
|
+
}
|
|
12460
|
+
};
|
|
12461
|
+
}
|
|
12462
|
+
function parseWaitTimeout(raw) {
|
|
12463
|
+
const seconds = Number(raw);
|
|
12464
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
12465
|
+
throw new SimApiError(`Invalid ${WAIT_TIMEOUT_FLAG} "${raw}". Use a non-negative number of seconds, or 0 to wait indefinitely.`, 0);
|
|
12466
|
+
}
|
|
12467
|
+
return seconds;
|
|
12468
|
+
}
|
|
12469
|
+
function explain(outcome, runId, workflowId, snapshot) {
|
|
12470
|
+
if (outcome === "completed")
|
|
12471
|
+
return null;
|
|
12472
|
+
if (outcome === "failed")
|
|
12473
|
+
return `Run ${runId} failed.`;
|
|
12474
|
+
if (outcome === "cancelled")
|
|
12475
|
+
return `Run ${runId} was cancelled.`;
|
|
12476
|
+
const context = snapshot.contextId ? ` --context ${snapshot.contextId}` : "";
|
|
12477
|
+
return `Run ${runId} is paused waiting for input. Resume it: sim workflows runs resume ${runId} --workflow ${workflowId}${context}`;
|
|
12478
|
+
}
|
|
12479
|
+
function runSpec() {
|
|
12480
|
+
return CLI_CONTRACT.getWorkflowRun ?? {};
|
|
12481
|
+
}
|
|
12482
|
+
function attachWorkflowRunWait(runs) {
|
|
12483
|
+
runs.command("wait").argument("<runId>", V2_OPERATIONS.getWorkflowRun.pathParamDocs?.runId).description("Wait for a run to reach a terminal state, then show it").addOption(new Option("--workflow <workflowId>", "Workflow ID (required)").makeOptionMandatory()).addOption(new Option(WAIT_TIMEOUT_FLAG, `Give up after this many seconds, or 0 to wait indefinitely (default: ${DEFAULT_WAIT_TIMEOUT_SECONDS}). Bounds the whole wait; SIM_TIMEOUT_SECONDS bounds one request`)).action(async (runId, options, command) => {
|
|
12484
|
+
const timeoutSeconds = options.waitTimeout === undefined ? DEFAULT_WAIT_TIMEOUT_SECONDS : parseWaitTimeout(options.waitTimeout);
|
|
12485
|
+
const { client, profile } = clientFrom(command);
|
|
12486
|
+
const operation = V2_OPERATIONS.getWorkflowRun;
|
|
12487
|
+
const path = resolvePath(operation.path, { id: options.workflow, runId });
|
|
12488
|
+
const startedAt = Date.now();
|
|
12489
|
+
const deadline = timeoutSeconds === 0 ? Number.POSITIVE_INFINITY : startedAt + timeoutSeconds * 1000;
|
|
12490
|
+
const progress = waitProgress();
|
|
12491
|
+
let delayMs = FIRST_POLL_DELAY_MS;
|
|
12492
|
+
try {
|
|
12493
|
+
while (true) {
|
|
12494
|
+
const raw = await client.request(path, { method: operation.method });
|
|
12495
|
+
const snapshot = readRun(raw);
|
|
12496
|
+
const outcome = classify(snapshot);
|
|
12497
|
+
if (outcome) {
|
|
12498
|
+
progress.finish();
|
|
12499
|
+
renderResult("getWorkflowRun", profile.output, raw, runSpec());
|
|
12500
|
+
const message = explain(outcome, runId, options.workflow, snapshot);
|
|
12501
|
+
if (message)
|
|
12502
|
+
console.error(source_default.red(message));
|
|
12503
|
+
process.exitCode = WAIT_EXIT_CODES[outcome];
|
|
12504
|
+
return;
|
|
12505
|
+
}
|
|
12506
|
+
const remainingMs = deadline - Date.now();
|
|
12507
|
+
if (remainingMs <= 0) {
|
|
12508
|
+
progress.finish();
|
|
12509
|
+
renderResult("getWorkflowRun", profile.output, raw, runSpec());
|
|
12510
|
+
console.error(source_default.red(`Timed out after ${timeoutSeconds}s waiting for run ${runId} (status: ${snapshot.status}${snapshot.resumeAt ? `, resuming at ${snapshot.resumeAt}` : ""}). Raise ${WAIT_TIMEOUT_FLAG}, or set it to 0 to wait indefinitely.`));
|
|
12511
|
+
process.exitCode = WAIT_EXIT_CODES.timeout;
|
|
12512
|
+
return;
|
|
12513
|
+
}
|
|
12514
|
+
progress.advance(snapshot.status, Date.now() - startedAt);
|
|
12515
|
+
await sleep(Math.min(delayMs, remainingMs));
|
|
12516
|
+
delayMs = Math.min(delayMs * POLL_BACKOFF_FACTOR, MAX_POLL_DELAY_MS);
|
|
12517
|
+
}
|
|
12518
|
+
} finally {
|
|
12519
|
+
progress.finish();
|
|
12520
|
+
}
|
|
12521
|
+
});
|
|
12522
|
+
}
|
|
12523
|
+
|
|
11363
12524
|
// src/commands/protocol/index.ts
|
|
11364
12525
|
function group(program2, name) {
|
|
11365
12526
|
const existing = program2.commands.find((command) => command.name() === name);
|
|
@@ -11395,12 +12556,16 @@ function attachProtocolCommands(program2) {
|
|
|
11395
12556
|
folders: "listTableFolders",
|
|
11396
12557
|
createFolder: "createTableFolder"
|
|
11397
12558
|
});
|
|
11398
|
-
|
|
12559
|
+
const workflows = group(program2, "workflows");
|
|
12560
|
+
attachResourceDirectoryCommands(workflows, {
|
|
11399
12561
|
kind: "workflow",
|
|
11400
12562
|
resources: "listWorkflows",
|
|
11401
12563
|
folders: "listWorkflowFolders",
|
|
11402
12564
|
createFolder: "createWorkflowFolder"
|
|
11403
12565
|
});
|
|
12566
|
+
attachWorkflowRunFollow(workflows);
|
|
12567
|
+
attachWorkflowRunWait(group(workflows, "runs"));
|
|
12568
|
+
attachLogsFollow(group(program2, "logs"));
|
|
11404
12569
|
}
|
|
11405
12570
|
|
|
11406
12571
|
// src/terminal/secret-input.ts
|
|
@@ -11481,7 +12646,8 @@ var SECRET_RESULT = {
|
|
|
11481
12646
|
{ header: "name" },
|
|
11482
12647
|
{ header: "scope" },
|
|
11483
12648
|
{ header: "role" },
|
|
11484
|
-
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
12649
|
+
{ header: "updated", path: "updatedAt", format: "timestamp" },
|
|
12650
|
+
{ header: "description" }
|
|
11485
12651
|
]
|
|
11486
12652
|
};
|
|
11487
12653
|
function validateSecretValue(value) {
|
|
@@ -11492,7 +12658,16 @@ function validateSecretValue(value) {
|
|
|
11492
12658
|
}
|
|
11493
12659
|
return value;
|
|
11494
12660
|
}
|
|
12661
|
+
function validateDescriptionScope(description, scope) {
|
|
12662
|
+
if (description === undefined)
|
|
12663
|
+
return;
|
|
12664
|
+
if (scope === "personal") {
|
|
12665
|
+
throw new SimApiError("--description is only supported for a workspace secret.", 0);
|
|
12666
|
+
}
|
|
12667
|
+
return description;
|
|
12668
|
+
}
|
|
11495
12669
|
async function setSecret(name, options, command) {
|
|
12670
|
+
const description = validateDescriptionScope(options.description, options.scope);
|
|
11496
12671
|
const value = validateSecretValue(options.value ?? await promptSecret());
|
|
11497
12672
|
const { client, profile } = clientFrom(command);
|
|
11498
12673
|
const operation = V2_OPERATIONS.setSecret;
|
|
@@ -11501,7 +12676,8 @@ async function setSecret(name, options, command) {
|
|
|
11501
12676
|
body: {
|
|
11502
12677
|
workspaceId: client.requireWorkspace(),
|
|
11503
12678
|
scope: options.scope,
|
|
11504
|
-
value
|
|
12679
|
+
value,
|
|
12680
|
+
description
|
|
11505
12681
|
}
|
|
11506
12682
|
});
|
|
11507
12683
|
renderResult("setSecret", profile.output, response.data, SECRET_RESULT);
|
|
@@ -11510,72 +12686,7 @@ function attachSecretCommands(program2) {
|
|
|
11510
12686
|
const secrets = program2.commands.find((command) => command.name() === "secrets");
|
|
11511
12687
|
if (!secrets)
|
|
11512
12688
|
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));
|
|
11514
|
-
}
|
|
11515
|
-
|
|
11516
|
-
// src/runtime/execute.ts
|
|
11517
|
-
function cursorSlot(operationSpec) {
|
|
11518
|
-
if (operationSpec.query && "cursor" in operationSpec.query)
|
|
11519
|
-
return "query";
|
|
11520
|
-
if (operationSpec.body && "cursor" in operationSpec.body)
|
|
11521
|
-
return "body";
|
|
11522
|
-
return null;
|
|
11523
|
-
}
|
|
11524
|
-
async function executeOperation(operation, commandSpec, operationSpec, invocation) {
|
|
11525
|
-
const host = invocation[invocation.length - 1];
|
|
11526
|
-
const inheritedFlags = host.optsWithGlobals();
|
|
11527
|
-
const flags = {
|
|
11528
|
-
...inheritedFlags.workspace === undefined ? {} : { workspace: inheritedFlags.workspace },
|
|
11529
|
-
...inheritedFlags.allWorkspaces === undefined ? {} : { allWorkspaces: inheritedFlags.allWorkspaces },
|
|
11530
|
-
...invocation[invocation.length - 2]
|
|
11531
|
-
};
|
|
11532
|
-
const pathPositionalCount = operationSpec.pathParams.filter((param) => !commandSpec.pathFlags?.[param] && !isProfileWorkspacePath(commandSpec, param)).length;
|
|
11533
|
-
const positional = invocation.slice(0, pathPositionalCount);
|
|
11534
|
-
const requestFlags = { ...flags };
|
|
11535
|
-
for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
|
|
11536
|
-
requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
|
|
11537
|
-
}
|
|
11538
|
-
if (commandSpec.confirm && !requestFlags.yes) {
|
|
11539
|
-
throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
|
|
11540
|
-
}
|
|
11541
|
-
if (commandSpec.allWorkspaces && requestFlags.allWorkspaces && requestFlags.workspace) {
|
|
11542
|
-
throw new SimApiError("--all-workspaces cannot be combined with --workspace", 0);
|
|
11543
|
-
}
|
|
11544
|
-
const { client, profile } = clientFrom(host);
|
|
11545
|
-
const hasWorkspaceField = Boolean(operationSpec.query && PROFILE_INJECTED_FIELD in operationSpec.query || operationSpec.body && PROFILE_INJECTED_FIELD in operationSpec.body);
|
|
11546
|
-
const omitsWorkspace = commandSpec.allWorkspaces && requestFlags.allWorkspaces === true;
|
|
11547
|
-
const request = buildRequest(operation, positional, requestFlags, hasWorkspaceField && !omitsWorkspace ? client.requireWorkspace() : profile.workspaceId);
|
|
11548
|
-
const paging = cursorSlot(operationSpec);
|
|
11549
|
-
if (paging) {
|
|
11550
|
-
const rawLimit = Number.parseInt(String(requestFlags.limit ?? DEFAULT_LIMIT), 10);
|
|
11551
|
-
if (Number.isNaN(rawLimit) || rawLimit < 0) {
|
|
11552
|
-
throw new SimApiError("--limit must be a non-negative number", 0);
|
|
11553
|
-
}
|
|
11554
|
-
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
11555
|
-
const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
11556
|
-
const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
|
|
11557
|
-
const rows = [];
|
|
11558
|
-
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);
|
|
11568
|
-
renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
|
|
11569
|
-
return;
|
|
11570
|
-
}
|
|
11571
|
-
const result = await client.request(request.path, {
|
|
11572
|
-
method: operationSpec.method,
|
|
11573
|
-
query: request.query,
|
|
11574
|
-
body: request.body
|
|
11575
|
-
});
|
|
11576
|
-
renderResult(operation, profile.output, result?.data ?? result, commandSpec, {
|
|
11577
|
-
expandedTrace: requestFlags.trace === true
|
|
11578
|
-
});
|
|
12689
|
+
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));
|
|
11579
12690
|
}
|
|
11580
12691
|
|
|
11581
12692
|
// src/runtime/build.ts
|
|
@@ -11693,6 +12804,19 @@ function configureOperation(command, operation, spec) {
|
|
|
11693
12804
|
function buildLeaf(operation, spec, leafName) {
|
|
11694
12805
|
return addMissingArgumentExample(configureOperation(new Command(leafName), operation, spec));
|
|
11695
12806
|
}
|
|
12807
|
+
function addRenamedCommand(groups, operation, spec, from, to) {
|
|
12808
|
+
const segments = from.split(" ");
|
|
12809
|
+
const [groupName, ...rest] = segments;
|
|
12810
|
+
if (rest.length === 0)
|
|
12811
|
+
throw new Error(`${operation}.renamedFrom "${from}" must include a verb`);
|
|
12812
|
+
let parent = groupFor(groups, groupName);
|
|
12813
|
+
for (const segment of rest.slice(0, -1)) {
|
|
12814
|
+
parent = nestedGroup(parent, segment, { hidden: true });
|
|
12815
|
+
}
|
|
12816
|
+
const leaf = buildLeaf(operation, spec, rest[rest.length - 1]);
|
|
12817
|
+
leaf.hook("preAction", () => warnRenamedCommand(from, to));
|
|
12818
|
+
parent.addCommand(leaf, { hidden: true });
|
|
12819
|
+
}
|
|
11696
12820
|
function groupFor(groups, name) {
|
|
11697
12821
|
const existing = groups.get(name);
|
|
11698
12822
|
if (existing)
|
|
@@ -11708,12 +12832,12 @@ function resourceLabel(name) {
|
|
|
11708
12832
|
const label = name.endsWith("s") ? name.slice(0, -1) : name;
|
|
11709
12833
|
return label.replaceAll("-", " ");
|
|
11710
12834
|
}
|
|
11711
|
-
function nestedGroup(parent, name) {
|
|
12835
|
+
function nestedGroup(parent, name, options = {}) {
|
|
11712
12836
|
const existing = parent.commands.find((candidate) => candidate.name() === name);
|
|
11713
12837
|
if (existing)
|
|
11714
12838
|
return existing;
|
|
11715
12839
|
const created = new Command(name).description(`Manage ${resourceLabel(parent.name())} ${name.replaceAll("-", " ")}`);
|
|
11716
|
-
parent.addCommand(created);
|
|
12840
|
+
parent.addCommand(created, { hidden: options.hidden });
|
|
11717
12841
|
return created;
|
|
11718
12842
|
}
|
|
11719
12843
|
function addLeafCommand(groups, operation, spec, segments) {
|
|
@@ -11742,6 +12866,7 @@ function variantCommandSpec(spec, variant) {
|
|
|
11742
12866
|
}
|
|
11743
12867
|
function buildGeneratedCommands() {
|
|
11744
12868
|
const groups = new Map;
|
|
12869
|
+
const renamed = [];
|
|
11745
12870
|
for (const operation of Object.keys(V2_OPERATIONS)) {
|
|
11746
12871
|
const spec = CLI_CONTRACT[operation] ?? {};
|
|
11747
12872
|
const operationSpec = V2_OPERATIONS[operation];
|
|
@@ -11764,6 +12889,12 @@ function buildGeneratedCommands() {
|
|
|
11764
12889
|
for (const variant of spec.variants ?? []) {
|
|
11765
12890
|
addLeafCommand(groups, operation, variantCommandSpec(spec, variant), variant.command.split(" "));
|
|
11766
12891
|
}
|
|
12892
|
+
for (const from of spec.renamedFrom ?? []) {
|
|
12893
|
+
renamed.push({ operation, spec, from, to: segments.join(" ") });
|
|
12894
|
+
}
|
|
12895
|
+
}
|
|
12896
|
+
for (const { operation, spec, from, to } of renamed) {
|
|
12897
|
+
addRenamedCommand(groups, operation, spec, from, to);
|
|
11767
12898
|
}
|
|
11768
12899
|
return [...groups.values()].sort((a, b) => a.name().localeCompare(b.name()));
|
|
11769
12900
|
}
|
|
@@ -11772,7 +12903,8 @@ function buildGeneratedCommands() {
|
|
|
11772
12903
|
var PROGRAM_DESCRIPTION = "Talk to the Sim API from your terminal";
|
|
11773
12904
|
var HELP_EPILOGUE = `
|
|
11774
12905
|
Profiles work like the AWS CLI: settings live in ~/.sim/config, keys in
|
|
11775
|
-
~/.sim/credentials (0600)
|
|
12906
|
+
~/.sim/credentials (0600), or under SIM_CONFIG_DIR when it is set. Select one
|
|
12907
|
+
with -P, --profile, or SIM_PROFILE.
|
|
11776
12908
|
|
|
11777
12909
|
Examples:
|
|
11778
12910
|
$ sim login Authorize the default profile
|
|
@@ -11786,18 +12918,11 @@ Examples:
|
|
|
11786
12918
|
$ sim workflows import --workflow @wf.json
|
|
11787
12919
|
$ sim whoami --profile dev
|
|
11788
12920
|
`;
|
|
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
12921
|
function buildProgram(options = {}) {
|
|
11797
12922
|
const program2 = new Command;
|
|
11798
12923
|
program2.name("sim").description(PROGRAM_DESCRIPTION);
|
|
11799
12924
|
if (options.version !== false)
|
|
11800
|
-
program2.version(
|
|
12925
|
+
program2.version(CLI_VERSION);
|
|
11801
12926
|
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
12927
|
program2.addCommand(loginCommand());
|
|
11803
12928
|
program2.addCommand(logoutCommand());
|
|
@@ -11823,6 +12948,10 @@ async function main() {
|
|
|
11823
12948
|
console.error(source_default.red(`Error: ${sanitize(error.message)}`));
|
|
11824
12949
|
process.exit(1);
|
|
11825
12950
|
}
|
|
12951
|
+
if (isRequestTimeout(error)) {
|
|
12952
|
+
console.error(source_default.red(`Error: the request timed out. ${RAISE_TIMEOUT_HINT}`));
|
|
12953
|
+
process.exit(1);
|
|
12954
|
+
}
|
|
11826
12955
|
if (error instanceof SimApiError) {
|
|
11827
12956
|
console.error(source_default.red(`Error: ${sanitize(error.message)}`));
|
|
11828
12957
|
if (error.code)
|