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