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