sim 2.0.0 → 2.1.0-dev.28.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 +1653 -506
- 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
|
},
|
|
@@ -7993,7 +8071,25 @@ var V2_OPERATIONS = {
|
|
|
7993
8071
|
},
|
|
7994
8072
|
folderPath: {
|
|
7995
8073
|
kind: "string",
|
|
7996
|
-
describe: "Restrict results to files
|
|
8074
|
+
describe: "Restrict results to files inside this folder — its direct children, or its whole subtree when `recursive` is true. A path that names no folder narrows the result to nothing, so the response is an empty page rather than an error."
|
|
8075
|
+
},
|
|
8076
|
+
recursive: {
|
|
8077
|
+
kind: "enum",
|
|
8078
|
+
values: [
|
|
8079
|
+
"true",
|
|
8080
|
+
"1",
|
|
8081
|
+
"yes",
|
|
8082
|
+
"on",
|
|
8083
|
+
"y",
|
|
8084
|
+
"enabled",
|
|
8085
|
+
"false",
|
|
8086
|
+
"0",
|
|
8087
|
+
"no",
|
|
8088
|
+
"off",
|
|
8089
|
+
"n",
|
|
8090
|
+
"disabled"
|
|
8091
|
+
],
|
|
8092
|
+
describe: "Whether the folder filter includes files in subfolders. Defaults to true when a search is set, false otherwise, so listing a folder shows that folder while searching one looks through everything in it. Ignored when no folder filter is set, which already spans the workspace. The listed spellings are the whole accepted vocabulary and are case-sensitive; any other value is rejected."
|
|
7997
8093
|
},
|
|
7998
8094
|
scope: {
|
|
7999
8095
|
kind: "enum",
|
|
@@ -8968,6 +9064,10 @@ var V2_OPERATIONS = {
|
|
|
8968
9064
|
kind: "string",
|
|
8969
9065
|
required: true,
|
|
8970
9066
|
describe: "Write-only secret value. It is never returned."
|
|
9067
|
+
},
|
|
9068
|
+
description: {
|
|
9069
|
+
kind: "string",
|
|
9070
|
+
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
9071
|
}
|
|
8972
9072
|
}
|
|
8973
9073
|
},
|
|
@@ -9380,71 +9480,328 @@ var V2_OPERATIONS = {
|
|
|
9380
9480
|
}
|
|
9381
9481
|
};
|
|
9382
9482
|
|
|
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"
|
|
9483
|
+
// src/commands/auth.ts
|
|
9484
|
+
function openBrowser(url) {
|
|
9485
|
+
const [command, args] = process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : [process.platform === "darwin" ? "open" : "xdg-open", [url]];
|
|
9486
|
+
try {
|
|
9487
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true });
|
|
9488
|
+
child.on("error", () => {});
|
|
9489
|
+
child.unref();
|
|
9490
|
+
} catch {}
|
|
9491
|
+
}
|
|
9492
|
+
function presentAuthentication(source) {
|
|
9493
|
+
switch (source) {
|
|
9494
|
+
case "flag":
|
|
9495
|
+
return { authenticated: true, source: "flag" };
|
|
9496
|
+
case "env":
|
|
9497
|
+
return { authenticated: true, source: "env" };
|
|
9498
|
+
case "credentials":
|
|
9499
|
+
return { authenticated: true, source: "credentials" };
|
|
9500
|
+
case "unset":
|
|
9501
|
+
return { authenticated: false, source: "unset" };
|
|
9502
|
+
case "config":
|
|
9503
|
+
case "default":
|
|
9504
|
+
throw new SimApiError(`Unexpected API key source "${source}".`, 0);
|
|
9408
9505
|
}
|
|
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
9506
|
}
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9507
|
+
async function confirmProfileOverwrite(profileName) {
|
|
9508
|
+
if (!process.stdin.isTTY) {
|
|
9509
|
+
throw new SimApiError(`Profile "${profileName}" already exists. Re-run with --yes to overwrite it.`, 0);
|
|
9510
|
+
}
|
|
9511
|
+
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
9512
|
+
try {
|
|
9513
|
+
const answer = await prompt.question(`Profile "${profileName}" already exists. Replace its API key and login defaults? (y/N) `);
|
|
9514
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
9515
|
+
} finally {
|
|
9516
|
+
prompt.close();
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9519
|
+
function loginCommand() {
|
|
9520
|
+
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) => {
|
|
9521
|
+
const profile = profileFrom(command);
|
|
9522
|
+
if (options.scope !== "platform" && options.scope !== "copilot") {
|
|
9523
|
+
throw new SimApiError(`Unknown scope "${options.scope}". Use platform or copilot.`, 0);
|
|
9524
|
+
}
|
|
9525
|
+
const scope = options.scope;
|
|
9526
|
+
if (readCredentialsProfile(profile.name).api_key && !options.yes) {
|
|
9527
|
+
const confirmed = await confirmProfileOverwrite(profile.name);
|
|
9528
|
+
if (!confirmed) {
|
|
9529
|
+
console.log(source_default.dim("Login cancelled; the existing profile was not changed."));
|
|
9530
|
+
return;
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
const auth = createAuthRequest();
|
|
9534
|
+
const url = buildApprovalUrl(profile.endpoint, auth, scope, profile.workspaceId ?? undefined);
|
|
9535
|
+
console.log(`Signing in to ${source_default.bold(profile.endpoint)} as profile ${source_default.bold(profile.name)}`);
|
|
9536
|
+
console.log(`
|
|
9537
|
+
Pairing code: ${source_default.bold(auth.pairing)}`);
|
|
9538
|
+
console.log(source_default.dim(`Confirm this code matches what the browser shows before approving.
|
|
9539
|
+
`));
|
|
9540
|
+
console.log(url);
|
|
9541
|
+
if (options.browser)
|
|
9542
|
+
openBrowser(url);
|
|
9543
|
+
console.log(source_default.dim(`
|
|
9544
|
+
Waiting for approval…`));
|
|
9545
|
+
const key = await pollForKey(profile.endpoint, auth);
|
|
9546
|
+
if (key.scope !== scope) {
|
|
9547
|
+
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);
|
|
9548
|
+
}
|
|
9549
|
+
writeCredentialsProfile(profile.name, key.apiKey);
|
|
9550
|
+
const settings = {
|
|
9551
|
+
endpoint: profile.endpoint,
|
|
9552
|
+
workspace: key.workspaceId ?? null
|
|
9553
|
+
};
|
|
9554
|
+
writeConfigProfile(profile.name, settings);
|
|
9555
|
+
console.log(source_default.green(`
|
|
9556
|
+
✓ Logged in. Key stored in ${credentialsPath()}`));
|
|
9557
|
+
if (key.workspaceBound && key.workspaceId) {
|
|
9558
|
+
console.log(source_default.dim(` Workspace-scoped key — it can only reach ${key.workspaceId}.`));
|
|
9559
|
+
} else if (key.workspaceId) {
|
|
9560
|
+
console.log(source_default.dim(` Personal key, defaulting to ${key.workspaceId}. Override per command with --workspace.`));
|
|
9561
|
+
} else {
|
|
9562
|
+
console.log(source_default.dim(" Personal key with no default workspace. Set one with: sim configure --set-workspace <id>"));
|
|
9563
|
+
}
|
|
9564
|
+
});
|
|
9565
|
+
}
|
|
9566
|
+
function logoutCommand() {
|
|
9567
|
+
return new Command("logout").description("Remove the profile's stored API key").option("--all", "Remove the profile entirely, including its settings").action((options, command) => {
|
|
9568
|
+
const profile = profileFrom(command);
|
|
9569
|
+
if (options.all) {
|
|
9570
|
+
const removed = deleteProfile(profile.name);
|
|
9571
|
+
if (!removed.config && !removed.credentials) {
|
|
9572
|
+
console.log(source_default.dim(`Nothing stored for profile "${profile.name}".`));
|
|
9573
|
+
return;
|
|
9574
|
+
}
|
|
9575
|
+
console.log(source_default.green(`✓ Removed profile "${profile.name}".`));
|
|
9576
|
+
return;
|
|
9577
|
+
}
|
|
9578
|
+
if (!readCredentialsProfile(profile.name).api_key) {
|
|
9579
|
+
console.log(source_default.dim(`No stored key for profile "${profile.name}".`));
|
|
9580
|
+
return;
|
|
9581
|
+
}
|
|
9582
|
+
writeCredentialsProfile(profile.name, null);
|
|
9583
|
+
console.log(source_default.green(`✓ Removed the stored key for profile "${profile.name}".`));
|
|
9584
|
+
console.log(source_default.dim(" The key itself is still active — revoke it in Settings → API keys."));
|
|
9585
|
+
});
|
|
9586
|
+
}
|
|
9587
|
+
var CREDENTIAL_VERDICT_STATUSES = new Set([401, 403, 404]);
|
|
9588
|
+
var WHOAMI_EXIT_CODES = {
|
|
9589
|
+
verified: 0,
|
|
9590
|
+
disabled: 0,
|
|
9591
|
+
unauthenticated: 1,
|
|
9592
|
+
rejected: 1,
|
|
9593
|
+
unreachable: 2,
|
|
9594
|
+
"no-workspace": 2
|
|
9595
|
+
};
|
|
9596
|
+
async function verifyProfile(client, profile) {
|
|
9597
|
+
if (!profile.apiKey) {
|
|
9598
|
+
return {
|
|
9599
|
+
status: "unauthenticated",
|
|
9600
|
+
workspace: null,
|
|
9601
|
+
detail: `no API key — run: sim login --profile ${profile.name}`
|
|
9602
|
+
};
|
|
9603
|
+
}
|
|
9604
|
+
if (!profile.workspaceId) {
|
|
9605
|
+
return {
|
|
9606
|
+
status: "no-workspace",
|
|
9607
|
+
workspace: null,
|
|
9608
|
+
detail: `no workspace to check against — run: sim configure --profile ${profile.name} --set-workspace <id>`
|
|
9609
|
+
};
|
|
9610
|
+
}
|
|
9611
|
+
const operation = V2_OPERATIONS.getWorkspace;
|
|
9612
|
+
try {
|
|
9613
|
+
const response = await client.request(resolvePath(operation.path, { workspaceId: profile.workspaceId }), { method: operation.method });
|
|
9614
|
+
const { id, name, memberCount } = response.data;
|
|
9615
|
+
return { status: "verified", workspace: { id, name, memberCount }, detail: null };
|
|
9616
|
+
} catch (error) {
|
|
9617
|
+
if (!(error instanceof SimApiError))
|
|
9618
|
+
throw error;
|
|
9619
|
+
return {
|
|
9620
|
+
status: CREDENTIAL_VERDICT_STATUSES.has(error.status) ? "rejected" : "unreachable",
|
|
9621
|
+
workspace: null,
|
|
9622
|
+
detail: error.message
|
|
9623
|
+
};
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
function presentVerification(verification) {
|
|
9627
|
+
if (verification.status === "verified") {
|
|
9628
|
+
const { name, memberCount } = verification.workspace;
|
|
9629
|
+
const members = `${memberCount} ${memberCount === 1 ? "member" : "members"}`;
|
|
9630
|
+
return `${source_default.green("✓")} ${safeOneLine(name)} · ${members}`;
|
|
9631
|
+
}
|
|
9632
|
+
const detail = safeOneLine(verification.detail);
|
|
9633
|
+
switch (verification.status) {
|
|
9634
|
+
case "rejected":
|
|
9635
|
+
return `${source_default.red("✗")} ${detail}`;
|
|
9636
|
+
case "unauthenticated":
|
|
9637
|
+
return source_default.yellow(`not logged in — ${detail}`);
|
|
9638
|
+
case "disabled":
|
|
9639
|
+
return source_default.dim(detail);
|
|
9640
|
+
default:
|
|
9641
|
+
return source_default.yellow(`could not check — ${detail}`);
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
function whoamiCommand() {
|
|
9645
|
+
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) => {
|
|
9646
|
+
const { client, profile } = clientFrom(command);
|
|
9647
|
+
const { sources } = profile;
|
|
9648
|
+
const authentication = presentAuthentication(sources.apiKey);
|
|
9649
|
+
const verification = options.verify ? await verifyProfile(client, profile) : { status: "disabled", workspace: null, detail: "not checked (--no-verify)" };
|
|
9650
|
+
const annotate = (value, source) => source === "unset" ? source_default.dim("not set") : `${value} ${source_default.dim(`(${source})`)}`;
|
|
9651
|
+
printRecord(profile.output, [
|
|
9652
|
+
["Profile", profile.name],
|
|
9653
|
+
["Endpoint", annotate(profile.endpoint, sources.endpoint)],
|
|
9654
|
+
[
|
|
9655
|
+
"API key",
|
|
9656
|
+
authentication.authenticated ? annotate("configured", authentication.source) : source_default.yellow("not logged in")
|
|
9657
|
+
],
|
|
9658
|
+
["Workspace", annotate(profile.workspaceId ?? "", sources.workspaceId)],
|
|
9659
|
+
["Output", annotate(profile.output, sources.output)],
|
|
9660
|
+
["Verified", presentVerification(verification)]
|
|
9661
|
+
], {
|
|
9662
|
+
profile: profile.name,
|
|
9663
|
+
endpoint: profile.endpoint,
|
|
9664
|
+
workspaceId: profile.workspaceId,
|
|
9665
|
+
output: profile.output,
|
|
9666
|
+
authenticated: authentication.authenticated,
|
|
9667
|
+
sources: {
|
|
9668
|
+
endpoint: sources.endpoint,
|
|
9669
|
+
authentication: authentication.source,
|
|
9670
|
+
workspaceId: sources.workspaceId,
|
|
9671
|
+
output: sources.output
|
|
9672
|
+
},
|
|
9673
|
+
verification: {
|
|
9674
|
+
status: verification.status,
|
|
9675
|
+
workspace: verification.workspace,
|
|
9676
|
+
detail: verification.detail
|
|
9677
|
+
}
|
|
9678
|
+
});
|
|
9679
|
+
const exitCode = WHOAMI_EXIT_CODES[verification.status];
|
|
9680
|
+
if (exitCode !== 0)
|
|
9681
|
+
process.exitCode = exitCode;
|
|
9682
|
+
});
|
|
9683
|
+
}
|
|
9684
|
+
function profilesCommand() {
|
|
9685
|
+
return new Command("profiles").alias("profile").description("List the profiles defined in the config and credentials files").action((_options, command) => {
|
|
9686
|
+
const profiles = listProfiles();
|
|
9687
|
+
if (profiles.length === 0) {
|
|
9688
|
+
console.log(source_default.dim("No profiles yet. Run: sim login"));
|
|
9689
|
+
return;
|
|
9690
|
+
}
|
|
9691
|
+
const active = profileFrom(command).name;
|
|
9692
|
+
for (const name of profiles) {
|
|
9693
|
+
const marker = name === active ? source_default.green("*") : " ";
|
|
9694
|
+
const hasKey = Boolean(readCredentialsProfile(name).api_key);
|
|
9695
|
+
console.log(`${marker} ${name}${hasKey ? "" : source_default.dim(" (no key)")}`);
|
|
9696
|
+
}
|
|
9697
|
+
});
|
|
9698
|
+
}
|
|
9699
|
+
|
|
9700
|
+
// src/commands/configure.ts
|
|
9701
|
+
function configureCommand() {
|
|
9702
|
+
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) => {
|
|
9703
|
+
const profile = profileFrom(command);
|
|
9704
|
+
const updates = {};
|
|
9705
|
+
if (options.setEndpoint) {
|
|
9706
|
+
updates.endpoint = normalizeEndpoint(options.setEndpoint, "--set-endpoint");
|
|
9707
|
+
}
|
|
9708
|
+
if (options.setWorkspace)
|
|
9709
|
+
updates.workspace = options.setWorkspace;
|
|
9710
|
+
if (options.setOutput) {
|
|
9711
|
+
if (!OUTPUT_FORMATS.includes(options.setOutput)) {
|
|
9712
|
+
throw new SimApiError(`Unknown output format "${options.setOutput}". Use one of: ${OUTPUT_FORMATS.join(", ")}`, 0);
|
|
9713
|
+
}
|
|
9714
|
+
updates.output = options.setOutput;
|
|
9715
|
+
}
|
|
9716
|
+
for (const key of options.unset ?? []) {
|
|
9717
|
+
if (!["endpoint", "workspace", "output"].includes(key)) {
|
|
9718
|
+
throw new SimApiError(`Cannot unset "${key}". Use endpoint, workspace, or output.`, 0);
|
|
9719
|
+
}
|
|
9720
|
+
updates[key] = null;
|
|
9721
|
+
}
|
|
9722
|
+
if (Object.keys(updates).length === 0) {
|
|
9723
|
+
const current = readConfigProfile(profile.name);
|
|
9724
|
+
if (Object.keys(current).length === 0) {
|
|
9725
|
+
console.log(source_default.dim(`No settings stored for profile "${profile.name}".`));
|
|
9726
|
+
return;
|
|
9727
|
+
}
|
|
9728
|
+
for (const [key, value] of Object.entries(current)) {
|
|
9729
|
+
console.log(`${source_default.dim(`${key}:`)} ${value}`);
|
|
9730
|
+
}
|
|
9731
|
+
return;
|
|
9732
|
+
}
|
|
9733
|
+
writeConfigProfile(profile.name, updates);
|
|
9734
|
+
console.log(source_default.green(`✓ Updated profile "${profile.name}" in ${configPath()}`));
|
|
9735
|
+
});
|
|
9736
|
+
}
|
|
9737
|
+
|
|
9738
|
+
// src/runtime/request.ts
|
|
9739
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readSync } from "node:fs";
|
|
9740
|
+
|
|
9741
|
+
// src/contract/commands.ts
|
|
9742
|
+
var TABLE_NAME_HELP = "Identifier: letters, numbers, and underscores; cannot start with a number";
|
|
9743
|
+
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';
|
|
9744
|
+
var TABLE_SORT_HELP = 'Ordered sort keys: [{"field":"createdAt","direction":"desc"}] (direction: asc or desc)';
|
|
9745
|
+
var CUSTOM_TOOL_SCHEMA_HELP = 'OpenAI function schema: {"type":"function","function":{"name":"...","parameters":{"type":"object","properties":{}}}}';
|
|
9746
|
+
var FOLDER_PATH_INPUT = {
|
|
9747
|
+
describe: "Folder path as shown in the app; the leading / is optional",
|
|
9748
|
+
folderPath: true
|
|
9749
|
+
};
|
|
9750
|
+
var FOLDER_PATH_FLAG = {
|
|
9751
|
+
...FOLDER_PATH_INPUT,
|
|
9752
|
+
name: "folder"
|
|
9753
|
+
};
|
|
9754
|
+
var FOLDER_DELETE_FLAGS = {
|
|
9755
|
+
path: FOLDER_PATH_INPUT,
|
|
9756
|
+
recursive: { boolean: true, describe: "Delete the folder and its descendants" }
|
|
9757
|
+
};
|
|
9758
|
+
var KNOWLEDGE_BASE_PATH_ARGUMENT = { id: "knowledgeBaseId" };
|
|
9759
|
+
var WORKFLOW_RUN_SCOPE = {
|
|
9760
|
+
id: {
|
|
9761
|
+
name: "workflow",
|
|
9762
|
+
placeholder: "workflowId",
|
|
9763
|
+
describe: "Workflow ID"
|
|
9764
|
+
}
|
|
9765
|
+
};
|
|
9766
|
+
var FOLDER_COLUMN = { header: "folder", path: "folderPath", format: "folder-path" };
|
|
9767
|
+
var FOLDER_LIST_COLUMNS = [
|
|
9768
|
+
{ header: "path", format: "folder-path" },
|
|
9769
|
+
{ header: "name" },
|
|
9770
|
+
{ header: "parent", path: "parentPath", format: "folder-path" },
|
|
9771
|
+
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9772
|
+
];
|
|
9773
|
+
function moveResource(command, resource) {
|
|
9774
|
+
return {
|
|
9775
|
+
command,
|
|
9776
|
+
positionals: ["folderPath"],
|
|
9777
|
+
requestFields: ["folderPath"],
|
|
9778
|
+
describe: `Move a ${resource} to a folder`
|
|
9779
|
+
};
|
|
9780
|
+
}
|
|
9781
|
+
var CLI_CONTRACT = {
|
|
9782
|
+
createCredentialConnection: { hidden: true },
|
|
9783
|
+
createServiceAccountCredential: { hidden: true },
|
|
9784
|
+
getBillingStatus: {
|
|
9785
|
+
command: "billing status",
|
|
9786
|
+
allWorkspaces: true,
|
|
9787
|
+
describe: "Show billing status and current-period credit usage",
|
|
9788
|
+
fields: [
|
|
9789
|
+
{ header: "plan" },
|
|
9790
|
+
{ header: "status" },
|
|
9791
|
+
{ header: "workspace", path: "workspaceId" },
|
|
9792
|
+
{ header: "period start", path: "period.start", format: "timestamp" },
|
|
9793
|
+
{ header: "period end", path: "period.end", format: "timestamp" },
|
|
9794
|
+
{ header: "used credits", path: "credits.used" },
|
|
9795
|
+
{ header: "limit credits", path: "credits.limit" },
|
|
9796
|
+
{ header: "remaining credits", path: "credits.remaining" }
|
|
9797
|
+
]
|
|
9798
|
+
},
|
|
9799
|
+
listBillingLogs: {
|
|
9800
|
+
command: "billing logs",
|
|
9801
|
+
allWorkspaces: true,
|
|
9802
|
+
describe: "List credit usage events",
|
|
9803
|
+
flags: {
|
|
9804
|
+
source: { describe: "Filter by usage source; sim-chat combines Copilot and workspace chat" },
|
|
9448
9805
|
period: { describe: "Billing period" },
|
|
9449
9806
|
startDate: { describe: "Custom period start (ISO 8601)" },
|
|
9450
9807
|
endDate: { describe: "Custom period end (ISO 8601)" }
|
|
@@ -9480,7 +9837,7 @@ var CLI_CONTRACT = {
|
|
|
9480
9837
|
bulkUpdateKnowledgeDocuments: {
|
|
9481
9838
|
command: "knowledge documents batch-update",
|
|
9482
9839
|
describe: "Enable or disable every matching document",
|
|
9483
|
-
pathArgumentNames:
|
|
9840
|
+
pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
|
|
9484
9841
|
flags: {
|
|
9485
9842
|
documentIds: { name: "document", list: true },
|
|
9486
9843
|
selectAll: { boolean: true, describe: "Apply to every document in the knowledge base" }
|
|
@@ -9490,6 +9847,11 @@ var CLI_CONTRACT = {
|
|
|
9490
9847
|
command: "workflows undeploy",
|
|
9491
9848
|
describe: "Take a workflow out of deployment"
|
|
9492
9849
|
},
|
|
9850
|
+
getWorkflowDeployment: {
|
|
9851
|
+
command: "workflows deployment status",
|
|
9852
|
+
renamedFrom: ["workflows deployment list"],
|
|
9853
|
+
describe: "Show a workflow’s current deployment"
|
|
9854
|
+
},
|
|
9493
9855
|
setSecret: { hidden: true },
|
|
9494
9856
|
deleteTable: { confirm: "This deletes the table and all of its rows." },
|
|
9495
9857
|
deleteTableRow: { confirm: "This deletes the row." },
|
|
@@ -9499,7 +9861,7 @@ var CLI_CONTRACT = {
|
|
|
9499
9861
|
},
|
|
9500
9862
|
deleteKnowledgeBase: { confirm: "This deletes the knowledge base and every document in it." },
|
|
9501
9863
|
deleteKnowledgeDocument: {
|
|
9502
|
-
pathArgumentNames:
|
|
9864
|
+
pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
|
|
9503
9865
|
confirm: "This deletes the document and its embeddings."
|
|
9504
9866
|
},
|
|
9505
9867
|
deleteFile: { confirm: "This archives the file." },
|
|
@@ -9529,7 +9891,10 @@ var CLI_CONTRACT = {
|
|
|
9529
9891
|
workflowIds: { name: "workflow", list: true },
|
|
9530
9892
|
folderPaths: { ...FOLDER_PATH_FLAG, list: true },
|
|
9531
9893
|
triggers: { name: "trigger", list: true },
|
|
9532
|
-
details: {
|
|
9894
|
+
details: {
|
|
9895
|
+
requestDefault: "full",
|
|
9896
|
+
describe: "Response detail level; full is requested by default to name each run’s workflow"
|
|
9897
|
+
},
|
|
9533
9898
|
includeTraceSpans: {
|
|
9534
9899
|
boolean: true,
|
|
9535
9900
|
describe: "Include trace spans in JSON or YAML output (implies full detail)"
|
|
@@ -9582,7 +9947,7 @@ var CLI_CONTRACT = {
|
|
|
9582
9947
|
},
|
|
9583
9948
|
itemsPath: "results",
|
|
9584
9949
|
columns: [
|
|
9585
|
-
{ header: "score", path: "similarity" },
|
|
9950
|
+
{ header: "score", path: "similarity", format: "score" },
|
|
9586
9951
|
{ header: "document", path: "documentName" },
|
|
9587
9952
|
{ header: "chunk", path: "chunkIndex" },
|
|
9588
9953
|
{ header: "content" }
|
|
@@ -9656,7 +10021,7 @@ var CLI_CONTRACT = {
|
|
|
9656
10021
|
columns: [
|
|
9657
10022
|
{ header: "id" },
|
|
9658
10023
|
{ header: "name" },
|
|
9659
|
-
|
|
10024
|
+
FOLDER_COLUMN,
|
|
9660
10025
|
{ header: "rows", path: "rowCount" },
|
|
9661
10026
|
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9662
10027
|
]
|
|
@@ -9666,7 +10031,7 @@ var CLI_CONTRACT = {
|
|
|
9666
10031
|
columns: [
|
|
9667
10032
|
{ header: "id" },
|
|
9668
10033
|
{ header: "name" },
|
|
9669
|
-
|
|
10034
|
+
FOLDER_COLUMN,
|
|
9670
10035
|
{ header: "deployed", path: "isDeployed", format: "bool" },
|
|
9671
10036
|
{ header: "runs", path: "runCount" },
|
|
9672
10037
|
{ header: "last run", path: "lastRunAt", format: "timestamp" }
|
|
@@ -9677,7 +10042,7 @@ var CLI_CONTRACT = {
|
|
|
9677
10042
|
columns: [
|
|
9678
10043
|
{ header: "id" },
|
|
9679
10044
|
{ header: "name" },
|
|
9680
|
-
|
|
10045
|
+
FOLDER_COLUMN,
|
|
9681
10046
|
{ header: "size", format: "bytes" },
|
|
9682
10047
|
{ header: "type" },
|
|
9683
10048
|
{ header: "uploaded by", path: "uploadedByEmail" },
|
|
@@ -9690,15 +10055,17 @@ var CLI_CONTRACT = {
|
|
|
9690
10055
|
columns: [
|
|
9691
10056
|
{ header: "id" },
|
|
9692
10057
|
{ header: "name" },
|
|
9693
|
-
|
|
10058
|
+
FOLDER_COLUMN,
|
|
9694
10059
|
{ header: "docs", path: "docCount" },
|
|
9695
10060
|
{ header: "tokens", path: "tokenCount" },
|
|
9696
10061
|
{ header: "model", path: "embeddingModel" }
|
|
9697
10062
|
]
|
|
9698
10063
|
},
|
|
9699
|
-
getKnowledgeDocument: { pathArgumentNames:
|
|
10064
|
+
getKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
|
|
10065
|
+
updateKnowledgeDocument: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
|
|
10066
|
+
listKnowledgeTags: { pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT },
|
|
9700
10067
|
listKnowledgeDocuments: {
|
|
9701
|
-
pathArgumentNames:
|
|
10068
|
+
pathArgumentNames: KNOWLEDGE_BASE_PATH_ARGUMENT,
|
|
9702
10069
|
columns: [
|
|
9703
10070
|
{ header: "id" },
|
|
9704
10071
|
{ header: "filename" },
|
|
@@ -9742,12 +10109,24 @@ var CLI_CONTRACT = {
|
|
|
9742
10109
|
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
9743
10110
|
]
|
|
9744
10111
|
},
|
|
10112
|
+
listCredentialProviders: {
|
|
10113
|
+
columns: [
|
|
10114
|
+
{ header: "type" },
|
|
10115
|
+
{ header: "service", path: "serviceId" },
|
|
10116
|
+
{ header: "provider", path: "providerId" },
|
|
10117
|
+
{ header: "name" },
|
|
10118
|
+
{ header: "family", path: "providerFamily" },
|
|
10119
|
+
{ header: "available", format: "bool" },
|
|
10120
|
+
{ header: "description" }
|
|
10121
|
+
]
|
|
10122
|
+
},
|
|
9745
10123
|
listSecrets: {
|
|
9746
10124
|
columns: [
|
|
9747
10125
|
{ header: "name" },
|
|
9748
10126
|
{ header: "scope" },
|
|
9749
10127
|
{ header: "role" },
|
|
9750
|
-
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
10128
|
+
{ header: "updated", path: "updatedAt", format: "timestamp" },
|
|
10129
|
+
{ header: "description" }
|
|
9751
10130
|
]
|
|
9752
10131
|
},
|
|
9753
10132
|
getWorkspace: {
|
|
@@ -9813,7 +10192,7 @@ var CLI_CONTRACT = {
|
|
|
9813
10192
|
{ header: "name" },
|
|
9814
10193
|
{ header: "size", format: "bytes" },
|
|
9815
10194
|
{ header: "type" },
|
|
9816
|
-
|
|
10195
|
+
FOLDER_COLUMN,
|
|
9817
10196
|
{ header: "uploaded by", path: "uploadedByEmail" },
|
|
9818
10197
|
{ header: "uploaded", path: "uploadedAt", format: "timestamp" },
|
|
9819
10198
|
{ header: "updated", path: "updatedAt", format: "timestamp" },
|
|
@@ -9840,6 +10219,11 @@ var CLI_CONTRACT = {
|
|
|
9840
10219
|
command: "files rename",
|
|
9841
10220
|
describe: "Rename a file"
|
|
9842
10221
|
},
|
|
10222
|
+
restoreFile: {
|
|
10223
|
+
command: "files restore",
|
|
10224
|
+
renamedFrom: ["files restore create"],
|
|
10225
|
+
describe: "Restore an archived file"
|
|
10226
|
+
},
|
|
9843
10227
|
updateFileContent: {
|
|
9844
10228
|
command: "files set-content",
|
|
9845
10229
|
describe: "Replace a file’s contents",
|
|
@@ -9992,13 +10376,26 @@ var CLI_CONTRACT = {
|
|
|
9992
10376
|
command: "tables rows find",
|
|
9993
10377
|
describe: "Find rows matching a predicate",
|
|
9994
10378
|
flags: {
|
|
9995
|
-
q: { describe: "Value to find" },
|
|
10379
|
+
q: { name: "query", renamedFrom: ["q"], describe: "Value to find" },
|
|
9996
10380
|
predicate: { name: "filter", json: true, describe: TABLE_FILTER_HELP },
|
|
9997
10381
|
sort: { json: true, describe: TABLE_SORT_HELP }
|
|
9998
10382
|
},
|
|
9999
10383
|
itemsPath: "matches",
|
|
10000
10384
|
columns: [{ header: "ordinal" }, { header: "row", path: "rowId" }, { header: "column" }]
|
|
10001
10385
|
},
|
|
10386
|
+
queryRowsCount: {
|
|
10387
|
+
command: "tables rows count",
|
|
10388
|
+
renamedFrom: ["tables count create"],
|
|
10389
|
+
describe: "Count rows matching a filter",
|
|
10390
|
+
flags: {
|
|
10391
|
+
predicate: {
|
|
10392
|
+
name: "filter",
|
|
10393
|
+
renamedFrom: ["predicate"],
|
|
10394
|
+
json: true,
|
|
10395
|
+
describe: TABLE_FILTER_HELP
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
},
|
|
10002
10399
|
runTableColumn: {
|
|
10003
10400
|
command: "tables columns run",
|
|
10004
10401
|
describe: "Run a column’s workflow",
|
|
@@ -10216,7 +10613,7 @@ function readArgumentSource(raw, flagName) {
|
|
|
10216
10613
|
}
|
|
10217
10614
|
}
|
|
10218
10615
|
try {
|
|
10219
|
-
return { text:
|
|
10616
|
+
return { text: readFileSync3(path, "utf8"), from: ` (read from ${path})` };
|
|
10220
10617
|
} catch (error) {
|
|
10221
10618
|
throw new SimApiError(`--${flagName} cannot read ${path}: ${error.message}`, 0);
|
|
10222
10619
|
}
|
|
@@ -10251,6 +10648,26 @@ function readListValues(raw, flagName) {
|
|
|
10251
10648
|
return trimmed;
|
|
10252
10649
|
});
|
|
10253
10650
|
}
|
|
10651
|
+
var PERCENT_ESCAPE = /%[0-9A-Fa-f]{2}/;
|
|
10652
|
+
var SUB_DELIMITERS = /[!'()*]/g;
|
|
10653
|
+
function encodeFolderPathSegment(name) {
|
|
10654
|
+
if (name === ".")
|
|
10655
|
+
return "%2E";
|
|
10656
|
+
if (name === "..")
|
|
10657
|
+
return "%2E%2E";
|
|
10658
|
+
return encodeURIComponent(name).replace(SUB_DELIMITERS, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
10659
|
+
}
|
|
10660
|
+
function encodeFolderPath(value) {
|
|
10661
|
+
return value.split("/").map((segment) => {
|
|
10662
|
+
if (!PERCENT_ESCAPE.test(segment))
|
|
10663
|
+
return encodeFolderPathSegment(segment);
|
|
10664
|
+
try {
|
|
10665
|
+
return encodeFolderPathSegment(decodeURIComponent(segment));
|
|
10666
|
+
} catch {
|
|
10667
|
+
return encodeFolderPathSegment(segment);
|
|
10668
|
+
}
|
|
10669
|
+
}).join("/");
|
|
10670
|
+
}
|
|
10254
10671
|
function pathHint(raw) {
|
|
10255
10672
|
if (raw.startsWith("@") || /^\s*[[{"\-\d]|^\s*(true|false|null)/.test(raw))
|
|
10256
10673
|
return "";
|
|
@@ -10260,7 +10677,7 @@ function coerce(raw, field, flag, flagName) {
|
|
|
10260
10677
|
if (raw === undefined)
|
|
10261
10678
|
return;
|
|
10262
10679
|
if (flag.list) {
|
|
10263
|
-
const values = readListValues(raw, flagName);
|
|
10680
|
+
const values = readListValues(raw, flagName).map((value) => flag.folderPath ? encodeFolderPath(value) : value);
|
|
10264
10681
|
return field.kind === "string" ? values.join(",") : values;
|
|
10265
10682
|
}
|
|
10266
10683
|
if (takesJson(field, flag)) {
|
|
@@ -10285,6 +10702,8 @@ function coerce(raw, field, flag, flagName) {
|
|
|
10285
10702
|
if (choices && !choices.includes(String(raw))) {
|
|
10286
10703
|
throw new SimApiError(`--${flagName} must be one of: ${choices.join(", ")}`, 0);
|
|
10287
10704
|
}
|
|
10705
|
+
if (flag.folderPath && typeof raw === "string")
|
|
10706
|
+
return encodeFolderPath(raw);
|
|
10288
10707
|
return raw;
|
|
10289
10708
|
}
|
|
10290
10709
|
function asQueryValue(value) {
|
|
@@ -10325,7 +10744,8 @@ function buildRequest(operation, positional, flags, workspaceId) {
|
|
|
10325
10744
|
continue;
|
|
10326
10745
|
const flagName = flagNameFor(operation, field);
|
|
10327
10746
|
const omitProfileWorkspace = commandSpec.allWorkspaces && flags.allWorkspaces === true;
|
|
10328
|
-
const
|
|
10747
|
+
const provided = field === PROFILE_INJECTED_FIELD ? omitProfileWorkspace ? undefined : workspaceId : flags[camel(flagName)];
|
|
10748
|
+
const raw = provided ?? flag.requestDefault;
|
|
10329
10749
|
const value = coerce(raw ?? undefined, descriptor, flag, flagName);
|
|
10330
10750
|
if (value === undefined) {
|
|
10331
10751
|
if (descriptor.required) {
|
|
@@ -10498,6 +10918,15 @@ function countTraceSpans(value) {
|
|
|
10498
10918
|
function at(row, path) {
|
|
10499
10919
|
return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
|
|
10500
10920
|
}
|
|
10921
|
+
function decodeFolderPath(value) {
|
|
10922
|
+
return value.split("/").map((segment) => {
|
|
10923
|
+
try {
|
|
10924
|
+
return decodeURIComponent(segment);
|
|
10925
|
+
} catch {
|
|
10926
|
+
return segment;
|
|
10927
|
+
}
|
|
10928
|
+
}).join("/");
|
|
10929
|
+
}
|
|
10501
10930
|
function renderCell(value, format, options = {}) {
|
|
10502
10931
|
switch (format) {
|
|
10503
10932
|
case "timestamp":
|
|
@@ -10510,8 +10939,12 @@ function renderCell(value, format, options = {}) {
|
|
|
10510
10939
|
return bool2(value);
|
|
10511
10940
|
case "cost":
|
|
10512
10941
|
return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
|
|
10942
|
+
case "score":
|
|
10943
|
+
return typeof value === "number" ? value.toFixed(4) : text(null);
|
|
10513
10944
|
case "count":
|
|
10514
10945
|
return Array.isArray(value) ? String(value.length) : text(null);
|
|
10946
|
+
case "folder-path":
|
|
10947
|
+
return typeof value === "string" ? text(decodeFolderPath(value)) : text(value);
|
|
10515
10948
|
case "trace-count": {
|
|
10516
10949
|
const count = countTraceSpans(value);
|
|
10517
10950
|
return `${count} ${count === 1 ? "span" : "spans"}${options.expandedTrace ? "" : " (use --trace)"}`;
|
|
@@ -10522,10 +10955,42 @@ function renderCell(value, format, options = {}) {
|
|
|
10522
10955
|
return sanitize(typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
10523
10956
|
}
|
|
10524
10957
|
}
|
|
10525
|
-
var
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10958
|
+
var TIMESTAMP_KEY = /(?:At|Date)$/;
|
|
10959
|
+
var DURATION_KEY = /Ms$|^duration/;
|
|
10960
|
+
var BYTES_KEY = /^size$|(?:Size|Bytes)$/;
|
|
10961
|
+
var BOOL_KEY = /^(?:is|has)[A-Z]/;
|
|
10962
|
+
var RATIO_KEY = /^(?:similarity|score)$|(?:Similarity|Score)$/;
|
|
10963
|
+
var FOLDER_PATH_KEY = /^(?:path|parentPath|folderPath)$/;
|
|
10964
|
+
var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/;
|
|
10965
|
+
var RATIO_PRECISION = 4;
|
|
10966
|
+
function inferFormat(key, value) {
|
|
10967
|
+
if (typeof value === "boolean")
|
|
10968
|
+
return BOOL_KEY.test(key) ? "bool" : null;
|
|
10969
|
+
if (typeof value === "string") {
|
|
10970
|
+
if (FOLDER_PATH_KEY.test(key))
|
|
10971
|
+
return "folder-path";
|
|
10972
|
+
return TIMESTAMP_KEY.test(key) && ISO_TIMESTAMP.test(value) && !Number.isNaN(Date.parse(value)) ? "timestamp" : null;
|
|
10973
|
+
}
|
|
10974
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
10975
|
+
return null;
|
|
10976
|
+
if (DURATION_KEY.test(key))
|
|
10977
|
+
return "duration";
|
|
10978
|
+
if (BYTES_KEY.test(key))
|
|
10979
|
+
return "bytes";
|
|
10980
|
+
return null;
|
|
10981
|
+
}
|
|
10982
|
+
function inferredCell(key, value) {
|
|
10983
|
+
if (typeof value === "number" && Number.isFinite(value) && RATIO_KEY.test(key)) {
|
|
10984
|
+
return value.toFixed(RATIO_PRECISION);
|
|
10985
|
+
}
|
|
10986
|
+
return renderCell(value, inferFormat(key, value) ?? "auto");
|
|
10987
|
+
}
|
|
10988
|
+
function humanizeKey(key) {
|
|
10989
|
+
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();
|
|
10990
|
+
}
|
|
10991
|
+
function inferHeader(key, format) {
|
|
10992
|
+
const trimmed = format === "duration" || format === "bytes" ? key.replace(/(?:Ms|Bytes)$/, "") : format === "bool" ? key.replace(/^is(?=[A-Z])/, "") : key;
|
|
10993
|
+
return humanizeKey(trimmed || key);
|
|
10529
10994
|
}
|
|
10530
10995
|
function columnsFrom(specs) {
|
|
10531
10996
|
return specs.map((spec) => ({
|
|
@@ -10534,9 +10999,9 @@ function columnsFrom(specs) {
|
|
|
10534
10999
|
}));
|
|
10535
11000
|
}
|
|
10536
11001
|
function fieldsFrom(data, specs, options = {}) {
|
|
10537
|
-
return specs.
|
|
11002
|
+
return specs.map((spec) => {
|
|
10538
11003
|
const value = at(data, spec.path ?? spec.header);
|
|
10539
|
-
return value === undefined ?
|
|
11004
|
+
return [spec.header, value === undefined ? text(null) : renderCell(value, spec.format, options)];
|
|
10540
11005
|
});
|
|
10541
11006
|
}
|
|
10542
11007
|
function inferColumns(rows, expand) {
|
|
@@ -10551,7 +11016,7 @@ function inferColumns(rows, expand) {
|
|
|
10551
11016
|
if (value !== null && typeof value === "object")
|
|
10552
11017
|
continue;
|
|
10553
11018
|
seen.add(key);
|
|
10554
|
-
paths.push({ path: key, header: key });
|
|
11019
|
+
paths.push({ path: key, key, header: inferHeader(key, inferFormat(key, value)), owned: true });
|
|
10555
11020
|
}
|
|
10556
11021
|
}
|
|
10557
11022
|
if (expand) {
|
|
@@ -10564,13 +11029,18 @@ function inferColumns(rows, expand) {
|
|
|
10564
11029
|
if (nested.has(key))
|
|
10565
11030
|
continue;
|
|
10566
11031
|
nested.add(key);
|
|
10567
|
-
paths.push({
|
|
11032
|
+
paths.push({
|
|
11033
|
+
path: `${expand}.${key}`,
|
|
11034
|
+
key,
|
|
11035
|
+
header: seen.has(key) ? `${expand}.${key}` : key,
|
|
11036
|
+
owned: false
|
|
11037
|
+
});
|
|
10568
11038
|
}
|
|
10569
11039
|
}
|
|
10570
11040
|
}
|
|
10571
|
-
return paths.map(({ path, header }) => ({
|
|
11041
|
+
return paths.map(({ path, key, header, owned }) => ({
|
|
10572
11042
|
header: sanitize(header),
|
|
10573
|
-
value: (row) => renderCell(at(row, path), "auto")
|
|
11043
|
+
value: (row) => owned ? inferredCell(key, at(row, path)) : renderCell(at(row, path), "auto")
|
|
10574
11044
|
}));
|
|
10575
11045
|
}
|
|
10576
11046
|
function unwrapResource(data) {
|
|
@@ -10603,7 +11073,10 @@ function renderResult(operation, format, raw, spec, options = {}) {
|
|
|
10603
11073
|
printList(format, data, spec.columns ? columnsFrom(spec.columns) : inferColumns(data, spec.expand));
|
|
10604
11074
|
return;
|
|
10605
11075
|
}
|
|
10606
|
-
const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [
|
|
11076
|
+
const fields = spec.fields ? fieldsFrom(data, spec.fields, options) : data && typeof data === "object" ? Object.entries(data).map(([key, value]) => [
|
|
11077
|
+
inferHeader(key, inferFormat(key, value)),
|
|
11078
|
+
inferredCell(key, value)
|
|
11079
|
+
]) : [];
|
|
10607
11080
|
printRecord(format, fields, data);
|
|
10608
11081
|
if (spec.expandedTrace && options.expandedTrace) {
|
|
10609
11082
|
const traceSpans = at(data, "traceSpans");
|
|
@@ -10716,7 +11189,7 @@ function attachCredentialCommands(program2) {
|
|
|
10716
11189
|
}
|
|
10717
11190
|
|
|
10718
11191
|
// src/commands/protocol/files-get.ts
|
|
10719
|
-
import { once } from "node:events";
|
|
11192
|
+
import { once as once2 } from "node:events";
|
|
10720
11193
|
import { createWriteStream } from "node:fs";
|
|
10721
11194
|
import { link, lstat, mkdtemp, readlink, rename, rm } from "node:fs/promises";
|
|
10722
11195
|
import { dirname as dirname2, join as join2, resolve as resolve2 } from "node:path";
|
|
@@ -10731,6 +11204,9 @@ function printProtocolResult(format, result) {
|
|
|
10731
11204
|
|
|
10732
11205
|
// src/commands/protocol/files-get.ts
|
|
10733
11206
|
function writeFailure(path, error) {
|
|
11207
|
+
if (isRequestTimeout(error)) {
|
|
11208
|
+
return new SimApiError(`Downloading ${path} timed out. ${RAISE_TIMEOUT_HINT}`, 0);
|
|
11209
|
+
}
|
|
10734
11210
|
const code = error.code;
|
|
10735
11211
|
if (code === "EEXIST") {
|
|
10736
11212
|
return new SimApiError(`${path} already exists. Pass --force to overwrite it, or choose another output path.`, 0);
|
|
@@ -10821,7 +11297,7 @@ async function streamToStdout(body, output = process.stdout) {
|
|
|
10821
11297
|
if (done)
|
|
10822
11298
|
return;
|
|
10823
11299
|
if (!output.write(value))
|
|
10824
|
-
await
|
|
11300
|
+
await once2(output, "drain");
|
|
10825
11301
|
}
|
|
10826
11302
|
} finally {
|
|
10827
11303
|
reader.releaseLock();
|
|
@@ -11001,7 +11477,7 @@ async function finishUploadSession(client, workspaceId, session, path) {
|
|
|
11001
11477
|
|
|
11002
11478
|
// src/commands/protocol/files-upload.ts
|
|
11003
11479
|
function attachFileUpload(files) {
|
|
11004
|
-
files.command("upload").argument("<path>", "Local file to upload").description("Upload a file to the workspace").option("--folder <path>", "
|
|
11480
|
+
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
11481
|
const { client, profile } = clientFrom(command);
|
|
11006
11482
|
const workspaceId = client.requireWorkspace();
|
|
11007
11483
|
const { name, size } = await localFile(path, options.name);
|
|
@@ -11012,7 +11488,7 @@ function attachFileUpload(files) {
|
|
|
11012
11488
|
name,
|
|
11013
11489
|
contentType: contentTypeFor(name),
|
|
11014
11490
|
size,
|
|
11015
|
-
...options.folder !== undefined ? { folderPath: options.folder } : {}
|
|
11491
|
+
...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
|
|
11016
11492
|
}
|
|
11017
11493
|
});
|
|
11018
11494
|
const { session, uploadToken, transfer } = created.data;
|
|
@@ -11071,13 +11547,285 @@ function attachKnowledgeDocumentUpload(documents) {
|
|
|
11071
11547
|
if (!completed.document) {
|
|
11072
11548
|
throw new Error(`Knowledge upload ${session.id} completed without a document`);
|
|
11073
11549
|
}
|
|
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
|
-
});
|
|
11550
|
+
printProtocolResult(profile.output, {
|
|
11551
|
+
id: completed.document.id,
|
|
11552
|
+
knowledgeBaseId: completed.document.knowledgeBaseId,
|
|
11553
|
+
name: completed.document.filename,
|
|
11554
|
+
size: completed.document.fileSize,
|
|
11555
|
+
status: completed.document.processingStatus
|
|
11556
|
+
});
|
|
11557
|
+
});
|
|
11558
|
+
}
|
|
11559
|
+
|
|
11560
|
+
// src/commands/protocol/logs-follow.ts
|
|
11561
|
+
var DEFAULT_BACKLOG = 10;
|
|
11562
|
+
var DEFAULT_INTERVAL_SECONDS = 3;
|
|
11563
|
+
var MIN_INTERVAL_SECONDS = 0.1;
|
|
11564
|
+
var MAX_BACKOFF_MS = 30000;
|
|
11565
|
+
var POLL_PAGE_SIZE = 100;
|
|
11566
|
+
var MAX_PAGES_PER_POLL = 10;
|
|
11567
|
+
var MAX_REMEMBERED_RUNS = 5000;
|
|
11568
|
+
var MAX_CELL_WIDTH2 = 60;
|
|
11569
|
+
var WAIT_SLICE_MS = 250;
|
|
11570
|
+
var RETRYABLE_CLIENT_STATUSES = new Set([408, 425, 429]);
|
|
11571
|
+
var ERASE_LINE = `${String.fromCharCode(27)}[K`;
|
|
11572
|
+
function collect(value, previous) {
|
|
11573
|
+
return [...previous, value];
|
|
11574
|
+
}
|
|
11575
|
+
function at2(row, path) {
|
|
11576
|
+
return path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : undefined, row);
|
|
11577
|
+
}
|
|
11578
|
+
function renderCell2(value, format) {
|
|
11579
|
+
switch (format) {
|
|
11580
|
+
case "timestamp":
|
|
11581
|
+
return timestamp2(value);
|
|
11582
|
+
case "duration":
|
|
11583
|
+
return duration(value);
|
|
11584
|
+
case "bytes":
|
|
11585
|
+
return bytes(value);
|
|
11586
|
+
case "bool":
|
|
11587
|
+
return bool2(value);
|
|
11588
|
+
case "cost":
|
|
11589
|
+
return typeof value === "number" ? `$${value.toFixed(4)}` : text(null);
|
|
11590
|
+
default:
|
|
11591
|
+
return text(typeof value === "object" && value !== null ? JSON.stringify(value) : value);
|
|
11592
|
+
}
|
|
11593
|
+
}
|
|
11594
|
+
var COLUMNS = (CLI_CONTRACT.listLogs?.columns ?? []).map((spec) => ({
|
|
11595
|
+
header: spec.header,
|
|
11596
|
+
value: (row) => renderCell2(at2(row, spec.path ?? spec.header), spec.format)
|
|
11597
|
+
}));
|
|
11598
|
+
function oneLine2(value) {
|
|
11599
|
+
return value.replace(/\s*[\r\n\t]+\s*/g, " ");
|
|
11600
|
+
}
|
|
11601
|
+
function pad2(value, width) {
|
|
11602
|
+
return value + " ".repeat(Math.max(0, width - visibleWidth(value)));
|
|
11603
|
+
}
|
|
11604
|
+
function clamp2(value, width) {
|
|
11605
|
+
if (visibleWidth(value) <= width || visibleWidth(value) !== value.length)
|
|
11606
|
+
return value;
|
|
11607
|
+
return `${value.slice(0, Math.max(1, width - 1))}…`;
|
|
11608
|
+
}
|
|
11609
|
+
function createTableWriter() {
|
|
11610
|
+
let widths = null;
|
|
11611
|
+
return (rows) => {
|
|
11612
|
+
const lines = rows.map((row) => COLUMNS.map((column) => oneLine2(column.value(row))));
|
|
11613
|
+
if (!widths) {
|
|
11614
|
+
widths = COLUMNS.map((column, index) => Math.min(MAX_CELL_WIDTH2, Math.max(visibleWidth(column.header), ...lines.map((line) => visibleWidth(line[index])))));
|
|
11615
|
+
const header = widths;
|
|
11616
|
+
console.log(source_default.dim(COLUMNS.map((column, index) => pad2(column.header.toUpperCase(), header[index])).join(" ").trimEnd()));
|
|
11617
|
+
}
|
|
11618
|
+
const locked = widths;
|
|
11619
|
+
for (const line of lines) {
|
|
11620
|
+
console.log(line.map((cell, index) => pad2(clamp2(cell, locked[index]), locked[index])).join(" ").trimEnd());
|
|
11621
|
+
}
|
|
11622
|
+
};
|
|
11623
|
+
}
|
|
11624
|
+
function createWriter(format) {
|
|
11625
|
+
if (format === "json") {
|
|
11626
|
+
return (rows) => {
|
|
11627
|
+
for (const row of rows)
|
|
11628
|
+
console.log(JSON.stringify(row));
|
|
11629
|
+
};
|
|
11630
|
+
}
|
|
11631
|
+
if (format === "yaml") {
|
|
11632
|
+
return (rows) => {
|
|
11633
|
+
for (const row of rows) {
|
|
11634
|
+
console.log(`---
|
|
11635
|
+
${dump(row, { lineWidth: 0, noRefs: true }).trimEnd()}`);
|
|
11636
|
+
}
|
|
11637
|
+
};
|
|
11638
|
+
}
|
|
11639
|
+
if (format === "text") {
|
|
11640
|
+
return (rows) => {
|
|
11641
|
+
if (rows.length > 0)
|
|
11642
|
+
printList("text", rows, COLUMNS);
|
|
11643
|
+
};
|
|
11644
|
+
}
|
|
11645
|
+
return createTableWriter();
|
|
11646
|
+
}
|
|
11647
|
+
function followStatus() {
|
|
11648
|
+
let reported2 = false;
|
|
11649
|
+
return {
|
|
11650
|
+
note: (message) => {
|
|
11651
|
+
if (!process.stderr.isTTY)
|
|
11652
|
+
return;
|
|
11653
|
+
reported2 = true;
|
|
11654
|
+
process.stderr.write(`\r${source_default.dim(message)}${ERASE_LINE}`);
|
|
11655
|
+
},
|
|
11656
|
+
warn: (message) => {
|
|
11657
|
+
if (reported2) {
|
|
11658
|
+
reported2 = false;
|
|
11659
|
+
process.stderr.write(`\r${ERASE_LINE}`);
|
|
11660
|
+
}
|
|
11661
|
+
process.stderr.write(`warning: ${message}
|
|
11662
|
+
`);
|
|
11663
|
+
},
|
|
11664
|
+
clear: () => {
|
|
11665
|
+
if (!reported2)
|
|
11666
|
+
return;
|
|
11667
|
+
reported2 = false;
|
|
11668
|
+
process.stderr.write(`\r${ERASE_LINE}`);
|
|
11669
|
+
}
|
|
11670
|
+
};
|
|
11671
|
+
}
|
|
11672
|
+
function watchForInterrupt() {
|
|
11673
|
+
let stopped = false;
|
|
11674
|
+
const stop = () => {
|
|
11675
|
+
stopped = true;
|
|
11676
|
+
};
|
|
11677
|
+
process.on("SIGINT", stop);
|
|
11678
|
+
process.on("SIGTERM", stop);
|
|
11679
|
+
return {
|
|
11680
|
+
interrupted: () => stopped,
|
|
11681
|
+
dispose: () => {
|
|
11682
|
+
process.off("SIGINT", stop);
|
|
11683
|
+
process.off("SIGTERM", stop);
|
|
11684
|
+
}
|
|
11685
|
+
};
|
|
11686
|
+
}
|
|
11687
|
+
async function waitFor(ms, interrupted) {
|
|
11688
|
+
let remaining = ms;
|
|
11689
|
+
while (remaining > 0 && !interrupted()) {
|
|
11690
|
+
const step = Math.min(WAIT_SLICE_MS, remaining);
|
|
11691
|
+
await sleep(step);
|
|
11692
|
+
remaining -= step;
|
|
11693
|
+
}
|
|
11694
|
+
}
|
|
11695
|
+
function isUnprinted(state, row) {
|
|
11696
|
+
if (state.seen.has(row.runId))
|
|
11697
|
+
return false;
|
|
11698
|
+
return state.floor === null || row.startedAt >= state.floor;
|
|
11699
|
+
}
|
|
11700
|
+
function remember(state, rows) {
|
|
11701
|
+
for (const row of rows)
|
|
11702
|
+
state.seen.set(row.runId, row.startedAt);
|
|
11703
|
+
let excess = state.seen.size - MAX_REMEMBERED_RUNS;
|
|
11704
|
+
if (excess <= 0)
|
|
11705
|
+
return;
|
|
11706
|
+
for (const [runId, startedAt] of state.seen) {
|
|
11707
|
+
if (excess <= 0)
|
|
11708
|
+
break;
|
|
11709
|
+
if (state.floor === null || startedAt > state.floor)
|
|
11710
|
+
state.floor = startedAt;
|
|
11711
|
+
state.seen.delete(runId);
|
|
11712
|
+
excess -= 1;
|
|
11713
|
+
}
|
|
11714
|
+
}
|
|
11715
|
+
async function collectUnprinted(client, path, query, state, pageSize, maxPages) {
|
|
11716
|
+
const rows = [];
|
|
11717
|
+
let cursor = null;
|
|
11718
|
+
let truncated = false;
|
|
11719
|
+
for (let page = 0;page < maxPages; page += 1) {
|
|
11720
|
+
const response = await client.request(path, {
|
|
11721
|
+
query: { ...query, limit: pageSize, cursor }
|
|
11722
|
+
});
|
|
11723
|
+
const page_rows = response?.data ?? [];
|
|
11724
|
+
const unprinted = page_rows.filter((row) => isUnprinted(state, row));
|
|
11725
|
+
rows.push(...unprinted);
|
|
11726
|
+
cursor = response?.nextCursor ?? null;
|
|
11727
|
+
if (!cursor || page_rows.length === 0 || unprinted.length < page_rows.length)
|
|
11728
|
+
break;
|
|
11729
|
+
if (page === maxPages - 1)
|
|
11730
|
+
truncated = true;
|
|
11731
|
+
}
|
|
11732
|
+
return { rows, truncated };
|
|
11733
|
+
}
|
|
11734
|
+
function isTransient(error) {
|
|
11735
|
+
if (!(error instanceof SimApiError))
|
|
11736
|
+
return false;
|
|
11737
|
+
if (error.status === 0 || error.status >= 500)
|
|
11738
|
+
return true;
|
|
11739
|
+
return RETRYABLE_CLIENT_STATUSES.has(error.status);
|
|
11740
|
+
}
|
|
11741
|
+
function nonNegativeInteger(raw, flag) {
|
|
11742
|
+
const value = Number(raw);
|
|
11743
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
11744
|
+
throw new SimApiError(`${flag} must be a non-negative integer`, 0);
|
|
11745
|
+
}
|
|
11746
|
+
return value;
|
|
11747
|
+
}
|
|
11748
|
+
function intervalMs(raw) {
|
|
11749
|
+
const seconds = Number(raw);
|
|
11750
|
+
if (!Number.isFinite(seconds) || seconds < MIN_INTERVAL_SECONDS) {
|
|
11751
|
+
throw new SimApiError(`--interval must be at least ${MIN_INTERVAL_SECONDS} seconds`, 0);
|
|
11752
|
+
}
|
|
11753
|
+
return Math.round(seconds * 1000);
|
|
11754
|
+
}
|
|
11755
|
+
function inSeconds(ms) {
|
|
11756
|
+
return Math.round(ms / 100) / 10;
|
|
11757
|
+
}
|
|
11758
|
+
function attachLogsFollow(logs) {
|
|
11759
|
+
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([
|
|
11760
|
+
...V2_OPERATIONS.listLogs.query.level.values
|
|
11761
|
+
])).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", `
|
|
11762
|
+
Each run prints once, when it is first seen, so its status is the status it had
|
|
11763
|
+
at that moment. With --output json every run is a JSON object on its own line
|
|
11764
|
+
(JSONL) rather than a member of an array, because a follow never ends and so can
|
|
11765
|
+
never close one; --output yaml emits a --- separated document stream. Progress
|
|
11766
|
+
and retries go to stderr, leaving stdout a clean stream of rows. Ctrl-C stops the
|
|
11767
|
+
follow.
|
|
11768
|
+
|
|
11769
|
+
Examples:
|
|
11770
|
+
$ sim logs follow --level error
|
|
11771
|
+
$ sim logs follow --workflow wf_123 -n 0
|
|
11772
|
+
$ sim --output json logs follow | jq -r '.runId'
|
|
11773
|
+
`).action(async (options, command) => {
|
|
11774
|
+
const lines = nonNegativeInteger(options.lines, "--lines");
|
|
11775
|
+
const delay = intervalMs(options.interval);
|
|
11776
|
+
const { client, profile } = clientFrom(command);
|
|
11777
|
+
const path = V2_OPERATIONS.listLogs.path;
|
|
11778
|
+
const query = {
|
|
11779
|
+
workspaceId: client.requireWorkspace(),
|
|
11780
|
+
workflowIds: options.workflow?.length ? options.workflow.join(",") : undefined,
|
|
11781
|
+
folderPaths: options.folder?.length ? options.folder.map(encodeFolderPath).join(",") : undefined,
|
|
11782
|
+
triggers: options.trigger?.length ? options.trigger.join(",") : undefined,
|
|
11783
|
+
level: options.level,
|
|
11784
|
+
details: options.details,
|
|
11785
|
+
order: "desc"
|
|
11786
|
+
};
|
|
11787
|
+
const write = createWriter(profile.output);
|
|
11788
|
+
const status = followStatus();
|
|
11789
|
+
const interrupt = watchForInterrupt();
|
|
11790
|
+
const state = { seen: new Map, floor: null };
|
|
11791
|
+
try {
|
|
11792
|
+
const seed = await collectUnprinted(client, path, query, state, Math.max(lines, 1), 1);
|
|
11793
|
+
remember(state, seed.rows);
|
|
11794
|
+
state.floor = seed.rows.at(-1)?.startedAt ?? null;
|
|
11795
|
+
if (seed.truncated && seed.rows.length < lines) {
|
|
11796
|
+
status.warn(`asked for ${lines} earlier runs but a page holds ${seed.rows.length}; following from there — see sim logs list for more`);
|
|
11797
|
+
}
|
|
11798
|
+
write(lines > 0 ? seed.rows.slice(0, lines).reverse() : []);
|
|
11799
|
+
let failures = 0;
|
|
11800
|
+
while (!interrupt.interrupted()) {
|
|
11801
|
+
await waitFor(failures === 0 ? delay : Math.min(delay * 2 ** failures, MAX_BACKOFF_MS), interrupt.interrupted);
|
|
11802
|
+
if (interrupt.interrupted())
|
|
11803
|
+
break;
|
|
11804
|
+
let fresh;
|
|
11805
|
+
try {
|
|
11806
|
+
fresh = await collectUnprinted(client, path, query, state, POLL_PAGE_SIZE, MAX_PAGES_PER_POLL);
|
|
11807
|
+
} catch (error) {
|
|
11808
|
+
if (!isTransient(error))
|
|
11809
|
+
throw error;
|
|
11810
|
+
failures += 1;
|
|
11811
|
+
const next = Math.min(delay * 2 ** failures, MAX_BACKOFF_MS);
|
|
11812
|
+
status.note(`poll failed (${error.message}); retrying in ${inSeconds(next)}s…`);
|
|
11813
|
+
continue;
|
|
11814
|
+
}
|
|
11815
|
+
failures = 0;
|
|
11816
|
+
status.clear();
|
|
11817
|
+
if (fresh.truncated) {
|
|
11818
|
+
status.warn(`more than ${MAX_PAGES_PER_POLL * POLL_PAGE_SIZE} runs arrived at once; older ones were skipped — see sim logs list`);
|
|
11819
|
+
}
|
|
11820
|
+
if (fresh.rows.length === 0)
|
|
11821
|
+
continue;
|
|
11822
|
+
remember(state, fresh.rows);
|
|
11823
|
+
write(fresh.rows.reverse());
|
|
11824
|
+
}
|
|
11825
|
+
} finally {
|
|
11826
|
+
status.clear();
|
|
11827
|
+
interrupt.dispose();
|
|
11828
|
+
}
|
|
11081
11829
|
});
|
|
11082
11830
|
}
|
|
11083
11831
|
|
|
@@ -11114,15 +11862,22 @@ function addFieldOption(command, operation, field, descriptor) {
|
|
|
11114
11862
|
const placeholder = takesList ? "<value...>" : wantsJson ? "<json|@file>" : "<value>";
|
|
11115
11863
|
const choices = flag.choices ?? descriptor.values;
|
|
11116
11864
|
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)" : ""}`;
|
|
11865
|
+
const renamedFrom = flag.renamedFrom ?? [];
|
|
11117
11866
|
const option = new Option(`${short}--${name} ${placeholder}`, describe);
|
|
11118
11867
|
if (choices && !takesList)
|
|
11119
11868
|
option.choices([...choices]);
|
|
11120
11869
|
if (descriptor.default !== undefined && field !== "limit") {
|
|
11121
11870
|
option.default(undefined, String(descriptor.default));
|
|
11122
11871
|
}
|
|
11123
|
-
if (descriptor.required)
|
|
11872
|
+
if (descriptor.required && renamedFrom.length === 0)
|
|
11124
11873
|
option.makeOptionMandatory();
|
|
11125
11874
|
command.addOption(option);
|
|
11875
|
+
for (const previous of renamedFrom) {
|
|
11876
|
+
const retired = new Option(`--${previous} ${placeholder}`).hideHelp();
|
|
11877
|
+
if (choices && !takesList)
|
|
11878
|
+
retired.choices([...choices]);
|
|
11879
|
+
command.addOption(retired);
|
|
11880
|
+
}
|
|
11126
11881
|
}
|
|
11127
11882
|
function addOperationOptions(command, operation, commandSpec, operationSpec) {
|
|
11128
11883
|
for (const param of operationSpec.pathParams) {
|
|
@@ -11158,16 +11913,19 @@ function addOperationOptions(command, operation, commandSpec, operationSpec) {
|
|
|
11158
11913
|
}
|
|
11159
11914
|
}
|
|
11160
11915
|
if (commandSpec.confirm) {
|
|
11161
|
-
command.option("-y, --yes", "
|
|
11916
|
+
command.option("-y, --yes", "Confirm this destructive operation (required)");
|
|
11162
11917
|
}
|
|
11163
11918
|
}
|
|
11164
11919
|
|
|
11165
11920
|
// src/commands/protocol/resource-directory.ts
|
|
11166
|
-
var
|
|
11921
|
+
var COLUMNS2 = [
|
|
11167
11922
|
{ header: "kind", value: (entry) => text(entry.kind) },
|
|
11168
11923
|
{ header: "name", value: (entry) => text(entry.name) },
|
|
11169
|
-
{
|
|
11170
|
-
|
|
11924
|
+
{
|
|
11925
|
+
header: "ref",
|
|
11926
|
+
value: (entry) => text(entry.kind === "folder" ? decodeFolderPath(entry.ref) : entry.ref)
|
|
11927
|
+
},
|
|
11928
|
+
{ header: "folder", value: (entry) => text(decodeFolderPath(entry.folderPath)) },
|
|
11171
11929
|
{ header: "updated", value: (entry) => timestamp2(entry.updatedAt) }
|
|
11172
11930
|
];
|
|
11173
11931
|
function operationPath(operation) {
|
|
@@ -11218,7 +11976,7 @@ function attachResourceDirectoryCommands(group, config) {
|
|
|
11218
11976
|
throw new SimApiError("--limit must be a non-negative integer", 0);
|
|
11219
11977
|
}
|
|
11220
11978
|
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
11221
|
-
const folderPath = path ?? "/";
|
|
11979
|
+
const folderPath = encodeFolderPath(path ?? "/");
|
|
11222
11980
|
const { client, profile } = clientFrom(command);
|
|
11223
11981
|
const workspaceId = client.requireWorkspace();
|
|
11224
11982
|
const [folders, resources] = await Promise.all([
|
|
@@ -11226,14 +11984,14 @@ function attachResourceDirectoryCommands(group, config) {
|
|
|
11226
11984
|
listResources(client, config, workspaceId, folderPath, options.search, limit)
|
|
11227
11985
|
]);
|
|
11228
11986
|
const entries = entriesFor(config, folders, resources);
|
|
11229
|
-
printList(profile.output, entries.slice(0, limit),
|
|
11987
|
+
printList(profile.output, entries.slice(0, limit), COLUMNS2);
|
|
11230
11988
|
});
|
|
11231
11989
|
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
11990
|
const { client, profile } = clientFrom(command);
|
|
11233
11991
|
const operation = V2_OPERATIONS[config.createFolder];
|
|
11234
11992
|
const result = await client.request(operation.path, {
|
|
11235
11993
|
method: operation.method,
|
|
11236
|
-
body: { workspaceId: client.requireWorkspace(), path }
|
|
11994
|
+
body: { workspaceId: client.requireWorkspace(), path: encodeFolderPath(path) }
|
|
11237
11995
|
});
|
|
11238
11996
|
renderResult(config.createFolder, profile.output, result.data ?? result, {});
|
|
11239
11997
|
});
|
|
@@ -11255,17 +12013,17 @@ function jsonFlag(raw, flagName, kind) {
|
|
|
11255
12013
|
}
|
|
11256
12014
|
async function watchImport(client, workspaceId, job) {
|
|
11257
12015
|
let current = job;
|
|
11258
|
-
let
|
|
12016
|
+
let reported2 = -1;
|
|
11259
12017
|
while (!IMPORT_SETTLED.has(current.status)) {
|
|
11260
12018
|
await sleep2(IMPORT_POLL_MS);
|
|
11261
12019
|
const next = await client.request(`/api/v2/tables/imports/${encodeURIComponent(current.id)}`, { query: { workspaceId } });
|
|
11262
12020
|
current = next.data;
|
|
11263
|
-
if (process.stderr.isTTY && current.rowsProcessed !==
|
|
11264
|
-
|
|
11265
|
-
process.stderr.write(`\r${source_default.dim(`${current.status}… ${
|
|
12021
|
+
if (process.stderr.isTTY && current.rowsProcessed !== reported2) {
|
|
12022
|
+
reported2 = current.rowsProcessed;
|
|
12023
|
+
process.stderr.write(`\r${source_default.dim(`${current.status}… ${reported2} rows`)}\x1B[K`);
|
|
11266
12024
|
}
|
|
11267
12025
|
}
|
|
11268
|
-
if (process.stderr.isTTY &&
|
|
12026
|
+
if (process.stderr.isTTY && reported2 >= 0)
|
|
11269
12027
|
process.stderr.write("\r\x1B[K");
|
|
11270
12028
|
return current;
|
|
11271
12029
|
}
|
|
@@ -11287,7 +12045,7 @@ function validateTargetOptions(options) {
|
|
|
11287
12045
|
return intoExisting;
|
|
11288
12046
|
}
|
|
11289
12047
|
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) => {
|
|
12048
|
+
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
12049
|
const { client, profile } = clientFrom(command);
|
|
11292
12050
|
const workspaceId = client.requireWorkspace();
|
|
11293
12051
|
if (Boolean(path) === Boolean(options.fileId)) {
|
|
@@ -11312,7 +12070,7 @@ function attachTableImport(tables) {
|
|
|
11312
12070
|
target = {
|
|
11313
12071
|
type: "new",
|
|
11314
12072
|
name,
|
|
11315
|
-
...options.folder !== undefined ? { folderPath: options.folder } : {}
|
|
12073
|
+
...options.folder !== undefined ? { folderPath: encodeFolderPath(options.folder) } : {}
|
|
11316
12074
|
};
|
|
11317
12075
|
}
|
|
11318
12076
|
const started = await client.request(V2_OPERATIONS.createTableImport.path, {
|
|
@@ -11360,6 +12118,427 @@ function attachTableImport(tables) {
|
|
|
11360
12118
|
});
|
|
11361
12119
|
}
|
|
11362
12120
|
|
|
12121
|
+
// src/runtime/renamed.ts
|
|
12122
|
+
var warned = new Set;
|
|
12123
|
+
function warn(kind, from, to) {
|
|
12124
|
+
const key = `${kind}:${from}`;
|
|
12125
|
+
if (warned.has(key))
|
|
12126
|
+
return;
|
|
12127
|
+
warned.add(key);
|
|
12128
|
+
process.stderr.write(`warning: ${kind} "${from}" has been renamed to "${to}". The old name still works.
|
|
12129
|
+
`);
|
|
12130
|
+
}
|
|
12131
|
+
function warnRenamedCommand(from, to) {
|
|
12132
|
+
warn("command", `sim ${from}`, `sim ${to}`);
|
|
12133
|
+
}
|
|
12134
|
+
function warnRenamedFlag(from, to) {
|
|
12135
|
+
warn("flag", `--${from}`, `--${to}`);
|
|
12136
|
+
}
|
|
12137
|
+
|
|
12138
|
+
// src/runtime/execute.ts
|
|
12139
|
+
function cursorSlot(operationSpec) {
|
|
12140
|
+
if (operationSpec.query && "cursor" in operationSpec.query)
|
|
12141
|
+
return "query";
|
|
12142
|
+
if (operationSpec.body && "cursor" in operationSpec.body)
|
|
12143
|
+
return "body";
|
|
12144
|
+
return null;
|
|
12145
|
+
}
|
|
12146
|
+
function foldRenamedFlags(operation, commandSpec, flags) {
|
|
12147
|
+
for (const [field, flag] of Object.entries(commandSpec.flags ?? {})) {
|
|
12148
|
+
if (!flag.renamedFrom?.length)
|
|
12149
|
+
continue;
|
|
12150
|
+
const current = flagNameFor(operation, field);
|
|
12151
|
+
for (const previous of flag.renamedFrom) {
|
|
12152
|
+
const supplied = flags[camel(previous)];
|
|
12153
|
+
if (supplied === undefined)
|
|
12154
|
+
continue;
|
|
12155
|
+
if (flags[camel(current)] !== undefined) {
|
|
12156
|
+
throw new SimApiError(`--${previous} is the former name of --${current}; pass one, not both`, 0);
|
|
12157
|
+
}
|
|
12158
|
+
warnRenamedFlag(previous, current);
|
|
12159
|
+
flags[camel(current)] = supplied;
|
|
12160
|
+
}
|
|
12161
|
+
}
|
|
12162
|
+
}
|
|
12163
|
+
async function executeOperation(operation, commandSpec, operationSpec, invocation) {
|
|
12164
|
+
const host = invocation[invocation.length - 1];
|
|
12165
|
+
const inheritedFlags = host.optsWithGlobals();
|
|
12166
|
+
const flags = {
|
|
12167
|
+
...inheritedFlags.workspace === undefined ? {} : { workspace: inheritedFlags.workspace },
|
|
12168
|
+
...inheritedFlags.allWorkspaces === undefined ? {} : { allWorkspaces: inheritedFlags.allWorkspaces },
|
|
12169
|
+
...invocation[invocation.length - 2]
|
|
12170
|
+
};
|
|
12171
|
+
const pathPositionalCount = operationSpec.pathParams.filter((param) => !commandSpec.pathFlags?.[param] && !isProfileWorkspacePath(commandSpec, param)).length;
|
|
12172
|
+
const positional = invocation.slice(0, pathPositionalCount);
|
|
12173
|
+
const requestFlags = { ...flags };
|
|
12174
|
+
for (const [index, field] of (commandSpec.positionals ?? []).entries()) {
|
|
12175
|
+
requestFlags[camel(flagNameFor(operation, field))] = invocation[pathPositionalCount + index];
|
|
12176
|
+
}
|
|
12177
|
+
foldRenamedFlags(operation, commandSpec, requestFlags);
|
|
12178
|
+
if (commandSpec.confirm && !requestFlags.yes) {
|
|
12179
|
+
throw new SimApiError(`${commandSpec.confirm} Re-run with --yes to confirm.`, 0);
|
|
12180
|
+
}
|
|
12181
|
+
if (commandSpec.allWorkspaces && requestFlags.allWorkspaces && requestFlags.workspace) {
|
|
12182
|
+
throw new SimApiError("--all-workspaces cannot be combined with --workspace", 0);
|
|
12183
|
+
}
|
|
12184
|
+
const { client, profile } = clientFrom(host);
|
|
12185
|
+
const hasWorkspaceField = Boolean(operationSpec.query && PROFILE_INJECTED_FIELD in operationSpec.query || operationSpec.body && PROFILE_INJECTED_FIELD in operationSpec.body);
|
|
12186
|
+
const omitsWorkspace = commandSpec.allWorkspaces && requestFlags.allWorkspaces === true;
|
|
12187
|
+
const request = buildRequest(operation, positional, requestFlags, hasWorkspaceField && !omitsWorkspace ? client.requireWorkspace() : profile.workspaceId);
|
|
12188
|
+
const paging = cursorSlot(operationSpec);
|
|
12189
|
+
if (paging) {
|
|
12190
|
+
const rawLimit = Number.parseInt(String(requestFlags.limit ?? DEFAULT_LIMIT), 10);
|
|
12191
|
+
if (Number.isNaN(rawLimit) || rawLimit < 0) {
|
|
12192
|
+
throw new SimApiError("--limit must be a non-negative number", 0);
|
|
12193
|
+
}
|
|
12194
|
+
const limit = rawLimit === 0 ? Number.POSITIVE_INFINITY : rawLimit;
|
|
12195
|
+
const pageSize = Math.min(Number.isFinite(limit) ? limit : DEFAULT_LIMIT, DEFAULT_LIMIT);
|
|
12196
|
+
const pageLimit = "limit" in (operationSpec[paging] ?? {}) ? { limit: pageSize } : {};
|
|
12197
|
+
const rows = [];
|
|
12198
|
+
const progress = pageProgress();
|
|
12199
|
+
let cursor = null;
|
|
12200
|
+
try {
|
|
12201
|
+
do {
|
|
12202
|
+
const page = await client.request(request.path, {
|
|
12203
|
+
method: operationSpec.method,
|
|
12204
|
+
query: paging === "query" ? { ...request.query, ...pageLimit, cursor } : request.query,
|
|
12205
|
+
body: paging === "body" ? { ...request.body ?? {}, ...pageLimit, ...cursor ? { cursor } : {} } : request.body
|
|
12206
|
+
});
|
|
12207
|
+
rows.push(...page.data);
|
|
12208
|
+
cursor = page.nextCursor;
|
|
12209
|
+
if (cursor && rows.length < limit)
|
|
12210
|
+
progress.advance(rows.length);
|
|
12211
|
+
} while (cursor && rows.length < limit);
|
|
12212
|
+
} finally {
|
|
12213
|
+
progress.finish();
|
|
12214
|
+
}
|
|
12215
|
+
renderPage(profile.output, Number.isFinite(limit) ? rows.slice(0, limit) : rows, commandSpec);
|
|
12216
|
+
return;
|
|
12217
|
+
}
|
|
12218
|
+
const result = await client.request(request.path, {
|
|
12219
|
+
method: operationSpec.method,
|
|
12220
|
+
query: request.query,
|
|
12221
|
+
body: request.body
|
|
12222
|
+
});
|
|
12223
|
+
renderResult(operation, profile.output, result?.data ?? result, commandSpec, {
|
|
12224
|
+
expandedTrace: requestFlags.trace === true
|
|
12225
|
+
});
|
|
12226
|
+
}
|
|
12227
|
+
|
|
12228
|
+
// src/commands/protocol/workflow-run-follow.ts
|
|
12229
|
+
var AGENT_STREAM_PROTOCOL_HEADER = "x-sim-stream-protocol";
|
|
12230
|
+
var AGENT_STREAM_PROTOCOL_V1 = "agent-events-v1";
|
|
12231
|
+
var DONE_SENTINEL = "[DONE]";
|
|
12232
|
+
function isRecord(value) {
|
|
12233
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12234
|
+
}
|
|
12235
|
+
function stringField(frame, key) {
|
|
12236
|
+
const value = frame[key];
|
|
12237
|
+
return typeof value === "string" ? value : null;
|
|
12238
|
+
}
|
|
12239
|
+
async function* sseData(body) {
|
|
12240
|
+
const reader = body.getReader();
|
|
12241
|
+
const decoder = new TextDecoder;
|
|
12242
|
+
let buffer = "";
|
|
12243
|
+
try {
|
|
12244
|
+
while (true) {
|
|
12245
|
+
const { done, value } = await reader.read();
|
|
12246
|
+
buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
|
|
12247
|
+
const lines = buffer.split(`
|
|
12248
|
+
`);
|
|
12249
|
+
buffer = done ? "" : lines.pop() ?? "";
|
|
12250
|
+
for (const rawLine of lines) {
|
|
12251
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
12252
|
+
if (!line.startsWith("data:"))
|
|
12253
|
+
continue;
|
|
12254
|
+
const payload = line.slice(5).startsWith(" ") ? line.slice(6) : line.slice(5);
|
|
12255
|
+
if (payload.length > 0)
|
|
12256
|
+
yield payload;
|
|
12257
|
+
}
|
|
12258
|
+
if (done)
|
|
12259
|
+
return;
|
|
12260
|
+
}
|
|
12261
|
+
} finally {
|
|
12262
|
+
reader.releaseLock();
|
|
12263
|
+
}
|
|
12264
|
+
}
|
|
12265
|
+
|
|
12266
|
+
class Commentary {
|
|
12267
|
+
sink;
|
|
12268
|
+
atLineStart = true;
|
|
12269
|
+
constructor(sink) {
|
|
12270
|
+
this.sink = sink;
|
|
12271
|
+
}
|
|
12272
|
+
inline(text2) {
|
|
12273
|
+
if (text2.length === 0)
|
|
12274
|
+
return;
|
|
12275
|
+
this.sink.write(text2);
|
|
12276
|
+
this.atLineStart = text2.endsWith(`
|
|
12277
|
+
`);
|
|
12278
|
+
}
|
|
12279
|
+
line(text2) {
|
|
12280
|
+
this.sink.write(`${this.atLineStart ? "" : `
|
|
12281
|
+
`}${text2}
|
|
12282
|
+
`);
|
|
12283
|
+
this.atLineStart = true;
|
|
12284
|
+
}
|
|
12285
|
+
endLine() {
|
|
12286
|
+
if (this.atLineStart)
|
|
12287
|
+
return;
|
|
12288
|
+
this.sink.write(`
|
|
12289
|
+
`);
|
|
12290
|
+
this.atLineStart = true;
|
|
12291
|
+
}
|
|
12292
|
+
}
|
|
12293
|
+
function toolNotice(frame) {
|
|
12294
|
+
const name = safeOneLine(stringField(frame, "name") ?? "tool");
|
|
12295
|
+
if (frame.phase === "start")
|
|
12296
|
+
return source_default.dim(`→ ${name}`);
|
|
12297
|
+
const status = stringField(frame, "status");
|
|
12298
|
+
if (status && status !== "success")
|
|
12299
|
+
return source_default.yellow(`✗ ${name} (${safeOneLine(status)})`);
|
|
12300
|
+
return source_default.dim(`✓ ${name}`);
|
|
12301
|
+
}
|
|
12302
|
+
async function renderRunStream(body, options) {
|
|
12303
|
+
const commentary = new Commentary(options.stderr);
|
|
12304
|
+
let final = null;
|
|
12305
|
+
for await (const payload of sseData(body)) {
|
|
12306
|
+
let frame;
|
|
12307
|
+
try {
|
|
12308
|
+
frame = JSON.parse(payload);
|
|
12309
|
+
} catch {
|
|
12310
|
+
continue;
|
|
12311
|
+
}
|
|
12312
|
+
if (frame === DONE_SENTINEL)
|
|
12313
|
+
break;
|
|
12314
|
+
if (!isRecord(frame))
|
|
12315
|
+
continue;
|
|
12316
|
+
if (frame.event === undefined && typeof frame.chunk === "string") {
|
|
12317
|
+
commentary.inline(sanitize(frame.chunk));
|
|
12318
|
+
continue;
|
|
12319
|
+
}
|
|
12320
|
+
switch (frame.event) {
|
|
12321
|
+
case "chunk_reset":
|
|
12322
|
+
commentary.line(source_default.dim("… retracted; that turn resolved to tool calls"));
|
|
12323
|
+
break;
|
|
12324
|
+
case "thinking":
|
|
12325
|
+
if (options.includeThinking && typeof frame.data === "string") {
|
|
12326
|
+
commentary.inline(source_default.dim(sanitize(frame.data)));
|
|
12327
|
+
}
|
|
12328
|
+
break;
|
|
12329
|
+
case "tool":
|
|
12330
|
+
if (options.includeToolCalls)
|
|
12331
|
+
commentary.line(toolNotice(frame));
|
|
12332
|
+
break;
|
|
12333
|
+
case "stream_error":
|
|
12334
|
+
commentary.line(source_default.yellow(`warning: ${safeOneLine(stringField(frame, "error") ?? "stream read failed")}`));
|
|
12335
|
+
break;
|
|
12336
|
+
case "error":
|
|
12337
|
+
commentary.endLine();
|
|
12338
|
+
throw new SimApiError(safeOneLine(stringField(frame, "error") ?? "The workflow run failed."), 0);
|
|
12339
|
+
case "final":
|
|
12340
|
+
if (isRecord(frame.data))
|
|
12341
|
+
final = frame.data;
|
|
12342
|
+
break;
|
|
12343
|
+
default:
|
|
12344
|
+
break;
|
|
12345
|
+
}
|
|
12346
|
+
}
|
|
12347
|
+
commentary.endLine();
|
|
12348
|
+
if (!final) {
|
|
12349
|
+
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);
|
|
12350
|
+
}
|
|
12351
|
+
return final;
|
|
12352
|
+
}
|
|
12353
|
+
async function followRun(workflowId, command) {
|
|
12354
|
+
const flags = command.optsWithGlobals();
|
|
12355
|
+
if (flags.async === true) {
|
|
12356
|
+
throw new SimApiError("--follow streams a run as it happens and --async returns before it starts; pass one, not both", 0);
|
|
12357
|
+
}
|
|
12358
|
+
const includeThinking = flags.includeThinking === true;
|
|
12359
|
+
const includeToolCalls = flags.includeToolCalls === true;
|
|
12360
|
+
const negotiates = includeThinking || includeToolCalls;
|
|
12361
|
+
const { client, profile } = clientFrom(command);
|
|
12362
|
+
const operation = V2_OPERATIONS.executeWorkflow;
|
|
12363
|
+
const request = buildRequest("executeWorkflow", [workflowId], flags, profile.workspaceId);
|
|
12364
|
+
const response = await client.requestRaw(request.path, {
|
|
12365
|
+
method: "POST",
|
|
12366
|
+
query: request.query,
|
|
12367
|
+
body: {
|
|
12368
|
+
...request.body ?? {},
|
|
12369
|
+
stream: true,
|
|
12370
|
+
...includeThinking ? { includeThinking: true } : {},
|
|
12371
|
+
...includeToolCalls ? { includeToolCalls: true } : {}
|
|
12372
|
+
},
|
|
12373
|
+
headers: {
|
|
12374
|
+
accept: "text/event-stream",
|
|
12375
|
+
...negotiates ? { [AGENT_STREAM_PROTOCOL_HEADER]: AGENT_STREAM_PROTOCOL_V1 } : {}
|
|
12376
|
+
}
|
|
12377
|
+
});
|
|
12378
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
12379
|
+
if (!contentType.toLowerCase().includes("text/event-stream")) {
|
|
12380
|
+
await response.body?.cancel();
|
|
12381
|
+
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);
|
|
12382
|
+
}
|
|
12383
|
+
if (!response.body) {
|
|
12384
|
+
throw new SimApiError("The run stream had no body.", response.status);
|
|
12385
|
+
}
|
|
12386
|
+
const final = await renderRunStream(response.body, {
|
|
12387
|
+
includeThinking,
|
|
12388
|
+
includeToolCalls,
|
|
12389
|
+
stderr: process.stderr
|
|
12390
|
+
});
|
|
12391
|
+
renderResult("executeWorkflow", profile.output, final, CLI_CONTRACT.executeWorkflow ?? {});
|
|
12392
|
+
if (final.success === false) {
|
|
12393
|
+
throw new SimApiError(safeOneLine(typeof final.error === "string" ? final.error : "The workflow run failed."), 0);
|
|
12394
|
+
}
|
|
12395
|
+
}
|
|
12396
|
+
function followOrDelegate(previous) {
|
|
12397
|
+
return async (workflowId, _options, command) => {
|
|
12398
|
+
const flags = command.optsWithGlobals();
|
|
12399
|
+
if (flags.follow !== true) {
|
|
12400
|
+
if (flags.includeThinking === true || flags.includeToolCalls === true) {
|
|
12401
|
+
throw new SimApiError("--include-thinking and --include-tool-calls describe a stream; add --follow", 0);
|
|
12402
|
+
}
|
|
12403
|
+
if (previous) {
|
|
12404
|
+
await previous(command.processedArgs);
|
|
12405
|
+
return;
|
|
12406
|
+
}
|
|
12407
|
+
await executeOperation("executeWorkflow", CLI_CONTRACT.executeWorkflow ?? {}, V2_OPERATIONS.executeWorkflow, [workflowId, command.opts(), command]);
|
|
12408
|
+
return;
|
|
12409
|
+
}
|
|
12410
|
+
await followRun(workflowId, command);
|
|
12411
|
+
};
|
|
12412
|
+
}
|
|
12413
|
+
function attachWorkflowRunFollow(workflows) {
|
|
12414
|
+
const run = workflows.commands.find((command) => command.name() === "run");
|
|
12415
|
+
if (!run) {
|
|
12416
|
+
throw new Error("workflows run must be registered before --follow can be attached to it");
|
|
12417
|
+
}
|
|
12418
|
+
const held = run._actionHandler;
|
|
12419
|
+
const previous = typeof held === "function" ? held : null;
|
|
12420
|
+
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));
|
|
12421
|
+
}
|
|
12422
|
+
|
|
12423
|
+
// src/commands/protocol/workflow-run-wait.ts
|
|
12424
|
+
var TERMINAL_STATUSES = new Set(["completed", "failed", "cancelled"]);
|
|
12425
|
+
var WAIT_EXIT_CODES = {
|
|
12426
|
+
completed: 0,
|
|
12427
|
+
failed: 1,
|
|
12428
|
+
cancelled: 2,
|
|
12429
|
+
paused: 3,
|
|
12430
|
+
timeout: 4
|
|
12431
|
+
};
|
|
12432
|
+
var FIRST_POLL_DELAY_MS = 2000;
|
|
12433
|
+
var MAX_POLL_DELAY_MS = 15000;
|
|
12434
|
+
var POLL_BACKOFF_FACTOR = 2;
|
|
12435
|
+
var DEFAULT_WAIT_TIMEOUT_SECONDS = 3600;
|
|
12436
|
+
var WAIT_TIMEOUT_FLAG = "--wait-timeout <seconds>";
|
|
12437
|
+
function isRecord2(value) {
|
|
12438
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12439
|
+
}
|
|
12440
|
+
function optionalString(value) {
|
|
12441
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
12442
|
+
}
|
|
12443
|
+
function readRun(raw) {
|
|
12444
|
+
const run = isRecord2(raw) && isRecord2(raw.data) ? raw.data : raw;
|
|
12445
|
+
if (!isRecord2(run) || typeof run.status !== "string") {
|
|
12446
|
+
throw new SimApiError("Run status response carried no status.", 0);
|
|
12447
|
+
}
|
|
12448
|
+
const paused = isRecord2(run.paused) ? run.paused : null;
|
|
12449
|
+
return {
|
|
12450
|
+
status: run.status,
|
|
12451
|
+
pauseKind: paused ? optionalString(paused.pauseKind) : null,
|
|
12452
|
+
resumeAt: paused ? optionalString(paused.resumeAt) : null,
|
|
12453
|
+
contextId: paused ? optionalString(paused.contextId) : null
|
|
12454
|
+
};
|
|
12455
|
+
}
|
|
12456
|
+
function classify(snapshot) {
|
|
12457
|
+
if (snapshot.status === "paused")
|
|
12458
|
+
return snapshot.pauseKind === "time" ? null : "paused";
|
|
12459
|
+
if (!TERMINAL_STATUSES.has(snapshot.status))
|
|
12460
|
+
return null;
|
|
12461
|
+
return snapshot.status === "completed" ? "completed" : snapshot.status === "cancelled" ? "cancelled" : "failed";
|
|
12462
|
+
}
|
|
12463
|
+
function waitProgress() {
|
|
12464
|
+
let reported2 = false;
|
|
12465
|
+
return {
|
|
12466
|
+
advance: (status, elapsedMs) => {
|
|
12467
|
+
if (!process.stderr.isTTY)
|
|
12468
|
+
return;
|
|
12469
|
+
reported2 = true;
|
|
12470
|
+
process.stderr.write(`\r${source_default.dim(`${status} — waiting ${Math.round(elapsedMs / 1000)}s…`)}\x1B[K`);
|
|
12471
|
+
},
|
|
12472
|
+
finish: () => {
|
|
12473
|
+
if (!reported2)
|
|
12474
|
+
return;
|
|
12475
|
+
reported2 = false;
|
|
12476
|
+
process.stderr.write("\r\x1B[K");
|
|
12477
|
+
}
|
|
12478
|
+
};
|
|
12479
|
+
}
|
|
12480
|
+
function parseWaitTimeout(raw) {
|
|
12481
|
+
const seconds = Number(raw);
|
|
12482
|
+
if (!Number.isFinite(seconds) || seconds < 0) {
|
|
12483
|
+
throw new SimApiError(`Invalid ${WAIT_TIMEOUT_FLAG} "${raw}". Use a non-negative number of seconds, or 0 to wait indefinitely.`, 0);
|
|
12484
|
+
}
|
|
12485
|
+
return seconds;
|
|
12486
|
+
}
|
|
12487
|
+
function explain(outcome, runId, workflowId, snapshot) {
|
|
12488
|
+
if (outcome === "completed")
|
|
12489
|
+
return null;
|
|
12490
|
+
if (outcome === "failed")
|
|
12491
|
+
return `Run ${runId} failed.`;
|
|
12492
|
+
if (outcome === "cancelled")
|
|
12493
|
+
return `Run ${runId} was cancelled.`;
|
|
12494
|
+
const context = snapshot.contextId ? ` --context ${snapshot.contextId}` : "";
|
|
12495
|
+
return `Run ${runId} is paused waiting for input. Resume it: sim workflows runs resume ${runId} --workflow ${workflowId}${context}`;
|
|
12496
|
+
}
|
|
12497
|
+
function runSpec() {
|
|
12498
|
+
return CLI_CONTRACT.getWorkflowRun ?? {};
|
|
12499
|
+
}
|
|
12500
|
+
function attachWorkflowRunWait(runs) {
|
|
12501
|
+
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) => {
|
|
12502
|
+
const timeoutSeconds = options.waitTimeout === undefined ? DEFAULT_WAIT_TIMEOUT_SECONDS : parseWaitTimeout(options.waitTimeout);
|
|
12503
|
+
const { client, profile } = clientFrom(command);
|
|
12504
|
+
const operation = V2_OPERATIONS.getWorkflowRun;
|
|
12505
|
+
const path = resolvePath(operation.path, { id: options.workflow, runId });
|
|
12506
|
+
const startedAt = Date.now();
|
|
12507
|
+
const deadline = timeoutSeconds === 0 ? Number.POSITIVE_INFINITY : startedAt + timeoutSeconds * 1000;
|
|
12508
|
+
const progress = waitProgress();
|
|
12509
|
+
let delayMs = FIRST_POLL_DELAY_MS;
|
|
12510
|
+
try {
|
|
12511
|
+
while (true) {
|
|
12512
|
+
const raw = await client.request(path, { method: operation.method });
|
|
12513
|
+
const snapshot = readRun(raw);
|
|
12514
|
+
const outcome = classify(snapshot);
|
|
12515
|
+
if (outcome) {
|
|
12516
|
+
progress.finish();
|
|
12517
|
+
renderResult("getWorkflowRun", profile.output, raw, runSpec());
|
|
12518
|
+
const message = explain(outcome, runId, options.workflow, snapshot);
|
|
12519
|
+
if (message)
|
|
12520
|
+
console.error(source_default.red(message));
|
|
12521
|
+
process.exitCode = WAIT_EXIT_CODES[outcome];
|
|
12522
|
+
return;
|
|
12523
|
+
}
|
|
12524
|
+
const remainingMs = deadline - Date.now();
|
|
12525
|
+
if (remainingMs <= 0) {
|
|
12526
|
+
progress.finish();
|
|
12527
|
+
renderResult("getWorkflowRun", profile.output, raw, runSpec());
|
|
12528
|
+
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.`));
|
|
12529
|
+
process.exitCode = WAIT_EXIT_CODES.timeout;
|
|
12530
|
+
return;
|
|
12531
|
+
}
|
|
12532
|
+
progress.advance(snapshot.status, Date.now() - startedAt);
|
|
12533
|
+
await sleep(Math.min(delayMs, remainingMs));
|
|
12534
|
+
delayMs = Math.min(delayMs * POLL_BACKOFF_FACTOR, MAX_POLL_DELAY_MS);
|
|
12535
|
+
}
|
|
12536
|
+
} finally {
|
|
12537
|
+
progress.finish();
|
|
12538
|
+
}
|
|
12539
|
+
});
|
|
12540
|
+
}
|
|
12541
|
+
|
|
11363
12542
|
// src/commands/protocol/index.ts
|
|
11364
12543
|
function group(program2, name) {
|
|
11365
12544
|
const existing = program2.commands.find((command) => command.name() === name);
|
|
@@ -11395,12 +12574,16 @@ function attachProtocolCommands(program2) {
|
|
|
11395
12574
|
folders: "listTableFolders",
|
|
11396
12575
|
createFolder: "createTableFolder"
|
|
11397
12576
|
});
|
|
11398
|
-
|
|
12577
|
+
const workflows = group(program2, "workflows");
|
|
12578
|
+
attachResourceDirectoryCommands(workflows, {
|
|
11399
12579
|
kind: "workflow",
|
|
11400
12580
|
resources: "listWorkflows",
|
|
11401
12581
|
folders: "listWorkflowFolders",
|
|
11402
12582
|
createFolder: "createWorkflowFolder"
|
|
11403
12583
|
});
|
|
12584
|
+
attachWorkflowRunFollow(workflows);
|
|
12585
|
+
attachWorkflowRunWait(group(workflows, "runs"));
|
|
12586
|
+
attachLogsFollow(group(program2, "logs"));
|
|
11404
12587
|
}
|
|
11405
12588
|
|
|
11406
12589
|
// src/terminal/secret-input.ts
|
|
@@ -11481,7 +12664,8 @@ var SECRET_RESULT = {
|
|
|
11481
12664
|
{ header: "name" },
|
|
11482
12665
|
{ header: "scope" },
|
|
11483
12666
|
{ header: "role" },
|
|
11484
|
-
{ header: "updated", path: "updatedAt", format: "timestamp" }
|
|
12667
|
+
{ header: "updated", path: "updatedAt", format: "timestamp" },
|
|
12668
|
+
{ header: "description" }
|
|
11485
12669
|
]
|
|
11486
12670
|
};
|
|
11487
12671
|
function validateSecretValue(value) {
|
|
@@ -11492,7 +12676,16 @@ function validateSecretValue(value) {
|
|
|
11492
12676
|
}
|
|
11493
12677
|
return value;
|
|
11494
12678
|
}
|
|
12679
|
+
function validateDescriptionScope(description, scope) {
|
|
12680
|
+
if (description === undefined)
|
|
12681
|
+
return;
|
|
12682
|
+
if (scope === "personal") {
|
|
12683
|
+
throw new SimApiError("--description is only supported for a workspace secret.", 0);
|
|
12684
|
+
}
|
|
12685
|
+
return description;
|
|
12686
|
+
}
|
|
11495
12687
|
async function setSecret(name, options, command) {
|
|
12688
|
+
const description = validateDescriptionScope(options.description, options.scope);
|
|
11496
12689
|
const value = validateSecretValue(options.value ?? await promptSecret());
|
|
11497
12690
|
const { client, profile } = clientFrom(command);
|
|
11498
12691
|
const operation = V2_OPERATIONS.setSecret;
|
|
@@ -11501,7 +12694,8 @@ async function setSecret(name, options, command) {
|
|
|
11501
12694
|
body: {
|
|
11502
12695
|
workspaceId: client.requireWorkspace(),
|
|
11503
12696
|
scope: options.scope,
|
|
11504
|
-
value
|
|
12697
|
+
value,
|
|
12698
|
+
description
|
|
11505
12699
|
}
|
|
11506
12700
|
});
|
|
11507
12701
|
renderResult("setSecret", profile.output, response.data, SECRET_RESULT);
|
|
@@ -11510,72 +12704,7 @@ function attachSecretCommands(program2) {
|
|
|
11510
12704
|
const secrets = program2.commands.find((command) => command.name() === "secrets");
|
|
11511
12705
|
if (!secrets)
|
|
11512
12706
|
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
|
-
});
|
|
12707
|
+
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
12708
|
}
|
|
11580
12709
|
|
|
11581
12710
|
// src/runtime/build.ts
|
|
@@ -11693,6 +12822,19 @@ function configureOperation(command, operation, spec) {
|
|
|
11693
12822
|
function buildLeaf(operation, spec, leafName) {
|
|
11694
12823
|
return addMissingArgumentExample(configureOperation(new Command(leafName), operation, spec));
|
|
11695
12824
|
}
|
|
12825
|
+
function addRenamedCommand(groups, operation, spec, from, to) {
|
|
12826
|
+
const segments = from.split(" ");
|
|
12827
|
+
const [groupName, ...rest] = segments;
|
|
12828
|
+
if (rest.length === 0)
|
|
12829
|
+
throw new Error(`${operation}.renamedFrom "${from}" must include a verb`);
|
|
12830
|
+
let parent = groupFor(groups, groupName);
|
|
12831
|
+
for (const segment of rest.slice(0, -1)) {
|
|
12832
|
+
parent = nestedGroup(parent, segment, { hidden: true });
|
|
12833
|
+
}
|
|
12834
|
+
const leaf = buildLeaf(operation, spec, rest[rest.length - 1]);
|
|
12835
|
+
leaf.hook("preAction", () => warnRenamedCommand(from, to));
|
|
12836
|
+
parent.addCommand(leaf, { hidden: true });
|
|
12837
|
+
}
|
|
11696
12838
|
function groupFor(groups, name) {
|
|
11697
12839
|
const existing = groups.get(name);
|
|
11698
12840
|
if (existing)
|
|
@@ -11708,12 +12850,12 @@ function resourceLabel(name) {
|
|
|
11708
12850
|
const label = name.endsWith("s") ? name.slice(0, -1) : name;
|
|
11709
12851
|
return label.replaceAll("-", " ");
|
|
11710
12852
|
}
|
|
11711
|
-
function nestedGroup(parent, name) {
|
|
12853
|
+
function nestedGroup(parent, name, options = {}) {
|
|
11712
12854
|
const existing = parent.commands.find((candidate) => candidate.name() === name);
|
|
11713
12855
|
if (existing)
|
|
11714
12856
|
return existing;
|
|
11715
12857
|
const created = new Command(name).description(`Manage ${resourceLabel(parent.name())} ${name.replaceAll("-", " ")}`);
|
|
11716
|
-
parent.addCommand(created);
|
|
12858
|
+
parent.addCommand(created, { hidden: options.hidden });
|
|
11717
12859
|
return created;
|
|
11718
12860
|
}
|
|
11719
12861
|
function addLeafCommand(groups, operation, spec, segments) {
|
|
@@ -11742,6 +12884,7 @@ function variantCommandSpec(spec, variant) {
|
|
|
11742
12884
|
}
|
|
11743
12885
|
function buildGeneratedCommands() {
|
|
11744
12886
|
const groups = new Map;
|
|
12887
|
+
const renamed = [];
|
|
11745
12888
|
for (const operation of Object.keys(V2_OPERATIONS)) {
|
|
11746
12889
|
const spec = CLI_CONTRACT[operation] ?? {};
|
|
11747
12890
|
const operationSpec = V2_OPERATIONS[operation];
|
|
@@ -11764,6 +12907,12 @@ function buildGeneratedCommands() {
|
|
|
11764
12907
|
for (const variant of spec.variants ?? []) {
|
|
11765
12908
|
addLeafCommand(groups, operation, variantCommandSpec(spec, variant), variant.command.split(" "));
|
|
11766
12909
|
}
|
|
12910
|
+
for (const from of spec.renamedFrom ?? []) {
|
|
12911
|
+
renamed.push({ operation, spec, from, to: segments.join(" ") });
|
|
12912
|
+
}
|
|
12913
|
+
}
|
|
12914
|
+
for (const { operation, spec, from, to } of renamed) {
|
|
12915
|
+
addRenamedCommand(groups, operation, spec, from, to);
|
|
11767
12916
|
}
|
|
11768
12917
|
return [...groups.values()].sort((a, b) => a.name().localeCompare(b.name()));
|
|
11769
12918
|
}
|
|
@@ -11772,7 +12921,8 @@ function buildGeneratedCommands() {
|
|
|
11772
12921
|
var PROGRAM_DESCRIPTION = "Talk to the Sim API from your terminal";
|
|
11773
12922
|
var HELP_EPILOGUE = `
|
|
11774
12923
|
Profiles work like the AWS CLI: settings live in ~/.sim/config, keys in
|
|
11775
|
-
~/.sim/credentials (0600)
|
|
12924
|
+
~/.sim/credentials (0600), or under SIM_CONFIG_DIR when it is set. Select one
|
|
12925
|
+
with -P, --profile, or SIM_PROFILE.
|
|
11776
12926
|
|
|
11777
12927
|
Examples:
|
|
11778
12928
|
$ sim login Authorize the default profile
|
|
@@ -11786,18 +12936,11 @@ Examples:
|
|
|
11786
12936
|
$ sim workflows import --workflow @wf.json
|
|
11787
12937
|
$ sim whoami --profile dev
|
|
11788
12938
|
`;
|
|
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
12939
|
function buildProgram(options = {}) {
|
|
11797
12940
|
const program2 = new Command;
|
|
11798
12941
|
program2.name("sim").description(PROGRAM_DESCRIPTION);
|
|
11799
12942
|
if (options.version !== false)
|
|
11800
|
-
program2.version(
|
|
12943
|
+
program2.version(CLI_VERSION);
|
|
11801
12944
|
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
12945
|
program2.addCommand(loginCommand());
|
|
11803
12946
|
program2.addCommand(logoutCommand());
|
|
@@ -11823,6 +12966,10 @@ async function main() {
|
|
|
11823
12966
|
console.error(source_default.red(`Error: ${sanitize(error.message)}`));
|
|
11824
12967
|
process.exit(1);
|
|
11825
12968
|
}
|
|
12969
|
+
if (isRequestTimeout(error)) {
|
|
12970
|
+
console.error(source_default.red(`Error: the request timed out. ${RAISE_TIMEOUT_HINT}`));
|
|
12971
|
+
process.exit(1);
|
|
12972
|
+
}
|
|
11826
12973
|
if (error instanceof SimApiError) {
|
|
11827
12974
|
console.error(source_default.red(`Error: ${sanitize(error.message)}`));
|
|
11828
12975
|
if (error.code)
|