@uru-intelligence/cli 0.1.0
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 +113 -0
- package/dist/uru.mjs +1605 -0
- package/package.json +44 -0
package/dist/uru.mjs
ADDED
|
@@ -0,0 +1,1605 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/cli.ts
|
|
5
|
+
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
6
|
+
|
|
7
|
+
// ../../packages/platform-operations/src/index.ts
|
|
8
|
+
var ALL_SURFACES = [
|
|
9
|
+
"cli",
|
|
10
|
+
"mcp",
|
|
11
|
+
"docs"
|
|
12
|
+
];
|
|
13
|
+
var gemOperations = [
|
|
14
|
+
op("gem.init", "gems", "Create a Gem project at a visible library/<name>.gem path.", "gem_control_plane", "init", "write"),
|
|
15
|
+
op("gem.inspect", "gems", "Inspect Gem release readiness, bindings, secrets, and setup checks.", "gem_query", "inspect", "read"),
|
|
16
|
+
op("gem.fs_tree", "gems", "List files in a Gem project directory.", "gem_query", "fs_tree", "read"),
|
|
17
|
+
op("gem.fs_read", "gems", "Read a file from a Gem project directory.", "gem_query", "fs_read", "read"),
|
|
18
|
+
op("gem.fs_write", "gems", "Write a file inside a Gem project directory.", "gem_control_plane", "fs_write", "write"),
|
|
19
|
+
op("gem.fs_batch_write", "gems", "Write multiple Gem files atomically through the server source layer.", "gem_control_plane", "fs_batch_write", "write"),
|
|
20
|
+
op("gem.fs_patch", "gems", "Apply a patch to Gem source files.", "gem_control_plane", "fs_patch", "write"),
|
|
21
|
+
op("gem.fs_mkdir", "gems", "Create a directory inside a Gem project.", "gem_control_plane", "fs_mkdir", "write"),
|
|
22
|
+
op("gem.fs_rm", "gems", "Remove a file or directory from a Gem project.", "gem_control_plane", "fs_rm", "destructive"),
|
|
23
|
+
op("gem.fs_mv", "gems", "Move or rename a Gem project file.", "gem_control_plane", "fs_mv", "write"),
|
|
24
|
+
op("gem.fs_cp", "gems", "Copy a Gem project file.", "gem_control_plane", "fs_cp", "write"),
|
|
25
|
+
op("gem.fs_status", "gems", "Compare current Gem source against live, last build, or another version.", "gem_query", "fs_status", "read"),
|
|
26
|
+
op("gem.fs_diff", "gems", "Read a git-like diff for Gem source changes.", "gem_query", "fs_diff", "read"),
|
|
27
|
+
op("gem.validate", "gems", "Validate and build-check the current Gem source without publishing.", "gem_control_plane", "validate", "read"),
|
|
28
|
+
op("gem.build", "gems", "Create or reuse a ready server-side Gem build.", "gem_control_plane", "build", "write"),
|
|
29
|
+
op("gem.build_logs", "gems", "Read build diagnostics for a Gem build.", "gem_query", "build_logs", "read"),
|
|
30
|
+
op("gem.build_inspect", "gems", "Inspect a Gem runtime contract and release overview.", "gem_query", "build_inspect", "read"),
|
|
31
|
+
op("gem.upsert_binding", "gems", "Bind a declared Gem need to an approved platform resource.", "gem_control_plane", "upsert_binding", "write"),
|
|
32
|
+
op("gem.resolve_safe_bindings", "gems", "Resolve deterministic safe bindings for a ready Gem build.", "gem_control_plane", "resolve_safe_bindings", "write"),
|
|
33
|
+
op("gem.store_secret", "gems", "Store a user-supplied Gem secret value server-side.", "gem_control_plane", "store_secret", "write"),
|
|
34
|
+
op("gem.deploy", "gems", "Deploy a Gem using the server-side build/deploy/publish lifecycle.", "gem_control_plane", "deploy", "write"),
|
|
35
|
+
op("gem.status", "gems", "Read Gem deployment status, checks, runtime runs, and recent logs.", "gem_query", "deployment_status", "read"),
|
|
36
|
+
op("gem.logs", "gems", "Read server-side Gem runtime logs with filters.", "gem_query", "logs", "read"),
|
|
37
|
+
op("gem.browser_verify", "gems", "Run browser verification for a Gem deployment.", "gem_query", "browser_verify", "read"),
|
|
38
|
+
op("gem.security_scan", "gems", "Run a Gem security scan.", "gem_query", "security_scan", "read"),
|
|
39
|
+
op("gem.promote", "gems", "Promote a published Gem link to a deployment.", "gem_control_plane", "promote", "write"),
|
|
40
|
+
op("gem.rollback", "gems", "Repoint a published Gem link to a previous immutable deployment.", "gem_control_plane", "rollback", "destructive"),
|
|
41
|
+
op("gem.publish", "gems", "Publish or repoint a Gem link to a source version or deployment.", "gem_control_plane", "publish", "write"),
|
|
42
|
+
op("gem.unpublish", "gems", "Remove the published primary link for a Gem.", "gem_control_plane", "unpublish", "destructive"),
|
|
43
|
+
op("gem.links_list", "gems", "List published links for a Gem.", "gem_query", "links_list", "read"),
|
|
44
|
+
op("gem.links_create", "gems", "Create an additional published Gem link.", "gem_control_plane", "links_create", "write"),
|
|
45
|
+
op("gem.set_access", "gems", "Update access gates for a Gem link.", "gem_control_plane", "set_access", "write"),
|
|
46
|
+
op("gem.analytics", "gems", "Read Gem view analytics.", "gem_query", "get_gem_analytics", "read"),
|
|
47
|
+
op("gem.domains_set", "gems", "Attach or update a custom domain for a Gem.", "gem_control_plane", "domains_set", "write"),
|
|
48
|
+
op("gem.domains_list", "gems", "List custom domains for a Gem.", "gem_query", "domains_list", "read"),
|
|
49
|
+
op("gem.components_list", "gems", "List verified Gem component templates.", "GEM_COMPONENTS_LIST", undefined, "read"),
|
|
50
|
+
op("gem.components_get", "gems", "Fetch one verified Gem component definition.", "GEM_COMPONENTS_GET", undefined, "read")
|
|
51
|
+
];
|
|
52
|
+
var libraryOperations = [
|
|
53
|
+
op("library.ls", "library", "List visible Library folders and resources.", "library_query", "ls", "read"),
|
|
54
|
+
op("library.stat", "library", "Read metadata for a visible Library path.", "library_query", "stat", "read"),
|
|
55
|
+
op("library.search", "library", "Search visible Library resources server-side.", "library_query", "search", "read"),
|
|
56
|
+
op("library.mkdir", "library", "Create a Library folder.", "library_fs", "mkdir", "write"),
|
|
57
|
+
op("library.mv", "library", "Move or rename a Library item.", "library_fs", "mv", "write"),
|
|
58
|
+
op("library.move_workspace", "library", "Move a Library item to another workspace.", "library_fs", "move_workspace", "destructive"),
|
|
59
|
+
op("library.rm", "library", "Trash a Library item.", "library_fs", "rm", "destructive"),
|
|
60
|
+
op("library.restore", "library", "Restore a trashed Library item.", "library_fs", "restore", "write"),
|
|
61
|
+
op("library.empty_trash", "library", "Permanently empty Library trash.", "library_fs", "empty_trash", "destructive"),
|
|
62
|
+
op("library.upload_preflight", "library", "Preflight a Library upload.", "library_upload", "preflight", "read"),
|
|
63
|
+
op("library.upload_create_session", "library", "Create a direct Library upload session.", "library_upload", "create_session", "write"),
|
|
64
|
+
op("library.upload_complete_session", "library", "Complete a direct Library upload session.", "library_upload", "complete_session", "write")
|
|
65
|
+
];
|
|
66
|
+
var sharingOperations = [
|
|
67
|
+
op("sharing.share", "sharing", "Share a Library resource with a user, team, or workspace.", "library_collab", "share", "write"),
|
|
68
|
+
op("sharing.unshare", "sharing", "Remove a Library resource grant.", "library_collab", "unshare", "destructive"),
|
|
69
|
+
op("sharing.permissions_list", "sharing", "List permissions for a Library resource.", "library_collab", "list_permissions", "read"),
|
|
70
|
+
op("sharing.permissions_set", "sharing", "Set permissions for a Library resource.", "library_collab", "set_permissions", "write"),
|
|
71
|
+
op("sharing.settings_set", "sharing", "Set share settings for a Library resource.", "library_collab", "set_share_settings", "write"),
|
|
72
|
+
op("sharing.transfer_ownership", "sharing", "Transfer ownership for a Library resource.", "library_collab", "transfer_ownership", "destructive"),
|
|
73
|
+
op("sharing.versions_list", "sharing", "List Library resource versions.", "library_collab", "list_versions", "read"),
|
|
74
|
+
op("sharing.version_restore", "sharing", "Restore a Library resource version.", "library_collab", "restore_version", "destructive"),
|
|
75
|
+
op("sharing.comment", "sharing", "Comment on a Library resource.", "library_collab", "comment", "write"),
|
|
76
|
+
op("sharing.comment_resolve", "sharing", "Resolve a Library resource comment.", "library_collab", "resolve_comment", "write"),
|
|
77
|
+
op("sharing.label", "sharing", "Apply a Library label.", "library_collab", "label", "write"),
|
|
78
|
+
op("sharing.unlabel", "sharing", "Remove a Library label.", "library_collab", "unlabel", "write")
|
|
79
|
+
];
|
|
80
|
+
var datasetOperations = [
|
|
81
|
+
op("dataset.query", "datasets", "Query dataset rows with server-side filters, sort, search, and aggregates.", "dataset_query", undefined, "read"),
|
|
82
|
+
op("dataset.rows_upsert", "datasets", "Upsert dataset rows.", "dataset_rows", "upsert", "write"),
|
|
83
|
+
op("dataset.rows_delete", "datasets", "Soft-delete dataset rows.", "dataset_rows", "delete", "destructive"),
|
|
84
|
+
op("dataset.rows_restore", "datasets", "Restore soft-deleted dataset rows.", "dataset_rows", "restore", "write"),
|
|
85
|
+
op("dataset.get", "datasets", "Get dataset metadata and canonical schema.", "dataset_get", undefined, "read"),
|
|
86
|
+
op("dataset.manage_create", "datasets", "Create a typed dataset with canonical schema.", "dataset_manage", "create", "write"),
|
|
87
|
+
op("dataset.manage_update", "datasets", "Update dataset metadata or canonical schema.", "dataset_manage", "update", "write"),
|
|
88
|
+
op("dataset.sql", "datasets", "Execute scoped dataset SQL.", "dataset_sql", undefined, "write")
|
|
89
|
+
];
|
|
90
|
+
var workspaceOperations = [
|
|
91
|
+
op("workspace.list", "workspace", "List available workspaces.", "list_workspaces", undefined, "read"),
|
|
92
|
+
op("workspace.switch", "workspace", "Set current workspace for a session.", "set_current_workspace", undefined, "write"),
|
|
93
|
+
op("workspace.teams_list", "workspace", "List workspace teams.", "list_workspace_teams", undefined, "read"),
|
|
94
|
+
op("workspace.members_list", "workspace", "List workspace members for sharing.", "workspace_members_list", undefined, "read")
|
|
95
|
+
];
|
|
96
|
+
var automationOperations = [
|
|
97
|
+
op("automation.list", "automations", "List workspace automations.", "LIST_AUTOMATIONS", undefined, "read"),
|
|
98
|
+
op("automation.get", "automations", "Get one automation definition.", "GET_AUTOMATION", undefined, "read"),
|
|
99
|
+
op("automation.runs_list", "automations", "List automation runs.", "LIST_AUTOMATION_RUNS", undefined, "read"),
|
|
100
|
+
op("automation.run_debug", "automations", "Debug an automation run.", "DEBUG_AUTOMATION_RUN", undefined, "read"),
|
|
101
|
+
op("automation.run_logs", "automations", "Read automation run logs.", "GET_AUTOMATION_RUN_LOGS", undefined, "read"),
|
|
102
|
+
op("automation.run_timeline", "automations", "Read automation run timeline.", "GET_AUTOMATION_RUN_TIMELINE", undefined, "read"),
|
|
103
|
+
op("automation.triggers_discover", "automations", "Discover automation triggers.", "DISCOVER_TRIGGERS", undefined, "read"),
|
|
104
|
+
op("automation.connections_list", "automations", "List automation connections.", "LIST_CONNECTIONS", undefined, "read"),
|
|
105
|
+
op("automation.create", "automations", "Create an automation.", "CREATE_AUTOMATION", undefined, "write"),
|
|
106
|
+
op("automation.update", "automations", "Update automation metadata.", "UPDATE_AUTOMATION", undefined, "write"),
|
|
107
|
+
op("automation.delete", "automations", "Delete an automation.", "DELETE_AUTOMATION", undefined, "destructive"),
|
|
108
|
+
op("automation.graph_patch", "automations", "Patch an automation graph.", "PATCH_AUTOMATION_GRAPH", undefined, "write"),
|
|
109
|
+
op("automation.trigger_create", "automations", "Create an automation trigger.", "CREATE_AUTOMATION_TRIGGER", undefined, "write"),
|
|
110
|
+
op("automation.trigger_update", "automations", "Update an automation trigger.", "UPDATE_AUTOMATION_TRIGGER", undefined, "write"),
|
|
111
|
+
op("automation.trigger_delete", "automations", "Delete an automation trigger.", "DELETE_AUTOMATION_TRIGGER", undefined, "destructive")
|
|
112
|
+
];
|
|
113
|
+
var webAndFileOperations = [
|
|
114
|
+
op("web.search", "web", "Search the web.", "web_search", undefined, "read"),
|
|
115
|
+
op("web.fetch", "web", "Fetch a web page.", "web_fetch", undefined, "read"),
|
|
116
|
+
op("web.search_and_fetch", "web", "Search and fetch web pages.", "web_search_and_fetch", undefined, "read"),
|
|
117
|
+
op("file.download_content", "files", "Download file content for agent use.", "DOWNLOAD_FILE_CONTENT", undefined, "read"),
|
|
118
|
+
op("file.download_for_user", "files", "Prepare a file download for the user.", "DOWNLOAD_FILE_FOR_USER", undefined, "read")
|
|
119
|
+
];
|
|
120
|
+
var businessSqlOperations = [
|
|
121
|
+
op("business_sql.transcript", "business_sql", "Query transcript SQL.", "TRANSCRIPT_SQL", undefined, "read"),
|
|
122
|
+
op("business_sql.maguire", "business_sql", "Query Maguire SQL.", "MAGUIRE_SQL", undefined, "read"),
|
|
123
|
+
op("business_sql.tyler", "business_sql", "Query Tyler SQL.", "TYLER_SQL", undefined, "read"),
|
|
124
|
+
op("business_sql.cfo", "business_sql", "Query CFO SQL.", "CFO_SQL", undefined, "read"),
|
|
125
|
+
op("business_sql.cfo_newsletter", "business_sql", "Query CFO newsletter SQL.", "CFO_NEWSLETTER_SQL", undefined, "read"),
|
|
126
|
+
op("business_sql.clients", "business_sql", "Query clients SQL.", "CLIENTS_SQL", undefined, "read"),
|
|
127
|
+
op("business_sql.peregrine", "business_sql", "Query Peregrine SQL.", "PEREGRINE_SQL", undefined, "read"),
|
|
128
|
+
op("business_sql.maguire_write", "business_sql", "Run Maguire write SQL.", "MAGUIRE_WRITE_SQL", undefined, "destructive")
|
|
129
|
+
];
|
|
130
|
+
var registryUtilityOperations = [
|
|
131
|
+
op("tools.search", "tools", "Search platform tools by task.", "search_tools", undefined, "read"),
|
|
132
|
+
op("tools.list", "tools", "List platform tools.", "list_platform_tools", undefined, "read", ["cli", "docs"]),
|
|
133
|
+
op("tools.schema", "tools", "Read a platform tool schema.", "get_platform_tool_schema", undefined, "read", ["cli", "docs"]),
|
|
134
|
+
op("tools.run", "tools", "Run a platform tool by name.", "execute_platform_tool_by_name", undefined, "write", ["cli", "docs"])
|
|
135
|
+
];
|
|
136
|
+
var plannedOperations = [
|
|
137
|
+
op("secret.put", "secrets", "Store a secret value; planned first-class CLI/MCP wrapper over Gem/platform secret storage.", "gem_control_plane", "store_secret", "write"),
|
|
138
|
+
op("secret.list", "secrets", "List secret metadata without values; planned first-class wrapper.", "gem_query", "inspect", "read"),
|
|
139
|
+
op("env.pull", "env", "Materialize environment metadata locally; planned CLI wrapper.", "gem_query", "inspect", "read", ["docs"]),
|
|
140
|
+
op("token.create", "tokens", "Create scoped platform or Gem deploy tokens; planned backend capability.", "TOKEN_MANAGE", "create", "write", ["docs"]),
|
|
141
|
+
op("token.list", "tokens", "List scoped platform or Gem deploy tokens; planned backend capability.", "TOKEN_MANAGE", "list", "read", ["docs"]),
|
|
142
|
+
op("token.revoke", "tokens", "Revoke a scoped platform or Gem deploy token; planned backend capability.", "TOKEN_MANAGE", "revoke", "destructive", ["docs"])
|
|
143
|
+
];
|
|
144
|
+
var platformOperations = [
|
|
145
|
+
...gemOperations,
|
|
146
|
+
...libraryOperations,
|
|
147
|
+
...sharingOperations,
|
|
148
|
+
...datasetOperations,
|
|
149
|
+
...workspaceOperations,
|
|
150
|
+
...automationOperations,
|
|
151
|
+
...webAndFileOperations,
|
|
152
|
+
...businessSqlOperations,
|
|
153
|
+
...registryUtilityOperations,
|
|
154
|
+
...plannedOperations
|
|
155
|
+
];
|
|
156
|
+
function getPlatformOperation(id) {
|
|
157
|
+
return platformOperations.find((operation) => operation.id === id);
|
|
158
|
+
}
|
|
159
|
+
function platformOperationsForSurface(surface) {
|
|
160
|
+
return platformOperations.filter((operation) => operation.surfaces.includes(surface));
|
|
161
|
+
}
|
|
162
|
+
function op(id, family, summary, backendToolName, backendOp, safety, surfaces = ALL_SURFACES) {
|
|
163
|
+
return {
|
|
164
|
+
id,
|
|
165
|
+
family,
|
|
166
|
+
summary,
|
|
167
|
+
backendToolName,
|
|
168
|
+
...backendOp ? { backendOp } : {},
|
|
169
|
+
safety,
|
|
170
|
+
surfaces
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/types.ts
|
|
175
|
+
var DEFAULT_API_URL = "https://api.uruintelligence.com";
|
|
176
|
+
var CLI_VERSION = "0.1.0";
|
|
177
|
+
var ExitCode = {
|
|
178
|
+
Ok: 0,
|
|
179
|
+
Failure: 1,
|
|
180
|
+
Cancelled: 2,
|
|
181
|
+
Conflict: 3,
|
|
182
|
+
AuthRequired: 4
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
class CliError extends Error {
|
|
186
|
+
exitCode;
|
|
187
|
+
constructor(message, options = {}) {
|
|
188
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
189
|
+
this.name = "CliError";
|
|
190
|
+
this.exitCode = options.code ?? ExitCode.Failure;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/args.ts
|
|
195
|
+
var VALUE_FLAGS = new Set([
|
|
196
|
+
"--api-url",
|
|
197
|
+
"--token",
|
|
198
|
+
"--token-file",
|
|
199
|
+
"--workspace",
|
|
200
|
+
"--cwd",
|
|
201
|
+
"--output-format"
|
|
202
|
+
]);
|
|
203
|
+
var BOOLEAN_FLAGS = new Set([
|
|
204
|
+
"--json",
|
|
205
|
+
"--help",
|
|
206
|
+
"-h",
|
|
207
|
+
"--no-input",
|
|
208
|
+
"--yes",
|
|
209
|
+
"--version",
|
|
210
|
+
"--token-stdin"
|
|
211
|
+
]);
|
|
212
|
+
function parseArgs(argv) {
|
|
213
|
+
const command = [];
|
|
214
|
+
const flags = {
|
|
215
|
+
json: false,
|
|
216
|
+
help: false,
|
|
217
|
+
noInput: false,
|
|
218
|
+
yes: false
|
|
219
|
+
};
|
|
220
|
+
let parsingFlags = true;
|
|
221
|
+
for (let index = 0;index < argv.length; index += 1) {
|
|
222
|
+
const arg = argv[index];
|
|
223
|
+
if (arg === undefined) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (parsingFlags && arg === "--") {
|
|
227
|
+
parsingFlags = false;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (parsingFlags && arg.startsWith("--")) {
|
|
231
|
+
const [flagName, inlineValue] = splitFlag(arg);
|
|
232
|
+
if (BOOLEAN_FLAGS.has(flagName)) {
|
|
233
|
+
if (inlineValue !== undefined) {
|
|
234
|
+
throw new CliError(`${flagName} does not take a value`);
|
|
235
|
+
}
|
|
236
|
+
applyBooleanFlag(flags, flagName);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (VALUE_FLAGS.has(flagName)) {
|
|
240
|
+
const value = inlineValue ?? argv[index + 1];
|
|
241
|
+
if (value === undefined || value.startsWith("--")) {
|
|
242
|
+
throw new CliError(`${flagName} requires a value`);
|
|
243
|
+
}
|
|
244
|
+
if (inlineValue === undefined) {
|
|
245
|
+
index += 1;
|
|
246
|
+
}
|
|
247
|
+
applyValueFlag(flags, flagName, value);
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (command.length === 0) {
|
|
251
|
+
throw new CliError(`Unknown flag: ${flagName}`);
|
|
252
|
+
}
|
|
253
|
+
command.push(arg);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (parsingFlags && BOOLEAN_FLAGS.has(arg)) {
|
|
257
|
+
applyBooleanFlag(flags, arg);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
command.push(arg);
|
|
261
|
+
}
|
|
262
|
+
return { flags, command };
|
|
263
|
+
}
|
|
264
|
+
function requireCommandValue(value, message) {
|
|
265
|
+
if (value === undefined || value.trim() === "") {
|
|
266
|
+
throw new CliError(message, { code: ExitCode.Failure });
|
|
267
|
+
}
|
|
268
|
+
return value;
|
|
269
|
+
}
|
|
270
|
+
function splitFlag(arg) {
|
|
271
|
+
const equals = arg.indexOf("=");
|
|
272
|
+
if (equals === -1) {
|
|
273
|
+
return [arg, undefined];
|
|
274
|
+
}
|
|
275
|
+
return [arg.slice(0, equals), arg.slice(equals + 1)];
|
|
276
|
+
}
|
|
277
|
+
function applyBooleanFlag(flags, flag) {
|
|
278
|
+
if (flag === "--json") {
|
|
279
|
+
flags.json = true;
|
|
280
|
+
flags.outputFormat = "json";
|
|
281
|
+
} else if (flag === "--help" || flag === "-h") {
|
|
282
|
+
flags.help = true;
|
|
283
|
+
} else if (flag === "--version") {
|
|
284
|
+
flags.version = true;
|
|
285
|
+
} else if (flag === "--token-stdin") {
|
|
286
|
+
flags.tokenStdin = true;
|
|
287
|
+
} else if (flag === "--no-input") {
|
|
288
|
+
flags.noInput = true;
|
|
289
|
+
} else if (flag === "--yes") {
|
|
290
|
+
flags.yes = true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function applyValueFlag(flags, flag, value) {
|
|
294
|
+
if (flag === "--api-url") {
|
|
295
|
+
flags.apiUrl = value;
|
|
296
|
+
} else if (flag === "--token") {
|
|
297
|
+
flags.token = value;
|
|
298
|
+
} else if (flag === "--token-file") {
|
|
299
|
+
flags.tokenFile = value;
|
|
300
|
+
} else if (flag === "--workspace") {
|
|
301
|
+
flags.workspace = value;
|
|
302
|
+
} else if (flag === "--cwd") {
|
|
303
|
+
flags.cwd = value;
|
|
304
|
+
} else if (flag === "--output-format") {
|
|
305
|
+
if (value !== "json" && value !== "stream-json" && value !== "text") {
|
|
306
|
+
throw new CliError("--output-format must be text, json, or stream-json");
|
|
307
|
+
}
|
|
308
|
+
flags.outputFormat = value;
|
|
309
|
+
flags.json = value === "json";
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/json.ts
|
|
314
|
+
function isRecord(value) {
|
|
315
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
316
|
+
}
|
|
317
|
+
function asOptionalString(value) {
|
|
318
|
+
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
319
|
+
}
|
|
320
|
+
function parseJsonObject(input, label) {
|
|
321
|
+
let parsed;
|
|
322
|
+
try {
|
|
323
|
+
parsed = JSON.parse(input);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
throw new Error(`${label} must be valid JSON`, { cause: error });
|
|
326
|
+
}
|
|
327
|
+
if (!isJsonObject(parsed)) {
|
|
328
|
+
throw new Error(`${label} must be a JSON object`);
|
|
329
|
+
}
|
|
330
|
+
return parsed;
|
|
331
|
+
}
|
|
332
|
+
function isJsonValue(value) {
|
|
333
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
334
|
+
return Number.isFinite(value) || typeof value !== "number";
|
|
335
|
+
}
|
|
336
|
+
if (Array.isArray(value)) {
|
|
337
|
+
return value.every(isJsonValue);
|
|
338
|
+
}
|
|
339
|
+
if (!isRecord(value)) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
return Object.values(value).every(isJsonValue);
|
|
343
|
+
}
|
|
344
|
+
function isJsonObject(value) {
|
|
345
|
+
return isRecord(value) && Object.values(value).every(isJsonValue);
|
|
346
|
+
}
|
|
347
|
+
function pickString(record, keys) {
|
|
348
|
+
for (const key of keys) {
|
|
349
|
+
const value = record[key];
|
|
350
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
351
|
+
return value;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/client.ts
|
|
358
|
+
class UruApiClient {
|
|
359
|
+
apiUrl;
|
|
360
|
+
token;
|
|
361
|
+
workspaceId;
|
|
362
|
+
fetchImpl;
|
|
363
|
+
constructor(options) {
|
|
364
|
+
this.apiUrl = normalizeApiUrl(options.apiUrl ?? DEFAULT_API_URL);
|
|
365
|
+
if (!options.token?.trim()) {
|
|
366
|
+
throw new CliError("Authentication required. Run `uru login --token <token>` or pass --token/URU_TOKEN.", { code: ExitCode.AuthRequired });
|
|
367
|
+
}
|
|
368
|
+
this.token = options.token;
|
|
369
|
+
if (options.workspaceId?.trim()) {
|
|
370
|
+
this.workspaceId = options.workspaceId;
|
|
371
|
+
}
|
|
372
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
373
|
+
}
|
|
374
|
+
async validateToken() {
|
|
375
|
+
const body = await this.request("/api/auth/api-key/validate", {
|
|
376
|
+
method: "POST"
|
|
377
|
+
});
|
|
378
|
+
if (!isRecord(body)) {
|
|
379
|
+
throw new CliError("Unexpected token validation response");
|
|
380
|
+
}
|
|
381
|
+
return normalizeWhoami(body);
|
|
382
|
+
}
|
|
383
|
+
async listTools() {
|
|
384
|
+
const body = await this.request("/api/platform/tools", { method: "GET" });
|
|
385
|
+
if (!Array.isArray(body)) {
|
|
386
|
+
throw new CliError("Unexpected tools list response");
|
|
387
|
+
}
|
|
388
|
+
return body.flatMap(normalizeTool);
|
|
389
|
+
}
|
|
390
|
+
async getToolSchema(name) {
|
|
391
|
+
return this.request(`/api/platform/tools/${encodeURIComponent(name)}/schema`, {
|
|
392
|
+
method: "GET"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async runTool(name, parameters) {
|
|
396
|
+
return this.request(`/api/platform/tools/${encodeURIComponent(name)}/execute`, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
body: { parameters }
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
async api(path, options) {
|
|
402
|
+
return this.request(path, options);
|
|
403
|
+
}
|
|
404
|
+
async request(path, options) {
|
|
405
|
+
const init = {
|
|
406
|
+
method: options.method,
|
|
407
|
+
headers: {
|
|
408
|
+
Authorization: `Bearer ${this.token}`,
|
|
409
|
+
Accept: "application/json",
|
|
410
|
+
...this.workspaceId === undefined ? {} : { "X-Uru-Workspace-Id": this.workspaceId },
|
|
411
|
+
...options.body === undefined ? {} : { "Content-Type": "application/json" }
|
|
412
|
+
},
|
|
413
|
+
...options.body === undefined ? {} : { body: JSON.stringify(options.body) }
|
|
414
|
+
};
|
|
415
|
+
const response = await this.fetchImpl(`${this.apiUrl}${path}`, init);
|
|
416
|
+
const text = await response.text();
|
|
417
|
+
const payload = parseResponseBody(text);
|
|
418
|
+
if (!response.ok) {
|
|
419
|
+
throw apiError(response.status, payload);
|
|
420
|
+
}
|
|
421
|
+
return payload;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
function normalizeApiUrl(value) {
|
|
425
|
+
const trimmed = value.trim();
|
|
426
|
+
return trimmed.endsWith("/") ? trimmed.slice(0, -1) : trimmed;
|
|
427
|
+
}
|
|
428
|
+
function parseResponseBody(text) {
|
|
429
|
+
if (text.trim() === "") {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
const parsed = JSON.parse(text);
|
|
433
|
+
if (!isJsonValue(parsed)) {
|
|
434
|
+
throw new CliError("API returned non-JSON data");
|
|
435
|
+
}
|
|
436
|
+
return parsed;
|
|
437
|
+
}
|
|
438
|
+
function apiError(status, payload) {
|
|
439
|
+
const message = errorMessage(payload) ?? `Uru API request failed with HTTP ${status}`;
|
|
440
|
+
return new CliError(message, {
|
|
441
|
+
code: status === 401 || status === 403 ? ExitCode.AuthRequired : ExitCode.Failure
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
function errorMessage(payload) {
|
|
445
|
+
if (!isRecord(payload)) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
return asOptionalString(payload["message"]) ?? asOptionalString(payload["detail"]) ?? asOptionalString(payload["error"]) ?? asOptionalString(payload["errorMessage"]);
|
|
449
|
+
}
|
|
450
|
+
function normalizeWhoami(body) {
|
|
451
|
+
const result = {
|
|
452
|
+
workspaces: Array.isArray(body["workspaces"]) ? body["workspaces"].flatMap(normalizeWorkspace) : [],
|
|
453
|
+
workspaceSelectionRequired: body["workspaceSelectionRequired"] === true
|
|
454
|
+
};
|
|
455
|
+
const userId = asOptionalString(body["userId"]);
|
|
456
|
+
if (userId !== undefined) {
|
|
457
|
+
result.userId = userId;
|
|
458
|
+
}
|
|
459
|
+
const email = asOptionalString(body["email"]);
|
|
460
|
+
if (email !== undefined) {
|
|
461
|
+
result.email = email;
|
|
462
|
+
}
|
|
463
|
+
const workspaceId = asOptionalString(body["workspaceId"]);
|
|
464
|
+
if (workspaceId !== undefined) {
|
|
465
|
+
result.workspaceId = workspaceId;
|
|
466
|
+
}
|
|
467
|
+
if (isJsonObject(body["workspace"])) {
|
|
468
|
+
result.workspace = body["workspace"];
|
|
469
|
+
}
|
|
470
|
+
const workspaceSelectionMessage = asOptionalString(body["workspaceSelectionMessage"]);
|
|
471
|
+
if (workspaceSelectionMessage !== undefined) {
|
|
472
|
+
result.workspaceSelectionMessage = workspaceSelectionMessage;
|
|
473
|
+
}
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
function normalizeWorkspace(value) {
|
|
477
|
+
if (!isRecord(value)) {
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
const nestedWorkspace = isRecord(value["workspaces"]) ? value["workspaces"] : undefined;
|
|
481
|
+
const source = nestedWorkspace ?? value;
|
|
482
|
+
const id = pickString(source, ["id", "workspaceId", "workspace_id"]);
|
|
483
|
+
if (id === undefined) {
|
|
484
|
+
return [];
|
|
485
|
+
}
|
|
486
|
+
const workspace = { id };
|
|
487
|
+
const name = pickString(source, ["name", "workspaceName", "workspace_name"]);
|
|
488
|
+
if (name !== undefined) {
|
|
489
|
+
workspace.name = name;
|
|
490
|
+
}
|
|
491
|
+
const slug = pickString(source, ["slug", "workspaceSlug", "workspace_slug"]);
|
|
492
|
+
if (slug !== undefined) {
|
|
493
|
+
workspace.slug = slug;
|
|
494
|
+
}
|
|
495
|
+
const role = pickString(value, ["role", "userRole", "user_role"]);
|
|
496
|
+
if (role !== undefined) {
|
|
497
|
+
workspace.role = role;
|
|
498
|
+
}
|
|
499
|
+
return [workspace];
|
|
500
|
+
}
|
|
501
|
+
function normalizeTool(value) {
|
|
502
|
+
if (!isJsonObject(value)) {
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
const name = asOptionalString(value["name"]);
|
|
506
|
+
if (name === undefined) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
const tool = { name, raw: value };
|
|
510
|
+
const description = asOptionalString(value["description"]);
|
|
511
|
+
if (description !== undefined) {
|
|
512
|
+
tool.description = description;
|
|
513
|
+
}
|
|
514
|
+
if (isJsonObject(value["inputSchema"])) {
|
|
515
|
+
tool.inputSchema = value["inputSchema"];
|
|
516
|
+
}
|
|
517
|
+
if (isJsonObject(value["outputSchema"])) {
|
|
518
|
+
tool.outputSchema = value["outputSchema"];
|
|
519
|
+
}
|
|
520
|
+
return [tool];
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/config.ts
|
|
524
|
+
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
525
|
+
import { constants } from "node:fs";
|
|
526
|
+
import { homedir } from "node:os";
|
|
527
|
+
import { dirname, join } from "node:path";
|
|
528
|
+
function resolveConfigHome(env = process.env) {
|
|
529
|
+
const explicit = env["URU_CONFIG_HOME"]?.trim();
|
|
530
|
+
if (explicit) {
|
|
531
|
+
return explicit;
|
|
532
|
+
}
|
|
533
|
+
const xdg = env["XDG_CONFIG_HOME"]?.trim();
|
|
534
|
+
if (xdg) {
|
|
535
|
+
return join(xdg, "uru");
|
|
536
|
+
}
|
|
537
|
+
return join(env["HOME"]?.trim() || homedir(), ".config", "uru");
|
|
538
|
+
}
|
|
539
|
+
function configPath(env = process.env) {
|
|
540
|
+
return join(resolveConfigHome(env), "config.json");
|
|
541
|
+
}
|
|
542
|
+
function createConfigStore(path = configPath()) {
|
|
543
|
+
return {
|
|
544
|
+
path,
|
|
545
|
+
async read() {
|
|
546
|
+
try {
|
|
547
|
+
const raw = await readFile(path, "utf8");
|
|
548
|
+
const parsed = JSON.parse(raw);
|
|
549
|
+
return sanitizeConfig(parsed);
|
|
550
|
+
} catch (error) {
|
|
551
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
552
|
+
return {};
|
|
553
|
+
}
|
|
554
|
+
throw error;
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
async write(config) {
|
|
558
|
+
await mkdir(dirname(path), { recursive: true });
|
|
559
|
+
const body = `${JSON.stringify(sanitizeConfig(config), null, 2)}
|
|
560
|
+
`;
|
|
561
|
+
await writeFile(path, body, {
|
|
562
|
+
mode: constants.S_IRUSR | constants.S_IWUSR
|
|
563
|
+
});
|
|
564
|
+
},
|
|
565
|
+
async clear() {
|
|
566
|
+
await rm(path, { force: true });
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
function sanitizeConfig(value) {
|
|
571
|
+
if (!isRecord(value)) {
|
|
572
|
+
return {};
|
|
573
|
+
}
|
|
574
|
+
const config = {};
|
|
575
|
+
for (const key of ["apiUrl", "token", "workspace"]) {
|
|
576
|
+
const item = value[key];
|
|
577
|
+
if (typeof item === "string" && item.trim() !== "") {
|
|
578
|
+
config[key] = item;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return config;
|
|
582
|
+
}
|
|
583
|
+
function isNodeError(error) {
|
|
584
|
+
return error instanceof Error && "code" in error;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/cli.ts
|
|
588
|
+
import { dirname as dirname3 } from "node:path";
|
|
589
|
+
|
|
590
|
+
// src/project.ts
|
|
591
|
+
import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
|
|
592
|
+
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
593
|
+
function projectDir(cwd) {
|
|
594
|
+
return join2(resolve(cwd), ".uru");
|
|
595
|
+
}
|
|
596
|
+
function projectPath(cwd) {
|
|
597
|
+
return join2(projectDir(cwd), "project.json");
|
|
598
|
+
}
|
|
599
|
+
async function readLinkedProject(cwd) {
|
|
600
|
+
try {
|
|
601
|
+
const parsed = JSON.parse(await readFile2(projectPath(cwd), "utf8"));
|
|
602
|
+
return sanitizeLinkedProject(parsed);
|
|
603
|
+
} catch (error) {
|
|
604
|
+
if (isNodeError2(error) && error.code === "ENOENT") {
|
|
605
|
+
return {};
|
|
606
|
+
}
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async function writeLinkedProject(cwd, project) {
|
|
611
|
+
const sanitized = sanitizeLinkedProject(project);
|
|
612
|
+
if (sanitized.workspaceId === undefined && sanitized.gemId === undefined && sanitized.libraryPath === undefined) {
|
|
613
|
+
throw new CliError("Nothing to link. Pass --workspace and --gem or --path.");
|
|
614
|
+
}
|
|
615
|
+
const path = projectPath(cwd);
|
|
616
|
+
await mkdir2(dirname2(path), { recursive: true });
|
|
617
|
+
await writeFile2(path, `${JSON.stringify(sanitized, null, 2)}
|
|
618
|
+
`, { mode: 384 });
|
|
619
|
+
}
|
|
620
|
+
async function unlinkProject(cwd) {
|
|
621
|
+
await rm2(projectPath(cwd), { force: true });
|
|
622
|
+
}
|
|
623
|
+
function sanitizeLinkedProject(value) {
|
|
624
|
+
if (!isRecord(value)) {
|
|
625
|
+
return {};
|
|
626
|
+
}
|
|
627
|
+
const project = {};
|
|
628
|
+
for (const [source, target] of [
|
|
629
|
+
["workspaceId", "workspaceId"],
|
|
630
|
+
["workspace_id", "workspaceId"],
|
|
631
|
+
["gemId", "gemId"],
|
|
632
|
+
["gem_id", "gemId"],
|
|
633
|
+
["libraryPath", "libraryPath"],
|
|
634
|
+
["library_path", "libraryPath"]
|
|
635
|
+
]) {
|
|
636
|
+
const raw = value[source];
|
|
637
|
+
if (typeof raw === "string" && raw.trim() !== "") {
|
|
638
|
+
project[target] = raw.trim();
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return project;
|
|
642
|
+
}
|
|
643
|
+
function isNodeError2(error) {
|
|
644
|
+
return error instanceof Error && "code" in error;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/cli-helpers.ts
|
|
648
|
+
function parseApiArgs(args) {
|
|
649
|
+
let method = "GET";
|
|
650
|
+
let path;
|
|
651
|
+
let body;
|
|
652
|
+
const fields = {};
|
|
653
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
654
|
+
const arg = args[index];
|
|
655
|
+
if (arg === undefined) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (arg === "-X" || arg === "--method") {
|
|
659
|
+
method = requireCommandValue(args[index + 1], `${arg} requires a method`).toUpperCase();
|
|
660
|
+
index += 1;
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
if (arg.startsWith("-X") && arg.length > 2) {
|
|
664
|
+
method = arg.slice(2).toUpperCase();
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (arg === "--body-json" || arg === "--data-json") {
|
|
668
|
+
body = parseParamsJson(requireCommandValue(args[index + 1], `${arg} requires a JSON object`), arg);
|
|
669
|
+
index += 1;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (arg === "-F" || arg === "--field") {
|
|
673
|
+
addApiField(fields, requireCommandValue(args[index + 1], `${arg} requires key=value`));
|
|
674
|
+
index += 1;
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (arg.startsWith("-F") && arg.length > 2) {
|
|
678
|
+
addApiField(fields, arg.slice(2));
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (path === undefined) {
|
|
682
|
+
path = normalizeApiPath(arg);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
throw new CliError(`Unexpected api argument: ${arg}`);
|
|
686
|
+
}
|
|
687
|
+
if (Object.keys(fields).length > 0) {
|
|
688
|
+
body = { ...body ?? {}, ...fields };
|
|
689
|
+
if (method === "GET") {
|
|
690
|
+
method = "POST";
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return {
|
|
694
|
+
path: requireCommandValue(path, "Usage: uru api <path> [-X POST] [-F k=v] [--body-json {...}]"),
|
|
695
|
+
method,
|
|
696
|
+
...body === undefined ? {} : { body }
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function normalizeApiPath(value) {
|
|
700
|
+
const trimmed = value.trim();
|
|
701
|
+
if (trimmed.startsWith("/")) {
|
|
702
|
+
return trimmed;
|
|
703
|
+
}
|
|
704
|
+
return `/api/${trimmed.replace(/^api\//, "")}`;
|
|
705
|
+
}
|
|
706
|
+
function addApiField(target, raw) {
|
|
707
|
+
const equals = raw.indexOf("=");
|
|
708
|
+
if (equals <= 0) {
|
|
709
|
+
throw new CliError("-F/--field requires key=value");
|
|
710
|
+
}
|
|
711
|
+
target[raw.slice(0, equals)] = raw.slice(equals + 1);
|
|
712
|
+
}
|
|
713
|
+
function paramsJson(args) {
|
|
714
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
715
|
+
const arg = args[index];
|
|
716
|
+
if (arg === "--params-json" || arg === "--args-json") {
|
|
717
|
+
const value = args[index + 1];
|
|
718
|
+
if (value === undefined) {
|
|
719
|
+
throw new CliError(`${arg} requires a JSON object`);
|
|
720
|
+
}
|
|
721
|
+
return parseParamsJson(value, arg);
|
|
722
|
+
}
|
|
723
|
+
if (arg?.startsWith("--params-json=")) {
|
|
724
|
+
return parseParamsJson(arg.slice("--params-json=".length), "--params-json");
|
|
725
|
+
}
|
|
726
|
+
if (arg?.startsWith("--args-json=")) {
|
|
727
|
+
return parseParamsJson(arg.slice("--args-json=".length), "--args-json");
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return {};
|
|
731
|
+
}
|
|
732
|
+
function parseParamsJson(value, label) {
|
|
733
|
+
try {
|
|
734
|
+
return parseJsonObject(value, label);
|
|
735
|
+
} catch (error) {
|
|
736
|
+
throw new CliError(error instanceof Error ? error.message : `Invalid ${label}`, {
|
|
737
|
+
cause: error
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function parseLocalFlags(args) {
|
|
742
|
+
const values = {};
|
|
743
|
+
const booleans = new Set;
|
|
744
|
+
const positionals = [];
|
|
745
|
+
for (let index = 0;index < args.length; index += 1) {
|
|
746
|
+
const arg = args[index];
|
|
747
|
+
if (arg === undefined) {
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (!arg.startsWith("--")) {
|
|
751
|
+
positionals.push(arg);
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const equals = arg.indexOf("=");
|
|
755
|
+
if (equals > -1) {
|
|
756
|
+
values[arg.slice(0, equals)] = arg.slice(equals + 1);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
const next = args[index + 1];
|
|
760
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
761
|
+
values[arg] = next;
|
|
762
|
+
index += 1;
|
|
763
|
+
} else {
|
|
764
|
+
booleans.add(arg);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return { values, booleans, positionals };
|
|
768
|
+
}
|
|
769
|
+
function integerFlag(flags, name, fallback) {
|
|
770
|
+
const raw = flags.values[name];
|
|
771
|
+
if (raw === undefined) {
|
|
772
|
+
return fallback;
|
|
773
|
+
}
|
|
774
|
+
const value = Number.parseInt(raw, 10);
|
|
775
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
776
|
+
throw new CliError(`${name} must be a non-negative integer`);
|
|
777
|
+
}
|
|
778
|
+
return value;
|
|
779
|
+
}
|
|
780
|
+
function copyStringFlag(flags, target, flagName, paramName) {
|
|
781
|
+
const value = flags.values[flagName];
|
|
782
|
+
if (value !== undefined) {
|
|
783
|
+
target[paramName] = value;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function isJsonRequested(argv) {
|
|
787
|
+
return argv.includes("--json") || argv.includes("--output-format=json");
|
|
788
|
+
}
|
|
789
|
+
function nonEmptyString(value) {
|
|
790
|
+
if (value === undefined) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const trimmed = value.trim();
|
|
794
|
+
return trimmed === "" ? undefined : trimmed;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/output.ts
|
|
798
|
+
function writeJson(io, value) {
|
|
799
|
+
io.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
800
|
+
`);
|
|
801
|
+
}
|
|
802
|
+
function writeStreamJson(io, event, value) {
|
|
803
|
+
io.stdout.write(`${JSON.stringify({ event, ...objectPayload(value) })}
|
|
804
|
+
`);
|
|
805
|
+
}
|
|
806
|
+
function writeText(io, value) {
|
|
807
|
+
io.stdout.write(value.endsWith(`
|
|
808
|
+
`) ? value : `${value}
|
|
809
|
+
`);
|
|
810
|
+
}
|
|
811
|
+
function formatWhoami(result) {
|
|
812
|
+
const lines = [`User: ${result.email ?? result.userId ?? "unknown"}`];
|
|
813
|
+
if (result.workspaceId !== undefined) {
|
|
814
|
+
lines.push(`Workspace: ${result.workspaceId}`);
|
|
815
|
+
} else if (result.workspaceSelectionRequired) {
|
|
816
|
+
lines.push("Workspace: not selected");
|
|
817
|
+
}
|
|
818
|
+
if (result.workspaces.length > 0) {
|
|
819
|
+
lines.push(`Workspaces: ${result.workspaces.length}`);
|
|
820
|
+
}
|
|
821
|
+
return lines.join(`
|
|
822
|
+
`);
|
|
823
|
+
}
|
|
824
|
+
function formatWorkspaces(workspaces) {
|
|
825
|
+
if (workspaces.length === 0) {
|
|
826
|
+
return "No workspaces found";
|
|
827
|
+
}
|
|
828
|
+
return workspaces.map((workspace) => {
|
|
829
|
+
const label = workspace.name ?? workspace.slug ?? workspace.id;
|
|
830
|
+
const suffix = workspace.role === undefined ? "" : ` (${workspace.role})`;
|
|
831
|
+
return `${workspace.id} ${label}${suffix}`;
|
|
832
|
+
}).join(`
|
|
833
|
+
`);
|
|
834
|
+
}
|
|
835
|
+
function formatTools(tools) {
|
|
836
|
+
if (tools.length === 0) {
|
|
837
|
+
return "No tools found";
|
|
838
|
+
}
|
|
839
|
+
return tools.map((tool) => {
|
|
840
|
+
const description = tool.description === undefined ? "" : ` ${tool.description}`;
|
|
841
|
+
return `${tool.name}${description}`;
|
|
842
|
+
}).join(`
|
|
843
|
+
`);
|
|
844
|
+
}
|
|
845
|
+
function formatOperations(family) {
|
|
846
|
+
const operations = platformOperationsForSurface("cli").filter((operation) => family === undefined || operation.family === family);
|
|
847
|
+
if (operations.length === 0) {
|
|
848
|
+
return family === undefined ? "No CLI operations found" : `No CLI operations found for family ${family}`;
|
|
849
|
+
}
|
|
850
|
+
return operations.map((operation) => `${operation.id} ${operation.backendToolName}${operation.backendOp === undefined ? "" : ` op=${operation.backendOp}`} ${operation.summary}`).join(`
|
|
851
|
+
`);
|
|
852
|
+
}
|
|
853
|
+
function helpText() {
|
|
854
|
+
return `Usage: uru [global flags] <command>
|
|
855
|
+
|
|
856
|
+
CORE
|
|
857
|
+
uru login --token <token> [--api-url <url>] Store token auth locally
|
|
858
|
+
uru logout Remove local CLI auth config
|
|
859
|
+
uru whoami [--json] Show authenticated user context
|
|
860
|
+
uru switch [workspace-id] List or set current workspace
|
|
861
|
+
uru link --path <library/...gem> [--gem <id>] Link cwd to a Gem (.uru/project.json)
|
|
862
|
+
uru unlink Remove cwd Gem link
|
|
863
|
+
|
|
864
|
+
GEMS
|
|
865
|
+
uru init <library/name.gem> Initialize a Gem project
|
|
866
|
+
uru deploy [library/name.gem] [--no-publish] Server-side build/deploy/publish
|
|
867
|
+
uru publish [library/name.gem] Publish the current Gem target
|
|
868
|
+
uru status [library/name.gem] Deployment status/checks/log summary
|
|
869
|
+
uru logs [library/name.gem] [--limit 100] Runtime logs
|
|
870
|
+
uru open [library/name.gem] Print first published URL
|
|
871
|
+
uru rollback <deployment-id> --link-id <id> Repoint a link to a deployment
|
|
872
|
+
uru gems inspect|validate|build [library/name.gem]
|
|
873
|
+
uru gems read <library/name.gem> <file>
|
|
874
|
+
uru gems write <library/name.gem> <file> --file ./local.html
|
|
875
|
+
|
|
876
|
+
LIBRARY / DATASETS / TOOLS
|
|
877
|
+
uru library ls [path] List Library items
|
|
878
|
+
uru library search <query> Search Library items
|
|
879
|
+
uru library mkdir <path> Create a Library folder
|
|
880
|
+
# Prompts, personas, contexts: use typed Library paths such as library/Prompts/Foo.prompt.md
|
|
881
|
+
uru datasets query <dataset-slug> [--limit 50] Query dataset rows
|
|
882
|
+
uru tools ls|schema <name>|run <name> Full platform-tool parity
|
|
883
|
+
uru operations ls [--family gems] Print generated operation registry
|
|
884
|
+
uru operations run <operation-id> --params-json Run any registry-backed operation
|
|
885
|
+
uru workspace|sharing|automations|web <op> Registry families for full-platform parity
|
|
886
|
+
uru mcp config|install Print/write MCP client config
|
|
887
|
+
uru api <path> [-X POST] [-F k=v] Raw authenticated API call
|
|
888
|
+
uru docs [query] Search local operation docs
|
|
889
|
+
|
|
890
|
+
FLAGS
|
|
891
|
+
--json Print JSON to stdout
|
|
892
|
+
--output-format text|json|stream-json Machine output mode
|
|
893
|
+
--api-url <url> Override API URL
|
|
894
|
+
--token <token> Override auth token for this invocation
|
|
895
|
+
--token-file <path> Read auth token from a local file
|
|
896
|
+
--token-stdin Read auth token from stdin
|
|
897
|
+
--workspace <id> Select workspace for scoped commands
|
|
898
|
+
--cwd <path> Resolve .uru link from this directory
|
|
899
|
+
--no-input Never prompt; fail if data is missing
|
|
900
|
+
--yes Confirm destructive operations
|
|
901
|
+
-h, --help Show help
|
|
902
|
+
`;
|
|
903
|
+
}
|
|
904
|
+
function objectPayload(value) {
|
|
905
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
906
|
+
return value;
|
|
907
|
+
}
|
|
908
|
+
return { value };
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/cli.ts
|
|
912
|
+
var GENERIC_FAMILY_COMMANDS = {
|
|
913
|
+
workspace: "workspace",
|
|
914
|
+
sharing: "sharing",
|
|
915
|
+
automations: "automations",
|
|
916
|
+
automation: "automations",
|
|
917
|
+
web: "web",
|
|
918
|
+
files: "files",
|
|
919
|
+
file: "files",
|
|
920
|
+
"business-sql": "business_sql",
|
|
921
|
+
business_sql: "business_sql",
|
|
922
|
+
secrets: "secrets",
|
|
923
|
+
secret: "secrets",
|
|
924
|
+
env: "env",
|
|
925
|
+
tokens: "tokens",
|
|
926
|
+
token: "tokens"
|
|
927
|
+
};
|
|
928
|
+
async function runCli(options) {
|
|
929
|
+
const io = options.io ?? { stdout: process.stdout, stderr: process.stderr };
|
|
930
|
+
try {
|
|
931
|
+
const parsed = parseArgs(options.argv);
|
|
932
|
+
if (parsed.flags.version) {
|
|
933
|
+
writeText(io, CLI_VERSION);
|
|
934
|
+
return ExitCode.Ok;
|
|
935
|
+
}
|
|
936
|
+
if (parsed.flags.help || parsed.command.length === 0) {
|
|
937
|
+
writeText(io, helpText());
|
|
938
|
+
return ExitCode.Ok;
|
|
939
|
+
}
|
|
940
|
+
const store = options.configStore ?? createConfigStore();
|
|
941
|
+
const config = await store.read();
|
|
942
|
+
const flagToken = await resolveFlagToken(parsed.flags, options.stdin);
|
|
943
|
+
const envToken = nonEmptyString(options.env?.["URU_TOKEN"]);
|
|
944
|
+
const envApiUrl = nonEmptyString(options.env?.["URU_API_BASE"]) ?? nonEmptyString(options.env?.["URU_API_URL"]);
|
|
945
|
+
const envWorkspace = nonEmptyString(options.env?.["URU_WORKSPACE_ID"]);
|
|
946
|
+
const apiUrl = parsed.flags.apiUrl ?? envApiUrl ?? config.apiUrl ?? DEFAULT_API_URL;
|
|
947
|
+
const token = flagToken ?? envToken ?? config.token;
|
|
948
|
+
const cwd = parsed.flags.cwd ?? options.env?.["PWD"] ?? process.cwd();
|
|
949
|
+
const linkedProject = await readLinkedProject(cwd);
|
|
950
|
+
const workspace = parsed.flags.workspace ?? envWorkspace ?? linkedProject.workspaceId ?? config.workspace;
|
|
951
|
+
const outputFormat = parsed.flags.outputFormat ?? (parsed.flags.json ? "json" : "text");
|
|
952
|
+
const ctx = {
|
|
953
|
+
io,
|
|
954
|
+
store,
|
|
955
|
+
config,
|
|
956
|
+
apiUrl,
|
|
957
|
+
...token !== undefined ? { token } : {},
|
|
958
|
+
...workspace !== undefined ? { workspace } : {},
|
|
959
|
+
cwd,
|
|
960
|
+
json: outputFormat === "json",
|
|
961
|
+
outputFormat,
|
|
962
|
+
noInput: parsed.flags.noInput,
|
|
963
|
+
yes: parsed.flags.yes,
|
|
964
|
+
...options.fetch !== undefined ? { fetch: options.fetch } : {}
|
|
965
|
+
};
|
|
966
|
+
await dispatch(ctx, parsed.command, linkedProject);
|
|
967
|
+
return ExitCode.Ok;
|
|
968
|
+
} catch (error) {
|
|
969
|
+
const cliError = normalizeError(error);
|
|
970
|
+
if (isJsonRequested(options.argv)) {
|
|
971
|
+
io.stderr.write(`${JSON.stringify({ error: cliError.message, exitCode: cliError.exitCode })}
|
|
972
|
+
`);
|
|
973
|
+
} else {
|
|
974
|
+
io.stderr.write(`Error: ${cliError.message}
|
|
975
|
+
`);
|
|
976
|
+
}
|
|
977
|
+
return cliError.exitCode;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
async function resolveFlagToken(flags, stdin) {
|
|
981
|
+
if (flags.token !== undefined) {
|
|
982
|
+
return flags.token;
|
|
983
|
+
}
|
|
984
|
+
if (flags.tokenFile !== undefined) {
|
|
985
|
+
const token = (await readFile3(flags.tokenFile, "utf8")).trim();
|
|
986
|
+
if (token === "") {
|
|
987
|
+
throw new CliError(`Token file is empty: ${flags.tokenFile}`, {
|
|
988
|
+
code: ExitCode.AuthRequired
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
return token;
|
|
992
|
+
}
|
|
993
|
+
if (flags.tokenStdin === true) {
|
|
994
|
+
const input = stdin ?? process.stdin;
|
|
995
|
+
if ("isTTY" in input && input.isTTY === true) {
|
|
996
|
+
throw new CliError("Refusing to read token from an interactive TTY. Pipe a token to --token-stdin or use --token-file.", {
|
|
997
|
+
code: ExitCode.AuthRequired
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
const token = (await readAllStdin(input)).trim();
|
|
1001
|
+
if (token === "") {
|
|
1002
|
+
throw new CliError("No token received on stdin.", {
|
|
1003
|
+
code: ExitCode.AuthRequired
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
return token;
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
async function readAllStdin(input) {
|
|
1011
|
+
const chunks = [];
|
|
1012
|
+
for await (const chunk of input) {
|
|
1013
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
1014
|
+
}
|
|
1015
|
+
return chunks.join("");
|
|
1016
|
+
}
|
|
1017
|
+
async function dispatch(ctx, command, linkedProject) {
|
|
1018
|
+
const [head, sub, third] = command;
|
|
1019
|
+
if (head === "login") {
|
|
1020
|
+
await login(ctx);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (head === "logout") {
|
|
1024
|
+
await ctx.store.clear();
|
|
1025
|
+
emit(ctx, { ok: true }, "Logged out");
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (head === "whoami") {
|
|
1029
|
+
const result = await client(ctx).validateToken();
|
|
1030
|
+
ctx.json ? writeJson(ctx.io, result) : writeText(ctx.io, formatWhoami(result));
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
if (head === "switch") {
|
|
1034
|
+
if (sub === undefined) {
|
|
1035
|
+
const identity = await client(ctx).validateToken();
|
|
1036
|
+
ctx.json ? writeJson(ctx.io, identity.workspaces) : writeText(ctx.io, formatWorkspaces(identity.workspaces));
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
await switchWorkspace(ctx, requireCommandValue(sub, "Usage: uru switch <workspace-id>"));
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (head === "link") {
|
|
1043
|
+
await linkProject(ctx, command.slice(1));
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
if (head === "unlink") {
|
|
1047
|
+
await unlinkProject(ctx.cwd);
|
|
1048
|
+
emit(ctx, { ok: true }, "Unlinked");
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (head === "operations") {
|
|
1052
|
+
await dispatchOperations(ctx, sub, command.slice(2));
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (head === "docs" || head === "help") {
|
|
1056
|
+
dispatchDocs(ctx, command.slice(1));
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
if (head === "api") {
|
|
1060
|
+
await dispatchApi(ctx, command.slice(1));
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (head === "mcp") {
|
|
1064
|
+
await dispatchMcp(ctx, sub, command.slice(2));
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
if (head === "tools") {
|
|
1068
|
+
await dispatchTools(ctx, sub, third, command);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
if (head === "init") {
|
|
1072
|
+
await withWorkspace(ctx);
|
|
1073
|
+
await gemInit(ctx, command.slice(1));
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
if (head === "deploy") {
|
|
1077
|
+
await withWorkspace(ctx);
|
|
1078
|
+
await gemDeploy(ctx, command.slice(1), linkedProject);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
if (head === "publish") {
|
|
1082
|
+
await withWorkspace(ctx);
|
|
1083
|
+
await emitGemTool(ctx, "gem.publish", {
|
|
1084
|
+
path: gemPathFromArgs(command.slice(1), linkedProject)
|
|
1085
|
+
});
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
if (head === "status") {
|
|
1089
|
+
await withWorkspace(ctx);
|
|
1090
|
+
await emitGemTool(ctx, "gem.status", {
|
|
1091
|
+
path: gemPathFromArgs(command.slice(1), linkedProject)
|
|
1092
|
+
});
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (head === "logs") {
|
|
1096
|
+
await withWorkspace(ctx);
|
|
1097
|
+
await gemLogs(ctx, command.slice(1), linkedProject);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (head === "open") {
|
|
1101
|
+
await withWorkspace(ctx);
|
|
1102
|
+
await gemOpen(ctx, command.slice(1), linkedProject);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (head === "rollback") {
|
|
1106
|
+
await withWorkspace(ctx);
|
|
1107
|
+
await gemRollback(ctx, command.slice(1), linkedProject);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
if (head === "gems") {
|
|
1111
|
+
await withWorkspace(ctx);
|
|
1112
|
+
await dispatchGems(ctx, sub, command.slice(2), linkedProject);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (head === "library") {
|
|
1116
|
+
await withWorkspace(ctx);
|
|
1117
|
+
await dispatchLibrary(ctx, sub, command.slice(2));
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
if (head === "datasets") {
|
|
1121
|
+
await withWorkspace(ctx);
|
|
1122
|
+
await dispatchDatasets(ctx, sub, command.slice(2));
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
const family = head === undefined ? undefined : GENERIC_FAMILY_COMMANDS[head];
|
|
1126
|
+
if (family !== undefined) {
|
|
1127
|
+
await withWorkspace(ctx);
|
|
1128
|
+
await dispatchRegistryFamily(ctx, family, sub, command.slice(2));
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
throw new CliError(`Unknown command: ${command.join(" ")}`);
|
|
1132
|
+
}
|
|
1133
|
+
function dispatchDocs(ctx, args) {
|
|
1134
|
+
const query = args.join(" ").trim();
|
|
1135
|
+
if (query === "") {
|
|
1136
|
+
writeText(ctx.io, helpText());
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const matches = formatOperations().split(`
|
|
1140
|
+
`).filter((line) => line.toLowerCase().includes(query.toLowerCase())).join(`
|
|
1141
|
+
`);
|
|
1142
|
+
writeText(ctx.io, matches || `No operation docs matched ${query}`);
|
|
1143
|
+
}
|
|
1144
|
+
async function dispatchApi(ctx, args) {
|
|
1145
|
+
await withWorkspace(ctx);
|
|
1146
|
+
const parsed = parseApiArgs(args);
|
|
1147
|
+
const result = await client(ctx).api(parsed.path, {
|
|
1148
|
+
method: parsed.method,
|
|
1149
|
+
...parsed.body === undefined ? {} : { body: parsed.body }
|
|
1150
|
+
});
|
|
1151
|
+
if (ctx.outputFormat === "stream-json") {
|
|
1152
|
+
writeStreamJson(ctx.io, "result", result);
|
|
1153
|
+
} else {
|
|
1154
|
+
writeJson(ctx.io, result);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
async function dispatchMcp(ctx, sub, args) {
|
|
1158
|
+
if (sub !== "config" && sub !== "install") {
|
|
1159
|
+
throw new CliError("Usage: uru mcp config|install [--api-key <key>] [--path <path>]");
|
|
1160
|
+
}
|
|
1161
|
+
const flags = parseLocalFlags(args);
|
|
1162
|
+
const config = mcpServerConfig({
|
|
1163
|
+
apiKey: flags.values["--api-key"] ?? ctx.token,
|
|
1164
|
+
serverName: flags.values["--server-name"] ?? "uru",
|
|
1165
|
+
packageName: flags.values["--package"] ?? "uru-mcp@latest"
|
|
1166
|
+
});
|
|
1167
|
+
if (sub === "config") {
|
|
1168
|
+
writeJson(ctx.io, config);
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const path = requireCommandValue(flags.values["--path"], "Usage: uru mcp install --path <mcp-config.json> [--api-key <key>]");
|
|
1172
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
1173
|
+
await writeFile3(path, `${JSON.stringify(config, null, 2)}
|
|
1174
|
+
`, { mode: 384 });
|
|
1175
|
+
emit(ctx, { ok: true, path }, `Wrote MCP config to ${path}`);
|
|
1176
|
+
}
|
|
1177
|
+
function mcpServerConfig(args) {
|
|
1178
|
+
const apiKey = requireCommandValue(args.apiKey, "MCP config requires --api-key, --token, URU_TOKEN, or a logged-in CLI token.");
|
|
1179
|
+
return {
|
|
1180
|
+
mcpServers: {
|
|
1181
|
+
[args.serverName]: {
|
|
1182
|
+
command: "npx",
|
|
1183
|
+
args: [args.packageName],
|
|
1184
|
+
env: { URU_API_KEY: apiKey }
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
async function dispatchOperations(ctx, sub, args) {
|
|
1190
|
+
if (sub === "ls" || sub === "list") {
|
|
1191
|
+
const flags = parseLocalFlags(args);
|
|
1192
|
+
const family = flags.values["--family"];
|
|
1193
|
+
writeText(ctx.io, formatOperations(family));
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
if (sub === "run") {
|
|
1197
|
+
await withWorkspace(ctx);
|
|
1198
|
+
const operationId = requireCommandValue(args[0], "Usage: uru operations run <operation-id> --params-json '{...}'");
|
|
1199
|
+
await emitTool(ctx, operationId, paramsJson(args.slice(1)));
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
throw new CliError("Usage: uru operations ls [--family <family>] | run <operation-id> --params-json {...}");
|
|
1203
|
+
}
|
|
1204
|
+
async function dispatchRegistryFamily(ctx, family, sub, args) {
|
|
1205
|
+
const verb = requireCommandValue(sub, `Usage: uru ${family.replace("_", "-")} <operation> --params-json '{...}'`).replace(/-/g, "_");
|
|
1206
|
+
const operation = platformOperationsForSurface("cli").find((candidate) => candidate.family === family && candidate.id === `${familyPrefix(family)}.${verb}`);
|
|
1207
|
+
if (operation === undefined) {
|
|
1208
|
+
throw new CliError(`Unknown ${family} operation: ${verb}. Run \`uru operations ls --family ${family}\`.`);
|
|
1209
|
+
}
|
|
1210
|
+
await emitTool(ctx, operation.id, paramsJson(args));
|
|
1211
|
+
}
|
|
1212
|
+
function familyPrefix(family) {
|
|
1213
|
+
if (family === "automations") {
|
|
1214
|
+
return "automation";
|
|
1215
|
+
}
|
|
1216
|
+
if (family === "datasets") {
|
|
1217
|
+
return "dataset";
|
|
1218
|
+
}
|
|
1219
|
+
if (family === "files") {
|
|
1220
|
+
return "file";
|
|
1221
|
+
}
|
|
1222
|
+
if (family === "secrets") {
|
|
1223
|
+
return "secret";
|
|
1224
|
+
}
|
|
1225
|
+
if (family === "tokens") {
|
|
1226
|
+
return "token";
|
|
1227
|
+
}
|
|
1228
|
+
return family;
|
|
1229
|
+
}
|
|
1230
|
+
async function dispatchTools(ctx, sub, third, command) {
|
|
1231
|
+
if (sub === "ls") {
|
|
1232
|
+
await withWorkspace(ctx);
|
|
1233
|
+
const tools = await client(ctx).listTools();
|
|
1234
|
+
ctx.json ? writeJson(ctx.io, tools.map((tool) => tool.raw)) : writeText(ctx.io, formatTools(tools));
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
if (sub === "schema") {
|
|
1238
|
+
await withWorkspace(ctx);
|
|
1239
|
+
const name = requireCommandValue(third, "Usage: uru tools schema <name>");
|
|
1240
|
+
writeJson(ctx.io, await client(ctx).getToolSchema(name));
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (sub === "run") {
|
|
1244
|
+
await withWorkspace(ctx);
|
|
1245
|
+
const name = requireCommandValue(third, "Usage: uru tools run <name> --params-json '{...}'");
|
|
1246
|
+
const params = paramsJson(command.slice(3));
|
|
1247
|
+
writeJson(ctx.io, await client(ctx).runTool(name, params));
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
throw new CliError("Usage: uru tools ls|schema|run");
|
|
1251
|
+
}
|
|
1252
|
+
async function dispatchGems(ctx, sub, args, linkedProject) {
|
|
1253
|
+
const gemPath = () => gemPathFromArgs(args, linkedProject);
|
|
1254
|
+
if (sub === "inspect") {
|
|
1255
|
+
await emitGemTool(ctx, "gem.inspect", { path: gemPath() });
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
if (sub === "validate") {
|
|
1259
|
+
await emitGemTool(ctx, "gem.validate", { path: gemPath() });
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
if (sub === "build") {
|
|
1263
|
+
const flags = parseLocalFlags(args);
|
|
1264
|
+
await emitGemTool(ctx, "gem.build", {
|
|
1265
|
+
path: gemPathFromArgs(flags.positionals, linkedProject),
|
|
1266
|
+
...flags.booleans.has("--force") ? { force: true } : {}
|
|
1267
|
+
});
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (sub === "read") {
|
|
1271
|
+
const [path, file] = args;
|
|
1272
|
+
await emitGemTool(ctx, "gem.fs_read", {
|
|
1273
|
+
path: gemFilePath(requireCommandValue(path, "Usage: uru gems read <gem-path> <file>"), requireCommandValue(file, "Usage: uru gems read <gem-path> <file>"))
|
|
1274
|
+
});
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
if (sub === "write") {
|
|
1278
|
+
await gemWrite(ctx, args);
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
throw new CliError("Usage: uru gems inspect|validate|build|read|write");
|
|
1282
|
+
}
|
|
1283
|
+
async function dispatchLibrary(ctx, sub, args) {
|
|
1284
|
+
if (sub === "ls") {
|
|
1285
|
+
await emitTool(ctx, "library.ls", {
|
|
1286
|
+
path: args[0] ?? "library",
|
|
1287
|
+
recursive: false
|
|
1288
|
+
});
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
if (sub === "search") {
|
|
1292
|
+
await emitTool(ctx, "library.search", {
|
|
1293
|
+
query: requireCommandValue(args[0], "Usage: uru library search <query>")
|
|
1294
|
+
});
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
if (sub === "mkdir") {
|
|
1298
|
+
await emitTool(ctx, "library.mkdir", {
|
|
1299
|
+
path: requireCommandValue(args[0], "Usage: uru library mkdir <path>"),
|
|
1300
|
+
recursive: true
|
|
1301
|
+
});
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
throw new CliError("Usage: uru library ls|search|mkdir");
|
|
1305
|
+
}
|
|
1306
|
+
async function dispatchDatasets(ctx, sub, args) {
|
|
1307
|
+
if (sub !== "query") {
|
|
1308
|
+
await dispatchRegistryFamily(ctx, "datasets", sub, args);
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
const flags = parseLocalFlags(args);
|
|
1312
|
+
const datasetId = requireCommandValue(flags.positionals[0], "Usage: uru datasets query <dataset-slug> [--limit 50]");
|
|
1313
|
+
await emitTool(ctx, "dataset.query", {
|
|
1314
|
+
dataset_id: datasetId,
|
|
1315
|
+
datasetId,
|
|
1316
|
+
limit: integerFlag(flags, "--limit", 50),
|
|
1317
|
+
offset: integerFlag(flags, "--offset", 0)
|
|
1318
|
+
});
|
|
1319
|
+
}
|
|
1320
|
+
async function login(ctx) {
|
|
1321
|
+
if (ctx.token === undefined || ctx.token.trim() === "") {
|
|
1322
|
+
throw new CliError("Token required. Run `uru login --token <token>`.", {
|
|
1323
|
+
code: ExitCode.AuthRequired
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
const result = await client(ctx).validateToken();
|
|
1327
|
+
const selectedWorkspace = selectWorkspace(result, ctx.workspace);
|
|
1328
|
+
const nextConfig = {
|
|
1329
|
+
...ctx.config,
|
|
1330
|
+
apiUrl: ctx.apiUrl,
|
|
1331
|
+
token: ctx.token,
|
|
1332
|
+
...selectedWorkspace !== undefined ? { workspace: selectedWorkspace } : {}
|
|
1333
|
+
};
|
|
1334
|
+
await ctx.store.write(nextConfig);
|
|
1335
|
+
if (ctx.json) {
|
|
1336
|
+
writeJson(ctx.io, {
|
|
1337
|
+
ok: true,
|
|
1338
|
+
apiUrl: ctx.apiUrl,
|
|
1339
|
+
userId: result.userId,
|
|
1340
|
+
email: result.email,
|
|
1341
|
+
workspaceId: selectedWorkspace,
|
|
1342
|
+
workspaceSelectionRequired: result.workspaceSelectionRequired
|
|
1343
|
+
});
|
|
1344
|
+
} else {
|
|
1345
|
+
writeText(ctx.io, "Logged in");
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
async function linkProject(ctx, args) {
|
|
1349
|
+
const flags = parseLocalFlags(args);
|
|
1350
|
+
const project = {};
|
|
1351
|
+
const workspace = flags.values["--workspace"] ?? ctx.workspace;
|
|
1352
|
+
const gemId = flags.values["--gem"];
|
|
1353
|
+
const libraryPath = flags.values["--path"] ?? flags.positionals[0];
|
|
1354
|
+
if (workspace !== undefined) {
|
|
1355
|
+
project.workspaceId = workspace;
|
|
1356
|
+
}
|
|
1357
|
+
if (gemId !== undefined) {
|
|
1358
|
+
project.gemId = gemId;
|
|
1359
|
+
}
|
|
1360
|
+
if (libraryPath !== undefined) {
|
|
1361
|
+
project.libraryPath = libraryPath;
|
|
1362
|
+
}
|
|
1363
|
+
await writeLinkedProject(ctx.cwd, project);
|
|
1364
|
+
emit(ctx, { ok: true, ...project }, `Linked ${project.libraryPath ?? project.gemId ?? "Gem"}`);
|
|
1365
|
+
}
|
|
1366
|
+
async function switchWorkspace(ctx, workspaceId) {
|
|
1367
|
+
const identity = await client(ctx).validateToken();
|
|
1368
|
+
assertWorkspaceAvailable(identity, workspaceId);
|
|
1369
|
+
const nextConfig = {
|
|
1370
|
+
...ctx.config,
|
|
1371
|
+
apiUrl: ctx.apiUrl,
|
|
1372
|
+
workspace: workspaceId
|
|
1373
|
+
};
|
|
1374
|
+
if (ctx.token !== undefined) {
|
|
1375
|
+
nextConfig.token = ctx.token;
|
|
1376
|
+
}
|
|
1377
|
+
await ctx.store.write(nextConfig);
|
|
1378
|
+
emit(ctx, { ok: true, workspaceId }, `Switched to workspace ${workspaceId}`);
|
|
1379
|
+
}
|
|
1380
|
+
async function withWorkspace(ctx) {
|
|
1381
|
+
if (ctx.workspace !== undefined && ctx.workspace.trim() !== "") {
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const identity = await client(ctx).validateToken();
|
|
1385
|
+
if (identity.workspaceSelectionRequired) {
|
|
1386
|
+
throw new CliError(`${identity.workspaceSelectionMessage ?? "Workspace selection required"} Run \`uru switch <workspace-id>\`, \`uru link --workspace <workspace-id>\`, or pass --workspace.`, { code: ExitCode.AuthRequired });
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async function gemInit(ctx, args) {
|
|
1390
|
+
const flags = parseLocalFlags(args);
|
|
1391
|
+
const path = requireCommandValue(flags.positionals[0], "Usage: uru init <library/name.gem> [--entrypoint main.html] [--file ./main.html]");
|
|
1392
|
+
const params = { path };
|
|
1393
|
+
copyStringFlag(flags, params, "--name", "name");
|
|
1394
|
+
copyStringFlag(flags, params, "--description", "description");
|
|
1395
|
+
copyStringFlag(flags, params, "--entrypoint", "entrypoint");
|
|
1396
|
+
copyStringFlag(flags, params, "--runtime", "runtime");
|
|
1397
|
+
copyStringFlag(flags, params, "--visibility", "visibility");
|
|
1398
|
+
if (flags.values["--content"] !== undefined) {
|
|
1399
|
+
params["content"] = flags.values["--content"];
|
|
1400
|
+
}
|
|
1401
|
+
if (flags.values["--file"] !== undefined) {
|
|
1402
|
+
params["content"] = await readFile3(flags.values["--file"], "utf8");
|
|
1403
|
+
}
|
|
1404
|
+
await emitGemTool(ctx, "gem.init", params);
|
|
1405
|
+
}
|
|
1406
|
+
async function gemDeploy(ctx, args, linkedProject) {
|
|
1407
|
+
const flags = parseLocalFlags(args);
|
|
1408
|
+
const params = {
|
|
1409
|
+
path: gemPathFromArgs(flags.positionals, linkedProject),
|
|
1410
|
+
publish: !flags.booleans.has("--no-publish")
|
|
1411
|
+
};
|
|
1412
|
+
copyStringFlag(flags, params, "--build-id", "build_id");
|
|
1413
|
+
copyStringFlag(flags, params, "--runtime-provider", "runtime_provider");
|
|
1414
|
+
if (flags.booleans.has("--force-build")) {
|
|
1415
|
+
params["force_build"] = true;
|
|
1416
|
+
}
|
|
1417
|
+
await emitGemTool(ctx, "gem.deploy", params, {
|
|
1418
|
+
textExtractor: extractDeploymentUrl
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
async function gemLogs(ctx, args, linkedProject) {
|
|
1422
|
+
const flags = parseLocalFlags(args);
|
|
1423
|
+
const params = {
|
|
1424
|
+
path: gemPathFromArgs(flags.positionals, linkedProject),
|
|
1425
|
+
limit: integerFlag(flags, "--limit", 100)
|
|
1426
|
+
};
|
|
1427
|
+
copyStringFlag(flags, params, "--level", "level");
|
|
1428
|
+
copyStringFlag(flags, params, "--deployment-id", "deployment_id");
|
|
1429
|
+
copyStringFlag(flags, params, "--request-id", "request_id");
|
|
1430
|
+
await emitGemTool(ctx, "gem.logs", params);
|
|
1431
|
+
}
|
|
1432
|
+
async function gemOpen(ctx, args, linkedProject) {
|
|
1433
|
+
const result = await executeOperation(ctx, "gem.links_list", {
|
|
1434
|
+
path: gemPathFromArgs(args, linkedProject)
|
|
1435
|
+
});
|
|
1436
|
+
const url = firstUrl(unwrapToolResult(result));
|
|
1437
|
+
if (url === undefined) {
|
|
1438
|
+
throw new CliError("No published URL found for this Gem. Run `uru deploy` first.");
|
|
1439
|
+
}
|
|
1440
|
+
ctx.json ? writeJson(ctx.io, { url }) : writeText(ctx.io, url);
|
|
1441
|
+
}
|
|
1442
|
+
async function gemRollback(ctx, args, linkedProject) {
|
|
1443
|
+
const flags = parseLocalFlags(args);
|
|
1444
|
+
if (!ctx.yes && !flags.booleans.has("--yes")) {
|
|
1445
|
+
throw new CliError("Rollback requires --yes to confirm the link pointer change.");
|
|
1446
|
+
}
|
|
1447
|
+
await emitGemTool(ctx, "gem.rollback", {
|
|
1448
|
+
path: gemPathFromArgs(flags.positionals.slice(1), linkedProject),
|
|
1449
|
+
app_deployment_id: requireCommandValue(flags.positionals[0], "Usage: uru rollback <deployment-id> --link-id <link-id> --yes"),
|
|
1450
|
+
link_id: requireCommandValue(flags.values["--link-id"], "Usage: uru rollback <deployment-id> --link-id <link-id> --yes")
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
async function gemWrite(ctx, args) {
|
|
1454
|
+
const flags = parseLocalFlags(args);
|
|
1455
|
+
const gemPath = requireCommandValue(flags.positionals[0], "Usage: uru gems write <gem-path> <file> --content <text>|--file <local-file>");
|
|
1456
|
+
const filePath = requireCommandValue(flags.positionals[1], "Usage: uru gems write <gem-path> <file> --content <text>|--file <local-file>");
|
|
1457
|
+
const content = flags.values["--content"] ?? (flags.values["--file"] === undefined ? undefined : await readFile3(flags.values["--file"], "utf8"));
|
|
1458
|
+
if (content === undefined) {
|
|
1459
|
+
throw new CliError("Gem write requires --content or --file.");
|
|
1460
|
+
}
|
|
1461
|
+
await emitGemTool(ctx, "gem.fs_write", {
|
|
1462
|
+
path: gemFilePath(gemPath, filePath),
|
|
1463
|
+
content,
|
|
1464
|
+
create: !flags.booleans.has("--no-create")
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
async function emitGemTool(ctx, operationId, params, options = {}) {
|
|
1468
|
+
await emitTool(ctx, operationId, params, options);
|
|
1469
|
+
}
|
|
1470
|
+
async function emitTool(ctx, operationId, params, options = {}) {
|
|
1471
|
+
const result = await executeOperation(ctx, operationId, params);
|
|
1472
|
+
const unwrapped = unwrapToolResult(result);
|
|
1473
|
+
if (ctx.outputFormat === "stream-json") {
|
|
1474
|
+
writeStreamJson(ctx.io, "result", unwrapped);
|
|
1475
|
+
} else if (ctx.json) {
|
|
1476
|
+
writeJson(ctx.io, unwrapped);
|
|
1477
|
+
} else {
|
|
1478
|
+
writeText(ctx.io, options.textExtractor?.(unwrapped) ?? JSON.stringify(unwrapped, null, 2));
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
async function executeOperation(ctx, operationId, params) {
|
|
1482
|
+
const operation = getPlatformOperation(operationId);
|
|
1483
|
+
if (operation === undefined) {
|
|
1484
|
+
throw new CliError(`Unknown operation registry id: ${operationId}`);
|
|
1485
|
+
}
|
|
1486
|
+
const toolParams = { ...params };
|
|
1487
|
+
if (operation.backendOp !== undefined) {
|
|
1488
|
+
toolParams["op"] = operation.backendOp;
|
|
1489
|
+
}
|
|
1490
|
+
return client(ctx).runTool(operation.backendToolName, toolParams);
|
|
1491
|
+
}
|
|
1492
|
+
function client(ctx) {
|
|
1493
|
+
return new UruApiClient({
|
|
1494
|
+
apiUrl: ctx.apiUrl,
|
|
1495
|
+
...ctx.token !== undefined ? { token: ctx.token } : {},
|
|
1496
|
+
...ctx.workspace !== undefined ? { workspaceId: ctx.workspace } : {},
|
|
1497
|
+
...ctx.fetch !== undefined ? { fetch: ctx.fetch } : {}
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
function selectWorkspace(identity, requestedWorkspaceId) {
|
|
1501
|
+
if (requestedWorkspaceId === undefined || requestedWorkspaceId.trim() === "") {
|
|
1502
|
+
return identity.workspaceId;
|
|
1503
|
+
}
|
|
1504
|
+
assertWorkspaceAvailable(identity, requestedWorkspaceId);
|
|
1505
|
+
return requestedWorkspaceId;
|
|
1506
|
+
}
|
|
1507
|
+
function assertWorkspaceAvailable(identity, workspaceId) {
|
|
1508
|
+
if (identity.workspaceId === workspaceId || identity.workspaces.some((workspace) => workspace.id === workspaceId)) {
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
throw new CliError(`Workspace ${workspaceId} is not available for the current token. Run \`uru switch\` to list available workspaces.`);
|
|
1512
|
+
}
|
|
1513
|
+
function gemPathFromArgs(args, linkedProject) {
|
|
1514
|
+
const flags = parseLocalFlags(args);
|
|
1515
|
+
return flags.values["--path"] ?? flags.positionals.find((value) => value.startsWith("library/")) ?? linkedProject.libraryPath ?? linkedProject.gemId ?? (() => {
|
|
1516
|
+
throw new CliError("Gem path required. Pass library/<name>.gem or run `uru link --path library/<name>.gem`.");
|
|
1517
|
+
})();
|
|
1518
|
+
}
|
|
1519
|
+
function gemFilePath(gemPath, filePath) {
|
|
1520
|
+
const normalizedGemPath = trimTrailingSlashes(gemPath);
|
|
1521
|
+
const normalizedFilePath = trimLeadingSlashes(filePath);
|
|
1522
|
+
return `${normalizedGemPath}/${normalizedFilePath}`;
|
|
1523
|
+
}
|
|
1524
|
+
function trimTrailingSlashes(value) {
|
|
1525
|
+
let end = value.length;
|
|
1526
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47) {
|
|
1527
|
+
end -= 1;
|
|
1528
|
+
}
|
|
1529
|
+
return value.slice(0, end);
|
|
1530
|
+
}
|
|
1531
|
+
function trimLeadingSlashes(value) {
|
|
1532
|
+
let start = 0;
|
|
1533
|
+
while (start < value.length && value.charCodeAt(start) === 47) {
|
|
1534
|
+
start += 1;
|
|
1535
|
+
}
|
|
1536
|
+
return value.slice(start);
|
|
1537
|
+
}
|
|
1538
|
+
function emit(ctx, value, text) {
|
|
1539
|
+
if (ctx.outputFormat === "stream-json") {
|
|
1540
|
+
writeStreamJson(ctx.io, "result", value);
|
|
1541
|
+
} else if (ctx.json) {
|
|
1542
|
+
writeJson(ctx.io, value);
|
|
1543
|
+
} else {
|
|
1544
|
+
writeText(ctx.io, text);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
function unwrapToolResult(value) {
|
|
1548
|
+
if (isJsonObject2(value) && isJsonObject2(value["result"])) {
|
|
1549
|
+
return value["result"];
|
|
1550
|
+
}
|
|
1551
|
+
return value;
|
|
1552
|
+
}
|
|
1553
|
+
function extractDeploymentUrl(value) {
|
|
1554
|
+
return firstUrl(value);
|
|
1555
|
+
}
|
|
1556
|
+
function firstUrl(value) {
|
|
1557
|
+
if (typeof value === "string" && /^https?:\/\//.test(value)) {
|
|
1558
|
+
return value;
|
|
1559
|
+
}
|
|
1560
|
+
if (Array.isArray(value)) {
|
|
1561
|
+
for (const item of value) {
|
|
1562
|
+
const found = firstUrl(item);
|
|
1563
|
+
if (found !== undefined) {
|
|
1564
|
+
return found;
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (!isJsonObject2(value)) {
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
for (const key of [
|
|
1573
|
+
"published_url",
|
|
1574
|
+
"publishedUrl",
|
|
1575
|
+
"preview_url",
|
|
1576
|
+
"previewUrl",
|
|
1577
|
+
"url",
|
|
1578
|
+
"href"
|
|
1579
|
+
]) {
|
|
1580
|
+
const candidate = value[key];
|
|
1581
|
+
if (typeof candidate === "string" && /^https?:\/\//.test(candidate)) {
|
|
1582
|
+
return candidate;
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
for (const candidate of Object.values(value)) {
|
|
1586
|
+
const found = firstUrl(candidate);
|
|
1587
|
+
if (found !== undefined) {
|
|
1588
|
+
return found;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
function isJsonObject2(value) {
|
|
1594
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1595
|
+
}
|
|
1596
|
+
function normalizeError(error) {
|
|
1597
|
+
if (error instanceof CliError) {
|
|
1598
|
+
return error;
|
|
1599
|
+
}
|
|
1600
|
+
return new CliError(error instanceof Error ? error.message : "Unknown error");
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
// src/index.ts
|
|
1604
|
+
var exitCode = await runCli({ argv: process.argv.slice(2), env: process.env });
|
|
1605
|
+
process.exitCode = exitCode;
|