@uru-intelligence/cli 0.3.53 → 0.3.55
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 +35 -5
- package/dist/uru.mjs +881 -114
- package/package.json +1 -1
package/dist/uru.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { spawn as spawn3 } from "node:child_process";
|
|
|
6
6
|
|
|
7
7
|
// src/types.ts
|
|
8
8
|
var DEFAULT_API_URL = "https://api.uruintelligence.com";
|
|
9
|
-
var CLI_VERSION = "0.3.
|
|
9
|
+
var CLI_VERSION = "0.3.55";
|
|
10
10
|
var ExitCode = {
|
|
11
11
|
Ok: 0,
|
|
12
12
|
Failure: 1,
|
|
@@ -202,6 +202,7 @@ function buildOperationGroups(op) {
|
|
|
202
202
|
op("gem.fs_read", "gems", "Read a file from a Gem project directory.", "gem_query", "fs_read", "read"),
|
|
203
203
|
op("gem.fs_write", "gems", "Write a file inside a Gem project directory.", "gem_control_plane", "fs_write", "write"),
|
|
204
204
|
op("gem.fs_batch_write", "gems", "Write multiple Gem files atomically through the server source layer.", "gem_control_plane", "fs_batch_write", "write"),
|
|
205
|
+
op("gem.fs_sync", "gems", "Synchronize Gem files and deletions atomically through the server source layer.", "gem_control_plane", "fs_sync", "write"),
|
|
205
206
|
op("gem.fs_patch", "gems", "Apply a patch to Gem source files.", "gem_control_plane", "fs_patch", "write"),
|
|
206
207
|
op("gem.fs_mkdir", "gems", "Create a directory inside a Gem project.", "gem_control_plane", "fs_mkdir", "write"),
|
|
207
208
|
op("gem.fs_rm", "gems", "Remove a file or directory from a Gem project.", "gem_control_plane", "fs_rm", "destructive"),
|
|
@@ -240,8 +241,12 @@ function buildOperationGroups(op) {
|
|
|
240
241
|
const libraryOperations = [
|
|
241
242
|
op("library.ls", "library", "List visible Library folders and resources.", "library_query", "ls", "read"),
|
|
242
243
|
op("library.stat", "library", "Read metadata for a visible Library path.", "library_query", "stat", "read"),
|
|
244
|
+
op("library.read", "library", "Read a visible text-backed Library resource by path.", "library_query", "read", "read"),
|
|
243
245
|
op("library.search", "library", "Search visible Library resources server-side.", "library_query", "search", "read"),
|
|
244
246
|
op("library.mkdir", "library", "Create a Library folder.", "library_fs", "mkdir", "write"),
|
|
247
|
+
op("library.write", "library", "Create or replace a text-backed Library resource by path.", "library_fs", "write", "write"),
|
|
248
|
+
op("library.patch", "library", "Replace text content for a text-backed Library resource by path.", "library_fs", "patch", "write"),
|
|
249
|
+
op("library.cp", "library", "Copy a supported Library item when the backing resource supports copy semantics.", "library_fs", "cp", "write"),
|
|
245
250
|
op("library.mv", "library", "Move or rename a Library item.", "library_fs", "mv", "write"),
|
|
246
251
|
op("library.move_workspace", "library", "Move a Library item to another workspace.", "library_fs", "move_workspace", "destructive"),
|
|
247
252
|
op("library.rm", "library", "Trash a Library item.", "library_fs", "rm", "destructive"),
|
|
@@ -249,19 +254,17 @@ function buildOperationGroups(op) {
|
|
|
249
254
|
op("library.empty_trash", "library", "Permanently empty Library trash.", "library_fs", "empty_trash", "destructive"),
|
|
250
255
|
op("library.upload_preflight", "library", "Preflight a Library upload.", "library_upload", "preflight", "read"),
|
|
251
256
|
op("library.upload_create_session", "library", "Create a direct Library upload session.", "library_upload", "create_session", "write"),
|
|
252
|
-
op("library.upload_complete_session", "library", "Complete a direct Library upload session.", "library_upload", "complete_session", "write")
|
|
253
|
-
op("context.manage", "library", "Manage typed Context Library metadata such as workspace-default, teams, category, and favorite state.", "context_manage", undefined, "write"),
|
|
254
|
-
op("prompt.render", "library", "Render a typed Prompt Library resource with supplied variables.", "prompt_render", undefined, "read"),
|
|
255
|
-
op("persona.query", "library", "Query typed Persona Library resources and linked prompt/context resources.", "persona_query", undefined, "read")
|
|
257
|
+
op("library.upload_complete_session", "library", "Complete a direct Library upload session.", "library_upload", "complete_session", "write")
|
|
256
258
|
];
|
|
257
259
|
const sharingOperations = [
|
|
258
260
|
op("sharing.share", "sharing", "Share a Library resource with a user, team, or workspace.", "library_collab", "share", "write"),
|
|
259
261
|
op("sharing.unshare", "sharing", "Remove a Library resource grant.", "library_collab", "unshare", "destructive"),
|
|
260
|
-
op("sharing.permissions_list", "sharing", "List permissions for a Library resource.", "
|
|
262
|
+
op("sharing.permissions_list", "sharing", "List permissions for a Library resource.", "sharing_query", "list_permissions", "read"),
|
|
263
|
+
op("sharing.shares_list", "sharing", "List active share grants for a Library resource.", "sharing_query", "list_shares", "read"),
|
|
261
264
|
op("sharing.permissions_set", "sharing", "Set permissions for a Library resource.", "library_collab", "set_permissions", "write"),
|
|
262
265
|
op("sharing.settings_set", "sharing", "Set share settings for a Library resource.", "library_collab", "set_share_settings", "write"),
|
|
263
266
|
op("sharing.transfer_ownership", "sharing", "Transfer ownership for a Library resource.", "library_collab", "transfer_ownership", "destructive"),
|
|
264
|
-
op("sharing.versions_list", "sharing", "List Library resource versions.", "
|
|
267
|
+
op("sharing.versions_list", "sharing", "List Library resource versions.", "sharing_query", "list_versions", "read"),
|
|
265
268
|
op("sharing.version_restore", "sharing", "Restore a Library resource version.", "library_collab", "restore_version", "destructive"),
|
|
266
269
|
op("sharing.comment", "sharing", "Comment on a Library resource.", "library_collab", "comment", "write"),
|
|
267
270
|
op("sharing.comment_resolve", "sharing", "Resolve a Library resource comment.", "library_collab", "resolve_comment", "write"),
|
|
@@ -414,7 +417,7 @@ function buildOperationGroups(op) {
|
|
|
414
417
|
op("file.download_for_user", "files", "Prepare a file download for the user.", "DOWNLOAD_FILE_FOR_USER", undefined, "read")
|
|
415
418
|
];
|
|
416
419
|
const businessSqlOperations = [
|
|
417
|
-
op("business_sql.transcript", "business_sql", "Query transcript SQL.", "
|
|
420
|
+
op("business_sql.transcript", "business_sql", "Query transcript SQL.", "transcript_sql", undefined, "read"),
|
|
418
421
|
op("business_sql.maguire", "business_sql", "Query Maguire SQL.", "MAGUIRE_SQL", undefined, "read"),
|
|
419
422
|
op("business_sql.tyler", "business_sql", "Query Tyler SQL.", "TYLER_SQL", undefined, "read"),
|
|
420
423
|
op("business_sql.cfo", "business_sql", "Query CFO SQL.", "CFO_SQL", undefined, "read"),
|
|
@@ -471,10 +474,13 @@ var internalOnlyPlatformToolIds = new Set([
|
|
|
471
474
|
"MAGUIRE_WRITE_SQL",
|
|
472
475
|
"PEREGRINE_SQL",
|
|
473
476
|
"RESTORE_DATASET_ROWS",
|
|
474
|
-
"TRANSCRIPT_SQL",
|
|
475
477
|
"TYLER_SQL",
|
|
476
478
|
"UPSERT_DATASET_ROWS"
|
|
477
479
|
]);
|
|
480
|
+
// ../../packages/platform-operations/src/external_company_mcp_policy.ts
|
|
481
|
+
var externalCompanyMcpDeniedToolIds = new Set([
|
|
482
|
+
"MAGUIRE_WRITE_SQL"
|
|
483
|
+
]);
|
|
478
484
|
// ../../packages/platform-operations/src/index.ts
|
|
479
485
|
var ALL_SURFACES = [
|
|
480
486
|
"cli",
|
|
@@ -621,6 +627,12 @@ class UruApiClient {
|
|
|
621
627
|
body: { parameters }
|
|
622
628
|
});
|
|
623
629
|
}
|
|
630
|
+
async streamTool(name, parameters, onEvent) {
|
|
631
|
+
return this.requestStream(`/api/platform/tools/${encodeURIComponent(name)}/execute`, {
|
|
632
|
+
method: "POST",
|
|
633
|
+
body: { parameters }
|
|
634
|
+
}, onEvent);
|
|
635
|
+
}
|
|
624
636
|
async api(path, options) {
|
|
625
637
|
return this.request(path, options);
|
|
626
638
|
}
|
|
@@ -644,6 +656,30 @@ class UruApiClient {
|
|
|
644
656
|
}
|
|
645
657
|
return payload;
|
|
646
658
|
}
|
|
659
|
+
async requestStream(path, options, onEvent) {
|
|
660
|
+
const init = {
|
|
661
|
+
method: options.method,
|
|
662
|
+
...this.signal === undefined ? {} : { signal: this.signal },
|
|
663
|
+
headers: {
|
|
664
|
+
Authorization: `Bearer ${this.token}`,
|
|
665
|
+
Accept: "application/x-ndjson, application/json",
|
|
666
|
+
...this.workspaceId === undefined ? {} : { "X-Uru-Workspace-Id": this.workspaceId },
|
|
667
|
+
...options.body === undefined ? {} : { "Content-Type": "application/json" }
|
|
668
|
+
},
|
|
669
|
+
...options.body === undefined ? {} : { body: JSON.stringify(options.body) }
|
|
670
|
+
};
|
|
671
|
+
const response = await this.fetchImpl(`${this.apiUrl}${path}`, init);
|
|
672
|
+
if (response.ok && response.body !== null && isNdjsonResponse(response.headers.get("content-type"))) {
|
|
673
|
+
await readNdjsonStream(response.body, onEvent);
|
|
674
|
+
return { streamed: true };
|
|
675
|
+
}
|
|
676
|
+
const text = await response.text();
|
|
677
|
+
const payload = parseResponseBody(text);
|
|
678
|
+
if (!response.ok) {
|
|
679
|
+
throw apiError(response.status, payload, options.body);
|
|
680
|
+
}
|
|
681
|
+
return { streamed: false, payload };
|
|
682
|
+
}
|
|
647
683
|
}
|
|
648
684
|
function normalizeApiUrl(value) {
|
|
649
685
|
const trimmed = value.trim();
|
|
@@ -659,6 +695,52 @@ function parseResponseBody(text) {
|
|
|
659
695
|
}
|
|
660
696
|
return parsed;
|
|
661
697
|
}
|
|
698
|
+
function isNdjsonResponse(contentType) {
|
|
699
|
+
const normalized = String(contentType ?? "").toLowerCase();
|
|
700
|
+
return normalized.includes("application/x-ndjson") || normalized.includes("application/ndjson") || normalized.includes("text/x-ndjson");
|
|
701
|
+
}
|
|
702
|
+
async function readNdjsonStream(body, onEvent) {
|
|
703
|
+
const reader = body.getReader();
|
|
704
|
+
const decoder = new TextDecoder;
|
|
705
|
+
let buffered = "";
|
|
706
|
+
try {
|
|
707
|
+
while (true) {
|
|
708
|
+
const { done, value } = await reader.read();
|
|
709
|
+
if (done) {
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
buffered += decoder.decode(value, { stream: true });
|
|
713
|
+
buffered = emitCompleteNdjsonLines(buffered, onEvent);
|
|
714
|
+
}
|
|
715
|
+
buffered += decoder.decode();
|
|
716
|
+
emitNdjsonLine(buffered, onEvent);
|
|
717
|
+
} finally {
|
|
718
|
+
reader.releaseLock();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function emitCompleteNdjsonLines(buffered, onEvent) {
|
|
722
|
+
let start = 0;
|
|
723
|
+
while (true) {
|
|
724
|
+
const newline = buffered.indexOf(`
|
|
725
|
+
`, start);
|
|
726
|
+
if (newline === -1) {
|
|
727
|
+
return buffered.slice(start);
|
|
728
|
+
}
|
|
729
|
+
emitNdjsonLine(buffered.slice(start, newline), onEvent);
|
|
730
|
+
start = newline + 1;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
function emitNdjsonLine(line, onEvent) {
|
|
734
|
+
const trimmed = line.trim();
|
|
735
|
+
if (trimmed === "") {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const parsed = JSON.parse(trimmed);
|
|
739
|
+
if (!isJsonObject(parsed)) {
|
|
740
|
+
throw new CliError("API returned a non-object stream event");
|
|
741
|
+
}
|
|
742
|
+
onEvent(parsed);
|
|
743
|
+
}
|
|
662
744
|
function apiError(status, payload, requestBody) {
|
|
663
745
|
const redactions = collectSensitiveValues(requestBody);
|
|
664
746
|
const summary = errorMessage(payload) ?? `Uru API request failed with HTTP ${status}`;
|
|
@@ -1005,6 +1087,15 @@ function writeStreamJson(io, event, value) {
|
|
|
1005
1087
|
io.stdout.write(`${JSON.stringify({ event, ...payload })}
|
|
1006
1088
|
`);
|
|
1007
1089
|
}
|
|
1090
|
+
function writeStreamJsonEvent(io, value) {
|
|
1091
|
+
const payload = objectPayload(value);
|
|
1092
|
+
if (typeof payload["event"] !== "string" || payload["event"].trim() === "") {
|
|
1093
|
+
writeStreamJson(io, "event", payload);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
io.stdout.write(`${JSON.stringify(payload)}
|
|
1097
|
+
`);
|
|
1098
|
+
}
|
|
1008
1099
|
function writeText(io, value) {
|
|
1009
1100
|
io.stdout.write(value.endsWith(`
|
|
1010
1101
|
`) ? value : `${value}
|
|
@@ -1075,13 +1166,13 @@ GEMS
|
|
|
1075
1166
|
uru build [library/name.gem] --dry-run --outdir Build/validate and export build manifest
|
|
1076
1167
|
uru deploy [library/name.gem] [--prod] [--dry-run] Server-side build/deploy/publish
|
|
1077
1168
|
uru deploy [library/name.gem] --skip-promote Deploy without promoting the published link
|
|
1078
|
-
uru promote <version-id> [library/name.gem] Promote a Gem version
|
|
1169
|
+
uru promote <version-id> [library/name.gem] Promote a staged Gem version/deployment
|
|
1079
1170
|
uru status [library/name.gem] Local source status versus source lock
|
|
1080
1171
|
uru logs [library/name.gem] [--limit 100] [--follow]
|
|
1081
1172
|
Runtime logs
|
|
1082
1173
|
uru open [library/name.gem] Print first published URL
|
|
1083
|
-
uru rollback <version-id> [library/name.gem] --yes
|
|
1084
|
-
Roll back a
|
|
1174
|
+
uru rollback [<version-id>] [library/name.gem] --yes
|
|
1175
|
+
Roll back a live Gem link
|
|
1085
1176
|
uru gems inspect|validate|build|status [library/name.gem]
|
|
1086
1177
|
uru gems versions upload [library/name.gem] Create an immutable Gem version
|
|
1087
1178
|
uru gems versions deploy <version-id> [library/name.gem]
|
|
@@ -1093,20 +1184,29 @@ LIBRARY / DATASETS / TOOLS
|
|
|
1093
1184
|
uru library ls [path] List Library items
|
|
1094
1185
|
uru library search <query> Search Library items
|
|
1095
1186
|
uru library mkdir <path> Create a Library folder
|
|
1187
|
+
uru library read <path> Print a text-backed Library resource
|
|
1188
|
+
uru library write <path> --file ./note.md --create Create/replace text-backed Library content
|
|
1189
|
+
uru library patch <path> --content "..." Replace text-backed Library content
|
|
1190
|
+
uru library cp|mv <path> <target-path> Copy or move visible Library items
|
|
1191
|
+
uru library rm <path> [--permanent --yes] Trash or permanently delete Library items
|
|
1192
|
+
uru library restore <path> Restore a trashed Library item
|
|
1096
1193
|
# Prompts, personas, contexts: use typed Library paths such as library/Prompts/Foo.prompt.md
|
|
1097
|
-
uru datasets query <dataset-slug> [--limit 50] Query dataset rows
|
|
1194
|
+
uru datasets query <dataset-slug> [--limit 50] Query dataset rows; add --response-format concise for compact output
|
|
1195
|
+
uru automations ls|get|create|enable|disable Manage automations without raw JSON parameters
|
|
1196
|
+
uru automations trigger <id> Start a manual automation run
|
|
1197
|
+
uru automations runs ls|tail <run-id> Inspect automation runs and logs
|
|
1098
1198
|
uru secrets put <name> [library/name.gem] --value-stdin Store Gem secret without shell history
|
|
1099
1199
|
uru secrets ls [library/name.gem] List Gem secret metadata only
|
|
1100
1200
|
uru secrets rm <name> [library/name.gem] --yes Remove a Gem secret value
|
|
1101
1201
|
uru env pull [library/name.gem] Write a local metadata-only .env template
|
|
1102
1202
|
uru env run -- <cmd> Run with local env values and redact output
|
|
1103
|
-
uru tokens create [library/name.gem] --expiry 30d Create a Gem-scoped deploy token
|
|
1203
|
+
uru tokens create [library/name.gem] --name CI --expiry 30d Create a Gem-scoped deploy token
|
|
1104
1204
|
uru tokens ls [library/name.gem] List Gem token metadata only
|
|
1105
1205
|
uru tokens revoke <token-id> --yes Revoke a Gem token immediately
|
|
1106
1206
|
uru tools ls|schema <name>|run <name> Full platform-tool parity
|
|
1107
1207
|
uru operations ls [--family gems] Print generated operation registry
|
|
1108
1208
|
uru operations run <operation-id> --params-json Run any registry-backed operation
|
|
1109
|
-
uru workspace|sharing|
|
|
1209
|
+
uru workspace|sharing|web <op> Registry families for full-platform parity
|
|
1110
1210
|
uru mcp config|install Print/write MCP client config
|
|
1111
1211
|
uru complete bash|zsh|fish Print shell completions
|
|
1112
1212
|
uru update Print the self-update command
|
|
@@ -1180,8 +1280,6 @@ qualified operation form.
|
|
|
1180
1280
|
var REGISTRY_FAMILY_COMMANDS = {
|
|
1181
1281
|
workspace: "workspace",
|
|
1182
1282
|
sharing: "sharing",
|
|
1183
|
-
automations: "automations",
|
|
1184
|
-
automation: "automations",
|
|
1185
1283
|
web: "web",
|
|
1186
1284
|
files: "files",
|
|
1187
1285
|
file: "files",
|
|
@@ -1242,13 +1340,16 @@ deployment that can be promoted after checks; --prod cannot be combined with
|
|
|
1242
1340
|
--no-publish.
|
|
1243
1341
|
`,
|
|
1244
1342
|
promote: `Usage: uru promote <version-id> [library/name.gem] [--link-id <published-link-id>] [--no-wait]
|
|
1343
|
+
uru promote --deployment-id <deployment-id> [library/name.gem] [--link-id <published-link-id>] [--no-wait]
|
|
1245
1344
|
|
|
1246
|
-
|
|
1247
|
-
is
|
|
1345
|
+
Promote an already-staged immutable Gem version or ready deployment. With
|
|
1346
|
+
--link-id, this is a published-link pointer flip after checks pass. If --link-id
|
|
1347
|
+
is omitted, Uru promotes through the default clean Gem link.
|
|
1248
1348
|
`,
|
|
1249
|
-
pull: `Usage: uru pull [library/name.gem]
|
|
1349
|
+
pull: `Usage: uru pull [library/name.gem] [--force]
|
|
1250
1350
|
|
|
1251
1351
|
Download Gem source into the current directory and write .uru/source-lock.json.
|
|
1352
|
+
Refuses to overwrite dirty local files unless --force is supplied.
|
|
1252
1353
|
`,
|
|
1253
1354
|
status: `Usage: uru status [library/name.gem] [--json]
|
|
1254
1355
|
|
|
@@ -1275,11 +1376,12 @@ before the command to emit NDJSON events.
|
|
|
1275
1376
|
|
|
1276
1377
|
Print the first available hosted/published Gem URL.
|
|
1277
1378
|
`,
|
|
1278
|
-
rollback: `Usage: uru rollback <version-id> [library/name.gem] [--link-id <published-link-id>] --yes
|
|
1379
|
+
rollback: `Usage: uru rollback [<version-id>] [library/name.gem] [--deployment-id <deployment-id>] [--link-id <published-link-id>] --yes
|
|
1279
1380
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1381
|
+
Rollback retargets a live link to an already-materialized older Gem version or
|
|
1382
|
+
deployment after server-side rollback preflight passes. Requires --yes because it
|
|
1383
|
+
mutates live traffic. If --link-id is omitted, Uru rolls back through the active
|
|
1384
|
+
Primary Gem link.
|
|
1283
1385
|
`,
|
|
1284
1386
|
gems: `Usage: uru gems <inspect|validate|build|status|read|write|versions|checks> [...]
|
|
1285
1387
|
|
|
@@ -1290,8 +1392,9 @@ same platform operations used by MCP and the web UI.
|
|
|
1290
1392
|
uru gems versions deploy <version-id> [library/name.gem] [--pct 100]
|
|
1291
1393
|
|
|
1292
1394
|
Create immutable Gem versions and deploy them through the Gem control plane.
|
|
1293
|
-
Upload accepts --message, --tag/--tags, and --preview-alias.
|
|
1294
|
-
|
|
1395
|
+
Upload accepts --message, --tag/--tags, and --preview-alias. Non-100
|
|
1396
|
+
percentages are accepted only with --skip-promote to stage rollout intent; live
|
|
1397
|
+
promote/rollback requires 100 until weighted link routing is enabled.
|
|
1295
1398
|
`,
|
|
1296
1399
|
"gems checks": `Usage: uru gems checks <version-id> [library/name.gem] [--deployment-id <id>]
|
|
1297
1400
|
uru gems checks run browser|security [library/name.gem] [--deployment-id <id>] [--blocking]
|
|
@@ -1303,12 +1406,39 @@ the check a release gate; security also accepts --deep.
|
|
|
1303
1406
|
library: `Usage: uru library ls [path]
|
|
1304
1407
|
uru library search <query>
|
|
1305
1408
|
uru library mkdir <path>
|
|
1409
|
+
uru library read <path>
|
|
1410
|
+
uru library write <path> (--file <local-path>|--content <text>|--stdin) [--create]
|
|
1411
|
+
uru library patch <path> (--file <local-path>|--content <text>|--stdin)
|
|
1412
|
+
uru library cp <path> <target-path>
|
|
1413
|
+
uru library mv <path> <target-path>
|
|
1414
|
+
uru library rm <path> [--permanent] [--yes]
|
|
1415
|
+
uru library restore <path>
|
|
1416
|
+
uru library empty-trash --yes
|
|
1306
1417
|
|
|
1307
|
-
Operate on Library items through platform tools.
|
|
1418
|
+
Operate on Library items through platform tools. Use write --create to create
|
|
1419
|
+
text-backed Context, Prompt, Persona, note, and plain .md/.txt resources at
|
|
1420
|
+
visible Library paths. For binary files, use library_upload through
|
|
1421
|
+
\`uru operations run\` or \`uru tools run\`; for Gem child source files, use
|
|
1422
|
+
\`uru gems read|write\`.
|
|
1308
1423
|
`,
|
|
1309
|
-
datasets: `Usage: uru datasets query <dataset-slug> [--limit 50] [--offset 0]
|
|
1424
|
+
datasets: `Usage: uru datasets query <dataset-slug> [--limit 50] [--offset 0] [--response-format detailed|concise]
|
|
1310
1425
|
|
|
1311
|
-
Query dataset rows through the platform dataset operation.
|
|
1426
|
+
Query dataset rows through the platform dataset operation. Use concise for id plus selected/display fields while keeping server totals and aggregates.
|
|
1427
|
+
`,
|
|
1428
|
+
automations: `Usage: uru automations ls [--status draft|active|paused|archived] [--limit 100] [--view summary|detailed]
|
|
1429
|
+
uru automations get <automation-id>
|
|
1430
|
+
uru automations create <name> [--description <text>] [--status draft|active|paused]
|
|
1431
|
+
uru automations enable <automation-id>
|
|
1432
|
+
uru automations disable <automation-id>
|
|
1433
|
+
uru automations trigger <automation-id> [--input-message <text>] [--idempotency-key <key>]
|
|
1434
|
+
uru automations runs ls [automation-id] [--scope personal|workspace] [--limit 100]
|
|
1435
|
+
uru automations runs tail <run-id> [--limit 1000] [--level debug|info|warn|error]
|
|
1436
|
+
uru automations runs debug|timeline <run-id>
|
|
1437
|
+
uru automations runs cancel <run-id> --yes
|
|
1438
|
+
|
|
1439
|
+
Manage automations through first-class CLI commands backed by the shared
|
|
1440
|
+
operation registry. Reads default to compact summary views; use --view detailed
|
|
1441
|
+
only when you need full graph/config payloads.
|
|
1312
1442
|
`,
|
|
1313
1443
|
tokens: `Usage: uru tokens create [library/name.gem] --expiry <duration> --name <name>
|
|
1314
1444
|
uru tokens ls [library/name.gem]
|
|
@@ -1412,6 +1542,19 @@ async function emitTool(ctx, operationId, params, options = {}) {
|
|
|
1412
1542
|
operation: options.streamStart["operation"] ?? operationId,
|
|
1413
1543
|
phase: "requesting"
|
|
1414
1544
|
});
|
|
1545
|
+
const streamed = await executeOperationStream(ctx, operationId, params, (event) => {
|
|
1546
|
+
writeStreamJsonEvent(ctx.io, event);
|
|
1547
|
+
});
|
|
1548
|
+
if (streamed.streamed) {
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const unwrapped2 = unwrapToolResult(streamed.payload ?? null);
|
|
1552
|
+
const output2 = options.transform?.(unwrapped2) ?? unwrapped2;
|
|
1553
|
+
writeStreamJson(ctx.io, "result", output2);
|
|
1554
|
+
writeStreamJson(ctx.io, "done", {
|
|
1555
|
+
operation: options.streamStart["operation"] ?? operationId
|
|
1556
|
+
});
|
|
1557
|
+
return;
|
|
1415
1558
|
}
|
|
1416
1559
|
const result = await executeOperation(ctx, operationId, params);
|
|
1417
1560
|
const unwrapped = unwrapToolResult(result);
|
|
@@ -1429,6 +1572,17 @@ async function emitTool(ctx, operationId, params, options = {}) {
|
|
|
1429
1572
|
writeText(ctx.io, options.textExtractor?.(output) ?? JSON.stringify(output, null, 2));
|
|
1430
1573
|
}
|
|
1431
1574
|
}
|
|
1575
|
+
async function executeOperationStream(ctx, operationId, params, onEvent) {
|
|
1576
|
+
const operation = getPlatformOperation(operationId);
|
|
1577
|
+
if (operation === undefined) {
|
|
1578
|
+
throw new CliError(`Unknown operation registry id: ${operationId}`);
|
|
1579
|
+
}
|
|
1580
|
+
const toolParams = { ...params };
|
|
1581
|
+
if (operation.backendOp !== undefined) {
|
|
1582
|
+
toolParams["op"] = operation.backendOp;
|
|
1583
|
+
}
|
|
1584
|
+
return client(ctx).streamTool(operation.backendToolName, toolParams, onEvent);
|
|
1585
|
+
}
|
|
1432
1586
|
async function executeOperation(ctx, operationId, params) {
|
|
1433
1587
|
const operation = getPlatformOperation(operationId);
|
|
1434
1588
|
if (operation === undefined) {
|
|
@@ -1474,7 +1628,7 @@ function tokenGemRefParams(flags, linkedProject, options = {}) {
|
|
|
1474
1628
|
}
|
|
1475
1629
|
const explicitGem = flags.values["--gem"];
|
|
1476
1630
|
if (explicitGem !== undefined) {
|
|
1477
|
-
return { gem_id: explicitGem };
|
|
1631
|
+
return explicitGem.startsWith("library/") ? { path: explicitGem } : { gem_id: explicitGem };
|
|
1478
1632
|
}
|
|
1479
1633
|
const positional = flags.positionals[0];
|
|
1480
1634
|
if (positional !== undefined) {
|
|
@@ -1624,6 +1778,18 @@ function sanitizeConfig(value) {
|
|
|
1624
1778
|
if (value["insecureStorage"] === true) {
|
|
1625
1779
|
config.insecureStorage = true;
|
|
1626
1780
|
}
|
|
1781
|
+
if (isRecord(value["update"])) {
|
|
1782
|
+
const update = {};
|
|
1783
|
+
for (const key of ["lastCheckedAt", "latestVersion"]) {
|
|
1784
|
+
const item = value["update"][key];
|
|
1785
|
+
if (typeof item === "string" && item.trim() !== "") {
|
|
1786
|
+
update[key] = item;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
if (Object.keys(update).length > 0) {
|
|
1790
|
+
config.update = update;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1627
1793
|
return config;
|
|
1628
1794
|
}
|
|
1629
1795
|
function isNodeError(error) {
|
|
@@ -2008,10 +2174,15 @@ function credentialStoreUnavailableError(backend, message, error) {
|
|
|
2008
2174
|
var CLI_DOCS_URL = "https://docs.uruintelligence.com/cli/errors";
|
|
2009
2175
|
var ERROR_TIPS = {
|
|
2010
2176
|
auth_required: "Run `uru login`, pass `--token`, or set `URU_TOKEN` before retrying.",
|
|
2177
|
+
interactive_login_required: "Run `uru login` before this command, use `uru login --device` on remote shells, or pass `--token`/`URU_TOKEN` in CI.",
|
|
2011
2178
|
conflict: "Run `uru status` or `uru pull` to inspect remote changes; use `--force` only when you intentionally want to overwrite.",
|
|
2012
2179
|
api_request_failed: "Check the request parameters, workspace context, and server response before retrying.",
|
|
2013
2180
|
keychain_access_failed: "Unlock the OS credential store, or retry with `--insecure-storage` only if plaintext local storage is acceptable.",
|
|
2014
2181
|
keychain_unavailable: "Install or unlock the OS credential helper, or retry with `--insecure-storage` only if plaintext local storage is acceptable.",
|
|
2182
|
+
oauth_state_mismatch: "Restart `uru login`; this usually means the browser callback came from an old or mismatched login tab.",
|
|
2183
|
+
oauth_authorization_failed: "Restart `uru login` and approve access in the browser, or use `uru login --device` if the browser cannot reach the callback.",
|
|
2184
|
+
oauth_missing_code: "Restart `uru login`; if the browser callback keeps failing, retry with `uru login --device`.",
|
|
2185
|
+
oauth_request_failed: "Check the API URL and network access, then retry `uru login`; use `uru login --device` from SSH/headless environments.",
|
|
2015
2186
|
oauth_device_failed: "Restart `uru login --device` and complete authorization in the browser before the code expires.",
|
|
2016
2187
|
oauth_device_timeout: "Run `uru login --device` again and complete browser authorization before the timeout.",
|
|
2017
2188
|
cancelled: "The command was cancelled; rerun it only if you still want the operation to continue."
|
|
@@ -2257,6 +2428,120 @@ function terminalWidth(stream, env) {
|
|
|
2257
2428
|
return 80;
|
|
2258
2429
|
}
|
|
2259
2430
|
|
|
2431
|
+
// src/update-notifier.ts
|
|
2432
|
+
var PACKAGE_NAME = "@uru-intelligence/cli";
|
|
2433
|
+
var REGISTRY_LATEST_URL = "https://registry.npmjs.org/@uru-intelligence%2Fcli/latest";
|
|
2434
|
+
var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
2435
|
+
var UPDATE_CHECK_TIMEOUT_MS = 750;
|
|
2436
|
+
async function maybeNotifyCliUpdate(ctx, options = {}) {
|
|
2437
|
+
if (!shouldCheckForUpdate(ctx, options.env ?? process.env)) {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const now = options.now?.() ?? Date.now();
|
|
2441
|
+
if (!isUpdateCheckDue(ctx.config, now)) {
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
const checkedConfig = withUpdateCheckTimestamp(ctx.config, now);
|
|
2445
|
+
await writeConfigBestEffort(ctx, checkedConfig);
|
|
2446
|
+
const latestVersion = await fetchLatestVersion({
|
|
2447
|
+
fetchImpl: ctx.fetch ?? fetch,
|
|
2448
|
+
parentSignal: ctx.abortSignal
|
|
2449
|
+
});
|
|
2450
|
+
if (latestVersion === undefined) {
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
await writeConfigBestEffort(ctx, {
|
|
2454
|
+
...checkedConfig,
|
|
2455
|
+
update: {
|
|
2456
|
+
...checkedConfig.update,
|
|
2457
|
+
latestVersion
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
if (compareVersions(latestVersion, CLI_VERSION) <= 0) {
|
|
2461
|
+
return;
|
|
2462
|
+
}
|
|
2463
|
+
ctx.io.stderr.write(`A newer Uru CLI is available: ${CLI_VERSION} → ${latestVersion}. ` + `Run npm install -g ${PACKAGE_NAME}@latest
|
|
2464
|
+
`);
|
|
2465
|
+
}
|
|
2466
|
+
function shouldCheckForUpdate(ctx, env) {
|
|
2467
|
+
return ctx.outputFormat === "text" && ctx.terminal.stderrIsTty && !ctx.terminal.ci && env["NO_UPDATE_NOTIFIER"] === undefined && env["URU_NO_UPDATE_NOTIFIER"] === undefined;
|
|
2468
|
+
}
|
|
2469
|
+
function isUpdateCheckDue(config, now) {
|
|
2470
|
+
const raw = config.update?.lastCheckedAt;
|
|
2471
|
+
if (raw === undefined) {
|
|
2472
|
+
return true;
|
|
2473
|
+
}
|
|
2474
|
+
const lastCheckedAt = Date.parse(raw);
|
|
2475
|
+
return !Number.isFinite(lastCheckedAt) || now - lastCheckedAt >= UPDATE_CHECK_INTERVAL_MS;
|
|
2476
|
+
}
|
|
2477
|
+
function withUpdateCheckTimestamp(config, now) {
|
|
2478
|
+
return {
|
|
2479
|
+
...config,
|
|
2480
|
+
update: {
|
|
2481
|
+
...config.update,
|
|
2482
|
+
lastCheckedAt: new Date(now).toISOString()
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
async function fetchLatestVersion(args) {
|
|
2487
|
+
const controller = new AbortController;
|
|
2488
|
+
const timeout = setTimeout(() => {
|
|
2489
|
+
controller.abort();
|
|
2490
|
+
}, UPDATE_CHECK_TIMEOUT_MS);
|
|
2491
|
+
timeout.unref?.();
|
|
2492
|
+
const abortFromParent = () => {
|
|
2493
|
+
controller.abort(args.parentSignal.reason);
|
|
2494
|
+
};
|
|
2495
|
+
if (args.parentSignal.aborted) {
|
|
2496
|
+
abortFromParent();
|
|
2497
|
+
} else {
|
|
2498
|
+
args.parentSignal.addEventListener("abort", abortFromParent, { once: true });
|
|
2499
|
+
}
|
|
2500
|
+
try {
|
|
2501
|
+
const response = await args.fetchImpl(REGISTRY_LATEST_URL, {
|
|
2502
|
+
headers: { Accept: "application/json" },
|
|
2503
|
+
signal: controller.signal
|
|
2504
|
+
});
|
|
2505
|
+
if (!response.ok) {
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
const payload = await response.json();
|
|
2509
|
+
if (!isRecord2(payload)) {
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
const version = payload["version"];
|
|
2513
|
+
return typeof version === "string" && version.trim() !== "" ? version.trim() : undefined;
|
|
2514
|
+
} catch {
|
|
2515
|
+
return;
|
|
2516
|
+
} finally {
|
|
2517
|
+
clearTimeout(timeout);
|
|
2518
|
+
args.parentSignal.removeEventListener("abort", abortFromParent);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
async function writeConfigBestEffort(ctx, config) {
|
|
2522
|
+
try {
|
|
2523
|
+
await ctx.store.write(config);
|
|
2524
|
+
} catch {}
|
|
2525
|
+
}
|
|
2526
|
+
function compareVersions(left, right) {
|
|
2527
|
+
const leftParts = versionParts(left);
|
|
2528
|
+
const rightParts = versionParts(right);
|
|
2529
|
+
const width = Math.max(leftParts.length, rightParts.length);
|
|
2530
|
+
for (let index = 0;index < width; index += 1) {
|
|
2531
|
+
const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
2532
|
+
if (delta !== 0) {
|
|
2533
|
+
return delta;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
return 0;
|
|
2537
|
+
}
|
|
2538
|
+
function versionParts(version) {
|
|
2539
|
+
return version.replace(/^v/, "").split(/[.-]/).map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
|
|
2540
|
+
}
|
|
2541
|
+
function isRecord2(value) {
|
|
2542
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2260
2545
|
// src/auth-commands.ts
|
|
2261
2546
|
import { readFile as readFile3 } from "node:fs/promises";
|
|
2262
2547
|
import { createServer } from "node:http";
|
|
@@ -2633,14 +2918,19 @@ async function resolveFlagToken(flags, stdin) {
|
|
|
2633
2918
|
return;
|
|
2634
2919
|
}
|
|
2635
2920
|
async function login(ctx, options = {}) {
|
|
2636
|
-
const
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2921
|
+
const existingToken = ctx.token === undefined || ctx.token.trim() === "" ? undefined : ctx.token;
|
|
2922
|
+
let token = existingToken ?? await obtainLoginSessionToken(ctx);
|
|
2923
|
+
let result;
|
|
2924
|
+
try {
|
|
2925
|
+
result = await validateSessionToken(ctx, token);
|
|
2926
|
+
} catch (error) {
|
|
2927
|
+
const storedTokenIsStale = existingToken !== undefined && !ctx.tokenIsExplicit && isAuthRequiredError(error);
|
|
2928
|
+
if (!storedTokenIsStale) {
|
|
2929
|
+
throw error;
|
|
2930
|
+
}
|
|
2931
|
+
token = await obtainLoginSessionToken(ctx);
|
|
2932
|
+
result = await validateSessionToken(ctx, token);
|
|
2933
|
+
}
|
|
2644
2934
|
const selectedWorkspace = selectWorkspace(result, ctx.workspace);
|
|
2645
2935
|
await persistLoginToken(ctx, token, selectedWorkspace);
|
|
2646
2936
|
if (options.silentSuccess === true) {
|
|
@@ -2661,6 +2951,18 @@ async function login(ctx, options = {}) {
|
|
|
2661
2951
|
writeText(ctx.io, ctx.insecureStorage ? "Logged in (token stored in local config)" : "Logged in");
|
|
2662
2952
|
}
|
|
2663
2953
|
}
|
|
2954
|
+
async function validateSessionToken(ctx, token) {
|
|
2955
|
+
const loginClient = new UruApiClient({
|
|
2956
|
+
apiUrl: ctx.apiUrl,
|
|
2957
|
+
token,
|
|
2958
|
+
...ctx.workspace !== undefined ? { workspaceId: ctx.workspace } : {},
|
|
2959
|
+
...ctx.fetch !== undefined ? { fetch: ctx.fetch } : {}
|
|
2960
|
+
});
|
|
2961
|
+
return loginClient.validateToken();
|
|
2962
|
+
}
|
|
2963
|
+
function isAuthRequiredError(error) {
|
|
2964
|
+
return error instanceof CliError && error.exitCode === ExitCode.AuthRequired;
|
|
2965
|
+
}
|
|
2664
2966
|
async function readAllStdin(input) {
|
|
2665
2967
|
const chunks = [];
|
|
2666
2968
|
for await (const chunk of input) {
|
|
@@ -2900,13 +3202,11 @@ async function switchWorkspace(ctx, workspaceId) {
|
|
|
2900
3202
|
}
|
|
2901
3203
|
|
|
2902
3204
|
// src/local-commands.ts
|
|
2903
|
-
import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2904
|
-
import { dirname as dirname3 } from "node:path";
|
|
3205
|
+
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
3206
|
+
import { dirname as dirname3, resolve as resolve2 } from "node:path";
|
|
2905
3207
|
var GENERIC_FAMILY_COMMANDS = {
|
|
2906
3208
|
workspace: "workspace",
|
|
2907
3209
|
sharing: "sharing",
|
|
2908
|
-
automations: "automations",
|
|
2909
|
-
automation: "automations",
|
|
2910
3210
|
web: "web",
|
|
2911
3211
|
files: "files",
|
|
2912
3212
|
file: "files",
|
|
@@ -2942,6 +3242,7 @@ var TOP_LEVEL_COMMANDS = [
|
|
|
2942
3242
|
"open",
|
|
2943
3243
|
"rollback",
|
|
2944
3244
|
"gems",
|
|
3245
|
+
"automations",
|
|
2945
3246
|
"library",
|
|
2946
3247
|
"datasets",
|
|
2947
3248
|
"tokens",
|
|
@@ -3131,7 +3432,109 @@ async function dispatchLibrary(ctx, sub, args) {
|
|
|
3131
3432
|
});
|
|
3132
3433
|
return;
|
|
3133
3434
|
}
|
|
3134
|
-
|
|
3435
|
+
if (sub === "read" || sub === "cat") {
|
|
3436
|
+
await emitTool(ctx, "library.read", {
|
|
3437
|
+
path: requireCommandValue(args[0], "Usage: uru library read <path>")
|
|
3438
|
+
}, { textExtractor: extractLibraryTextContent });
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
if (sub === "write") {
|
|
3442
|
+
const flags = parseLocalFlags(args);
|
|
3443
|
+
const path = requireCommandValue(flags.positionals[0], "Usage: uru library write <path> (--file <local-path>|--content <text>|--stdin) [--create]");
|
|
3444
|
+
await emitTool(ctx, "library.write", {
|
|
3445
|
+
path,
|
|
3446
|
+
content: await libraryTextFromFlags(ctx, flags, "write"),
|
|
3447
|
+
create: flags.booleans.has("--create")
|
|
3448
|
+
});
|
|
3449
|
+
return;
|
|
3450
|
+
}
|
|
3451
|
+
if (sub === "patch") {
|
|
3452
|
+
const flags = parseLocalFlags(args);
|
|
3453
|
+
const path = requireCommandValue(flags.positionals[0], "Usage: uru library patch <path> (--file <local-path>|--content <text>|--stdin)");
|
|
3454
|
+
await emitTool(ctx, "library.patch", {
|
|
3455
|
+
path,
|
|
3456
|
+
content: await libraryTextFromFlags(ctx, flags, "patch")
|
|
3457
|
+
});
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3460
|
+
if (sub === "cp" || sub === "copy") {
|
|
3461
|
+
const [path, targetPath] = args;
|
|
3462
|
+
await emitTool(ctx, "library.cp", {
|
|
3463
|
+
path: requireCommandValue(path, "Usage: uru library cp <path> <target-path>"),
|
|
3464
|
+
target_path: requireCommandValue(targetPath, "Usage: uru library cp <path> <target-path>")
|
|
3465
|
+
});
|
|
3466
|
+
return;
|
|
3467
|
+
}
|
|
3468
|
+
if (sub === "mv" || sub === "move" || sub === "rename") {
|
|
3469
|
+
const [path, targetPath] = args;
|
|
3470
|
+
await emitTool(ctx, "library.mv", {
|
|
3471
|
+
path: requireCommandValue(path, "Usage: uru library mv <path> <target-path>"),
|
|
3472
|
+
target_path: requireCommandValue(targetPath, "Usage: uru library mv <path> <target-path>")
|
|
3473
|
+
});
|
|
3474
|
+
return;
|
|
3475
|
+
}
|
|
3476
|
+
if (sub === "rm" || sub === "remove" || sub === "delete") {
|
|
3477
|
+
const flags = parseLocalFlags(args);
|
|
3478
|
+
if ((flags.booleans.has("--permanent") || flags.booleans.has("--hard-delete")) && !ctx.yes && !flags.booleans.has("--yes")) {
|
|
3479
|
+
throw new CliError("Permanent Library deletion cannot be undone. Re-run with --yes to confirm.");
|
|
3480
|
+
}
|
|
3481
|
+
const path = requireCommandValue(flags.positionals[0], "Usage: uru library rm <path> [--permanent] [--yes]");
|
|
3482
|
+
await emitTool(ctx, "library.rm", {
|
|
3483
|
+
path,
|
|
3484
|
+
permanent: flags.booleans.has("--permanent") || flags.booleans.has("--hard-delete")
|
|
3485
|
+
});
|
|
3486
|
+
return;
|
|
3487
|
+
}
|
|
3488
|
+
if (sub === "restore") {
|
|
3489
|
+
await emitTool(ctx, "library.restore", {
|
|
3490
|
+
path: requireCommandValue(args[0], "Usage: uru library restore <path>"),
|
|
3491
|
+
include_trashed: true
|
|
3492
|
+
});
|
|
3493
|
+
return;
|
|
3494
|
+
}
|
|
3495
|
+
if (sub === "empty-trash") {
|
|
3496
|
+
const flags = parseLocalFlags(args);
|
|
3497
|
+
if (!ctx.yes && !flags.booleans.has("--yes")) {
|
|
3498
|
+
throw new CliError("Emptying Library trash permanently deletes all trashed items. Re-run with --yes to confirm.");
|
|
3499
|
+
}
|
|
3500
|
+
await emitTool(ctx, "library.empty_trash", {});
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
throw new CliError("Usage: uru library ls|search|mkdir|read|write|patch|cp|mv|rm|restore|empty-trash");
|
|
3504
|
+
}
|
|
3505
|
+
async function libraryTextFromFlags(ctx, flags, verb) {
|
|
3506
|
+
const content = flags.values["--content"];
|
|
3507
|
+
const file = flags.values["--file"];
|
|
3508
|
+
const fromStdin = flags.booleans.has("--stdin");
|
|
3509
|
+
const sources = [content !== undefined, file !== undefined, fromStdin].filter(Boolean).length;
|
|
3510
|
+
if (sources !== 1) {
|
|
3511
|
+
throw new CliError(`Library ${verb} requires exactly one of --file <path>, --content <text>, or --stdin.`);
|
|
3512
|
+
}
|
|
3513
|
+
if (content !== undefined) {
|
|
3514
|
+
return content;
|
|
3515
|
+
}
|
|
3516
|
+
if (file !== undefined) {
|
|
3517
|
+
return await readFile4(resolve2(ctx.cwd, file), "utf8");
|
|
3518
|
+
}
|
|
3519
|
+
return await readStdinText(ctx.stdin ?? process.stdin);
|
|
3520
|
+
}
|
|
3521
|
+
async function readStdinText(stdin) {
|
|
3522
|
+
const chunks = [];
|
|
3523
|
+
for await (const chunk of stdin) {
|
|
3524
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
3525
|
+
}
|
|
3526
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3527
|
+
}
|
|
3528
|
+
function extractLibraryTextContent(value) {
|
|
3529
|
+
if (!isJsonObject2(value)) {
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
const content = value["content"];
|
|
3533
|
+
if (typeof content === "string") {
|
|
3534
|
+
return content;
|
|
3535
|
+
}
|
|
3536
|
+
const text = value["text"];
|
|
3537
|
+
return typeof text === "string" ? text : undefined;
|
|
3135
3538
|
}
|
|
3136
3539
|
async function dispatchDatasets(ctx, sub, args) {
|
|
3137
3540
|
if (sub !== "query") {
|
|
@@ -3139,13 +3542,154 @@ async function dispatchDatasets(ctx, sub, args) {
|
|
|
3139
3542
|
return;
|
|
3140
3543
|
}
|
|
3141
3544
|
const flags = parseLocalFlags(args);
|
|
3142
|
-
const datasetId = requireCommandValue(flags.positionals[0], "Usage: uru datasets query <dataset-slug> [--limit 50]");
|
|
3143
|
-
|
|
3545
|
+
const datasetId = requireCommandValue(flags.positionals[0], "Usage: uru datasets query <dataset-slug> [--limit 50] [--response-format detailed|concise]");
|
|
3546
|
+
const params = {
|
|
3144
3547
|
dataset_id: datasetId,
|
|
3145
3548
|
datasetId,
|
|
3146
3549
|
limit: integerFlag(flags, "--limit", 50),
|
|
3147
3550
|
offset: integerFlag(flags, "--offset", 0)
|
|
3148
|
-
}
|
|
3551
|
+
};
|
|
3552
|
+
const responseFormat = flags.values["--response-format"];
|
|
3553
|
+
if (responseFormat !== undefined) {
|
|
3554
|
+
if (responseFormat !== "detailed" && responseFormat !== "concise") {
|
|
3555
|
+
throw new CliError('--response-format must be "detailed" or "concise"');
|
|
3556
|
+
}
|
|
3557
|
+
params["response_format"] = responseFormat;
|
|
3558
|
+
}
|
|
3559
|
+
await emitTool(ctx, "dataset.query", params);
|
|
3560
|
+
}
|
|
3561
|
+
async function dispatchAutomations(ctx, sub, args) {
|
|
3562
|
+
if (sub === "ls" || sub === "list") {
|
|
3563
|
+
const flags = parseLocalFlags(args);
|
|
3564
|
+
const params = {
|
|
3565
|
+
limit: integerFlag(flags, "--limit", 100)
|
|
3566
|
+
};
|
|
3567
|
+
copyOptionalFlag(flags, params, "--status", "status_filter");
|
|
3568
|
+
copyOptionalFlag(flags, params, "--parent-folder-id", "parent_folder_id");
|
|
3569
|
+
copyOptionalFlag(flags, params, "--folder-id", "parent_folder_id");
|
|
3570
|
+
copyAutomationView(flags, params);
|
|
3571
|
+
await emitTool(ctx, "automation.list", params);
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
if (sub === "get" || sub === "inspect") {
|
|
3575
|
+
const flags = parseLocalFlags(args);
|
|
3576
|
+
await emitTool(ctx, "automation.get", {
|
|
3577
|
+
automation_id: requireCommandValue(flags.positionals[0], "Usage: uru automations get <automation-id>")
|
|
3578
|
+
});
|
|
3579
|
+
return;
|
|
3580
|
+
}
|
|
3581
|
+
if (sub === "create") {
|
|
3582
|
+
const flags = parseLocalFlags(args);
|
|
3583
|
+
const params = {
|
|
3584
|
+
name: requireCommandValue(flags.positionals[0], "Usage: uru automations create <name> [--description <text>] [--status draft|active|paused] [--graph-json {...}]")
|
|
3585
|
+
};
|
|
3586
|
+
copyOptionalFlag(flags, params, "--description", "description");
|
|
3587
|
+
copyOptionalFlag(flags, params, "--status", "status");
|
|
3588
|
+
copyOptionalFlag(flags, params, "--parent-folder-id", "parent_folder_id");
|
|
3589
|
+
copyOptionalFlag(flags, params, "--folder-id", "parent_folder_id");
|
|
3590
|
+
const graphJson = flags.values["--graph-json"];
|
|
3591
|
+
if (graphJson !== undefined) {
|
|
3592
|
+
params["graph_json"] = parseParamsJson(graphJson, "--graph-json");
|
|
3593
|
+
}
|
|
3594
|
+
await emitTool(ctx, "automation.create", params);
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
if (sub === "enable" || sub === "disable") {
|
|
3598
|
+
const flags = parseLocalFlags(args);
|
|
3599
|
+
await emitTool(ctx, "automation.update", {
|
|
3600
|
+
automation_id: requireCommandValue(flags.positionals[0], `Usage: uru automations ${sub} <automation-id>`),
|
|
3601
|
+
status: sub === "enable" ? "active" : "paused"
|
|
3602
|
+
});
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
if (sub === "trigger" || sub === "run" || sub === "start") {
|
|
3606
|
+
const flags = parseLocalFlags(args);
|
|
3607
|
+
const params = {
|
|
3608
|
+
automation_id: requireCommandValue(flags.values["--automation-id"] ?? flags.positionals[0], "Usage: uru automations trigger <automation-id> [--input-message <text>] [--idempotency-key <key>]")
|
|
3609
|
+
};
|
|
3610
|
+
copyOptionalFlag(flags, params, "--input-message", "input_message");
|
|
3611
|
+
copyOptionalFlag(flags, params, "--idempotency-key", "idempotency_key");
|
|
3612
|
+
await emitTool(ctx, "automation.run_start", params);
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
if (sub === "runs") {
|
|
3616
|
+
await dispatchAutomationRuns(ctx, args[0], args.slice(1));
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
throw new CliError("Usage: uru automations ls|get|create|enable|disable|trigger|runs");
|
|
3620
|
+
}
|
|
3621
|
+
async function dispatchAutomationRuns(ctx, sub, args) {
|
|
3622
|
+
if (sub === "ls" || sub === "list") {
|
|
3623
|
+
const flags = parseLocalFlags(args);
|
|
3624
|
+
const params = {
|
|
3625
|
+
limit: integerFlag(flags, "--limit", 100)
|
|
3626
|
+
};
|
|
3627
|
+
copyOptionalFlag(flags, params, "--automation-id", "automation_id");
|
|
3628
|
+
copyOptionalFlag(flags, params, "--scope", "scope");
|
|
3629
|
+
copyOptionalFlag(flags, params, "--cursor", "cursor");
|
|
3630
|
+
copyOptionalFlag(flags, params, "--include", "include");
|
|
3631
|
+
if (params["automation_id"] === undefined && flags.positionals[0] !== undefined) {
|
|
3632
|
+
params["automation_id"] = flags.positionals[0];
|
|
3633
|
+
}
|
|
3634
|
+
await emitTool(ctx, "automation.runs_list", params);
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
if (sub === "tail" || sub === "logs") {
|
|
3638
|
+
const flags = parseLocalFlags(args);
|
|
3639
|
+
const params = {
|
|
3640
|
+
run_id: requireCommandValue(flags.values["--run-id"] ?? flags.positionals[0], "Usage: uru automations runs tail <run-id> [--limit 1000] [--level debug|info|warn|error]"),
|
|
3641
|
+
limit: integerFlag(flags, "--limit", 1000)
|
|
3642
|
+
};
|
|
3643
|
+
copyOptionalFlag(flags, params, "--level", "level");
|
|
3644
|
+
await emitTool(ctx, "automation.run_logs", params);
|
|
3645
|
+
return;
|
|
3646
|
+
}
|
|
3647
|
+
if (sub === "debug") {
|
|
3648
|
+
const flags = parseLocalFlags(args);
|
|
3649
|
+
const params = {
|
|
3650
|
+
run_id: requireCommandValue(flags.values["--run-id"] ?? flags.positionals[0], "Usage: uru automations runs debug <run-id> [--log-limit 100] [--transcript-limit 50]"),
|
|
3651
|
+
log_limit: integerFlag(flags, "--log-limit", 100),
|
|
3652
|
+
transcript_limit: integerFlag(flags, "--transcript-limit", 50)
|
|
3653
|
+
};
|
|
3654
|
+
copyOptionalFlag(flags, params, "--log-level", "log_level");
|
|
3655
|
+
await emitTool(ctx, "automation.run_debug", params);
|
|
3656
|
+
return;
|
|
3657
|
+
}
|
|
3658
|
+
if (sub === "timeline") {
|
|
3659
|
+
const flags = parseLocalFlags(args);
|
|
3660
|
+
await emitTool(ctx, "automation.run_timeline", {
|
|
3661
|
+
run_id: requireCommandValue(flags.values["--run-id"] ?? flags.positionals[0], "Usage: uru automations runs timeline <run-id> [--limit 5000]"),
|
|
3662
|
+
limit: integerFlag(flags, "--limit", 5000)
|
|
3663
|
+
});
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
if (sub === "cancel") {
|
|
3667
|
+
if (!ctx.yes) {
|
|
3668
|
+
throw new CliError("Cancelling an automation run requires --yes because it mutates execution state.");
|
|
3669
|
+
}
|
|
3670
|
+
const flags = parseLocalFlags(args);
|
|
3671
|
+
await emitTool(ctx, "automation.run_cancel", {
|
|
3672
|
+
run_id: requireCommandValue(flags.values["--run-id"] ?? flags.positionals[0], "Usage: uru automations runs cancel <run-id> --yes")
|
|
3673
|
+
});
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
throw new CliError("Usage: uru automations runs ls|tail|debug|timeline|cancel");
|
|
3677
|
+
}
|
|
3678
|
+
function copyOptionalFlag(flags, target, flagName, paramName) {
|
|
3679
|
+
const value = flags.values[flagName];
|
|
3680
|
+
if (value !== undefined) {
|
|
3681
|
+
target[paramName] = value;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
function copyAutomationView(flags, target) {
|
|
3685
|
+
const value = flags.values["--view"];
|
|
3686
|
+
if (value === undefined) {
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
if (value !== "summary" && value !== "detailed") {
|
|
3690
|
+
throw new CliError('--view must be "summary" or "detailed"');
|
|
3691
|
+
}
|
|
3692
|
+
target["view"] = value;
|
|
3149
3693
|
}
|
|
3150
3694
|
function mcpServerConfig(args) {
|
|
3151
3695
|
const apiKey = requireCommandValue(args.apiKey, "MCP config requires --api-key, --token, URU_TOKEN, or a logged-in CLI token.");
|
|
@@ -3199,9 +3743,9 @@ function levenshtein(left, right) {
|
|
|
3199
3743
|
}
|
|
3200
3744
|
|
|
3201
3745
|
// src/gem-lifecycle-commands.ts
|
|
3202
|
-
import { mkdir as mkdir4, readFile as
|
|
3746
|
+
import { mkdir as mkdir4, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
3203
3747
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
3204
|
-
import { join as join3, resolve as
|
|
3748
|
+
import { join as join3, resolve as resolve3 } from "node:path";
|
|
3205
3749
|
async function dispatchGems(ctx, sub, args, linkedProject) {
|
|
3206
3750
|
const gemPath = () => gemPathFromArgs(args, linkedProject);
|
|
3207
3751
|
if (sub === "inspect") {
|
|
@@ -3254,7 +3798,7 @@ async function gemInit(ctx, args) {
|
|
|
3254
3798
|
params["content"] = flags.values["--content"];
|
|
3255
3799
|
}
|
|
3256
3800
|
if (flags.values["--file"] !== undefined) {
|
|
3257
|
-
params["content"] = await
|
|
3801
|
+
params["content"] = await readFile5(flags.values["--file"], "utf8");
|
|
3258
3802
|
}
|
|
3259
3803
|
await emitGemTool(ctx, "gem.init", params);
|
|
3260
3804
|
}
|
|
@@ -3353,10 +3897,10 @@ async function gemBuild(ctx, args, linkedProject) {
|
|
|
3353
3897
|
}
|
|
3354
3898
|
async function gemPromote(ctx, args, linkedProject) {
|
|
3355
3899
|
const flags = parseLocalFlags(args);
|
|
3356
|
-
assertAllowedLocalFlags(flags, ["--gem", "--link-id", "--no-wait", "--path", "--pct"], "uru promote");
|
|
3900
|
+
assertAllowedLocalFlags(flags, ["--deployment-id", "--gem", "--link-id", "--no-wait", "--path", "--pct"], "uru promote");
|
|
3357
3901
|
await deployVersionToPublishedLink(ctx, flags, linkedProject, {
|
|
3358
3902
|
operation: "promote",
|
|
3359
|
-
usage: "Usage: uru promote <version-id> [library/name.gem] [--link-id <published-link-id>]"
|
|
3903
|
+
usage: "Usage: uru promote <version-id> [library/name.gem] [--link-id <published-link-id>] | uru promote --deployment-id <deployment-id> [library/name.gem]"
|
|
3360
3904
|
});
|
|
3361
3905
|
}
|
|
3362
3906
|
async function gemChecks(ctx, args, linkedProject) {
|
|
@@ -3366,10 +3910,11 @@ async function gemChecks(ctx, args, linkedProject) {
|
|
|
3366
3910
|
return;
|
|
3367
3911
|
}
|
|
3368
3912
|
assertAllowedLocalFlags(flags, ["--deployment-id", "--gem", "--path"], "uru gems checks");
|
|
3913
|
+
const explicitDeploymentId = flags.values["--deployment-id"];
|
|
3914
|
+
const gemPathArgs = explicitDeploymentId === undefined ? flags.positionals.slice(1) : flags.positionals;
|
|
3369
3915
|
const params = {
|
|
3370
|
-
path: gemPathFromArgs(
|
|
3916
|
+
path: gemPathFromArgs(gemPathArgs, linkedProject)
|
|
3371
3917
|
};
|
|
3372
|
-
const explicitDeploymentId = flags.values["--deployment-id"];
|
|
3373
3918
|
if (explicitDeploymentId !== undefined) {
|
|
3374
3919
|
params["deployment_id"] = explicitDeploymentId;
|
|
3375
3920
|
} else if (flags.positionals[0] !== undefined) {
|
|
@@ -3576,20 +4121,28 @@ async function gemOpen(ctx, args, linkedProject) {
|
|
|
3576
4121
|
}
|
|
3577
4122
|
async function gemRollback(ctx, args, linkedProject) {
|
|
3578
4123
|
const flags = parseLocalFlags(args);
|
|
3579
|
-
assertAllowedLocalFlags(flags, [
|
|
4124
|
+
assertAllowedLocalFlags(flags, [
|
|
4125
|
+
"--deployment-id",
|
|
4126
|
+
"--gem",
|
|
4127
|
+
"--link-id",
|
|
4128
|
+
"--no-wait",
|
|
4129
|
+
"--path",
|
|
4130
|
+
"--pct",
|
|
4131
|
+
"--yes"
|
|
4132
|
+
], "uru rollback");
|
|
3580
4133
|
if (!ctx.yes && !flags.booleans.has("--yes")) {
|
|
3581
4134
|
throw new CliError("Rollback requires --yes to confirm the link pointer change.");
|
|
3582
4135
|
}
|
|
3583
4136
|
await deployVersionToPublishedLink(ctx, flags, linkedProject, {
|
|
3584
4137
|
operation: "rollback",
|
|
3585
|
-
usage: "Usage: uru rollback <version-id> [library/name.gem] [--link-id <published-link-id>] --yes"
|
|
4138
|
+
usage: "Usage: uru rollback [<version-id>] [library/name.gem] [--deployment-id <deployment-id>] [--link-id <published-link-id>] --yes"
|
|
3586
4139
|
});
|
|
3587
4140
|
}
|
|
3588
4141
|
async function gemWrite(ctx, args) {
|
|
3589
4142
|
const flags = parseLocalFlags(args);
|
|
3590
4143
|
const gemPath = requireCommandValue(flags.positionals[0], "Usage: uru gems write <gem-path> <file> --content <text>|--file <local-file>");
|
|
3591
4144
|
const filePath = requireCommandValue(flags.positionals[1], "Usage: uru gems write <gem-path> <file> --content <text>|--file <local-file>");
|
|
3592
|
-
const content = flags.values["--content"] ?? (flags.values["--file"] === undefined ? undefined : await
|
|
4145
|
+
const content = flags.values["--content"] ?? (flags.values["--file"] === undefined ? undefined : await readFile5(flags.values["--file"], "utf8"));
|
|
3593
4146
|
if (content === undefined) {
|
|
3594
4147
|
throw new CliError("Gem write requires --content or --file.");
|
|
3595
4148
|
}
|
|
@@ -3619,12 +4172,15 @@ async function dispatchGemVersions(ctx, args, linkedProject) {
|
|
|
3619
4172
|
const gemPath = gemPathFromArgs(flags.positionals.slice(1), linkedProject);
|
|
3620
4173
|
const pct = flags.values["--pct"] ?? trafficPercent;
|
|
3621
4174
|
const trafficPercentValue = pct === undefined || pct === "" ? 100 : parseTrafficPercent(pct);
|
|
3622
|
-
|
|
4175
|
+
const publish = !flags.booleans.has("--skip-promote");
|
|
4176
|
+
if (publish && trafficPercentValue !== 100) {
|
|
4177
|
+
throw new CliError("Gradual Gem traffic shifts are not enabled yet. Use --skip-promote to create the deployment without changing live traffic, or deploy with --pct 100.");
|
|
4178
|
+
}
|
|
3623
4179
|
const params = {
|
|
3624
4180
|
path: gemPath,
|
|
3625
4181
|
version_id: requireCommandValue(versionId, "Usage: uru gems versions deploy <version-id> [library/name.gem] [--pct 100] [--skip-promote]"),
|
|
3626
4182
|
traffic_percent: trafficPercentValue,
|
|
3627
|
-
publish
|
|
4183
|
+
publish
|
|
3628
4184
|
};
|
|
3629
4185
|
copyStringFlag(flags, params, "--link-id", "link_id");
|
|
3630
4186
|
if (flags.booleans.has("--no-wait")) {
|
|
@@ -3659,13 +4215,17 @@ async function gemVersionUpload(ctx, args, linkedProject) {
|
|
|
3659
4215
|
}
|
|
3660
4216
|
copyStringFlag(flags, params, "--source-version-id", "source_version_id");
|
|
3661
4217
|
copyStringFlag(flags, params, "--message", "message");
|
|
3662
|
-
|
|
4218
|
+
const previewAlias = flags.values["--preview-alias"];
|
|
4219
|
+
if (previewAlias !== undefined) {
|
|
4220
|
+
params["preview_alias"] = validatePreviewAlias(previewAlias);
|
|
4221
|
+
}
|
|
3663
4222
|
const tags = tagsFromFlags(flags);
|
|
3664
4223
|
if (tags.length > 0) {
|
|
3665
4224
|
params["tags"] = tags;
|
|
3666
4225
|
}
|
|
3667
4226
|
await emitGemTool(ctx, "gem.version_upload", params, {
|
|
3668
|
-
streamStart: { operation: "gem.version_upload", path: gemPath }
|
|
4227
|
+
streamStart: { operation: "gem.version_upload", path: gemPath },
|
|
4228
|
+
textExtractor: extractVersionUploadSummary
|
|
3669
4229
|
});
|
|
3670
4230
|
}
|
|
3671
4231
|
function parseTrafficPercent(raw) {
|
|
@@ -3675,29 +4235,33 @@ function parseTrafficPercent(raw) {
|
|
|
3675
4235
|
}
|
|
3676
4236
|
return value;
|
|
3677
4237
|
}
|
|
3678
|
-
function assertCurrentBackendTrafficSupport(value) {
|
|
3679
|
-
if (value !== 100) {
|
|
3680
|
-
throw new CliError("Gradual percentage Gem deploys require backend traffic-splitting support; omit @pct/--pct or use 100 until split traffic is enabled.");
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
4238
|
async function deployVersionToPublishedLink(ctx, flags, linkedProject, options) {
|
|
3684
|
-
const
|
|
3685
|
-
const
|
|
4239
|
+
const deploymentId = flags.values["--deployment-id"];
|
|
4240
|
+
const firstPositional = flags.positionals[0];
|
|
4241
|
+
const versionId = deploymentId === undefined && firstPositional !== undefined ? firstPositional.startsWith("library/") ? undefined : firstPositional : undefined;
|
|
4242
|
+
if (deploymentId === undefined && versionId === undefined) {
|
|
4243
|
+
throw new CliError(options.usage);
|
|
4244
|
+
}
|
|
4245
|
+
const gemPath = gemPathFromArgs(versionId === undefined ? flags.positionals : flags.positionals.slice(1), linkedProject);
|
|
3686
4246
|
const trafficPercent = parseTrafficPercent(flags.values["--pct"] ?? "100");
|
|
3687
|
-
|
|
4247
|
+
if (trafficPercent !== 100) {
|
|
4248
|
+
throw new CliError(`${options.operation} does not support partial traffic shifts yet. Deploy the version with --skip-promote for validation, or ${options.operation} with --pct 100.`);
|
|
4249
|
+
}
|
|
3688
4250
|
const params = {
|
|
3689
|
-
path: gemPath
|
|
3690
|
-
version_id: versionId,
|
|
3691
|
-
traffic_percent: trafficPercent,
|
|
3692
|
-
publish: true,
|
|
3693
|
-
operation: options.operation
|
|
4251
|
+
path: gemPath
|
|
3694
4252
|
};
|
|
4253
|
+
if (deploymentId === undefined) {
|
|
4254
|
+
params["version_id"] = requireCommandValue(versionId, options.usage);
|
|
4255
|
+
} else {
|
|
4256
|
+
params["app_deployment_id"] = deploymentId;
|
|
4257
|
+
}
|
|
3695
4258
|
copyStringFlag(flags, params, "--link-id", "link_id");
|
|
3696
4259
|
if (flags.booleans.has("--no-wait")) {
|
|
3697
4260
|
params["wait"] = false;
|
|
3698
4261
|
}
|
|
3699
|
-
|
|
3700
|
-
|
|
4262
|
+
const operation = `gem.${options.operation}`;
|
|
4263
|
+
await emitGemTool(ctx, operation, params, {
|
|
4264
|
+
streamStart: { operation, path: gemPath },
|
|
3701
4265
|
textExtractor: extractDeploymentUrl
|
|
3702
4266
|
});
|
|
3703
4267
|
}
|
|
@@ -3708,6 +4272,13 @@ function tagsFromFlags(flags) {
|
|
|
3708
4272
|
}
|
|
3709
4273
|
return raw.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0);
|
|
3710
4274
|
}
|
|
4275
|
+
function validatePreviewAlias(rawAlias) {
|
|
4276
|
+
const alias = rawAlias.trim();
|
|
4277
|
+
if (alias.length === 0 || alias.length > 63 || !/^[a-z][a-z0-9-]*[a-z0-9]$/u.test(alias)) {
|
|
4278
|
+
throw new CliError("--preview-alias must be a DNS-safe label: 1-63 characters, start with a lowercase letter, contain only lowercase letters, numbers, and dashes, and not end with a dash.");
|
|
4279
|
+
}
|
|
4280
|
+
return alias;
|
|
4281
|
+
}
|
|
3711
4282
|
function assertAllowedLocalFlags(flags, allowedFlags, commandName) {
|
|
3712
4283
|
const allowed = new Set(allowedFlags);
|
|
3713
4284
|
const unexpected = localFlagNames(flags).filter((flag) => !allowed.has(flag));
|
|
@@ -3727,9 +4298,12 @@ function localFlagNames(flags) {
|
|
|
3727
4298
|
}
|
|
3728
4299
|
async function writeBuildManifest(ctx, outdir, buildResult) {
|
|
3729
4300
|
const manifest = gemBuildManifestFromBuildResult(buildResult);
|
|
3730
|
-
const
|
|
4301
|
+
const providerDecision = gemProviderDecisionFromBuildResult(buildResult, manifest);
|
|
4302
|
+
const absoluteOutdir = resolve3(ctx.cwd, outdir);
|
|
3731
4303
|
await mkdir4(absoluteOutdir, { recursive: true });
|
|
3732
4304
|
await writeFile4(join3(absoluteOutdir, "gem-build-manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
4305
|
+
`, { mode: 384 });
|
|
4306
|
+
await writeFile4(join3(absoluteOutdir, "gem-provider-decision.json"), `${JSON.stringify(providerDecision, null, 2)}
|
|
3733
4307
|
`, { mode: 384 });
|
|
3734
4308
|
}
|
|
3735
4309
|
function gemBuildManifestFromBuildResult(buildResult) {
|
|
@@ -3782,6 +4356,80 @@ function normalizeGemBuildManifest(value) {
|
|
|
3782
4356
|
manifest_kind: "gem_build_manifest"
|
|
3783
4357
|
};
|
|
3784
4358
|
}
|
|
4359
|
+
function gemProviderDecisionFromBuildResult(buildResult, manifest) {
|
|
4360
|
+
const root = jsonObjectOrNull(buildResult);
|
|
4361
|
+
const build = jsonObjectOrNull(root?.["build"]);
|
|
4362
|
+
const buildResultDetails = jsonObjectOrNull(build?.["build_result"]);
|
|
4363
|
+
const buildMetadata = jsonObjectOrNull(buildResultDetails?.["metadata"]);
|
|
4364
|
+
const runtimeProfile = firstJsonObject([
|
|
4365
|
+
root?.["runtime_profile"],
|
|
4366
|
+
root?.["runtimeProfile"],
|
|
4367
|
+
build?.["runtime_profile"],
|
|
4368
|
+
build?.["runtimeProfile"],
|
|
4369
|
+
buildResultDetails?.["runtime_profile"],
|
|
4370
|
+
buildResultDetails?.["runtimeProfile"],
|
|
4371
|
+
buildMetadata?.["runtime_profile"],
|
|
4372
|
+
buildMetadata?.["runtimeProfile"]
|
|
4373
|
+
]) ?? {};
|
|
4374
|
+
const providerDecision = firstJsonObject([
|
|
4375
|
+
root?.["provider_decision"],
|
|
4376
|
+
root?.["providerDecision"],
|
|
4377
|
+
build?.["provider_decision"],
|
|
4378
|
+
build?.["providerDecision"],
|
|
4379
|
+
buildResultDetails?.["provider_decision"],
|
|
4380
|
+
buildResultDetails?.["providerDecision"],
|
|
4381
|
+
buildMetadata?.["provider_decision"],
|
|
4382
|
+
buildMetadata?.["providerDecision"]
|
|
4383
|
+
]) ?? {};
|
|
4384
|
+
const runtimeContract = firstJsonObject([
|
|
4385
|
+
root?.["runtime_contract"],
|
|
4386
|
+
root?.["runtimeContract"],
|
|
4387
|
+
build?.["runtime_contract"],
|
|
4388
|
+
build?.["runtimeContract"],
|
|
4389
|
+
buildResultDetails?.["runtime_contract"],
|
|
4390
|
+
buildResultDetails?.["runtimeContract"],
|
|
4391
|
+
buildMetadata?.["runtime_contract"],
|
|
4392
|
+
buildMetadata?.["runtimeContract"],
|
|
4393
|
+
manifest["runtime_contract"],
|
|
4394
|
+
manifest["runtimeContract"]
|
|
4395
|
+
]) ?? {};
|
|
4396
|
+
return {
|
|
4397
|
+
schema: "uru.gem.provider_decision.v1",
|
|
4398
|
+
schema_version: 1,
|
|
4399
|
+
manifest_kind: "gem_provider_decision",
|
|
4400
|
+
provider: stringFieldFromJsonObjects([providerDecision, runtimeProfile, manifest], ["provider", "runtime_provider", "runtimeProvider", "selected_provider"]),
|
|
4401
|
+
runtime_class: stringFieldFromJsonObjects([providerDecision, runtimeProfile, manifest], ["runtime_class", "runtimeClass", "class", "serving_mode"]),
|
|
4402
|
+
runtime_profile: runtimeProfile,
|
|
4403
|
+
provider_decision: providerDecision,
|
|
4404
|
+
runtime_contract: runtimeContract,
|
|
4405
|
+
manifest_summary: {
|
|
4406
|
+
serving_mode: manifest["serving_mode"] ?? null,
|
|
4407
|
+
worker_compatibility: manifest["worker_compatibility"] ?? null,
|
|
4408
|
+
serverful_compatibility: manifest["serverful_compatibility"] ?? null,
|
|
4409
|
+
runtime_requirements: manifest["runtime_requirements"] ?? null
|
|
4410
|
+
}
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
function firstJsonObject(values) {
|
|
4414
|
+
for (const value of values) {
|
|
4415
|
+
const object = jsonObjectOrNull(value);
|
|
4416
|
+
if (object !== null) {
|
|
4417
|
+
return object;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
return null;
|
|
4421
|
+
}
|
|
4422
|
+
function stringFieldFromJsonObjects(objects, keys) {
|
|
4423
|
+
for (const object of objects) {
|
|
4424
|
+
for (const key of keys) {
|
|
4425
|
+
const value = object[key];
|
|
4426
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
4427
|
+
return value.trim();
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
return null;
|
|
4432
|
+
}
|
|
3785
4433
|
function jsonObjectOrNull(value) {
|
|
3786
4434
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3787
4435
|
return null;
|
|
@@ -3791,6 +4439,48 @@ function jsonObjectOrNull(value) {
|
|
|
3791
4439
|
function extractDeploymentUrl(value) {
|
|
3792
4440
|
return firstUrl(value);
|
|
3793
4441
|
}
|
|
4442
|
+
function extractVersionUploadSummary(value) {
|
|
4443
|
+
const objects = collectJsonObjects(value);
|
|
4444
|
+
const versionId = stringFieldFromJsonObjects(objects, [
|
|
4445
|
+
"version_id",
|
|
4446
|
+
"versionId",
|
|
4447
|
+
"id"
|
|
4448
|
+
]);
|
|
4449
|
+
const previewUrl = stringFieldFromJsonObjects(objects, [
|
|
4450
|
+
"preview_url",
|
|
4451
|
+
"previewUrl",
|
|
4452
|
+
"preview_alias_url",
|
|
4453
|
+
"previewAliasUrl",
|
|
4454
|
+
"url"
|
|
4455
|
+
]) ?? firstUrl(value);
|
|
4456
|
+
const lines = [
|
|
4457
|
+
versionId === null ? null : `Version: ${versionId}`,
|
|
4458
|
+
previewUrl === undefined || previewUrl === null ? null : `Preview: ${previewUrl}`
|
|
4459
|
+
].filter((line) => line !== null);
|
|
4460
|
+
return lines.length === 0 ? undefined : lines.join(`
|
|
4461
|
+
`);
|
|
4462
|
+
}
|
|
4463
|
+
function collectJsonObjects(value) {
|
|
4464
|
+
const objects = [];
|
|
4465
|
+
collectJsonObjectsInto(value, objects);
|
|
4466
|
+
return objects;
|
|
4467
|
+
}
|
|
4468
|
+
function collectJsonObjectsInto(value, objects) {
|
|
4469
|
+
if (Array.isArray(value)) {
|
|
4470
|
+
for (const item of value) {
|
|
4471
|
+
collectJsonObjectsInto(item, objects);
|
|
4472
|
+
}
|
|
4473
|
+
return;
|
|
4474
|
+
}
|
|
4475
|
+
const object = jsonObjectOrNull(value);
|
|
4476
|
+
if (object === null) {
|
|
4477
|
+
return;
|
|
4478
|
+
}
|
|
4479
|
+
objects.push(object);
|
|
4480
|
+
for (const child of Object.values(object)) {
|
|
4481
|
+
collectJsonObjectsInto(child, objects);
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
3794
4484
|
function trimTrailingSlashes(value) {
|
|
3795
4485
|
let end = value.length;
|
|
3796
4486
|
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
@@ -3807,10 +4497,20 @@ function trimLeadingSlashes(value) {
|
|
|
3807
4497
|
}
|
|
3808
4498
|
|
|
3809
4499
|
// src/gem-source-commands.ts
|
|
3810
|
-
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
3811
|
-
import { dirname as dirname4, join as join4, resolve as
|
|
4500
|
+
import { mkdir as mkdir5, rm as rm3, writeFile as writeFile5 } from "node:fs/promises";
|
|
4501
|
+
import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
|
|
3812
4502
|
async function gemSourcePull(ctx, args, linkedProject) {
|
|
3813
|
-
const
|
|
4503
|
+
const flags = parseLocalFlags(args);
|
|
4504
|
+
const gemPath = gemPathFromArgs(flags.positionals, linkedProject);
|
|
4505
|
+
const force = flags.booleans.has("--force");
|
|
4506
|
+
const previousLock = await readSourceLock(ctx.cwd);
|
|
4507
|
+
const localFilesBeforePull = await collectLocalSourceFiles(ctx.cwd);
|
|
4508
|
+
const localStatusBeforePull = compareSourceLock(previousLock, localFilesBeforePull);
|
|
4509
|
+
if (!force && !localStatusBeforePull.clean) {
|
|
4510
|
+
throw new CliError(dirtyPullErrorMessage(localStatusBeforePull), {
|
|
4511
|
+
code: ExitCode.Conflict
|
|
4512
|
+
});
|
|
4513
|
+
}
|
|
3814
4514
|
const tree = unwrapToolResult(await executeOperation(ctx, "gem.fs_tree", {
|
|
3815
4515
|
path: gemPath,
|
|
3816
4516
|
recursive: true
|
|
@@ -3830,6 +4530,7 @@ async function gemSourcePull(ctx, args, linkedProject) {
|
|
|
3830
4530
|
await writePulledSourceFile(ctx.cwd, filePath, content);
|
|
3831
4531
|
files[filePath] = sha256Hex(content);
|
|
3832
4532
|
}
|
|
4533
|
+
const pruned = await pruneRemoteDeletedSourceFiles(ctx.cwd, previousLock, localFilesBeforePull, new Set(filePaths));
|
|
3833
4534
|
const lock = {
|
|
3834
4535
|
...sourceVersionId === undefined ? {} : { sourceVersionId },
|
|
3835
4536
|
files,
|
|
@@ -3840,8 +4541,10 @@ async function gemSourcePull(ctx, args, linkedProject) {
|
|
|
3840
4541
|
ok: true,
|
|
3841
4542
|
path: gemPath,
|
|
3842
4543
|
files: filePaths,
|
|
4544
|
+
prunedFiles: pruned.deleted,
|
|
4545
|
+
preservedFiles: pruned.preserved,
|
|
3843
4546
|
...sourceVersionId === undefined ? {} : { sourceVersionId }
|
|
3844
|
-
},
|
|
4547
|
+
}, formatPullSummary(gemPath, filePaths.length, pruned));
|
|
3845
4548
|
}
|
|
3846
4549
|
async function gemSourceStatus(ctx, args, linkedProject) {
|
|
3847
4550
|
const flags = parseLocalFlags(args);
|
|
@@ -3901,24 +4604,16 @@ async function gemSourcePush(ctx, args, linkedProject) {
|
|
|
3901
4604
|
...lock.sourceVersionId === undefined ? {} : { sourceVersionId: lock.sourceVersionId }
|
|
3902
4605
|
};
|
|
3903
4606
|
try {
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
}
|
|
3913
|
-
|
|
3914
|
-
const writeResult = unwrapToolResult(await executeOperation(ctx, "gem.fs_batch_write", {
|
|
3915
|
-
path: gemPath,
|
|
3916
|
-
files: sortedFiles.map(localFileBatchWritePayload),
|
|
3917
|
-
...sourceMutationCasParams(sourceCas, force),
|
|
3918
|
-
change_summary: force ? "Force-push local Gem source via Uru CLI." : "Push local Gem source via Uru CLI."
|
|
3919
|
-
}));
|
|
3920
|
-
sourceCas = nextSourceMutationCas(sourceCas, writeResult);
|
|
3921
|
-
}
|
|
4607
|
+
const syncResult = unwrapToolResult(await executeOperation(ctx, "gem.fs_sync", {
|
|
4608
|
+
path: gemPath,
|
|
4609
|
+
files: sortedFiles.map(localFileBatchWritePayload),
|
|
4610
|
+
delete_paths: localStatus.deleted.map((deletedPath) => ({
|
|
4611
|
+
path: deletedPath
|
|
4612
|
+
})),
|
|
4613
|
+
...sourceMutationCasParams(sourceCas, force),
|
|
4614
|
+
change_summary: force ? "Force-push local Gem source via Uru CLI." : "Push local Gem source via Uru CLI."
|
|
4615
|
+
}));
|
|
4616
|
+
sourceCas = nextSourceMutationCas(sourceCas, syncResult);
|
|
3922
4617
|
} catch (error) {
|
|
3923
4618
|
if (isConflictLikeError(error)) {
|
|
3924
4619
|
throw new CliError(error instanceof Error ? error.message : "Gem source conflict", { code: ExitCode.Conflict, cause: error });
|
|
@@ -3945,6 +4640,44 @@ function localFileBatchWritePayload(file) {
|
|
|
3945
4640
|
content_base64: file.contentBase64
|
|
3946
4641
|
};
|
|
3947
4642
|
}
|
|
4643
|
+
async function pruneRemoteDeletedSourceFiles(cwd, previousLock, localFilesBeforePull, remotePaths) {
|
|
4644
|
+
const deleted = [];
|
|
4645
|
+
const preserved = [];
|
|
4646
|
+
const lockedFiles = Object.entries(previousLock.files ?? {}).sort(([left], [right]) => left.localeCompare(right));
|
|
4647
|
+
for (const [projectPath2, lockedHash] of lockedFiles) {
|
|
4648
|
+
if (remotePaths.has(projectPath2)) {
|
|
4649
|
+
continue;
|
|
4650
|
+
}
|
|
4651
|
+
const localFile = localFilesBeforePull[projectPath2];
|
|
4652
|
+
if (localFile === undefined) {
|
|
4653
|
+
continue;
|
|
4654
|
+
}
|
|
4655
|
+
if (localFile.sha256 !== lockedHash) {
|
|
4656
|
+
preserved.push(projectPath2);
|
|
4657
|
+
continue;
|
|
4658
|
+
}
|
|
4659
|
+
const safePath = safeProjectPath(projectPath2);
|
|
4660
|
+
if (safePath === null) {
|
|
4661
|
+
preserved.push(projectPath2);
|
|
4662
|
+
continue;
|
|
4663
|
+
}
|
|
4664
|
+
await rm3(join4(resolve4(cwd), ...safePath.split("/")), { force: true });
|
|
4665
|
+
deleted.push(projectPath2);
|
|
4666
|
+
}
|
|
4667
|
+
return { deleted, preserved };
|
|
4668
|
+
}
|
|
4669
|
+
function formatPullSummary(gemPath, writtenCount, pruned) {
|
|
4670
|
+
const parts = [
|
|
4671
|
+
`Pulled ${writtenCount} file${writtenCount === 1 ? "" : "s"} from ${gemPath}`
|
|
4672
|
+
];
|
|
4673
|
+
if (pruned.deleted.length > 0) {
|
|
4674
|
+
parts.push(`pruned ${pruned.deleted.length} remote-deleted file${pruned.deleted.length === 1 ? "" : "s"}`);
|
|
4675
|
+
}
|
|
4676
|
+
if (pruned.preserved.length > 0) {
|
|
4677
|
+
parts.push(`preserved ${pruned.preserved.length} locally changed remote-deleted file${pruned.preserved.length === 1 ? "" : "s"}`);
|
|
4678
|
+
}
|
|
4679
|
+
return parts.join("; ");
|
|
4680
|
+
}
|
|
3948
4681
|
function sourceMutationCasParams(cas, force) {
|
|
3949
4682
|
if (force || cas.sourceVersionId === undefined) {
|
|
3950
4683
|
return { force: true };
|
|
@@ -4036,6 +4769,17 @@ function formatLocalStatusEntries(status) {
|
|
|
4036
4769
|
...status.deleted.map((path) => `D ${path}`)
|
|
4037
4770
|
];
|
|
4038
4771
|
}
|
|
4772
|
+
function dirtyPullErrorMessage(status) {
|
|
4773
|
+
const entries = formatLocalStatusEntries(status);
|
|
4774
|
+
const preview = entries.slice(0, 10).map((entry) => ` ${entry}`).join(`
|
|
4775
|
+
`);
|
|
4776
|
+
const moreCount = Math.max(0, entries.length - 10);
|
|
4777
|
+
const moreLine = moreCount > 0 ? `
|
|
4778
|
+
...and ${moreCount} more local change(s)` : "";
|
|
4779
|
+
return `Refusing to pull because local Gem source has uncommitted changes that could be overwritten.
|
|
4780
|
+
Run \`uru status\` to inspect them, save or push your changes, or re-run \`uru pull --force\` to replace local files with server source.
|
|
4781
|
+
${preview}${moreLine}`;
|
|
4782
|
+
}
|
|
4039
4783
|
async function tryRemoteSourceOperation(ctx, operationId, params) {
|
|
4040
4784
|
try {
|
|
4041
4785
|
return {
|
|
@@ -4138,7 +4882,7 @@ async function writePulledSourceFile(cwd, projectPath2, content) {
|
|
|
4138
4882
|
if (safePath === null) {
|
|
4139
4883
|
throw new CliError(`Refusing to write unsafe Gem source path: ${projectPath2}`);
|
|
4140
4884
|
}
|
|
4141
|
-
const localPath = join4(
|
|
4885
|
+
const localPath = join4(resolve4(cwd), ...safePath.split("/"));
|
|
4142
4886
|
await mkdir5(dirname4(localPath), { recursive: true });
|
|
4143
4887
|
await writeFile5(localPath, content);
|
|
4144
4888
|
}
|
|
@@ -4193,20 +4937,34 @@ function isConflictLikeError(error) {
|
|
|
4193
4937
|
|
|
4194
4938
|
// src/token-env-secret-commands.ts
|
|
4195
4939
|
import { spawn as spawn2 } from "node:child_process";
|
|
4196
|
-
import { chmod, mkdir as mkdir6, readFile as
|
|
4197
|
-
import { dirname as dirname5, resolve as
|
|
4940
|
+
import { chmod, mkdir as mkdir6, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
|
|
4941
|
+
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
4198
4942
|
async function dispatchTokens(ctx, sub, args, linkedProject) {
|
|
4199
4943
|
if (sub === "create") {
|
|
4200
4944
|
const flags = parseLocalFlags(args);
|
|
4945
|
+
const usage = "Usage: uru tokens create [library/name.gem] [--gem <id>] --name <name> (--expiry 30d|--expires-at <iso>)";
|
|
4946
|
+
const name = requireCommandValue(flags.values["--name"], usage);
|
|
4947
|
+
const expiry = flags.values["--expiry"];
|
|
4948
|
+
const expiresAt = flags.values["--expires-at"];
|
|
4949
|
+
if (expiry === undefined && expiresAt === undefined) {
|
|
4950
|
+
throw new CliError("Gem deploy token creation requires --expiry <duration> or --expires-at <iso timestamp>.");
|
|
4951
|
+
}
|
|
4952
|
+
if (expiry !== undefined && expiresAt !== undefined) {
|
|
4953
|
+
throw new CliError("Pass only one token lifetime flag: --expiry or --expires-at.");
|
|
4954
|
+
}
|
|
4201
4955
|
const params = {
|
|
4202
4956
|
...tokenGemRefParams(flags, linkedProject, {
|
|
4203
4957
|
required: true,
|
|
4204
|
-
usage
|
|
4205
|
-
})
|
|
4958
|
+
usage
|
|
4959
|
+
}),
|
|
4960
|
+
name
|
|
4206
4961
|
};
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4962
|
+
if (expiry !== undefined) {
|
|
4963
|
+
params["expiry"] = expiry;
|
|
4964
|
+
}
|
|
4965
|
+
if (expiresAt !== undefined) {
|
|
4966
|
+
params["expires_at"] = expiresAt;
|
|
4967
|
+
}
|
|
4210
4968
|
const scopes = tokenScopes(flags);
|
|
4211
4969
|
if (scopes.length > 0) {
|
|
4212
4970
|
params["scopes"] = scopes;
|
|
@@ -4294,7 +5052,7 @@ async function dispatchEnv(ctx, sub, args, linkedProject) {
|
|
|
4294
5052
|
})));
|
|
4295
5053
|
const secrets = extractSecretRows(result);
|
|
4296
5054
|
const content = formatEnvTemplate(secrets);
|
|
4297
|
-
const absolutePath =
|
|
5055
|
+
const absolutePath = resolve5(ctx.cwd, outputPath);
|
|
4298
5056
|
await mkdir6(dirname5(absolutePath), { recursive: true });
|
|
4299
5057
|
await writeFile6(absolutePath, content, { mode: 384 });
|
|
4300
5058
|
await chmod(absolutePath, 384);
|
|
@@ -4311,8 +5069,8 @@ async function dispatchEnv(ctx, sub, args, linkedProject) {
|
|
|
4311
5069
|
}
|
|
4312
5070
|
if (sub === "run") {
|
|
4313
5071
|
const parsed = parseEnvRunArgs(args);
|
|
4314
|
-
const envFilePath =
|
|
4315
|
-
const envVars = parseEnvFile(await
|
|
5072
|
+
const envFilePath = resolve5(ctx.cwd, parsed.envFile);
|
|
5073
|
+
const envVars = parseEnvFile(await readFile6(envFilePath, "utf8"));
|
|
4316
5074
|
await runEnvCommand(ctx, parsed.command, envVars);
|
|
4317
5075
|
return;
|
|
4318
5076
|
}
|
|
@@ -4332,10 +5090,10 @@ async function secretValueFromFlags(ctx, flags) {
|
|
|
4332
5090
|
}
|
|
4333
5091
|
const valueFile = flags.values["--value-file"];
|
|
4334
5092
|
if (valueFile !== undefined) {
|
|
4335
|
-
return (await
|
|
5093
|
+
return (await readFile6(resolve5(ctx.cwd, valueFile), "utf8")).trimEnd();
|
|
4336
5094
|
}
|
|
4337
5095
|
if (flags.booleans.has("--value-stdin")) {
|
|
4338
|
-
return await
|
|
5096
|
+
return await readStdinText2(ctx.stdin ?? process.stdin);
|
|
4339
5097
|
}
|
|
4340
5098
|
throw new CliError("Secret put requires --value-file <path> or --value-stdin. Values are never printed by the CLI.");
|
|
4341
5099
|
}
|
|
@@ -4480,7 +5238,7 @@ function redactValues2(content, values) {
|
|
|
4480
5238
|
}
|
|
4481
5239
|
return redacted;
|
|
4482
5240
|
}
|
|
4483
|
-
async function
|
|
5241
|
+
async function readStdinText2(stdin) {
|
|
4484
5242
|
const chunks = [];
|
|
4485
5243
|
for await (const chunk of stdin) {
|
|
4486
5244
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
@@ -4640,6 +5398,7 @@ async function runCli(options) {
|
|
|
4640
5398
|
credentialStore,
|
|
4641
5399
|
apiUrl,
|
|
4642
5400
|
...token !== undefined ? { token } : {},
|
|
5401
|
+
tokenIsExplicit: flagToken !== undefined || envToken !== undefined,
|
|
4643
5402
|
...workspace !== undefined ? { workspace } : {},
|
|
4644
5403
|
cwd,
|
|
4645
5404
|
json: outputFormat === "json",
|
|
@@ -4661,6 +5420,9 @@ async function runCli(options) {
|
|
|
4661
5420
|
};
|
|
4662
5421
|
throwIfCancelled(cancellation.signal);
|
|
4663
5422
|
await dispatchWithImplicitLogin(ctx, parsed.command, linkedProject);
|
|
5423
|
+
await maybeNotifyCliUpdate(ctx, {
|
|
5424
|
+
...options.env === undefined ? {} : { env: options.env }
|
|
5425
|
+
});
|
|
4664
5426
|
return ExitCode.Ok;
|
|
4665
5427
|
} catch (error) {
|
|
4666
5428
|
const cliError = normalizeError(error);
|
|
@@ -4894,6 +5656,11 @@ async function dispatch(ctx, command, linkedProject) {
|
|
|
4894
5656
|
await dispatchDatasets(ctx, sub, command.slice(2));
|
|
4895
5657
|
return;
|
|
4896
5658
|
}
|
|
5659
|
+
if (head === "automations") {
|
|
5660
|
+
await withWorkspace(ctx);
|
|
5661
|
+
await dispatchAutomations(ctx, sub, command.slice(2));
|
|
5662
|
+
return;
|
|
5663
|
+
}
|
|
4897
5664
|
if (head === "tokens" || head === "token") {
|
|
4898
5665
|
await withWorkspace(ctx);
|
|
4899
5666
|
await dispatchTokens(ctx, sub, command.slice(2), linkedProject);
|
|
@@ -4947,14 +5714,14 @@ function writeSystemClipboard(text) {
|
|
|
4947
5714
|
const platform = process.platform;
|
|
4948
5715
|
const command = platform === "darwin" ? "pbcopy" : platform === "win32" ? "clip" : "xclip";
|
|
4949
5716
|
const args = platform === "linux" ? ["-selection", "clipboard"] : [];
|
|
4950
|
-
return new Promise((
|
|
5717
|
+
return new Promise((resolve6, reject) => {
|
|
4951
5718
|
const child = spawn3(command, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
4952
5719
|
child.on("error", (error) => {
|
|
4953
5720
|
reject(new CliError("Could not copy login URL to clipboard. Re-run without --clipboard to print the URL.", { cause: error }));
|
|
4954
5721
|
});
|
|
4955
5722
|
child.on("close", (code) => {
|
|
4956
5723
|
if (code === 0) {
|
|
4957
|
-
|
|
5724
|
+
resolve6();
|
|
4958
5725
|
return;
|
|
4959
5726
|
}
|
|
4960
5727
|
reject(new CliError(`Could not copy login URL to clipboard (${command} exited ${code ?? "unknown"}). Re-run without --clipboard to print the URL.`));
|