@elnora-ai/cli 1.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/LICENSE +201 -0
- package/README.md +137 -0
- package/dist/cli.cjs +442 -0
- package/dist/cli.js +4383 -0
- package/package.json +67 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,4383 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/lib/config.ts
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
function getVersion() {
|
|
17
|
+
try {
|
|
18
|
+
const dir = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const pkg = JSON.parse(readFileSync(join(dir, "../../package.json"), "utf-8"));
|
|
20
|
+
return pkg.version ?? "0.0.0-dev";
|
|
21
|
+
} catch {
|
|
22
|
+
return "0.0.0-dev";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function buildUrl(endpointKey, params, baseUrl) {
|
|
26
|
+
const path = ENDPOINTS[endpointKey];
|
|
27
|
+
if (!path) throw new Error(`Unknown endpoint: ${endpointKey}`);
|
|
28
|
+
let resolved = path;
|
|
29
|
+
if (params) {
|
|
30
|
+
for (const [key, value] of Object.entries(params)) {
|
|
31
|
+
resolved = resolved.replace(`{${key}}`, value);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return `${baseUrl ?? BASE_URL}${resolved}`;
|
|
35
|
+
}
|
|
36
|
+
var VERSION, BASE_URL, AI_SERVER_URL, ENDPOINTS, DEFAULT_HEADERS;
|
|
37
|
+
var init_config = __esm({
|
|
38
|
+
"src/lib/config.ts"() {
|
|
39
|
+
"use strict";
|
|
40
|
+
VERSION = getVersion();
|
|
41
|
+
BASE_URL = "https://platform.elnora.ai/api/v1";
|
|
42
|
+
AI_SERVER_URL = process.env.ELNORA_AI_SERVER_URL ?? "https://platform.elnora.ai/ai-server";
|
|
43
|
+
ENDPOINTS = {
|
|
44
|
+
// Projects
|
|
45
|
+
projects: "/projects",
|
|
46
|
+
project: "/projects/{id}",
|
|
47
|
+
project_tasks: "/projects/{id}/tasks",
|
|
48
|
+
project_files: "/projects/{id}/files",
|
|
49
|
+
project_folders: "/projects/{id}/folders",
|
|
50
|
+
project_members: "/projects/{id}/members",
|
|
51
|
+
project_member_role: "/projects/{id}/members/{uid}/role",
|
|
52
|
+
project_member: "/projects/{id}/members/{uid}",
|
|
53
|
+
project_leave: "/projects/{id}/leave",
|
|
54
|
+
// Tasks
|
|
55
|
+
tasks: "/tasks",
|
|
56
|
+
task: "/tasks/{id}",
|
|
57
|
+
task_messages: "/tasks/{id}/messages",
|
|
58
|
+
// Files
|
|
59
|
+
files: "/files",
|
|
60
|
+
file: "/files/{id}",
|
|
61
|
+
file_content: "/files/{id}/content",
|
|
62
|
+
file_download: "/files/{id}/download",
|
|
63
|
+
file_upload: "/files/upload",
|
|
64
|
+
file_upload_confirm: "/files/{id}/upload/confirm",
|
|
65
|
+
file_versions: "/files/{id}/versions",
|
|
66
|
+
file_version_content: "/files/{id}/versions/{vid}/content",
|
|
67
|
+
file_version_restore: "/files/{id}/versions/{vid}/restore",
|
|
68
|
+
file_promote: "/files/{id}/promote",
|
|
69
|
+
file_fork: "/files/{id}/fork",
|
|
70
|
+
file_working_copy: "/files/{id}/working-copy",
|
|
71
|
+
file_commit: "/files/{id}/commit",
|
|
72
|
+
file_upload_batch: "/files/upload/batch",
|
|
73
|
+
// Folders
|
|
74
|
+
folder: "/folders/{id}",
|
|
75
|
+
folder_move: "/folders/{id}/move",
|
|
76
|
+
// Organizations
|
|
77
|
+
organizations: "/organizations",
|
|
78
|
+
organization: "/organizations/{id}",
|
|
79
|
+
organization_members: "/organizations/{id}/members",
|
|
80
|
+
organization_member_role: "/organizations/{id}/members/{mid}/role",
|
|
81
|
+
organization_member: "/organizations/{id}/members/{mid}",
|
|
82
|
+
organization_billing: "/organizations/{id}/billing-status",
|
|
83
|
+
organization_stripe_customer: "/organizations/{id}/stripe-customer",
|
|
84
|
+
organization_set_default: "/organizations/{id}/set-default",
|
|
85
|
+
organizations_all: "/organizations/all",
|
|
86
|
+
org_files: "/organizations/{orgId}/files",
|
|
87
|
+
// Organization invitations
|
|
88
|
+
org_invitations: "/organizations/{orgId}/invitations",
|
|
89
|
+
org_invitation: "/organizations/{orgId}/invitations/{invId}",
|
|
90
|
+
// Public invitations
|
|
91
|
+
invitation_info: "/invitations/{token}",
|
|
92
|
+
invitation_accept: "/invitations/{token}/accept",
|
|
93
|
+
// Organization library
|
|
94
|
+
library_files: "/organizations/{orgId}/library/files",
|
|
95
|
+
library_folders: "/organizations/{orgId}/library/folders",
|
|
96
|
+
library_folder: "/organizations/{orgId}/library/folders/{id}",
|
|
97
|
+
// Search
|
|
98
|
+
search_tasks: "/search/tasks",
|
|
99
|
+
search_files: "/search/files",
|
|
100
|
+
search_all: "/search",
|
|
101
|
+
search_file_content: "/search/file-content",
|
|
102
|
+
// API Keys
|
|
103
|
+
api_keys: "/api-keys",
|
|
104
|
+
api_key: "/api-keys/{id}",
|
|
105
|
+
api_key_policy: "/api-keys/policy",
|
|
106
|
+
// Audit
|
|
107
|
+
audit_log: "/organizations/{orgId}/audit-log",
|
|
108
|
+
// Feedback
|
|
109
|
+
feedback: "/feedback",
|
|
110
|
+
// Account
|
|
111
|
+
account_user: "/account/user/{id}",
|
|
112
|
+
account_delete: "/account/me",
|
|
113
|
+
account_users: "/account/user/list",
|
|
114
|
+
// User agreements
|
|
115
|
+
user_agreements: "/userAgreement/userAgreements",
|
|
116
|
+
user_agreement: "/userAgreement/userAgreement",
|
|
117
|
+
// Legal docs (anonymous)
|
|
118
|
+
legal_doc_version: "/userAgreement/legalDocumentVersion",
|
|
119
|
+
legal_doc_version_id: "/userAgreement/legalDocumentVersion/{id}",
|
|
120
|
+
// Feature flags (SystemAdmin)
|
|
121
|
+
feature_flags: "/globalFeatureFlags",
|
|
122
|
+
feature_flag: "/globalFeatureFlags/{key}",
|
|
123
|
+
// Auth
|
|
124
|
+
auth_validate: "/auth/validate-token",
|
|
125
|
+
// Health (outside /api/v1 prefix — handled specially by buildUrl)
|
|
126
|
+
health: "/health"
|
|
127
|
+
};
|
|
128
|
+
DEFAULT_HEADERS = {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
Accept: "application/json",
|
|
131
|
+
"User-Agent": `Elnora-CLI/${VERSION}`
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// src/lib/errors.ts
|
|
137
|
+
function scrub(text) {
|
|
138
|
+
let result = text;
|
|
139
|
+
const envKeys = ["ELNORA_API_KEY", "ELNORA_MCP_API_KEY"];
|
|
140
|
+
for (const envVar of envKeys) {
|
|
141
|
+
const key = process.env[envVar];
|
|
142
|
+
if (key && result.includes(key)) {
|
|
143
|
+
result = result.replaceAll(key, "[REDACTED]");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
result = result.replace(SCRUB_KEY_VALUE_RE, "[REDACTED]");
|
|
147
|
+
result = result.replace(SCRUB_LONG_TOKEN_RE, "[REDACTED]");
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
function scrubData(obj) {
|
|
151
|
+
if (typeof obj === "string") return scrub(obj);
|
|
152
|
+
if (Array.isArray(obj)) return obj.map(scrubData);
|
|
153
|
+
if (obj !== null && typeof obj === "object") {
|
|
154
|
+
const result = {};
|
|
155
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
156
|
+
result[k] = scrubData(v);
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
return obj;
|
|
161
|
+
}
|
|
162
|
+
function getExitCode(err) {
|
|
163
|
+
for (const [ErrorClass, code] of EXIT_CODES) {
|
|
164
|
+
if (err instanceof ErrorClass) return code;
|
|
165
|
+
}
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
function formatErrorPayload(err) {
|
|
169
|
+
const payload = { error: scrub(err.message) };
|
|
170
|
+
if (err instanceof ElnoraError) {
|
|
171
|
+
payload.code = err.code;
|
|
172
|
+
if (err.suggestion) payload.suggestion = err.suggestion;
|
|
173
|
+
} else {
|
|
174
|
+
payload.code = err.constructor.name;
|
|
175
|
+
}
|
|
176
|
+
return payload;
|
|
177
|
+
}
|
|
178
|
+
var ElnoraError, AuthError, NotFoundError, RateLimitError, ValidationError, ServerError, EXIT_CODES, SCRUB_KEY_VALUE_RE, SCRUB_LONG_TOKEN_RE;
|
|
179
|
+
var init_errors = __esm({
|
|
180
|
+
"src/lib/errors.ts"() {
|
|
181
|
+
"use strict";
|
|
182
|
+
ElnoraError = class extends Error {
|
|
183
|
+
code;
|
|
184
|
+
suggestion;
|
|
185
|
+
constructor(message, options) {
|
|
186
|
+
super(message);
|
|
187
|
+
this.name = "ElnoraError";
|
|
188
|
+
this.code = options?.code ?? "ELNORA_ERROR";
|
|
189
|
+
this.suggestion = options?.suggestion;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
AuthError = class extends ElnoraError {
|
|
193
|
+
constructor(message, options) {
|
|
194
|
+
super(message ?? "No Elnora API key found. Set ELNORA_API_KEY environment variable.", {
|
|
195
|
+
code: "AUTH_FAILED",
|
|
196
|
+
suggestion: options?.suggestion ?? "Get your API key from platform.elnora.ai > Settings > API Keys"
|
|
197
|
+
});
|
|
198
|
+
this.name = "AuthError";
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
NotFoundError = class extends ElnoraError {
|
|
202
|
+
constructor(entity, identifier) {
|
|
203
|
+
super(`${entity} not found: ${identifier}`, {
|
|
204
|
+
code: "NOT_FOUND",
|
|
205
|
+
suggestion: "Check the identifier and try again."
|
|
206
|
+
});
|
|
207
|
+
this.name = "NotFoundError";
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
RateLimitError = class extends ElnoraError {
|
|
211
|
+
constructor(message) {
|
|
212
|
+
super(message ?? "Elnora API rate limit exceeded.", {
|
|
213
|
+
code: "RATE_LIMITED",
|
|
214
|
+
suggestion: "Wait a moment and retry."
|
|
215
|
+
});
|
|
216
|
+
this.name = "RateLimitError";
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
ValidationError = class extends ElnoraError {
|
|
220
|
+
constructor(message, suggestion) {
|
|
221
|
+
super(message, { code: "VALIDATION_ERROR", suggestion });
|
|
222
|
+
this.name = "ValidationError";
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
ServerError = class extends ElnoraError {
|
|
226
|
+
constructor(message) {
|
|
227
|
+
super(message ?? "Elnora API server error.", {
|
|
228
|
+
code: "SERVER_ERROR",
|
|
229
|
+
suggestion: "Try again later. If the issue persists, contact support@elnora.ai."
|
|
230
|
+
});
|
|
231
|
+
this.name = "ServerError";
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
EXIT_CODES = /* @__PURE__ */ new Map([
|
|
235
|
+
[ValidationError, 2],
|
|
236
|
+
[AuthError, 3],
|
|
237
|
+
[NotFoundError, 4],
|
|
238
|
+
[RateLimitError, 5],
|
|
239
|
+
[ServerError, 6]
|
|
240
|
+
]);
|
|
241
|
+
SCRUB_KEY_VALUE_RE = /"?(?:ELNORA_API_KEY|ELNORA_MCP_API_KEY|api_key|x-api-key)"?\s*[=:]\s*"?([^\s"']+)"?/gi;
|
|
242
|
+
SCRUB_LONG_TOKEN_RE = /elnora_live_[a-zA-Z0-9_-]{8,}|[a-zA-Z0-9_-]{40,}/g;
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// src/lib/profiles.ts
|
|
247
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync as readFileSync2, unlinkSync, writeFileSync } from "node:fs";
|
|
248
|
+
import { homedir } from "node:os";
|
|
249
|
+
import { join as join2 } from "node:path";
|
|
250
|
+
function validateProfileName(name) {
|
|
251
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
252
|
+
throw new ValidationError(
|
|
253
|
+
`Invalid profile name '${name}'. Must be 1-32 chars, lowercase alphanumeric and hyphens, starting with alphanumeric.`,
|
|
254
|
+
"Examples: default, work, university, lab-2"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
return name;
|
|
258
|
+
}
|
|
259
|
+
function parseProfilesToml(text) {
|
|
260
|
+
const profiles = {};
|
|
261
|
+
let currentSection = null;
|
|
262
|
+
for (const line of text.split("\n")) {
|
|
263
|
+
const stripped = line.trim();
|
|
264
|
+
if (!stripped || stripped.startsWith("#")) continue;
|
|
265
|
+
if (stripped.startsWith("[") && stripped.endsWith("]")) {
|
|
266
|
+
const header = stripped.slice(1, -1).trim();
|
|
267
|
+
if (header === "default") {
|
|
268
|
+
currentSection = "default";
|
|
269
|
+
profiles.default ??= {};
|
|
270
|
+
} else if (header.startsWith("profiles.")) {
|
|
271
|
+
currentSection = header.slice("profiles.".length);
|
|
272
|
+
profiles[currentSection] ??= {};
|
|
273
|
+
} else {
|
|
274
|
+
currentSection = null;
|
|
275
|
+
}
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (currentSection !== null && stripped.includes("=")) {
|
|
279
|
+
const eqIdx = stripped.indexOf("=");
|
|
280
|
+
const key = stripped.slice(0, eqIdx).trim();
|
|
281
|
+
let val = stripped.slice(eqIdx + 1).trim();
|
|
282
|
+
val = val.replace(/^["']|["']$/g, "");
|
|
283
|
+
profiles[currentSection][key] = val;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return profiles;
|
|
287
|
+
}
|
|
288
|
+
function serializeProfiles(profiles) {
|
|
289
|
+
const parts = ["# Elnora CLI profiles", "# Managed by: elnora auth login", ""];
|
|
290
|
+
if (profiles.default) {
|
|
291
|
+
parts.push("[default]");
|
|
292
|
+
for (const [k, v] of Object.entries(profiles.default)) {
|
|
293
|
+
parts.push(`${k} = "${v}"`);
|
|
294
|
+
}
|
|
295
|
+
parts.push("");
|
|
296
|
+
}
|
|
297
|
+
for (const name of Object.keys(profiles).sort()) {
|
|
298
|
+
if (name === "default") continue;
|
|
299
|
+
parts.push(`[profiles.${name}]`);
|
|
300
|
+
for (const [k, v] of Object.entries(profiles[name])) {
|
|
301
|
+
parts.push(`${k} = "${v}"`);
|
|
302
|
+
}
|
|
303
|
+
parts.push("");
|
|
304
|
+
}
|
|
305
|
+
return parts.join("\n");
|
|
306
|
+
}
|
|
307
|
+
function writeSecureFile(filePath, content) {
|
|
308
|
+
const dir = join2(filePath, "..");
|
|
309
|
+
mkdirSync(dir, { recursive: true });
|
|
310
|
+
if (process.platform !== "win32") {
|
|
311
|
+
try {
|
|
312
|
+
chmodSync(dir, 448);
|
|
313
|
+
} catch {
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
writeFileSync(filePath, content, { encoding: "utf-8", mode: 384 });
|
|
317
|
+
}
|
|
318
|
+
function loadProfiles() {
|
|
319
|
+
if (!existsSync(PROFILES_FILE)) return {};
|
|
320
|
+
try {
|
|
321
|
+
const text = readFileSync2(PROFILES_FILE, "utf-8");
|
|
322
|
+
return parseProfilesToml(text);
|
|
323
|
+
} catch {
|
|
324
|
+
return {};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function saveProfile(name, apiKey) {
|
|
328
|
+
validateProfileName(name);
|
|
329
|
+
const profiles = loadProfiles();
|
|
330
|
+
profiles[name] = { api_key: apiKey };
|
|
331
|
+
writeSecureFile(PROFILES_FILE, serializeProfiles(profiles));
|
|
332
|
+
return PROFILES_FILE;
|
|
333
|
+
}
|
|
334
|
+
function removeProfile(name) {
|
|
335
|
+
const profiles = loadProfiles();
|
|
336
|
+
if (!(name in profiles)) return false;
|
|
337
|
+
delete profiles[name];
|
|
338
|
+
if (Object.keys(profiles).length === 0) {
|
|
339
|
+
try {
|
|
340
|
+
unlinkSync(PROFILES_FILE);
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
writeSecureFile(PROFILES_FILE, serializeProfiles(profiles));
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
function getApiKey(profileName = "default") {
|
|
349
|
+
const profiles = loadProfiles();
|
|
350
|
+
if (!(profileName in profiles)) {
|
|
351
|
+
const available = Object.keys(profiles);
|
|
352
|
+
const suggestion = available.length > 0 ? `Available profiles: ${available.join(", ")}` : "Run 'elnora auth login' to set up a profile.";
|
|
353
|
+
throw new AuthError(`Profile '${profileName}' not found.`, { suggestion });
|
|
354
|
+
}
|
|
355
|
+
const key = profiles[profileName].api_key ?? "";
|
|
356
|
+
if (!key) {
|
|
357
|
+
throw new AuthError(`Profile '${profileName}' has no API key.`, {
|
|
358
|
+
suggestion: `Run: elnora auth login --profile ${profileName}`
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return key;
|
|
362
|
+
}
|
|
363
|
+
function listProfileNames() {
|
|
364
|
+
return Object.keys(loadProfiles());
|
|
365
|
+
}
|
|
366
|
+
function migrateConfigIfNeeded() {
|
|
367
|
+
if (existsSync(PROFILES_FILE)) return false;
|
|
368
|
+
if (!existsSync(LEGACY_CONFIG_FILE)) return false;
|
|
369
|
+
let apiKey = "";
|
|
370
|
+
try {
|
|
371
|
+
const text = readFileSync2(LEGACY_CONFIG_FILE, "utf-8");
|
|
372
|
+
for (const line of text.split("\n")) {
|
|
373
|
+
const trimmed = line.trim();
|
|
374
|
+
if (trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
375
|
+
const eqIdx = trimmed.indexOf("=");
|
|
376
|
+
if (trimmed.slice(0, eqIdx).trim() === "api_key") {
|
|
377
|
+
apiKey = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
if (!apiKey) return false;
|
|
385
|
+
saveProfile("default", apiKey);
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
function resolveApiKey(profileName) {
|
|
389
|
+
const envKey = process.env.ELNORA_API_KEY ?? process.env.ELNORA_MCP_API_KEY;
|
|
390
|
+
if (envKey) return envKey;
|
|
391
|
+
migrateConfigIfNeeded();
|
|
392
|
+
return getApiKey(profileName ?? "default");
|
|
393
|
+
}
|
|
394
|
+
var CONFIG_DIR, PROFILES_FILE, LEGACY_CONFIG_FILE, PROFILE_NAME_RE;
|
|
395
|
+
var init_profiles = __esm({
|
|
396
|
+
"src/lib/profiles.ts"() {
|
|
397
|
+
"use strict";
|
|
398
|
+
init_errors();
|
|
399
|
+
CONFIG_DIR = join2(homedir(), ".elnora");
|
|
400
|
+
PROFILES_FILE = join2(CONFIG_DIR, "profiles.toml");
|
|
401
|
+
LEGACY_CONFIG_FILE = join2(CONFIG_DIR, "config.toml");
|
|
402
|
+
PROFILE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,31}$/;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// src/lib/client.ts
|
|
407
|
+
function validateApiUrl(url) {
|
|
408
|
+
const parsed = new URL(url);
|
|
409
|
+
if (parsed.protocol !== "https:") {
|
|
410
|
+
throw new ElnoraError(`SSRF blocked: refusing non-HTTPS connection to ${parsed.hostname}`, {
|
|
411
|
+
code: "SSRF_BLOCKED"
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (!ALLOWED_API_HOSTS.includes(parsed.hostname)) {
|
|
415
|
+
throw new ElnoraError(`SSRF blocked: refusing to connect to ${parsed.hostname}`, {
|
|
416
|
+
code: "SSRF_BLOCKED"
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
if (url.split("?")[0].includes("@")) {
|
|
420
|
+
throw new ElnoraError("SSRF blocked: URL contains userinfo (@)", { code: "SSRF_BLOCKED" });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function validateUploadUrl(url) {
|
|
424
|
+
const parsed = new URL(url);
|
|
425
|
+
if (parsed.protocol !== "https:") {
|
|
426
|
+
throw new ValidationError(
|
|
427
|
+
`Upload URL must use HTTPS, got '${parsed.protocol.replace(":", "")}'.`,
|
|
428
|
+
"Contact support \u2014 the API returned an insecure upload URL."
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (url.split("?")[0].includes("@")) {
|
|
432
|
+
throw new ValidationError(
|
|
433
|
+
"Upload URL contains userinfo (@).",
|
|
434
|
+
"Contact support \u2014 the API returned a suspicious upload URL."
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
const hostname = parsed.hostname;
|
|
438
|
+
if (!ALLOWED_UPLOAD_SUFFIXES.some((suffix) => hostname.endsWith(suffix))) {
|
|
439
|
+
throw new ValidationError(
|
|
440
|
+
`Upload URL hostname '${hostname}' is not an allowed storage provider.`,
|
|
441
|
+
"Contact support \u2014 the API returned an unexpected upload URL host."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
var ALLOWED_API_HOSTS, ALLOWED_UPLOAD_SUFFIXES, MIN_REQUEST_INTERVAL_MS, REQUEST_TIMEOUT_MS, MAX_RETRIES, RETRY_DELAYS_MS, JITTER_FACTOR, ElnoraApiClient;
|
|
446
|
+
var init_client = __esm({
|
|
447
|
+
"src/lib/client.ts"() {
|
|
448
|
+
"use strict";
|
|
449
|
+
init_config();
|
|
450
|
+
init_errors();
|
|
451
|
+
init_profiles();
|
|
452
|
+
ALLOWED_API_HOSTS = ["platform.elnora.ai"];
|
|
453
|
+
ALLOWED_UPLOAD_SUFFIXES = [
|
|
454
|
+
".amazonaws.com",
|
|
455
|
+
".storage.googleapis.com",
|
|
456
|
+
"storage.googleapis.com",
|
|
457
|
+
".blob.core.windows.net"
|
|
458
|
+
];
|
|
459
|
+
MIN_REQUEST_INTERVAL_MS = 100;
|
|
460
|
+
REQUEST_TIMEOUT_MS = 3e4;
|
|
461
|
+
MAX_RETRIES = 3;
|
|
462
|
+
RETRY_DELAYS_MS = [1e3, 2e3, 4e3];
|
|
463
|
+
JITTER_FACTOR = 0.25;
|
|
464
|
+
ElnoraApiClient = class _ElnoraApiClient {
|
|
465
|
+
apiKey;
|
|
466
|
+
baseUrl;
|
|
467
|
+
timeout;
|
|
468
|
+
lastRequestTime = 0;
|
|
469
|
+
constructor(apiKey, options) {
|
|
470
|
+
this.apiKey = apiKey;
|
|
471
|
+
this.baseUrl = options?.baseUrl ?? BASE_URL;
|
|
472
|
+
this.timeout = options?.timeout ?? REQUEST_TIMEOUT_MS;
|
|
473
|
+
}
|
|
474
|
+
static fromEnv(profileName, options) {
|
|
475
|
+
const key = resolveApiKey(profileName);
|
|
476
|
+
return new _ElnoraApiClient(key, options);
|
|
477
|
+
}
|
|
478
|
+
async request(endpoint, options) {
|
|
479
|
+
const method = options?.method ?? "GET";
|
|
480
|
+
let url;
|
|
481
|
+
if (endpoint.startsWith("/")) {
|
|
482
|
+
url = `${this.baseUrl}${endpoint}`;
|
|
483
|
+
} else if (endpoint in ENDPOINTS) {
|
|
484
|
+
url = buildUrl(endpoint, options?.pathParams, this.baseUrl);
|
|
485
|
+
} else {
|
|
486
|
+
url = `${this.baseUrl}${endpoint}`;
|
|
487
|
+
}
|
|
488
|
+
if (options?.queryParams) {
|
|
489
|
+
const qs = new URLSearchParams();
|
|
490
|
+
for (const [k, v] of Object.entries(options.queryParams)) {
|
|
491
|
+
qs.set(k, String(v));
|
|
492
|
+
}
|
|
493
|
+
url = `${url}?${qs.toString()}`;
|
|
494
|
+
}
|
|
495
|
+
if (this.baseUrl === BASE_URL) {
|
|
496
|
+
validateApiUrl(url);
|
|
497
|
+
}
|
|
498
|
+
const now = Date.now();
|
|
499
|
+
const elapsed = now - this.lastRequestTime;
|
|
500
|
+
if (elapsed < MIN_REQUEST_INTERVAL_MS) {
|
|
501
|
+
await new Promise((r) => setTimeout(r, MIN_REQUEST_INTERVAL_MS - elapsed));
|
|
502
|
+
}
|
|
503
|
+
this.lastRequestTime = Date.now();
|
|
504
|
+
const headers = {
|
|
505
|
+
...DEFAULT_HEADERS,
|
|
506
|
+
"X-API-Key": this.apiKey,
|
|
507
|
+
"User-Agent": `Elnora-CLI/${VERSION}`
|
|
508
|
+
};
|
|
509
|
+
if (method === "GET" || method === "DELETE") {
|
|
510
|
+
delete headers["Content-Type"];
|
|
511
|
+
}
|
|
512
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
513
|
+
try {
|
|
514
|
+
const response = await fetch(url, {
|
|
515
|
+
method,
|
|
516
|
+
headers,
|
|
517
|
+
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
518
|
+
redirect: "manual",
|
|
519
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
520
|
+
});
|
|
521
|
+
if (response.status >= 300 && response.status < 400) {
|
|
522
|
+
const location = response.headers.get("location") ?? "unknown";
|
|
523
|
+
let hostname;
|
|
524
|
+
try {
|
|
525
|
+
hostname = new URL(location, url).hostname;
|
|
526
|
+
} catch {
|
|
527
|
+
hostname = "unknown";
|
|
528
|
+
}
|
|
529
|
+
throw new ElnoraError(`Unexpected redirect to ${hostname} (blocked for security)`, {
|
|
530
|
+
code: "UNEXPECTED_REDIRECT"
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
if (response.ok) {
|
|
534
|
+
const text = await response.text();
|
|
535
|
+
if (!text) return void 0;
|
|
536
|
+
try {
|
|
537
|
+
return JSON.parse(text);
|
|
538
|
+
} catch {
|
|
539
|
+
return text;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
let bodyText = "";
|
|
543
|
+
try {
|
|
544
|
+
bodyText = scrub(await response.text());
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
if (response.status === 401 || response.status === 403) {
|
|
548
|
+
throw new AuthError(bodyText.slice(0, 200) || "Authentication failed");
|
|
549
|
+
}
|
|
550
|
+
if (response.status === 404) {
|
|
551
|
+
throw new NotFoundError("resource", bodyText.slice(0, 200) || "not found");
|
|
552
|
+
}
|
|
553
|
+
if (response.status === 422) {
|
|
554
|
+
throw new ValidationError(bodyText.slice(0, 500) || "Validation error");
|
|
555
|
+
}
|
|
556
|
+
if (response.status === 429) {
|
|
557
|
+
if (attempt < MAX_RETRIES) {
|
|
558
|
+
const retryAfter = response.headers.get("retry-after");
|
|
559
|
+
const baseDelay = retryAfter ? Number.parseInt(retryAfter, 10) * 1e3 : RETRY_DELAYS_MS[attempt];
|
|
560
|
+
const jitter = baseDelay * JITTER_FACTOR * (2 * Math.random() - 1);
|
|
561
|
+
await new Promise((r) => setTimeout(r, baseDelay + jitter));
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
throw new RateLimitError();
|
|
565
|
+
}
|
|
566
|
+
if (response.status >= 500) {
|
|
567
|
+
throw new ServerError(`Server error (HTTP ${response.status}): ${bodyText.slice(0, 500)}`);
|
|
568
|
+
}
|
|
569
|
+
throw new ElnoraError(`API error (HTTP ${response.status}): ${bodyText.slice(0, 500)}`, {
|
|
570
|
+
code: `HTTP_${response.status}`
|
|
571
|
+
});
|
|
572
|
+
} catch (err) {
|
|
573
|
+
if (err instanceof ElnoraError) throw err;
|
|
574
|
+
if (err instanceof DOMException && err.name === "TimeoutError") {
|
|
575
|
+
throw new ElnoraError("Request timed out", {
|
|
576
|
+
code: "TIMEOUT",
|
|
577
|
+
suggestion: "Check your internet connection and try again."
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
throw new ElnoraError(`Network error: ${scrub(String(err))}`, {
|
|
581
|
+
code: "NETWORK_ERROR",
|
|
582
|
+
suggestion: "Check your internet connection and try again."
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
throw new RateLimitError("Rate limit exceeded after retries");
|
|
587
|
+
}
|
|
588
|
+
// Convenience methods
|
|
589
|
+
async get(endpoint, opts) {
|
|
590
|
+
return this.request(endpoint, { method: "GET", ...opts });
|
|
591
|
+
}
|
|
592
|
+
async post(endpoint, body, opts) {
|
|
593
|
+
return this.request(endpoint, { method: "POST", body, ...opts });
|
|
594
|
+
}
|
|
595
|
+
async put(endpoint, body, opts) {
|
|
596
|
+
return this.request(endpoint, { method: "PUT", body, ...opts });
|
|
597
|
+
}
|
|
598
|
+
async patch(endpoint, body, opts) {
|
|
599
|
+
return this.request(endpoint, { method: "PATCH", body, ...opts });
|
|
600
|
+
}
|
|
601
|
+
async del(endpoint, opts) {
|
|
602
|
+
return this.request(endpoint, { method: "DELETE", ...opts });
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// src/mcp/server.ts
|
|
609
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
610
|
+
function createElnoraServer(registry2, getContext) {
|
|
611
|
+
const server = new McpServer({ name: "elnora", version: VERSION });
|
|
612
|
+
for (const command of registry2.all()) {
|
|
613
|
+
const toolName = `elnora_${command.name.replace(/\./g, "_")}`;
|
|
614
|
+
server.registerTool(
|
|
615
|
+
toolName,
|
|
616
|
+
{
|
|
617
|
+
title: toolName,
|
|
618
|
+
description: command.description,
|
|
619
|
+
inputSchema: command.inputSchema,
|
|
620
|
+
annotations: {
|
|
621
|
+
readOnlyHint: command.annotations?.readOnlyHint ?? false,
|
|
622
|
+
destructiveHint: command.annotations?.destructiveHint ?? false,
|
|
623
|
+
idempotentHint: command.annotations?.idempotentHint ?? false
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
async (params) => {
|
|
627
|
+
try {
|
|
628
|
+
const ctx = getContext();
|
|
629
|
+
const client = new ElnoraApiClient(ctx.apiKey ?? ctx.bearerToken ?? "");
|
|
630
|
+
const input = command.inputSchema.parse(params);
|
|
631
|
+
const commandCtx = {
|
|
632
|
+
client,
|
|
633
|
+
profileName: "mcp",
|
|
634
|
+
mode: "mcp",
|
|
635
|
+
output: { format: "json", compact: false }
|
|
636
|
+
};
|
|
637
|
+
const result = await command.execute(input, commandCtx);
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: "text", text: JSON.stringify(result) }]
|
|
640
|
+
};
|
|
641
|
+
} catch (error) {
|
|
642
|
+
const payload = formatErrorPayload(error instanceof Error ? error : new Error(String(error)));
|
|
643
|
+
return {
|
|
644
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
645
|
+
isError: true
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
);
|
|
650
|
+
}
|
|
651
|
+
return server;
|
|
652
|
+
}
|
|
653
|
+
var init_server = __esm({
|
|
654
|
+
"src/mcp/server.ts"() {
|
|
655
|
+
"use strict";
|
|
656
|
+
init_client();
|
|
657
|
+
init_config();
|
|
658
|
+
init_errors();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// src/mcp/stdio-server.ts
|
|
663
|
+
var stdio_server_exports = {};
|
|
664
|
+
__export(stdio_server_exports, {
|
|
665
|
+
startStdioServer: () => startStdioServer
|
|
666
|
+
});
|
|
667
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
668
|
+
async function startStdioServer(registry2) {
|
|
669
|
+
const apiKey = resolveApiKey();
|
|
670
|
+
const getContext = () => ({
|
|
671
|
+
apiKey,
|
|
672
|
+
clientId: "stdio",
|
|
673
|
+
scopes: ["*"]
|
|
674
|
+
});
|
|
675
|
+
const server = createElnoraServer(registry2, getContext);
|
|
676
|
+
const transport = new StdioServerTransport();
|
|
677
|
+
await server.connect(transport);
|
|
678
|
+
}
|
|
679
|
+
var init_stdio_server = __esm({
|
|
680
|
+
"src/mcp/stdio-server.ts"() {
|
|
681
|
+
"use strict";
|
|
682
|
+
init_profiles();
|
|
683
|
+
init_server();
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
// src/mcp/http-server.ts
|
|
688
|
+
var http_server_exports = {};
|
|
689
|
+
__export(http_server_exports, {
|
|
690
|
+
startHttpServer: () => startHttpServer
|
|
691
|
+
});
|
|
692
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
693
|
+
import express from "express";
|
|
694
|
+
import rateLimit from "express-rate-limit";
|
|
695
|
+
async function startHttpServer(registry2, config) {
|
|
696
|
+
const app = express();
|
|
697
|
+
app.set("trust proxy", 1);
|
|
698
|
+
app.use((_req, res, next) => {
|
|
699
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
700
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
701
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
702
|
+
res.setHeader("Strict-Transport-Security", "max-age=63072000; includeSubDomains");
|
|
703
|
+
res.setHeader("Cache-Control", "no-store");
|
|
704
|
+
next();
|
|
705
|
+
});
|
|
706
|
+
app.use(express.json({ limit: "1mb" }));
|
|
707
|
+
app.get("/health", (_req, res) => {
|
|
708
|
+
res.json({ status: "ok", service: "elnora-mcp" });
|
|
709
|
+
});
|
|
710
|
+
app.use(
|
|
711
|
+
"/mcp",
|
|
712
|
+
rateLimit({
|
|
713
|
+
windowMs: 6e4,
|
|
714
|
+
limit: 150,
|
|
715
|
+
standardHeaders: "draft-7",
|
|
716
|
+
legacyHeaders: false,
|
|
717
|
+
keyGenerator: (req) => {
|
|
718
|
+
const apiKey = req.headers["x-api-key"];
|
|
719
|
+
if (apiKey) return `key:${fnvHash(String(apiKey))}`;
|
|
720
|
+
return `ip:${req.ip ?? "unknown"}`;
|
|
721
|
+
},
|
|
722
|
+
message: { error: "rate_limit_exceeded", error_description: "Too many requests. Please retry later." }
|
|
723
|
+
})
|
|
724
|
+
);
|
|
725
|
+
app.post("/mcp", async (req, res) => {
|
|
726
|
+
const apiKey = req.headers["x-api-key"];
|
|
727
|
+
if (!apiKey) {
|
|
728
|
+
res.status(401).json({ error: "missing_api_key", error_description: "X-API-Key header required" });
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const userId = await validateApiKeyWithPlatform(apiKey, config);
|
|
732
|
+
if (!userId) {
|
|
733
|
+
res.status(401).json({ error: "invalid_api_key", error_description: "API key rejected by platform" });
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
let server;
|
|
737
|
+
let transport;
|
|
738
|
+
try {
|
|
739
|
+
const getContext = () => ({
|
|
740
|
+
apiKey,
|
|
741
|
+
clientId: `apikey:${userId}`,
|
|
742
|
+
scopes: ["*"]
|
|
743
|
+
});
|
|
744
|
+
server = createElnoraServer(registry2, getContext);
|
|
745
|
+
transport = new StreamableHTTPServerTransport({
|
|
746
|
+
sessionIdGenerator: void 0,
|
|
747
|
+
enableJsonResponse: true
|
|
748
|
+
});
|
|
749
|
+
res.on("close", () => {
|
|
750
|
+
transport?.close().catch(() => {
|
|
751
|
+
});
|
|
752
|
+
server?.close().catch(() => {
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
await server.connect(transport);
|
|
756
|
+
await transport.handleRequest(req, res, req.body);
|
|
757
|
+
} catch (error) {
|
|
758
|
+
transport?.close().catch(() => {
|
|
759
|
+
});
|
|
760
|
+
server?.close().catch(() => {
|
|
761
|
+
});
|
|
762
|
+
console.error("MCP request error:", error);
|
|
763
|
+
if (!res.headersSent) {
|
|
764
|
+
res.status(500).json({ error: "internal_error", error_description: "MCP request failed" });
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
app.get("/mcp", (_req, res) => {
|
|
769
|
+
res.status(405).json({ error: "method_not_allowed", error_description: "Stateless server \u2014 use POST" });
|
|
770
|
+
});
|
|
771
|
+
app.delete("/mcp", (_req, res) => {
|
|
772
|
+
res.status(405).json({ error: "method_not_allowed", error_description: "Sessions not supported" });
|
|
773
|
+
});
|
|
774
|
+
const httpServer = app.listen(config.port, config.host, () => {
|
|
775
|
+
console.error(`Elnora MCP server running on http://${config.host}:${config.port}/mcp`);
|
|
776
|
+
console.error(`Health check: http://localhost:${config.port}/health`);
|
|
777
|
+
console.error("API Key auth: Send X-API-Key header");
|
|
778
|
+
});
|
|
779
|
+
const shutdown = (signal) => {
|
|
780
|
+
console.error(`${signal} received \u2014 shutting down gracefully`);
|
|
781
|
+
httpServer.close(() => process.exit(0));
|
|
782
|
+
setTimeout(() => process.exit(1), 25e3).unref();
|
|
783
|
+
};
|
|
784
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
785
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
786
|
+
httpServer.on("error", (err) => {
|
|
787
|
+
if (err.code === "EADDRINUSE") {
|
|
788
|
+
console.error(`Port ${config.port} is already in use`);
|
|
789
|
+
} else {
|
|
790
|
+
console.error("HTTP server error:", err);
|
|
791
|
+
}
|
|
792
|
+
process.exit(1);
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
function fnvHash(value) {
|
|
796
|
+
let h = 2166136261;
|
|
797
|
+
for (let i = 0; i < value.length; i++) {
|
|
798
|
+
h ^= value.charCodeAt(i);
|
|
799
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
800
|
+
}
|
|
801
|
+
return h.toString(16).padStart(8, "0");
|
|
802
|
+
}
|
|
803
|
+
async function validateApiKeyWithPlatform(apiKey, config) {
|
|
804
|
+
try {
|
|
805
|
+
const response = await fetch(config.tokenValidationUrl, {
|
|
806
|
+
method: "POST",
|
|
807
|
+
headers: { "Content-Type": "application/json", "X-Service-Key": config.mcpServiceKey },
|
|
808
|
+
body: JSON.stringify({ token: apiKey }),
|
|
809
|
+
signal: AbortSignal.timeout(1e4)
|
|
810
|
+
});
|
|
811
|
+
const data = await response.json();
|
|
812
|
+
if (data.valid && data.user_id) return String(data.user_id);
|
|
813
|
+
return null;
|
|
814
|
+
} catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
var init_http_server = __esm({
|
|
819
|
+
"src/mcp/http-server.ts"() {
|
|
820
|
+
"use strict";
|
|
821
|
+
init_server();
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
// src/adapters/cli.ts
|
|
826
|
+
init_client();
|
|
827
|
+
init_errors();
|
|
828
|
+
import { Command } from "commander";
|
|
829
|
+
|
|
830
|
+
// src/lib/output.ts
|
|
831
|
+
init_errors();
|
|
832
|
+
function filterFields(data, fields) {
|
|
833
|
+
if (Array.isArray(data)) {
|
|
834
|
+
return data.map((row) => {
|
|
835
|
+
if (typeof row !== "object" || row === null) return row;
|
|
836
|
+
const filtered = {};
|
|
837
|
+
for (const f of fields) {
|
|
838
|
+
if (f in row) filtered[f] = row[f];
|
|
839
|
+
}
|
|
840
|
+
return filtered;
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
if (typeof data === "object" && data !== null) {
|
|
844
|
+
const obj = data;
|
|
845
|
+
if ("items" in obj && Array.isArray(obj.items)) {
|
|
846
|
+
return { ...obj, items: filterFields(obj.items, fields) };
|
|
847
|
+
}
|
|
848
|
+
const filtered = {};
|
|
849
|
+
for (const f of fields) {
|
|
850
|
+
if (f in obj) filtered[f] = obj[f];
|
|
851
|
+
}
|
|
852
|
+
return filtered;
|
|
853
|
+
}
|
|
854
|
+
return data;
|
|
855
|
+
}
|
|
856
|
+
function toRows(data) {
|
|
857
|
+
if (Array.isArray(data)) return data;
|
|
858
|
+
if (typeof data === "object" && data !== null) {
|
|
859
|
+
const obj = data;
|
|
860
|
+
if ("items" in obj && Array.isArray(obj.items)) return obj.items;
|
|
861
|
+
return [obj];
|
|
862
|
+
}
|
|
863
|
+
return [{ value: data }];
|
|
864
|
+
}
|
|
865
|
+
function toCsv(rows) {
|
|
866
|
+
if (rows.length === 0) return "";
|
|
867
|
+
const keys = [];
|
|
868
|
+
const seen = /* @__PURE__ */ new Set();
|
|
869
|
+
for (const row of rows) {
|
|
870
|
+
for (const k of Object.keys(row)) {
|
|
871
|
+
if (!seen.has(k)) {
|
|
872
|
+
keys.push(k);
|
|
873
|
+
seen.add(k);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const csvEscape = (v) => {
|
|
878
|
+
const s = String(v ?? "");
|
|
879
|
+
return s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
|
|
880
|
+
};
|
|
881
|
+
const lines = [keys.join(",")];
|
|
882
|
+
for (const row of rows) {
|
|
883
|
+
lines.push(keys.map((k) => csvEscape(row[k])).join(","));
|
|
884
|
+
}
|
|
885
|
+
return lines.join("\n");
|
|
886
|
+
}
|
|
887
|
+
function formatOutput(data, options) {
|
|
888
|
+
let output = scrubData(data);
|
|
889
|
+
if (options.fields) {
|
|
890
|
+
output = filterFields(output, options.fields);
|
|
891
|
+
}
|
|
892
|
+
if (options.format === "csv") {
|
|
893
|
+
return toCsv(toRows(output));
|
|
894
|
+
}
|
|
895
|
+
if (options.compact) {
|
|
896
|
+
return JSON.stringify(output);
|
|
897
|
+
}
|
|
898
|
+
return JSON.stringify(output, null, 2);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// src/adapters/cli.ts
|
|
902
|
+
function unwrapZodDef(schema) {
|
|
903
|
+
const s = schema;
|
|
904
|
+
const description = s.description ?? "";
|
|
905
|
+
let required = !s.isOptional?.();
|
|
906
|
+
let defaultValue;
|
|
907
|
+
let isBoolean = false;
|
|
908
|
+
let isNumber = false;
|
|
909
|
+
let choices;
|
|
910
|
+
const def = s._zod?.def ?? s._def;
|
|
911
|
+
const type = def?.type ?? "";
|
|
912
|
+
if (type === "default") {
|
|
913
|
+
defaultValue = def.defaultValue;
|
|
914
|
+
required = false;
|
|
915
|
+
const innerType = def.innerType?._zod?.def?.type ?? def.innerType?._def?.type ?? "";
|
|
916
|
+
if (innerType === "boolean") isBoolean = true;
|
|
917
|
+
if (innerType === "number" || innerType === "int") isNumber = true;
|
|
918
|
+
} else if (type === "optional" || type === "nullable") {
|
|
919
|
+
required = false;
|
|
920
|
+
const innerType = def.innerType?._zod?.def?.type ?? def.innerType?._def?.type ?? "";
|
|
921
|
+
if (innerType === "boolean") isBoolean = true;
|
|
922
|
+
if (innerType === "number" || innerType === "int") isNumber = true;
|
|
923
|
+
} else if (type === "boolean") {
|
|
924
|
+
isBoolean = true;
|
|
925
|
+
} else if (type === "number" || type === "int") {
|
|
926
|
+
isNumber = true;
|
|
927
|
+
} else if (type === "enum") {
|
|
928
|
+
const entries = def.entries;
|
|
929
|
+
if (entries && typeof entries === "object") {
|
|
930
|
+
choices = Object.values(entries);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return { description, required, defaultValue, isBoolean, isNumber, choices };
|
|
934
|
+
}
|
|
935
|
+
function zodToCommanderOptions(schema) {
|
|
936
|
+
const options = [];
|
|
937
|
+
const s = schema;
|
|
938
|
+
const def = s._zod?.def ?? s._def;
|
|
939
|
+
const shape = def?.shape;
|
|
940
|
+
if (!shape || typeof shape !== "object") return options;
|
|
941
|
+
const shapeObj = shape;
|
|
942
|
+
for (const [key, fieldSchema] of Object.entries(shapeObj)) {
|
|
943
|
+
const meta = unwrapZodDef(fieldSchema);
|
|
944
|
+
const kebabKey = camelToKebab(key);
|
|
945
|
+
const isPositional = meta.required && !meta.isBoolean && !meta.choices && key.endsWith("Id");
|
|
946
|
+
if (isPositional) {
|
|
947
|
+
options.push({
|
|
948
|
+
flags: `<${kebabKey}>`,
|
|
949
|
+
description: meta.description,
|
|
950
|
+
required: true,
|
|
951
|
+
isArgument: true
|
|
952
|
+
});
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (meta.isBoolean) {
|
|
956
|
+
options.push({
|
|
957
|
+
flags: `--${kebabKey}`,
|
|
958
|
+
description: meta.description,
|
|
959
|
+
required: false,
|
|
960
|
+
defaultValue: meta.defaultValue ?? false
|
|
961
|
+
});
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
const valuePlaceholder = meta.required ? ` <value>` : ` [value]`;
|
|
965
|
+
options.push({
|
|
966
|
+
flags: `--${kebabKey}${valuePlaceholder}`,
|
|
967
|
+
description: meta.description,
|
|
968
|
+
required: meta.required,
|
|
969
|
+
defaultValue: meta.defaultValue,
|
|
970
|
+
choices: meta.choices
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
return options;
|
|
974
|
+
}
|
|
975
|
+
function camelToKebab(str) {
|
|
976
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
977
|
+
}
|
|
978
|
+
function kebabToCamel(str) {
|
|
979
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
980
|
+
}
|
|
981
|
+
function buildProgram(registry2) {
|
|
982
|
+
const program2 = new Command().name("elnora").description("Elnora AI Platform CLI").version("0.0.1").option("--compact", "Token-efficient minimal output", false).option("--output <format>", "Output format (json, csv)", "json").option("--fields <fields>", "Comma-separated fields to include").option("--profile <name>", "Named profile to use").option("--json", "Force JSON output", false);
|
|
983
|
+
for (const group of registry2.groups()) {
|
|
984
|
+
const groupCmd = new Command(group).description(`Manage ${group}`);
|
|
985
|
+
const commands = registry2.byGroup(group);
|
|
986
|
+
for (const cmd of commands) {
|
|
987
|
+
const subName = cmd.name.split(".").slice(1).join("-");
|
|
988
|
+
const sub = new Command(subName).description(cmd.description);
|
|
989
|
+
const opts = zodToCommanderOptions(cmd.inputSchema);
|
|
990
|
+
for (const opt of opts) {
|
|
991
|
+
if (opt.isArgument) {
|
|
992
|
+
sub.argument(opt.flags, opt.description);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
for (const opt of opts) {
|
|
996
|
+
if (opt.isArgument) continue;
|
|
997
|
+
if (opt.choices) {
|
|
998
|
+
sub.option(opt.flags, opt.description, opt.defaultValue);
|
|
999
|
+
} else {
|
|
1000
|
+
sub.option(opt.flags, opt.description, opt.defaultValue);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
sub.action(async (...actionArgs) => {
|
|
1004
|
+
try {
|
|
1005
|
+
const parentOpts = program2.opts();
|
|
1006
|
+
const commanderOpts = actionArgs[actionArgs.length - 2];
|
|
1007
|
+
const positionalArgs = actionArgs.slice(0, -2);
|
|
1008
|
+
const input = { ...commanderOpts };
|
|
1009
|
+
const argDefs = opts.filter((o) => o.isArgument);
|
|
1010
|
+
for (let i = 0; i < argDefs.length; i++) {
|
|
1011
|
+
const fieldName = kebabToCamel(argDefs[i].flags.replace(/[<>]/g, ""));
|
|
1012
|
+
if (i < positionalArgs.length) {
|
|
1013
|
+
input[fieldName] = positionalArgs[i];
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
for (const opt of opts) {
|
|
1017
|
+
if (opt.isArgument) continue;
|
|
1018
|
+
const fieldName = kebabToCamel(opt.flags.replace(/--/, "").replace(/ .+/, ""));
|
|
1019
|
+
if (fieldName in input && typeof input[fieldName] === "string") {
|
|
1020
|
+
const numVal = Number(input[fieldName]);
|
|
1021
|
+
if (!Number.isNaN(numVal) && opt.defaultValue !== void 0 && typeof opt.defaultValue === "number") {
|
|
1022
|
+
input[fieldName] = numVal;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
const profileName = parentOpts.profile ?? "default";
|
|
1027
|
+
const client = ElnoraApiClient.fromEnv(profileName);
|
|
1028
|
+
const ctx = {
|
|
1029
|
+
client,
|
|
1030
|
+
profileName,
|
|
1031
|
+
mode: "cli",
|
|
1032
|
+
output: {
|
|
1033
|
+
format: (parentOpts.json ? "json" : parentOpts.output) ?? "json",
|
|
1034
|
+
compact: parentOpts.compact ?? false,
|
|
1035
|
+
fields: parentOpts.fields ? parentOpts.fields.split(",").map((f) => f.trim()) : void 0
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
const parsed = cmd.inputSchema.parse(input);
|
|
1039
|
+
const result = await cmd.execute(parsed, ctx);
|
|
1040
|
+
const formatted = formatOutput(result, {
|
|
1041
|
+
format: ctx.output.format,
|
|
1042
|
+
compact: ctx.output.compact,
|
|
1043
|
+
fields: ctx.output.fields
|
|
1044
|
+
});
|
|
1045
|
+
process.stdout.write(`${formatted}
|
|
1046
|
+
`);
|
|
1047
|
+
} catch (err) {
|
|
1048
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1049
|
+
const payload = formatErrorPayload(error);
|
|
1050
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
1051
|
+
`);
|
|
1052
|
+
process.exit(getExitCode(error));
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
groupCmd.addCommand(sub);
|
|
1056
|
+
}
|
|
1057
|
+
program2.addCommand(groupCmd);
|
|
1058
|
+
}
|
|
1059
|
+
return program2;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// src/commands/account/accept-terms.ts
|
|
1063
|
+
import { z } from "zod";
|
|
1064
|
+
var inputSchema = z.object({
|
|
1065
|
+
documentVersionId: z.string().describe("Document version ID to accept")
|
|
1066
|
+
});
|
|
1067
|
+
var accountAcceptTerms = {
|
|
1068
|
+
name: "account.acceptTerms",
|
|
1069
|
+
group: "account",
|
|
1070
|
+
description: "Accept a user agreement / terms of service",
|
|
1071
|
+
inputSchema,
|
|
1072
|
+
outputSchema: z.any(),
|
|
1073
|
+
async execute(input, ctx) {
|
|
1074
|
+
return ctx.client.post("user_agreement", {
|
|
1075
|
+
documentVersionId: input.documentVersionId
|
|
1076
|
+
});
|
|
1077
|
+
},
|
|
1078
|
+
formatOutput(output, format) {
|
|
1079
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1080
|
+
return JSON.stringify(output, null, 2);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
|
|
1084
|
+
// src/commands/account/add-legal-doc.ts
|
|
1085
|
+
import { z as z2 } from "zod";
|
|
1086
|
+
var inputSchema2 = z2.object({
|
|
1087
|
+
documentType: z2.string().min(1).describe("Document type"),
|
|
1088
|
+
version: z2.string().min(1).describe("Document version string"),
|
|
1089
|
+
content: z2.string().min(1).describe("Document content"),
|
|
1090
|
+
effectiveDate: z2.string().optional().describe("Effective date (ISO 8601)")
|
|
1091
|
+
});
|
|
1092
|
+
var accountAddLegalDoc = {
|
|
1093
|
+
name: "account.addLegalDoc",
|
|
1094
|
+
group: "account",
|
|
1095
|
+
description: "Add a legal document version (SystemAdmin only)",
|
|
1096
|
+
inputSchema: inputSchema2,
|
|
1097
|
+
outputSchema: z2.any(),
|
|
1098
|
+
async execute(input, ctx) {
|
|
1099
|
+
const body = {
|
|
1100
|
+
documentType: input.documentType,
|
|
1101
|
+
version: input.version,
|
|
1102
|
+
content: input.content
|
|
1103
|
+
};
|
|
1104
|
+
if (input.effectiveDate) body.effectiveDate = input.effectiveDate;
|
|
1105
|
+
return ctx.client.post("legal_doc_version", body);
|
|
1106
|
+
},
|
|
1107
|
+
formatOutput(output, format) {
|
|
1108
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1109
|
+
return JSON.stringify(output, null, 2);
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
// src/commands/account/agreements.ts
|
|
1114
|
+
import { z as z3 } from "zod";
|
|
1115
|
+
var inputSchema3 = z3.object({});
|
|
1116
|
+
var accountAgreements = {
|
|
1117
|
+
name: "account.agreements",
|
|
1118
|
+
group: "account",
|
|
1119
|
+
description: "List user agreements",
|
|
1120
|
+
inputSchema: inputSchema3,
|
|
1121
|
+
outputSchema: z3.any(),
|
|
1122
|
+
annotations: { readOnlyHint: true },
|
|
1123
|
+
async execute(_input, ctx) {
|
|
1124
|
+
return ctx.client.get("user_agreements");
|
|
1125
|
+
},
|
|
1126
|
+
formatOutput(output, format) {
|
|
1127
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1128
|
+
return JSON.stringify(output, null, 2);
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
// src/commands/account/delete.ts
|
|
1133
|
+
import { z as z4 } from "zod";
|
|
1134
|
+
|
|
1135
|
+
// src/commands/_shared/confirm.ts
|
|
1136
|
+
import { createInterface } from "node:readline/promises";
|
|
1137
|
+
async function confirmDestructive(prompt, requiredInput) {
|
|
1138
|
+
if (!process.stdin.isTTY) return false;
|
|
1139
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
1140
|
+
try {
|
|
1141
|
+
const answer = await rl.question(prompt);
|
|
1142
|
+
if (requiredInput) return answer.trim() === requiredInput;
|
|
1143
|
+
return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
|
|
1144
|
+
} finally {
|
|
1145
|
+
rl.close();
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// src/commands/account/delete.ts
|
|
1150
|
+
var inputSchema4 = z4.object({
|
|
1151
|
+
yes: z4.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
1152
|
+
});
|
|
1153
|
+
var accountDelete = {
|
|
1154
|
+
name: "account.delete",
|
|
1155
|
+
group: "account",
|
|
1156
|
+
description: "Delete your own account",
|
|
1157
|
+
inputSchema: inputSchema4,
|
|
1158
|
+
outputSchema: z4.any(),
|
|
1159
|
+
annotations: { destructiveHint: true },
|
|
1160
|
+
async execute(input, ctx) {
|
|
1161
|
+
if (!input.yes) {
|
|
1162
|
+
const confirmed = await confirmDestructive("Type DELETE to confirm account deletion: ", "DELETE");
|
|
1163
|
+
if (!confirmed) {
|
|
1164
|
+
return { deleted: false };
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
await ctx.client.del("account_delete");
|
|
1168
|
+
return { deleted: true };
|
|
1169
|
+
},
|
|
1170
|
+
formatOutput(output, format) {
|
|
1171
|
+
if (format === "compact") return String(output.deleted);
|
|
1172
|
+
return JSON.stringify(output, null, 2);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
|
|
1176
|
+
// src/commands/account/delete-legal-doc.ts
|
|
1177
|
+
import { z as z5 } from "zod";
|
|
1178
|
+
var inputSchema5 = z5.object({
|
|
1179
|
+
versionId: z5.string().describe("Legal document version ID"),
|
|
1180
|
+
yes: z5.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
1181
|
+
});
|
|
1182
|
+
var accountDeleteLegalDoc = {
|
|
1183
|
+
name: "account.deleteLegalDoc",
|
|
1184
|
+
group: "account",
|
|
1185
|
+
description: "Delete a legal document version (SystemAdmin only)",
|
|
1186
|
+
inputSchema: inputSchema5,
|
|
1187
|
+
outputSchema: z5.any(),
|
|
1188
|
+
annotations: { destructiveHint: true },
|
|
1189
|
+
async execute(input, ctx) {
|
|
1190
|
+
if (!input.yes) {
|
|
1191
|
+
const confirmed = await confirmDestructive(
|
|
1192
|
+
`Are you sure you want to delete legal document version ${input.versionId}? [y/N] `
|
|
1193
|
+
);
|
|
1194
|
+
if (!confirmed) {
|
|
1195
|
+
return { deleted: false, versionId: input.versionId };
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
await ctx.client.del("legal_doc_version_id", {
|
|
1199
|
+
pathParams: { id: input.versionId }
|
|
1200
|
+
});
|
|
1201
|
+
return { deleted: true, versionId: input.versionId };
|
|
1202
|
+
},
|
|
1203
|
+
formatOutput(output, format) {
|
|
1204
|
+
if (format === "compact") return output.versionId;
|
|
1205
|
+
return JSON.stringify(output, null, 2);
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// src/commands/account/get.ts
|
|
1210
|
+
import { z as z6 } from "zod";
|
|
1211
|
+
var inputSchema6 = z6.object({
|
|
1212
|
+
userId: z6.string().describe("User ID")
|
|
1213
|
+
});
|
|
1214
|
+
var accountGet = {
|
|
1215
|
+
name: "account.get",
|
|
1216
|
+
group: "account",
|
|
1217
|
+
description: "Get account details for a user",
|
|
1218
|
+
inputSchema: inputSchema6,
|
|
1219
|
+
outputSchema: z6.any(),
|
|
1220
|
+
annotations: { readOnlyHint: true },
|
|
1221
|
+
async execute(input, ctx) {
|
|
1222
|
+
return ctx.client.get("account_user", {
|
|
1223
|
+
pathParams: { id: input.userId }
|
|
1224
|
+
});
|
|
1225
|
+
},
|
|
1226
|
+
formatOutput(output, format) {
|
|
1227
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1228
|
+
return JSON.stringify(output, null, 2);
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
// src/commands/account/update.ts
|
|
1233
|
+
init_errors();
|
|
1234
|
+
import { z as z7 } from "zod";
|
|
1235
|
+
var inputSchema7 = z7.object({
|
|
1236
|
+
userId: z7.string().describe("User ID"),
|
|
1237
|
+
firstName: z7.string().optional().describe("First name"),
|
|
1238
|
+
lastName: z7.string().optional().describe("Last name")
|
|
1239
|
+
});
|
|
1240
|
+
var accountUpdate = {
|
|
1241
|
+
name: "account.update",
|
|
1242
|
+
group: "account",
|
|
1243
|
+
description: "Update account details for a user",
|
|
1244
|
+
inputSchema: inputSchema7,
|
|
1245
|
+
outputSchema: z7.any(),
|
|
1246
|
+
async execute(input, ctx) {
|
|
1247
|
+
const body = {};
|
|
1248
|
+
if (input.firstName !== void 0) body.firstName = input.firstName;
|
|
1249
|
+
if (input.lastName !== void 0) body.lastName = input.lastName;
|
|
1250
|
+
if (Object.keys(body).length === 0) {
|
|
1251
|
+
throw new ValidationError("At least one of --firstName or --lastName is required");
|
|
1252
|
+
}
|
|
1253
|
+
return ctx.client.patch("account_user", body, {
|
|
1254
|
+
pathParams: { id: input.userId }
|
|
1255
|
+
});
|
|
1256
|
+
},
|
|
1257
|
+
formatOutput(output, format) {
|
|
1258
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1259
|
+
return JSON.stringify(output, null, 2);
|
|
1260
|
+
}
|
|
1261
|
+
};
|
|
1262
|
+
|
|
1263
|
+
// src/commands/account/update-legal-doc.ts
|
|
1264
|
+
init_errors();
|
|
1265
|
+
import { z as z8 } from "zod";
|
|
1266
|
+
var inputSchema8 = z8.object({
|
|
1267
|
+
versionId: z8.string().describe("Legal document version ID"),
|
|
1268
|
+
content: z8.string().optional().describe("Updated content"),
|
|
1269
|
+
effectiveDate: z8.string().optional().describe("Updated effective date (ISO 8601)")
|
|
1270
|
+
});
|
|
1271
|
+
var accountUpdateLegalDoc = {
|
|
1272
|
+
name: "account.updateLegalDoc",
|
|
1273
|
+
group: "account",
|
|
1274
|
+
description: "Update a legal document version (SystemAdmin only)",
|
|
1275
|
+
inputSchema: inputSchema8,
|
|
1276
|
+
outputSchema: z8.any(),
|
|
1277
|
+
async execute(input, ctx) {
|
|
1278
|
+
const body = {};
|
|
1279
|
+
if (input.content !== void 0) body.content = input.content;
|
|
1280
|
+
if (input.effectiveDate !== void 0) body.effectiveDate = input.effectiveDate;
|
|
1281
|
+
if (Object.keys(body).length === 0) {
|
|
1282
|
+
throw new ValidationError("At least one of --content or --effectiveDate is required");
|
|
1283
|
+
}
|
|
1284
|
+
return ctx.client.patch("legal_doc_version_id", body, {
|
|
1285
|
+
pathParams: { id: input.versionId }
|
|
1286
|
+
});
|
|
1287
|
+
},
|
|
1288
|
+
formatOutput(output, format) {
|
|
1289
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1290
|
+
return JSON.stringify(output, null, 2);
|
|
1291
|
+
}
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
// src/commands/account/users.ts
|
|
1295
|
+
import { z as z9 } from "zod";
|
|
1296
|
+
var inputSchema9 = z9.object({
|
|
1297
|
+
state: z9.enum(["Active", "Pending", "Deleted"]).optional().describe("Filter by user state"),
|
|
1298
|
+
refCode: z9.string().optional().describe("Filter by referral code")
|
|
1299
|
+
});
|
|
1300
|
+
var accountUsers = {
|
|
1301
|
+
name: "account.users",
|
|
1302
|
+
group: "account",
|
|
1303
|
+
description: "List all users (admin)",
|
|
1304
|
+
inputSchema: inputSchema9,
|
|
1305
|
+
outputSchema: z9.any(),
|
|
1306
|
+
annotations: { readOnlyHint: true },
|
|
1307
|
+
async execute(input, ctx) {
|
|
1308
|
+
const queryParams = {};
|
|
1309
|
+
if (input.state) queryParams.state = input.state;
|
|
1310
|
+
if (input.refCode) queryParams.refCode = input.refCode;
|
|
1311
|
+
return ctx.client.get("account_users", {
|
|
1312
|
+
queryParams: Object.keys(queryParams).length > 0 ? queryParams : void 0
|
|
1313
|
+
});
|
|
1314
|
+
},
|
|
1315
|
+
formatOutput(output, format) {
|
|
1316
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1317
|
+
return JSON.stringify(output, null, 2);
|
|
1318
|
+
}
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
// src/commands/account/index.ts
|
|
1322
|
+
function registerAccountCommands() {
|
|
1323
|
+
return [
|
|
1324
|
+
accountGet,
|
|
1325
|
+
accountUpdate,
|
|
1326
|
+
accountAgreements,
|
|
1327
|
+
accountAcceptTerms,
|
|
1328
|
+
accountDelete,
|
|
1329
|
+
accountUsers,
|
|
1330
|
+
accountAddLegalDoc,
|
|
1331
|
+
accountUpdateLegalDoc,
|
|
1332
|
+
accountDeleteLegalDoc
|
|
1333
|
+
];
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/commands/api-keys/create.ts
|
|
1337
|
+
import { z as z10 } from "zod";
|
|
1338
|
+
var inputSchema10 = z10.object({
|
|
1339
|
+
name: z10.string().min(1).describe("API key name"),
|
|
1340
|
+
scopes: z10.string().optional().describe("Comma-separated list of scopes")
|
|
1341
|
+
});
|
|
1342
|
+
var apiKeysCreate = {
|
|
1343
|
+
name: "api-keys.create",
|
|
1344
|
+
group: "api-keys",
|
|
1345
|
+
description: "Create a new API key",
|
|
1346
|
+
inputSchema: inputSchema10,
|
|
1347
|
+
outputSchema: z10.any(),
|
|
1348
|
+
async execute(input, ctx) {
|
|
1349
|
+
const parsedScopes = input.scopes ? input.scopes.split(",").map((s) => s.trim()).filter((s) => s.length > 0) : void 0;
|
|
1350
|
+
return ctx.client.post("api_keys", { name: input.name, scopes: parsedScopes });
|
|
1351
|
+
},
|
|
1352
|
+
formatOutput(output, format) {
|
|
1353
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1354
|
+
return JSON.stringify(output, null, 2);
|
|
1355
|
+
}
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
// src/commands/api-keys/get-policy.ts
|
|
1359
|
+
import { z as z11 } from "zod";
|
|
1360
|
+
var inputSchema11 = z11.object({});
|
|
1361
|
+
var apiKeysGetPolicy = {
|
|
1362
|
+
name: "api-keys.getPolicy",
|
|
1363
|
+
group: "api-keys",
|
|
1364
|
+
description: "Get the API key creation policy",
|
|
1365
|
+
inputSchema: inputSchema11,
|
|
1366
|
+
outputSchema: z11.any(),
|
|
1367
|
+
annotations: { readOnlyHint: true },
|
|
1368
|
+
async execute(_input, ctx) {
|
|
1369
|
+
return ctx.client.get("api_key_policy");
|
|
1370
|
+
},
|
|
1371
|
+
formatOutput(output, format) {
|
|
1372
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1373
|
+
return JSON.stringify(output, null, 2);
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
// src/commands/api-keys/list.ts
|
|
1378
|
+
import { z as z12 } from "zod";
|
|
1379
|
+
var inputSchema12 = z12.object({});
|
|
1380
|
+
var apiKeysList = {
|
|
1381
|
+
name: "api-keys.list",
|
|
1382
|
+
group: "api-keys",
|
|
1383
|
+
description: "List all API keys",
|
|
1384
|
+
inputSchema: inputSchema12,
|
|
1385
|
+
outputSchema: z12.any(),
|
|
1386
|
+
annotations: { readOnlyHint: true },
|
|
1387
|
+
async execute(_input, ctx) {
|
|
1388
|
+
return ctx.client.get("api_keys");
|
|
1389
|
+
},
|
|
1390
|
+
formatOutput(output, format) {
|
|
1391
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1392
|
+
return JSON.stringify(output, null, 2);
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
// src/commands/api-keys/revoke.ts
|
|
1397
|
+
import { z as z13 } from "zod";
|
|
1398
|
+
var inputSchema13 = z13.object({
|
|
1399
|
+
keyId: z13.string().uuid().describe("API key ID to revoke")
|
|
1400
|
+
});
|
|
1401
|
+
var apiKeysRevoke = {
|
|
1402
|
+
name: "api-keys.revoke",
|
|
1403
|
+
group: "api-keys",
|
|
1404
|
+
description: "Revoke an API key",
|
|
1405
|
+
inputSchema: inputSchema13,
|
|
1406
|
+
outputSchema: z13.any(),
|
|
1407
|
+
annotations: { destructiveHint: true },
|
|
1408
|
+
async execute(input, ctx) {
|
|
1409
|
+
await ctx.client.del("api_key", {
|
|
1410
|
+
pathParams: { id: input.keyId }
|
|
1411
|
+
});
|
|
1412
|
+
return { revoked: true, keyId: input.keyId };
|
|
1413
|
+
},
|
|
1414
|
+
formatOutput(output, format) {
|
|
1415
|
+
if (format === "compact") return output.keyId;
|
|
1416
|
+
return JSON.stringify(output, null, 2);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/commands/api-keys/set-policy.ts
|
|
1421
|
+
import { z as z14 } from "zod";
|
|
1422
|
+
var inputSchema14 = z14.object({
|
|
1423
|
+
policy: z14.enum(["all_members", "admins_only"]).describe("API key creation policy")
|
|
1424
|
+
});
|
|
1425
|
+
var apiKeysSetPolicy = {
|
|
1426
|
+
name: "api-keys.setPolicy",
|
|
1427
|
+
group: "api-keys",
|
|
1428
|
+
description: "Set the API key creation policy",
|
|
1429
|
+
inputSchema: inputSchema14,
|
|
1430
|
+
outputSchema: z14.any(),
|
|
1431
|
+
async execute(input, ctx) {
|
|
1432
|
+
return ctx.client.put("api_key_policy", { policy: input.policy });
|
|
1433
|
+
},
|
|
1434
|
+
formatOutput(output, format) {
|
|
1435
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1436
|
+
return JSON.stringify(output, null, 2);
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
// src/commands/api-keys/index.ts
|
|
1441
|
+
function registerApiKeyCommands() {
|
|
1442
|
+
return [apiKeysCreate, apiKeysList, apiKeysRevoke, apiKeysGetPolicy, apiKeysSetPolicy];
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
// src/commands/audit/list.ts
|
|
1446
|
+
import { z as z16 } from "zod";
|
|
1447
|
+
|
|
1448
|
+
// src/commands/_shared/pagination.ts
|
|
1449
|
+
import { z as z15 } from "zod";
|
|
1450
|
+
var paginationInput = {
|
|
1451
|
+
page: z15.number().int().min(1).default(1).describe("Page number"),
|
|
1452
|
+
pageSize: z15.number().int().min(1).max(100).default(25).describe("Results per page")
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1455
|
+
// src/commands/audit/list.ts
|
|
1456
|
+
var inputSchema15 = z16.object({
|
|
1457
|
+
org: z16.string().uuid().describe("Organization ID"),
|
|
1458
|
+
...paginationInput,
|
|
1459
|
+
action: z16.string().optional().describe("Filter by action type"),
|
|
1460
|
+
userId: z16.string().optional().describe("Filter by user ID")
|
|
1461
|
+
});
|
|
1462
|
+
var auditList = {
|
|
1463
|
+
name: "audit.list",
|
|
1464
|
+
group: "audit",
|
|
1465
|
+
description: "List audit log entries for an organization",
|
|
1466
|
+
inputSchema: inputSchema15,
|
|
1467
|
+
outputSchema: z16.any(),
|
|
1468
|
+
annotations: { readOnlyHint: true },
|
|
1469
|
+
async execute(input, ctx) {
|
|
1470
|
+
const queryParams = {
|
|
1471
|
+
page: input.page,
|
|
1472
|
+
pageSize: input.pageSize
|
|
1473
|
+
};
|
|
1474
|
+
if (input.action) queryParams.action = input.action;
|
|
1475
|
+
if (input.userId) queryParams.userId = input.userId;
|
|
1476
|
+
return ctx.client.get("audit_log", {
|
|
1477
|
+
pathParams: { orgId: input.org },
|
|
1478
|
+
queryParams
|
|
1479
|
+
});
|
|
1480
|
+
},
|
|
1481
|
+
formatOutput(output, format) {
|
|
1482
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1483
|
+
return JSON.stringify(output, null, 2);
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
|
|
1487
|
+
// src/commands/audit/index.ts
|
|
1488
|
+
function registerAuditCommands() {
|
|
1489
|
+
return [auditList];
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
// src/commands/auth/login.ts
|
|
1493
|
+
init_client();
|
|
1494
|
+
init_errors();
|
|
1495
|
+
init_profiles();
|
|
1496
|
+
import { z as z17 } from "zod";
|
|
1497
|
+
var inputSchema16 = z17.object({
|
|
1498
|
+
apiKey: z17.string().optional().describe("Elnora API key (elnora_live_...)"),
|
|
1499
|
+
profile: z17.string().default("default").describe("Profile name to save key under")
|
|
1500
|
+
});
|
|
1501
|
+
var authLogin = {
|
|
1502
|
+
name: "auth.login",
|
|
1503
|
+
group: "auth",
|
|
1504
|
+
description: "Set up authentication by saving an API key to a profile",
|
|
1505
|
+
inputSchema: inputSchema16,
|
|
1506
|
+
outputSchema: z17.any(),
|
|
1507
|
+
async execute(input, _ctx) {
|
|
1508
|
+
const apiKey = input.apiKey;
|
|
1509
|
+
if (!apiKey) {
|
|
1510
|
+
throw new ValidationError(
|
|
1511
|
+
"API key is required. Use --api-key flag to provide it.",
|
|
1512
|
+
"Get your API key from: https://platform.elnora.ai > Settings > API Keys"
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
const key = apiKey.trim();
|
|
1516
|
+
if (!key.startsWith("elnora_live_")) {
|
|
1517
|
+
throw new AuthError("API key must start with 'elnora_live_'.");
|
|
1518
|
+
}
|
|
1519
|
+
if (key.length < 20) {
|
|
1520
|
+
throw new AuthError("API key looks too short. Check your key and try again.");
|
|
1521
|
+
}
|
|
1522
|
+
const profileName = input.profile;
|
|
1523
|
+
validateProfileName(profileName);
|
|
1524
|
+
const tempClient = new ElnoraApiClient(key);
|
|
1525
|
+
await tempClient.get("projects", { queryParams: { page: 1, pageSize: 1 } });
|
|
1526
|
+
const configPath = saveProfile(profileName, key);
|
|
1527
|
+
return { profile: profileName, verified: true, configPath };
|
|
1528
|
+
},
|
|
1529
|
+
formatOutput(output, format) {
|
|
1530
|
+
if (format === "compact") return output.profile ?? JSON.stringify(output);
|
|
1531
|
+
return JSON.stringify(output, null, 2);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
// src/commands/auth/logout.ts
|
|
1536
|
+
init_profiles();
|
|
1537
|
+
import { z as z18 } from "zod";
|
|
1538
|
+
var inputSchema17 = z18.object({
|
|
1539
|
+
profile: z18.string().optional().describe("Profile name to remove (default: 'default')"),
|
|
1540
|
+
all: z18.boolean().default(false).describe("Remove all profiles")
|
|
1541
|
+
});
|
|
1542
|
+
var authLogout = {
|
|
1543
|
+
name: "auth.logout",
|
|
1544
|
+
group: "auth",
|
|
1545
|
+
description: "Remove saved credentials",
|
|
1546
|
+
inputSchema: inputSchema17,
|
|
1547
|
+
outputSchema: z18.any(),
|
|
1548
|
+
annotations: { destructiveHint: true },
|
|
1549
|
+
async execute(input, _ctx) {
|
|
1550
|
+
if (input.all) {
|
|
1551
|
+
const names = listProfileNames();
|
|
1552
|
+
for (const name of names) {
|
|
1553
|
+
removeProfile(name);
|
|
1554
|
+
}
|
|
1555
|
+
return { loggedOut: true, profile: "all", removedCount: names.length };
|
|
1556
|
+
}
|
|
1557
|
+
const profileName = input.profile ?? "default";
|
|
1558
|
+
const removed = removeProfile(profileName);
|
|
1559
|
+
return { loggedOut: true, profile: profileName, removed };
|
|
1560
|
+
},
|
|
1561
|
+
formatOutput(output, format) {
|
|
1562
|
+
if (format === "compact") return output.profile ?? JSON.stringify(output);
|
|
1563
|
+
return JSON.stringify(output, null, 2);
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
|
|
1567
|
+
// src/commands/auth/profiles.ts
|
|
1568
|
+
init_profiles();
|
|
1569
|
+
import { z as z19 } from "zod";
|
|
1570
|
+
var inputSchema18 = z19.object({});
|
|
1571
|
+
var authProfiles = {
|
|
1572
|
+
name: "auth.profiles",
|
|
1573
|
+
group: "auth",
|
|
1574
|
+
description: "List all configured profiles",
|
|
1575
|
+
inputSchema: inputSchema18,
|
|
1576
|
+
outputSchema: z19.any(),
|
|
1577
|
+
annotations: { readOnlyHint: true },
|
|
1578
|
+
async execute(_input, _ctx) {
|
|
1579
|
+
const profiles = loadProfiles();
|
|
1580
|
+
const items = [];
|
|
1581
|
+
for (const [name, data] of Object.entries(profiles)) {
|
|
1582
|
+
const key = data.api_key ?? "";
|
|
1583
|
+
const masked = key.length > 16 ? `${key.slice(0, 12)}...${key.slice(-4)}` : "[invalid]";
|
|
1584
|
+
items.push({ name, apiKey: masked });
|
|
1585
|
+
}
|
|
1586
|
+
return { profiles: items };
|
|
1587
|
+
},
|
|
1588
|
+
formatOutput(output, format) {
|
|
1589
|
+
if (format === "compact") {
|
|
1590
|
+
const profiles = output.profiles ?? [];
|
|
1591
|
+
return profiles.map((p) => p.name).join(", ");
|
|
1592
|
+
}
|
|
1593
|
+
return JSON.stringify(output, null, 2);
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
|
|
1597
|
+
// src/commands/auth/status.ts
|
|
1598
|
+
import { z as z20 } from "zod";
|
|
1599
|
+
var inputSchema19 = z20.object({});
|
|
1600
|
+
var authStatus = {
|
|
1601
|
+
name: "auth.status",
|
|
1602
|
+
group: "auth",
|
|
1603
|
+
description: "Verify API key and show connection info",
|
|
1604
|
+
inputSchema: inputSchema19,
|
|
1605
|
+
outputSchema: z20.any(),
|
|
1606
|
+
annotations: { readOnlyHint: true },
|
|
1607
|
+
async execute(_input, ctx) {
|
|
1608
|
+
const result = await ctx.client.get("projects", {
|
|
1609
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
1610
|
+
});
|
|
1611
|
+
const projectCount = result.totalCount ?? result.items?.length ?? 0;
|
|
1612
|
+
return { profile: ctx.profileName, authenticated: true, projectCount };
|
|
1613
|
+
},
|
|
1614
|
+
formatOutput(output, format) {
|
|
1615
|
+
if (format === "compact") return String(output.authenticated ?? "");
|
|
1616
|
+
return JSON.stringify(output, null, 2);
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
// src/commands/auth/validate.ts
|
|
1621
|
+
init_profiles();
|
|
1622
|
+
import { z as z21 } from "zod";
|
|
1623
|
+
var inputSchema20 = z21.object({
|
|
1624
|
+
token: z21.string().optional().describe("Token to validate (defaults to current API key)")
|
|
1625
|
+
});
|
|
1626
|
+
var authValidate = {
|
|
1627
|
+
name: "auth.validate",
|
|
1628
|
+
group: "auth",
|
|
1629
|
+
description: "Validate a JWT or API key token",
|
|
1630
|
+
inputSchema: inputSchema20,
|
|
1631
|
+
outputSchema: z21.any(),
|
|
1632
|
+
async execute(input, ctx) {
|
|
1633
|
+
const tokenToValidate = input.token ?? resolveApiKey();
|
|
1634
|
+
return ctx.client.post("auth_validate", { token: tokenToValidate });
|
|
1635
|
+
},
|
|
1636
|
+
formatOutput(output, format) {
|
|
1637
|
+
if (format === "compact") return JSON.stringify(output);
|
|
1638
|
+
return JSON.stringify(output, null, 2);
|
|
1639
|
+
}
|
|
1640
|
+
};
|
|
1641
|
+
|
|
1642
|
+
// src/commands/auth/index.ts
|
|
1643
|
+
function registerAuthCommands() {
|
|
1644
|
+
return [authLogin, authStatus, authLogout, authProfiles, authValidate];
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
// src/commands/completion.ts
|
|
1648
|
+
function extractCommandTree(cmd) {
|
|
1649
|
+
return {
|
|
1650
|
+
name: cmd.name(),
|
|
1651
|
+
description: cmd.description(),
|
|
1652
|
+
subcommands: cmd.commands.map(extractCommandTree),
|
|
1653
|
+
options: cmd.options.map((o) => ({
|
|
1654
|
+
flags: o.long ?? o.short ?? "",
|
|
1655
|
+
description: o.description
|
|
1656
|
+
}))
|
|
1657
|
+
};
|
|
1658
|
+
}
|
|
1659
|
+
function generateBash(root) {
|
|
1660
|
+
const lines = [
|
|
1661
|
+
"# Bash completion for elnora",
|
|
1662
|
+
"# Install: elnora completion bash > ~/.local/share/bash-completion/completions/elnora",
|
|
1663
|
+
"",
|
|
1664
|
+
"_elnora() {",
|
|
1665
|
+
' local cur="${COMP_WORDS[COMP_CWORD]}"',
|
|
1666
|
+
' local prev="${COMP_WORDS[COMP_CWORD-1]}"',
|
|
1667
|
+
""
|
|
1668
|
+
];
|
|
1669
|
+
const topCmds = root.subcommands.map((c) => c.name).join(" ");
|
|
1670
|
+
lines.push(` if [ "$COMP_CWORD" -eq 1 ]; then`);
|
|
1671
|
+
lines.push(` COMPREPLY=( $(compgen -W "${topCmds}" -- "$cur") )`);
|
|
1672
|
+
lines.push(" return");
|
|
1673
|
+
lines.push(" fi");
|
|
1674
|
+
lines.push("");
|
|
1675
|
+
lines.push(' case "${COMP_WORDS[1]}" in');
|
|
1676
|
+
for (const group of root.subcommands) {
|
|
1677
|
+
if (group.subcommands.length > 0) {
|
|
1678
|
+
const subs = group.subcommands.map((s) => s.name).join(" ");
|
|
1679
|
+
lines.push(` ${group.name})`);
|
|
1680
|
+
lines.push(` COMPREPLY=( $(compgen -W "${subs}" -- "$cur") )`);
|
|
1681
|
+
lines.push(" ;;");
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
lines.push(" esac");
|
|
1685
|
+
lines.push("}");
|
|
1686
|
+
lines.push("");
|
|
1687
|
+
lines.push("complete -F _elnora elnora");
|
|
1688
|
+
return lines.join("\n");
|
|
1689
|
+
}
|
|
1690
|
+
function generateZsh(root) {
|
|
1691
|
+
const lines = [
|
|
1692
|
+
"#compdef elnora",
|
|
1693
|
+
"# Zsh completion for elnora",
|
|
1694
|
+
"# Install: elnora completion zsh > ~/.zfunc/_elnora",
|
|
1695
|
+
"",
|
|
1696
|
+
"_elnora() {",
|
|
1697
|
+
" local -a commands",
|
|
1698
|
+
""
|
|
1699
|
+
];
|
|
1700
|
+
lines.push(" if (( CURRENT == 2 )); then");
|
|
1701
|
+
lines.push(" commands=(");
|
|
1702
|
+
for (const cmd of root.subcommands) {
|
|
1703
|
+
lines.push(` '${cmd.name}:${cmd.description.replace(/'/g, "")}'`);
|
|
1704
|
+
}
|
|
1705
|
+
lines.push(" )");
|
|
1706
|
+
lines.push(" _describe 'command' commands");
|
|
1707
|
+
lines.push(" return");
|
|
1708
|
+
lines.push(" fi");
|
|
1709
|
+
lines.push("");
|
|
1710
|
+
lines.push(" case $words[2] in");
|
|
1711
|
+
for (const group of root.subcommands) {
|
|
1712
|
+
if (group.subcommands.length > 0) {
|
|
1713
|
+
lines.push(` ${group.name})`);
|
|
1714
|
+
lines.push(" local -a subcommands");
|
|
1715
|
+
lines.push(" subcommands=(");
|
|
1716
|
+
for (const sub of group.subcommands) {
|
|
1717
|
+
lines.push(` '${sub.name}:${sub.description.replace(/'/g, "")}'`);
|
|
1718
|
+
}
|
|
1719
|
+
lines.push(" )");
|
|
1720
|
+
lines.push(" _describe 'subcommand' subcommands");
|
|
1721
|
+
lines.push(" ;;");
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
lines.push(" esac");
|
|
1725
|
+
lines.push("}");
|
|
1726
|
+
lines.push("");
|
|
1727
|
+
lines.push("_elnora");
|
|
1728
|
+
return lines.join("\n");
|
|
1729
|
+
}
|
|
1730
|
+
function generateFish(root) {
|
|
1731
|
+
const lines = [
|
|
1732
|
+
"# Fish completion for elnora",
|
|
1733
|
+
"# Install: elnora completion fish > ~/.config/fish/completions/elnora.fish",
|
|
1734
|
+
""
|
|
1735
|
+
];
|
|
1736
|
+
for (const cmd of root.subcommands) {
|
|
1737
|
+
lines.push(
|
|
1738
|
+
`complete -c elnora -n "__fish_use_subcommand" -a "${cmd.name}" -d "${cmd.description.replace(/"/g, '\\"')}"`
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
lines.push("");
|
|
1742
|
+
for (const group of root.subcommands) {
|
|
1743
|
+
for (const sub of group.subcommands) {
|
|
1744
|
+
lines.push(
|
|
1745
|
+
`complete -c elnora -n "__fish_seen_subcommand_from ${group.name}" -a "${sub.name}" -d "${sub.description.replace(/"/g, '\\"')}"`
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return lines.join("\n");
|
|
1750
|
+
}
|
|
1751
|
+
function addCompletionCommand(program2) {
|
|
1752
|
+
program2.command("completion").argument("<shell>", "Shell type: bash, zsh, fish").description("Generate shell completion script").action((shell) => {
|
|
1753
|
+
const tree = extractCommandTree(program2);
|
|
1754
|
+
switch (shell) {
|
|
1755
|
+
case "bash":
|
|
1756
|
+
console.log(generateBash(tree));
|
|
1757
|
+
break;
|
|
1758
|
+
case "zsh":
|
|
1759
|
+
console.log(generateZsh(tree));
|
|
1760
|
+
break;
|
|
1761
|
+
case "fish":
|
|
1762
|
+
console.log(generateFish(tree));
|
|
1763
|
+
break;
|
|
1764
|
+
default:
|
|
1765
|
+
console.error(`Unsupported shell: ${shell}. Use bash, zsh, or fish.`);
|
|
1766
|
+
process.exit(1);
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// src/commands/doctor.ts
|
|
1772
|
+
init_client();
|
|
1773
|
+
init_config();
|
|
1774
|
+
init_profiles();
|
|
1775
|
+
import { existsSync as existsSync2, statSync } from "node:fs";
|
|
1776
|
+
import pc from "picocolors";
|
|
1777
|
+
|
|
1778
|
+
// src/lib/tty.ts
|
|
1779
|
+
function isTTY() {
|
|
1780
|
+
return Boolean(process.stdout.isTTY);
|
|
1781
|
+
}
|
|
1782
|
+
function isColorEnabled() {
|
|
1783
|
+
if (process.env.NO_COLOR !== void 0) return false;
|
|
1784
|
+
if (process.env.FORCE_COLOR !== void 0) return true;
|
|
1785
|
+
return isTTY();
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// src/commands/doctor.ts
|
|
1789
|
+
function addDoctorCommand(program2) {
|
|
1790
|
+
program2.command("doctor").description("Check Elnora CLI configuration and connectivity").action(async () => {
|
|
1791
|
+
const color = isColorEnabled();
|
|
1792
|
+
const ok = (msg) => color ? ` ${pc.green("\u2713")} ${msg}` : ` \u2713 ${msg}`;
|
|
1793
|
+
const fail = (msg) => color ? ` ${pc.red("\u2717")} ${msg}` : ` \u2717 ${msg}`;
|
|
1794
|
+
const warn = (msg) => color ? ` ${pc.yellow("!")} ${msg}` : ` ! ${msg}`;
|
|
1795
|
+
console.error(`
|
|
1796
|
+
Elnora CLI v${VERSION}
|
|
1797
|
+
`);
|
|
1798
|
+
let passed = 0;
|
|
1799
|
+
let total = 0;
|
|
1800
|
+
total++;
|
|
1801
|
+
try {
|
|
1802
|
+
const start = Date.now();
|
|
1803
|
+
const res = await fetch("https://platform.elnora.ai/health", { signal: AbortSignal.timeout(5e3) });
|
|
1804
|
+
const ms = Date.now() - start;
|
|
1805
|
+
if (res.ok) {
|
|
1806
|
+
console.error(ok(`API reachable platform.elnora.ai (${res.status} OK, ${ms}ms)`));
|
|
1807
|
+
passed++;
|
|
1808
|
+
} else console.error(fail(`API unreachable platform.elnora.ai (HTTP ${res.status})`));
|
|
1809
|
+
} catch (e) {
|
|
1810
|
+
console.error(
|
|
1811
|
+
fail(`API unreachable platform.elnora.ai (${e instanceof Error ? e.message : "network error"})`)
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1814
|
+
total++;
|
|
1815
|
+
try {
|
|
1816
|
+
const key = resolveApiKey();
|
|
1817
|
+
const client = new ElnoraApiClient(key);
|
|
1818
|
+
const result = await client.get("projects", {
|
|
1819
|
+
queryParams: { page: 1, pageSize: 1 }
|
|
1820
|
+
});
|
|
1821
|
+
const count = result?.totalCount ?? result?.items?.length ?? 0;
|
|
1822
|
+
console.error(ok(`Authenticated profile: default (${count} projects accessible)`));
|
|
1823
|
+
passed++;
|
|
1824
|
+
} catch (e) {
|
|
1825
|
+
console.error(fail(`Authentication failed ${e instanceof Error ? e.message : "no API key"}`));
|
|
1826
|
+
}
|
|
1827
|
+
total++;
|
|
1828
|
+
try {
|
|
1829
|
+
const res = await fetch("https://registry.npmjs.org/@elnora-ai/cli/latest", {
|
|
1830
|
+
signal: AbortSignal.timeout(3e3)
|
|
1831
|
+
});
|
|
1832
|
+
if (res.ok) {
|
|
1833
|
+
const data = await res.json();
|
|
1834
|
+
const latest = data.version ?? "unknown";
|
|
1835
|
+
if (latest === VERSION || latest === "unknown") {
|
|
1836
|
+
console.error(ok(`Version current v${VERSION} (latest: v${latest})`));
|
|
1837
|
+
} else {
|
|
1838
|
+
console.error(warn(`Update available v${VERSION} \u2192 v${latest}`));
|
|
1839
|
+
}
|
|
1840
|
+
passed++;
|
|
1841
|
+
} else {
|
|
1842
|
+
console.error(ok(`Version v${VERSION} (registry check failed)`));
|
|
1843
|
+
passed++;
|
|
1844
|
+
}
|
|
1845
|
+
} catch {
|
|
1846
|
+
console.error(ok(`Version v${VERSION} (registry unreachable)`));
|
|
1847
|
+
passed++;
|
|
1848
|
+
}
|
|
1849
|
+
total++;
|
|
1850
|
+
if (process.platform === "win32") {
|
|
1851
|
+
console.error(ok("Config permissions (skipped on Windows)"));
|
|
1852
|
+
passed++;
|
|
1853
|
+
} else if (!existsSync2(PROFILES_FILE)) {
|
|
1854
|
+
console.error(warn("Config permissions ~/.elnora/profiles.toml not found"));
|
|
1855
|
+
} else {
|
|
1856
|
+
const mode = statSync(PROFILES_FILE).mode & 511;
|
|
1857
|
+
if (mode === 384) {
|
|
1858
|
+
console.error(ok(`Config permissions ${PROFILES_FILE} (0600)`));
|
|
1859
|
+
passed++;
|
|
1860
|
+
} else {
|
|
1861
|
+
console.error(warn(`Config permissions ${PROFILES_FILE} (0${mode.toString(8)}) \u2014 should be 0600`));
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
total++;
|
|
1865
|
+
try {
|
|
1866
|
+
const start = Date.now();
|
|
1867
|
+
const res = await fetch(`${AI_SERVER_URL}/health`, { signal: AbortSignal.timeout(5e3) });
|
|
1868
|
+
const ms = Date.now() - start;
|
|
1869
|
+
if (res.ok) {
|
|
1870
|
+
console.error(ok(`AI Server reachable ${new URL(AI_SERVER_URL).hostname} (${res.status} OK, ${ms}ms)`));
|
|
1871
|
+
passed++;
|
|
1872
|
+
} else console.error(fail(`AI Server unreachable ${new URL(AI_SERVER_URL).hostname} (HTTP ${res.status})`));
|
|
1873
|
+
} catch {
|
|
1874
|
+
console.error(fail(`AI Server unreachable ${AI_SERVER_URL}`));
|
|
1875
|
+
}
|
|
1876
|
+
console.error("");
|
|
1877
|
+
if (passed === total) {
|
|
1878
|
+
console.error("All checks passed.");
|
|
1879
|
+
} else {
|
|
1880
|
+
console.error(`${passed}/${total} checks passed.`);
|
|
1881
|
+
if (!existsSync2(PROFILES_FILE)) console.error("\nRun: elnora auth login");
|
|
1882
|
+
}
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
// src/commands/feedback/submit.ts
|
|
1887
|
+
import { z as z22 } from "zod";
|
|
1888
|
+
var inputSchema21 = z22.object({
|
|
1889
|
+
title: z22.string().min(1).describe("Feedback title"),
|
|
1890
|
+
description: z22.string().min(1).describe("Feedback description")
|
|
1891
|
+
});
|
|
1892
|
+
var feedbackSubmit = {
|
|
1893
|
+
name: "feedback.submit",
|
|
1894
|
+
group: "feedback",
|
|
1895
|
+
description: "Submit feedback",
|
|
1896
|
+
inputSchema: inputSchema21,
|
|
1897
|
+
outputSchema: z22.any(),
|
|
1898
|
+
async execute(input, ctx) {
|
|
1899
|
+
return ctx.client.post("feedback", {
|
|
1900
|
+
title: input.title,
|
|
1901
|
+
description: input.description
|
|
1902
|
+
});
|
|
1903
|
+
},
|
|
1904
|
+
formatOutput(output, format) {
|
|
1905
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1906
|
+
return JSON.stringify(output, null, 2);
|
|
1907
|
+
}
|
|
1908
|
+
};
|
|
1909
|
+
|
|
1910
|
+
// src/commands/feedback/index.ts
|
|
1911
|
+
function registerFeedbackCommands() {
|
|
1912
|
+
return [feedbackSubmit];
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/commands/files/archive.ts
|
|
1916
|
+
import { z as z23 } from "zod";
|
|
1917
|
+
var inputSchema22 = z23.object({
|
|
1918
|
+
fileId: z23.string().uuid().describe("File ID to archive")
|
|
1919
|
+
});
|
|
1920
|
+
var filesArchive = {
|
|
1921
|
+
name: "files.archive",
|
|
1922
|
+
group: "files",
|
|
1923
|
+
description: "Archive (delete) a file",
|
|
1924
|
+
inputSchema: inputSchema22,
|
|
1925
|
+
outputSchema: z23.any(),
|
|
1926
|
+
annotations: { destructiveHint: true },
|
|
1927
|
+
async execute(input, ctx) {
|
|
1928
|
+
await ctx.client.del("file", {
|
|
1929
|
+
pathParams: { id: input.fileId }
|
|
1930
|
+
});
|
|
1931
|
+
return { archived: true, fileId: input.fileId };
|
|
1932
|
+
},
|
|
1933
|
+
formatOutput(output, format) {
|
|
1934
|
+
if (format === "compact") return output.fileId;
|
|
1935
|
+
return JSON.stringify(output, null, 2);
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
// src/commands/files/commit.ts
|
|
1940
|
+
import { z as z24 } from "zod";
|
|
1941
|
+
var inputSchema23 = z24.object({
|
|
1942
|
+
fileId: z24.string().uuid().describe("File ID to commit")
|
|
1943
|
+
});
|
|
1944
|
+
var filesCommit = {
|
|
1945
|
+
name: "files.commit",
|
|
1946
|
+
group: "files",
|
|
1947
|
+
description: "Commit a file's working copy",
|
|
1948
|
+
inputSchema: inputSchema23,
|
|
1949
|
+
outputSchema: z24.any(),
|
|
1950
|
+
async execute(input, ctx) {
|
|
1951
|
+
return ctx.client.post("file_commit", void 0, {
|
|
1952
|
+
pathParams: { id: input.fileId }
|
|
1953
|
+
});
|
|
1954
|
+
},
|
|
1955
|
+
formatOutput(output, format) {
|
|
1956
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1957
|
+
return JSON.stringify(output, null, 2);
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
|
|
1961
|
+
// src/commands/files/confirm-upload.ts
|
|
1962
|
+
import { z as z25 } from "zod";
|
|
1963
|
+
var inputSchema24 = z25.object({
|
|
1964
|
+
fileId: z25.string().uuid().describe("File ID to confirm upload")
|
|
1965
|
+
});
|
|
1966
|
+
var filesConfirmUpload = {
|
|
1967
|
+
name: "files.confirmUpload",
|
|
1968
|
+
group: "files",
|
|
1969
|
+
description: "Confirm a file upload",
|
|
1970
|
+
inputSchema: inputSchema24,
|
|
1971
|
+
outputSchema: z25.any(),
|
|
1972
|
+
async execute(input, ctx) {
|
|
1973
|
+
return ctx.client.post("file_upload_confirm", void 0, {
|
|
1974
|
+
pathParams: { id: input.fileId }
|
|
1975
|
+
});
|
|
1976
|
+
},
|
|
1977
|
+
formatOutput(output, format) {
|
|
1978
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
1979
|
+
return JSON.stringify(output, null, 2);
|
|
1980
|
+
}
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
// src/commands/files/content.ts
|
|
1984
|
+
import { z as z26 } from "zod";
|
|
1985
|
+
var inputSchema25 = z26.object({
|
|
1986
|
+
fileId: z26.string().uuid().describe("File ID")
|
|
1987
|
+
});
|
|
1988
|
+
var filesContent = {
|
|
1989
|
+
name: "files.content",
|
|
1990
|
+
group: "files",
|
|
1991
|
+
description: "Get the raw content of a file",
|
|
1992
|
+
inputSchema: inputSchema25,
|
|
1993
|
+
outputSchema: z26.any(),
|
|
1994
|
+
annotations: { readOnlyHint: true },
|
|
1995
|
+
async execute(input, ctx) {
|
|
1996
|
+
return ctx.client.get("file_content", {
|
|
1997
|
+
pathParams: { id: input.fileId }
|
|
1998
|
+
});
|
|
1999
|
+
},
|
|
2000
|
+
formatOutput(output, _format) {
|
|
2001
|
+
if (typeof output === "string") return output;
|
|
2002
|
+
return JSON.stringify(output, null, 2);
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
// src/commands/files/create.ts
|
|
2007
|
+
import { z as z27 } from "zod";
|
|
2008
|
+
var inputSchema26 = z27.object({
|
|
2009
|
+
project: z27.string().uuid().describe("Project ID"),
|
|
2010
|
+
name: z27.string().min(1).describe("File name"),
|
|
2011
|
+
type: z27.string().min(1).describe("File type"),
|
|
2012
|
+
folder: z27.string().uuid().optional().describe("Folder ID")
|
|
2013
|
+
});
|
|
2014
|
+
var filesCreate = {
|
|
2015
|
+
name: "files.create",
|
|
2016
|
+
group: "files",
|
|
2017
|
+
description: "Create a new file",
|
|
2018
|
+
inputSchema: inputSchema26,
|
|
2019
|
+
outputSchema: z27.any(),
|
|
2020
|
+
async execute(input, ctx) {
|
|
2021
|
+
return ctx.client.post("files", {
|
|
2022
|
+
projectId: input.project,
|
|
2023
|
+
name: input.name,
|
|
2024
|
+
type: input.type,
|
|
2025
|
+
folderId: input.folder
|
|
2026
|
+
});
|
|
2027
|
+
},
|
|
2028
|
+
formatOutput(output, format) {
|
|
2029
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2030
|
+
return JSON.stringify(output, null, 2);
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
// src/commands/files/create-version.ts
|
|
2035
|
+
import { z as z28 } from "zod";
|
|
2036
|
+
var inputSchema27 = z28.object({
|
|
2037
|
+
fileId: z28.string().uuid().describe("File ID"),
|
|
2038
|
+
content: z28.string().optional().describe("Version content")
|
|
2039
|
+
});
|
|
2040
|
+
var filesCreateVersion = {
|
|
2041
|
+
name: "files.createVersion",
|
|
2042
|
+
group: "files",
|
|
2043
|
+
description: "Create a new version of a file",
|
|
2044
|
+
inputSchema: inputSchema27,
|
|
2045
|
+
outputSchema: z28.any(),
|
|
2046
|
+
async execute(input, ctx) {
|
|
2047
|
+
const body = {};
|
|
2048
|
+
if (input.content !== void 0) body.content = input.content;
|
|
2049
|
+
return ctx.client.post("file_versions", body, {
|
|
2050
|
+
pathParams: { id: input.fileId }
|
|
2051
|
+
});
|
|
2052
|
+
},
|
|
2053
|
+
formatOutput(output, format) {
|
|
2054
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2055
|
+
return JSON.stringify(output, null, 2);
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
|
|
2059
|
+
// src/commands/files/download.ts
|
|
2060
|
+
import { z as z29 } from "zod";
|
|
2061
|
+
var inputSchema28 = z29.object({
|
|
2062
|
+
fileId: z29.string().uuid().describe("File ID to download")
|
|
2063
|
+
});
|
|
2064
|
+
var filesDownload = {
|
|
2065
|
+
name: "files.download",
|
|
2066
|
+
group: "files",
|
|
2067
|
+
description: "Download a file",
|
|
2068
|
+
inputSchema: inputSchema28,
|
|
2069
|
+
outputSchema: z29.any(),
|
|
2070
|
+
annotations: { readOnlyHint: true },
|
|
2071
|
+
async execute(input, ctx) {
|
|
2072
|
+
return ctx.client.get("file_download", {
|
|
2073
|
+
pathParams: { id: input.fileId }
|
|
2074
|
+
});
|
|
2075
|
+
},
|
|
2076
|
+
formatOutput(output, _format) {
|
|
2077
|
+
if (typeof output === "string") return output;
|
|
2078
|
+
return JSON.stringify(output, null, 2);
|
|
2079
|
+
}
|
|
2080
|
+
};
|
|
2081
|
+
|
|
2082
|
+
// src/commands/files/fork.ts
|
|
2083
|
+
import { z as z30 } from "zod";
|
|
2084
|
+
var inputSchema29 = z30.object({
|
|
2085
|
+
fileId: z30.string().uuid().describe("File ID to fork"),
|
|
2086
|
+
targetProject: z30.string().uuid().describe("Target project ID")
|
|
2087
|
+
});
|
|
2088
|
+
var filesFork = {
|
|
2089
|
+
name: "files.fork",
|
|
2090
|
+
group: "files",
|
|
2091
|
+
description: "Fork a file to another project",
|
|
2092
|
+
inputSchema: inputSchema29,
|
|
2093
|
+
outputSchema: z30.any(),
|
|
2094
|
+
async execute(input, ctx) {
|
|
2095
|
+
return ctx.client.post(
|
|
2096
|
+
"file_fork",
|
|
2097
|
+
{ targetProjectId: input.targetProject },
|
|
2098
|
+
{
|
|
2099
|
+
pathParams: { id: input.fileId }
|
|
2100
|
+
}
|
|
2101
|
+
);
|
|
2102
|
+
},
|
|
2103
|
+
formatOutput(output, format) {
|
|
2104
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2105
|
+
return JSON.stringify(output, null, 2);
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
|
|
2109
|
+
// src/commands/files/get.ts
|
|
2110
|
+
import { z as z31 } from "zod";
|
|
2111
|
+
var inputSchema30 = z31.object({
|
|
2112
|
+
fileId: z31.string().uuid().describe("File ID")
|
|
2113
|
+
});
|
|
2114
|
+
var filesGet = {
|
|
2115
|
+
name: "files.get",
|
|
2116
|
+
group: "files",
|
|
2117
|
+
description: "Get details of a specific file",
|
|
2118
|
+
inputSchema: inputSchema30,
|
|
2119
|
+
outputSchema: z31.any(),
|
|
2120
|
+
annotations: { readOnlyHint: true },
|
|
2121
|
+
async execute(input, ctx) {
|
|
2122
|
+
return ctx.client.get("file", {
|
|
2123
|
+
pathParams: { id: input.fileId }
|
|
2124
|
+
});
|
|
2125
|
+
},
|
|
2126
|
+
formatOutput(output, format) {
|
|
2127
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2128
|
+
return JSON.stringify(output, null, 2);
|
|
2129
|
+
}
|
|
2130
|
+
};
|
|
2131
|
+
|
|
2132
|
+
// src/commands/files/list.ts
|
|
2133
|
+
import { z as z32 } from "zod";
|
|
2134
|
+
var inputSchema31 = z32.object({
|
|
2135
|
+
project: z32.string().uuid().describe("Project ID"),
|
|
2136
|
+
...paginationInput
|
|
2137
|
+
});
|
|
2138
|
+
var filesList = {
|
|
2139
|
+
name: "files.list",
|
|
2140
|
+
group: "files",
|
|
2141
|
+
description: "List files in a project",
|
|
2142
|
+
inputSchema: inputSchema31,
|
|
2143
|
+
outputSchema: z32.any(),
|
|
2144
|
+
annotations: { readOnlyHint: true },
|
|
2145
|
+
async execute(input, ctx) {
|
|
2146
|
+
return ctx.client.get("project_files", {
|
|
2147
|
+
pathParams: { id: input.project },
|
|
2148
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
2149
|
+
});
|
|
2150
|
+
},
|
|
2151
|
+
formatOutput(output, format) {
|
|
2152
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2153
|
+
return JSON.stringify(output, null, 2);
|
|
2154
|
+
}
|
|
2155
|
+
};
|
|
2156
|
+
|
|
2157
|
+
// src/commands/files/promote.ts
|
|
2158
|
+
import { z as z33 } from "zod";
|
|
2159
|
+
var inputSchema32 = z33.object({
|
|
2160
|
+
fileId: z33.string().uuid().describe("File ID"),
|
|
2161
|
+
visibility: z33.string().min(1).describe("Visibility level")
|
|
2162
|
+
});
|
|
2163
|
+
var filesPromote = {
|
|
2164
|
+
name: "files.promote",
|
|
2165
|
+
group: "files",
|
|
2166
|
+
description: "Promote a file to a new visibility level",
|
|
2167
|
+
inputSchema: inputSchema32,
|
|
2168
|
+
outputSchema: z33.any(),
|
|
2169
|
+
async execute(input, ctx) {
|
|
2170
|
+
return ctx.client.post(
|
|
2171
|
+
"file_promote",
|
|
2172
|
+
{ visibility: input.visibility },
|
|
2173
|
+
{
|
|
2174
|
+
pathParams: { id: input.fileId }
|
|
2175
|
+
}
|
|
2176
|
+
);
|
|
2177
|
+
},
|
|
2178
|
+
formatOutput(output, format) {
|
|
2179
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2180
|
+
return JSON.stringify(output, null, 2);
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
|
|
2184
|
+
// src/commands/files/restore.ts
|
|
2185
|
+
import { z as z34 } from "zod";
|
|
2186
|
+
var inputSchema33 = z34.object({
|
|
2187
|
+
fileId: z34.string().uuid().describe("File ID"),
|
|
2188
|
+
versionId: z34.string().uuid().describe("Version ID to restore")
|
|
2189
|
+
});
|
|
2190
|
+
var filesRestore = {
|
|
2191
|
+
name: "files.restore",
|
|
2192
|
+
group: "files",
|
|
2193
|
+
description: "Restore a file to a specific version",
|
|
2194
|
+
inputSchema: inputSchema33,
|
|
2195
|
+
outputSchema: z34.any(),
|
|
2196
|
+
async execute(input, ctx) {
|
|
2197
|
+
return ctx.client.post("file_version_restore", void 0, {
|
|
2198
|
+
pathParams: { id: input.fileId, vid: input.versionId }
|
|
2199
|
+
});
|
|
2200
|
+
},
|
|
2201
|
+
formatOutput(output, format) {
|
|
2202
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2203
|
+
return JSON.stringify(output, null, 2);
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
|
|
2207
|
+
// src/commands/files/search-content.ts
|
|
2208
|
+
import { z as z35 } from "zod";
|
|
2209
|
+
var inputSchema34 = z35.object({
|
|
2210
|
+
query: z35.string().min(1).describe("Search query"),
|
|
2211
|
+
project: z35.string().uuid().optional().describe("Project ID to scope search"),
|
|
2212
|
+
...paginationInput
|
|
2213
|
+
});
|
|
2214
|
+
var filesSearchContent = {
|
|
2215
|
+
name: "files.searchContent",
|
|
2216
|
+
group: "files",
|
|
2217
|
+
description: "Search file content across projects",
|
|
2218
|
+
inputSchema: inputSchema34,
|
|
2219
|
+
outputSchema: z35.any(),
|
|
2220
|
+
annotations: { readOnlyHint: true },
|
|
2221
|
+
async execute(input, ctx) {
|
|
2222
|
+
const queryParams = {
|
|
2223
|
+
q: input.query,
|
|
2224
|
+
page: input.page,
|
|
2225
|
+
pageSize: input.pageSize
|
|
2226
|
+
};
|
|
2227
|
+
if (input.project) queryParams.projectId = input.project;
|
|
2228
|
+
return ctx.client.get("search_file_content", { queryParams });
|
|
2229
|
+
},
|
|
2230
|
+
formatOutput(output, format) {
|
|
2231
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2232
|
+
return JSON.stringify(output, null, 2);
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
|
|
2236
|
+
// src/commands/files/update.ts
|
|
2237
|
+
init_errors();
|
|
2238
|
+
import { z as z36 } from "zod";
|
|
2239
|
+
var inputSchema35 = z36.object({
|
|
2240
|
+
fileId: z36.string().uuid().describe("File ID"),
|
|
2241
|
+
name: z36.string().min(1).optional().describe("New file name"),
|
|
2242
|
+
folder: z36.string().uuid().optional().describe("New folder ID")
|
|
2243
|
+
});
|
|
2244
|
+
var filesUpdate = {
|
|
2245
|
+
name: "files.update",
|
|
2246
|
+
group: "files",
|
|
2247
|
+
description: "Update a file's metadata",
|
|
2248
|
+
inputSchema: inputSchema35,
|
|
2249
|
+
outputSchema: z36.any(),
|
|
2250
|
+
annotations: { idempotentHint: true },
|
|
2251
|
+
async execute(input, ctx) {
|
|
2252
|
+
const { fileId, ...fields } = input;
|
|
2253
|
+
if (!fields.name && !fields.folder) {
|
|
2254
|
+
throw new ValidationError(
|
|
2255
|
+
"At least one field (name, folder) must be provided.",
|
|
2256
|
+
"Provide at least one field to update."
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
const body = {};
|
|
2260
|
+
if (fields.name) body.name = fields.name;
|
|
2261
|
+
if (fields.folder) body.folderId = fields.folder;
|
|
2262
|
+
return ctx.client.patch("file", body, {
|
|
2263
|
+
pathParams: { id: fileId }
|
|
2264
|
+
});
|
|
2265
|
+
},
|
|
2266
|
+
formatOutput(output, format) {
|
|
2267
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2268
|
+
return JSON.stringify(output, null, 2);
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
|
|
2272
|
+
// src/commands/files/upload.ts
|
|
2273
|
+
init_client();
|
|
2274
|
+
import { readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
|
|
2275
|
+
import { basename } from "node:path";
|
|
2276
|
+
import { z as z37 } from "zod";
|
|
2277
|
+
var inputSchema36 = z37.object({
|
|
2278
|
+
project: z37.string().uuid().describe("Project ID"),
|
|
2279
|
+
filePath: z37.string().min(1).describe("Local file path to upload"),
|
|
2280
|
+
fileName: z37.string().optional().describe("Override file name"),
|
|
2281
|
+
contentType: z37.string().optional().describe("MIME content type")
|
|
2282
|
+
});
|
|
2283
|
+
var filesUpload = {
|
|
2284
|
+
name: "files.upload",
|
|
2285
|
+
group: "files",
|
|
2286
|
+
description: "Upload a file to a project (three-stage: presign, PUT, confirm)",
|
|
2287
|
+
inputSchema: inputSchema36,
|
|
2288
|
+
outputSchema: z37.any(),
|
|
2289
|
+
async execute(input, ctx) {
|
|
2290
|
+
const fileBuffer = readFileSync3(input.filePath);
|
|
2291
|
+
const fileName = input.fileName ?? basename(input.filePath);
|
|
2292
|
+
const contentType = input.contentType ?? "application/octet-stream";
|
|
2293
|
+
const fileSize = statSync2(input.filePath).size;
|
|
2294
|
+
const uploadInfo = await ctx.client.post("file_upload", {
|
|
2295
|
+
projectId: input.project,
|
|
2296
|
+
fileName,
|
|
2297
|
+
contentType,
|
|
2298
|
+
fileSizeBytes: fileSize
|
|
2299
|
+
});
|
|
2300
|
+
const presignedUrl = uploadInfo.uploadUrl ?? uploadInfo.url;
|
|
2301
|
+
const fileId = uploadInfo.fileId ?? uploadInfo.id;
|
|
2302
|
+
validateUploadUrl(presignedUrl);
|
|
2303
|
+
await fetch(presignedUrl, {
|
|
2304
|
+
method: "PUT",
|
|
2305
|
+
body: fileBuffer,
|
|
2306
|
+
headers: { "Content-Type": contentType, "Content-Length": String(fileSize) }
|
|
2307
|
+
});
|
|
2308
|
+
return ctx.client.post("file_upload_confirm", void 0, {
|
|
2309
|
+
pathParams: { id: fileId }
|
|
2310
|
+
});
|
|
2311
|
+
},
|
|
2312
|
+
formatOutput(output, format) {
|
|
2313
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2314
|
+
return JSON.stringify(output, null, 2);
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
|
|
2318
|
+
// src/commands/files/upload-batch.ts
|
|
2319
|
+
init_client();
|
|
2320
|
+
init_errors();
|
|
2321
|
+
import { readFileSync as readFileSync4, statSync as statSync3 } from "node:fs";
|
|
2322
|
+
import { basename as basename2 } from "node:path";
|
|
2323
|
+
import { z as z38 } from "zod";
|
|
2324
|
+
var inputSchema37 = z38.object({
|
|
2325
|
+
project: z38.string().uuid().describe("Project ID"),
|
|
2326
|
+
filePaths: z38.string().min(1).describe("Comma-separated local file paths to upload"),
|
|
2327
|
+
folder: z38.string().uuid().optional().describe("Folder ID")
|
|
2328
|
+
});
|
|
2329
|
+
var filesUploadBatch = {
|
|
2330
|
+
name: "files.uploadBatch",
|
|
2331
|
+
group: "files",
|
|
2332
|
+
description: "Upload multiple files to a project (max 50)",
|
|
2333
|
+
inputSchema: inputSchema37,
|
|
2334
|
+
outputSchema: z38.any(),
|
|
2335
|
+
async execute(input, ctx) {
|
|
2336
|
+
const paths = input.filePaths.split(",").map((p) => p.trim()).filter(Boolean);
|
|
2337
|
+
if (paths.length === 0) {
|
|
2338
|
+
throw new ValidationError("No file paths provided.", "Provide comma-separated file paths.");
|
|
2339
|
+
}
|
|
2340
|
+
if (paths.length > 50) {
|
|
2341
|
+
throw new ValidationError(
|
|
2342
|
+
`Too many files (${paths.length}). Maximum is 50.`,
|
|
2343
|
+
"Split the upload into batches of 50 or fewer."
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
const results = [];
|
|
2347
|
+
for (const filePath of paths) {
|
|
2348
|
+
const fileName = basename2(filePath);
|
|
2349
|
+
try {
|
|
2350
|
+
const fileBuffer = readFileSync4(filePath);
|
|
2351
|
+
const contentType = "application/octet-stream";
|
|
2352
|
+
const fileSize = statSync3(filePath).size;
|
|
2353
|
+
const uploadInfo = await ctx.client.post("file_upload", {
|
|
2354
|
+
projectId: input.project,
|
|
2355
|
+
fileName,
|
|
2356
|
+
contentType,
|
|
2357
|
+
fileSizeBytes: fileSize,
|
|
2358
|
+
folderId: input.folder
|
|
2359
|
+
});
|
|
2360
|
+
const presignedUrl = uploadInfo.uploadUrl ?? uploadInfo.url;
|
|
2361
|
+
const fileId = uploadInfo.fileId ?? uploadInfo.id;
|
|
2362
|
+
validateUploadUrl(presignedUrl);
|
|
2363
|
+
await fetch(presignedUrl, {
|
|
2364
|
+
method: "PUT",
|
|
2365
|
+
body: fileBuffer,
|
|
2366
|
+
headers: { "Content-Type": contentType, "Content-Length": String(fileSize) }
|
|
2367
|
+
});
|
|
2368
|
+
await ctx.client.post("file_upload_confirm", void 0, {
|
|
2369
|
+
pathParams: { id: fileId }
|
|
2370
|
+
});
|
|
2371
|
+
results.push({ fileName, success: true, fileId });
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
results.push({
|
|
2374
|
+
fileName,
|
|
2375
|
+
success: false,
|
|
2376
|
+
error: err instanceof Error ? err.message : String(err)
|
|
2377
|
+
});
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return results;
|
|
2381
|
+
},
|
|
2382
|
+
formatOutput(output, format) {
|
|
2383
|
+
if (format === "compact") {
|
|
2384
|
+
const results = output;
|
|
2385
|
+
return results.map((r) => `${r.fileName}: ${r.success ? "ok" : "FAILED"}`).join(", ");
|
|
2386
|
+
}
|
|
2387
|
+
return JSON.stringify(output, null, 2);
|
|
2388
|
+
}
|
|
2389
|
+
};
|
|
2390
|
+
|
|
2391
|
+
// src/commands/files/version-content.ts
|
|
2392
|
+
import { z as z39 } from "zod";
|
|
2393
|
+
var inputSchema38 = z39.object({
|
|
2394
|
+
fileId: z39.string().uuid().describe("File ID"),
|
|
2395
|
+
versionId: z39.string().uuid().describe("Version ID")
|
|
2396
|
+
});
|
|
2397
|
+
var filesVersionContent = {
|
|
2398
|
+
name: "files.versionContent",
|
|
2399
|
+
group: "files",
|
|
2400
|
+
description: "Get the raw content of a specific file version",
|
|
2401
|
+
inputSchema: inputSchema38,
|
|
2402
|
+
outputSchema: z39.any(),
|
|
2403
|
+
annotations: { readOnlyHint: true },
|
|
2404
|
+
async execute(input, ctx) {
|
|
2405
|
+
return ctx.client.get("file_version_content", {
|
|
2406
|
+
pathParams: { id: input.fileId, vid: input.versionId }
|
|
2407
|
+
});
|
|
2408
|
+
},
|
|
2409
|
+
formatOutput(output, _format) {
|
|
2410
|
+
if (typeof output === "string") return output;
|
|
2411
|
+
return JSON.stringify(output, null, 2);
|
|
2412
|
+
}
|
|
2413
|
+
};
|
|
2414
|
+
|
|
2415
|
+
// src/commands/files/versions.ts
|
|
2416
|
+
import { z as z40 } from "zod";
|
|
2417
|
+
var inputSchema39 = z40.object({
|
|
2418
|
+
fileId: z40.string().uuid().describe("File ID"),
|
|
2419
|
+
...paginationInput
|
|
2420
|
+
});
|
|
2421
|
+
var filesVersions = {
|
|
2422
|
+
name: "files.versions",
|
|
2423
|
+
group: "files",
|
|
2424
|
+
description: "List versions of a file",
|
|
2425
|
+
inputSchema: inputSchema39,
|
|
2426
|
+
outputSchema: z40.any(),
|
|
2427
|
+
annotations: { readOnlyHint: true },
|
|
2428
|
+
async execute(input, ctx) {
|
|
2429
|
+
return ctx.client.get("file_versions", {
|
|
2430
|
+
pathParams: { id: input.fileId },
|
|
2431
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
2432
|
+
});
|
|
2433
|
+
},
|
|
2434
|
+
formatOutput(output, format) {
|
|
2435
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2436
|
+
return JSON.stringify(output, null, 2);
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
|
|
2440
|
+
// src/commands/files/working-copy.ts
|
|
2441
|
+
import { z as z41 } from "zod";
|
|
2442
|
+
var inputSchema40 = z41.object({
|
|
2443
|
+
fileId: z41.string().uuid().describe("File ID"),
|
|
2444
|
+
task: z41.string().uuid().optional().describe("Task ID")
|
|
2445
|
+
});
|
|
2446
|
+
var filesWorkingCopy = {
|
|
2447
|
+
name: "files.workingCopy",
|
|
2448
|
+
group: "files",
|
|
2449
|
+
description: "Create a working copy of a file",
|
|
2450
|
+
inputSchema: inputSchema40,
|
|
2451
|
+
outputSchema: z41.any(),
|
|
2452
|
+
async execute(input, ctx) {
|
|
2453
|
+
const body = {};
|
|
2454
|
+
if (input.task !== void 0) body.taskId = input.task;
|
|
2455
|
+
return ctx.client.post("file_working_copy", body, {
|
|
2456
|
+
pathParams: { id: input.fileId }
|
|
2457
|
+
});
|
|
2458
|
+
},
|
|
2459
|
+
formatOutput(output, format) {
|
|
2460
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2461
|
+
return JSON.stringify(output, null, 2);
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
|
|
2465
|
+
// src/commands/files/index.ts
|
|
2466
|
+
function registerFileCommands() {
|
|
2467
|
+
return [
|
|
2468
|
+
filesList,
|
|
2469
|
+
filesGet,
|
|
2470
|
+
filesContent,
|
|
2471
|
+
filesCreate,
|
|
2472
|
+
filesUpload,
|
|
2473
|
+
filesUploadBatch,
|
|
2474
|
+
filesConfirmUpload,
|
|
2475
|
+
filesDownload,
|
|
2476
|
+
filesUpdate,
|
|
2477
|
+
filesArchive,
|
|
2478
|
+
filesVersions,
|
|
2479
|
+
filesVersionContent,
|
|
2480
|
+
filesCreateVersion,
|
|
2481
|
+
filesRestore,
|
|
2482
|
+
filesPromote,
|
|
2483
|
+
filesFork,
|
|
2484
|
+
filesWorkingCopy,
|
|
2485
|
+
filesCommit,
|
|
2486
|
+
filesSearchContent
|
|
2487
|
+
];
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
// src/commands/flags/get.ts
|
|
2491
|
+
import { z as z42 } from "zod";
|
|
2492
|
+
var inputSchema41 = z42.object({
|
|
2493
|
+
key: z42.string().min(1).describe("Feature flag key")
|
|
2494
|
+
});
|
|
2495
|
+
var flagsGet = {
|
|
2496
|
+
name: "flags.get",
|
|
2497
|
+
group: "flags",
|
|
2498
|
+
description: "Get a feature flag by key",
|
|
2499
|
+
inputSchema: inputSchema41,
|
|
2500
|
+
outputSchema: z42.any(),
|
|
2501
|
+
annotations: { readOnlyHint: true },
|
|
2502
|
+
async execute(input, ctx) {
|
|
2503
|
+
return ctx.client.get("feature_flag", {
|
|
2504
|
+
pathParams: { key: input.key }
|
|
2505
|
+
});
|
|
2506
|
+
},
|
|
2507
|
+
formatOutput(output, format) {
|
|
2508
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2509
|
+
return JSON.stringify(output, null, 2);
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
|
|
2513
|
+
// src/commands/flags/list.ts
|
|
2514
|
+
import { z as z43 } from "zod";
|
|
2515
|
+
var inputSchema42 = z43.object({});
|
|
2516
|
+
var flagsList = {
|
|
2517
|
+
name: "flags.list",
|
|
2518
|
+
group: "flags",
|
|
2519
|
+
description: "List all feature flags",
|
|
2520
|
+
inputSchema: inputSchema42,
|
|
2521
|
+
outputSchema: z43.any(),
|
|
2522
|
+
annotations: { readOnlyHint: true },
|
|
2523
|
+
async execute(_input, ctx) {
|
|
2524
|
+
return ctx.client.get("feature_flags");
|
|
2525
|
+
},
|
|
2526
|
+
formatOutput(output, format) {
|
|
2527
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2528
|
+
return JSON.stringify(output, null, 2);
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
// src/commands/flags/set.ts
|
|
2533
|
+
import { z as z44 } from "zod";
|
|
2534
|
+
var inputSchema43 = z44.object({
|
|
2535
|
+
key: z44.string().min(1).describe("Feature flag key"),
|
|
2536
|
+
value: z44.enum(["true", "false"]).describe("Feature flag value"),
|
|
2537
|
+
yes: z44.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
2538
|
+
});
|
|
2539
|
+
var flagsSet = {
|
|
2540
|
+
name: "flags.set",
|
|
2541
|
+
group: "flags",
|
|
2542
|
+
description: "Set a feature flag value",
|
|
2543
|
+
inputSchema: inputSchema43,
|
|
2544
|
+
outputSchema: z44.any(),
|
|
2545
|
+
async execute(input, ctx) {
|
|
2546
|
+
if (!input.yes) {
|
|
2547
|
+
const confirmed = await confirmDestructive(`Set feature flag "${input.key}" to ${input.value}? [y/N] `);
|
|
2548
|
+
if (!confirmed) {
|
|
2549
|
+
return { updated: false, key: input.key };
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
const boolValue = input.value === "true";
|
|
2553
|
+
return ctx.client.put(
|
|
2554
|
+
"feature_flag",
|
|
2555
|
+
{ value: boolValue },
|
|
2556
|
+
{
|
|
2557
|
+
pathParams: { key: input.key }
|
|
2558
|
+
}
|
|
2559
|
+
);
|
|
2560
|
+
},
|
|
2561
|
+
formatOutput(output, format) {
|
|
2562
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2563
|
+
return JSON.stringify(output, null, 2);
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
|
|
2567
|
+
// src/commands/flags/index.ts
|
|
2568
|
+
function registerFlagCommands() {
|
|
2569
|
+
return [flagsList, flagsGet, flagsSet];
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
// src/commands/folders/create.ts
|
|
2573
|
+
import { z as z45 } from "zod";
|
|
2574
|
+
var inputSchema44 = z45.object({
|
|
2575
|
+
projectId: z45.string().uuid().describe("Project ID"),
|
|
2576
|
+
name: z45.string().min(1).describe("Folder name"),
|
|
2577
|
+
parentId: z45.string().uuid().optional().describe("Parent folder ID")
|
|
2578
|
+
});
|
|
2579
|
+
var foldersCreate = {
|
|
2580
|
+
name: "folders.create",
|
|
2581
|
+
group: "folders",
|
|
2582
|
+
description: "Create a new folder in a project",
|
|
2583
|
+
inputSchema: inputSchema44,
|
|
2584
|
+
outputSchema: z45.any(),
|
|
2585
|
+
async execute(input, ctx) {
|
|
2586
|
+
const body = { name: input.name };
|
|
2587
|
+
if (input.parentId) body.parentId = input.parentId;
|
|
2588
|
+
return ctx.client.post("project_folders", body, {
|
|
2589
|
+
pathParams: { id: input.projectId }
|
|
2590
|
+
});
|
|
2591
|
+
},
|
|
2592
|
+
formatOutput(output, format) {
|
|
2593
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2594
|
+
return JSON.stringify(output, null, 2);
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
|
|
2598
|
+
// src/commands/folders/delete.ts
|
|
2599
|
+
import { z as z46 } from "zod";
|
|
2600
|
+
var inputSchema45 = z46.object({
|
|
2601
|
+
folderId: z46.string().uuid().describe("Folder ID to delete")
|
|
2602
|
+
});
|
|
2603
|
+
var foldersDelete = {
|
|
2604
|
+
name: "folders.delete",
|
|
2605
|
+
group: "folders",
|
|
2606
|
+
description: "Delete a folder",
|
|
2607
|
+
inputSchema: inputSchema45,
|
|
2608
|
+
outputSchema: z46.any(),
|
|
2609
|
+
annotations: { destructiveHint: true },
|
|
2610
|
+
async execute(input, ctx) {
|
|
2611
|
+
await ctx.client.del("folder", {
|
|
2612
|
+
pathParams: { id: input.folderId }
|
|
2613
|
+
});
|
|
2614
|
+
return { deleted: true, folderId: input.folderId };
|
|
2615
|
+
},
|
|
2616
|
+
formatOutput(output, format) {
|
|
2617
|
+
if (format === "compact") return output.folderId;
|
|
2618
|
+
return JSON.stringify(output, null, 2);
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
|
|
2622
|
+
// src/commands/folders/list.ts
|
|
2623
|
+
import { z as z47 } from "zod";
|
|
2624
|
+
var inputSchema46 = z47.object({
|
|
2625
|
+
projectId: z47.string().uuid().describe("Project ID")
|
|
2626
|
+
});
|
|
2627
|
+
var foldersList = {
|
|
2628
|
+
name: "folders.list",
|
|
2629
|
+
group: "folders",
|
|
2630
|
+
description: "List folders in a project",
|
|
2631
|
+
inputSchema: inputSchema46,
|
|
2632
|
+
outputSchema: z47.any(),
|
|
2633
|
+
annotations: { readOnlyHint: true },
|
|
2634
|
+
async execute(input, ctx) {
|
|
2635
|
+
return ctx.client.get("project_folders", {
|
|
2636
|
+
pathParams: { id: input.projectId }
|
|
2637
|
+
});
|
|
2638
|
+
},
|
|
2639
|
+
formatOutput(output, format) {
|
|
2640
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2641
|
+
return JSON.stringify(output, null, 2);
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
|
|
2645
|
+
// src/commands/folders/move.ts
|
|
2646
|
+
init_errors();
|
|
2647
|
+
import { z as z48 } from "zod";
|
|
2648
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2649
|
+
var inputSchema47 = z48.object({
|
|
2650
|
+
folderId: z48.string().uuid().describe("Folder ID to move"),
|
|
2651
|
+
parentId: z48.string().min(1).describe("Target parent folder ID, or 'root' to move to project root")
|
|
2652
|
+
});
|
|
2653
|
+
var foldersMove = {
|
|
2654
|
+
name: "folders.move",
|
|
2655
|
+
group: "folders",
|
|
2656
|
+
description: "Move a folder to a new parent (or to root)",
|
|
2657
|
+
inputSchema: inputSchema47,
|
|
2658
|
+
outputSchema: z48.any(),
|
|
2659
|
+
async execute(input, ctx) {
|
|
2660
|
+
let parentValue;
|
|
2661
|
+
if (input.parentId === "root") {
|
|
2662
|
+
parentValue = null;
|
|
2663
|
+
} else if (UUID_RE.test(input.parentId)) {
|
|
2664
|
+
parentValue = input.parentId;
|
|
2665
|
+
} else {
|
|
2666
|
+
throw new ValidationError(
|
|
2667
|
+
"parent must be a valid UUID or 'root'.",
|
|
2668
|
+
"Provide a folder UUID or the literal string 'root'."
|
|
2669
|
+
);
|
|
2670
|
+
}
|
|
2671
|
+
return ctx.client.put(
|
|
2672
|
+
"folder_move",
|
|
2673
|
+
{ parentId: parentValue },
|
|
2674
|
+
{
|
|
2675
|
+
pathParams: { id: input.folderId }
|
|
2676
|
+
}
|
|
2677
|
+
);
|
|
2678
|
+
},
|
|
2679
|
+
formatOutput(output, format) {
|
|
2680
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2681
|
+
return JSON.stringify(output, null, 2);
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
|
|
2685
|
+
// src/commands/folders/rename.ts
|
|
2686
|
+
import { z as z49 } from "zod";
|
|
2687
|
+
var inputSchema48 = z49.object({
|
|
2688
|
+
folderId: z49.string().uuid().describe("Folder ID"),
|
|
2689
|
+
name: z49.string().min(1).describe("New folder name")
|
|
2690
|
+
});
|
|
2691
|
+
var foldersRename = {
|
|
2692
|
+
name: "folders.rename",
|
|
2693
|
+
group: "folders",
|
|
2694
|
+
description: "Rename a folder",
|
|
2695
|
+
inputSchema: inputSchema48,
|
|
2696
|
+
outputSchema: z49.any(),
|
|
2697
|
+
annotations: { idempotentHint: true },
|
|
2698
|
+
async execute(input, ctx) {
|
|
2699
|
+
return ctx.client.put(
|
|
2700
|
+
"folder",
|
|
2701
|
+
{ name: input.name },
|
|
2702
|
+
{
|
|
2703
|
+
pathParams: { id: input.folderId }
|
|
2704
|
+
}
|
|
2705
|
+
);
|
|
2706
|
+
},
|
|
2707
|
+
formatOutput(output, format) {
|
|
2708
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2709
|
+
return JSON.stringify(output, null, 2);
|
|
2710
|
+
}
|
|
2711
|
+
};
|
|
2712
|
+
|
|
2713
|
+
// src/commands/folders/index.ts
|
|
2714
|
+
function registerFolderCommands() {
|
|
2715
|
+
return [foldersList, foldersCreate, foldersRename, foldersMove, foldersDelete];
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
// src/commands/health.ts
|
|
2719
|
+
import { z as z50 } from "zod";
|
|
2720
|
+
var healthInput = z50.object({});
|
|
2721
|
+
var healthOutput = z50.object({
|
|
2722
|
+
status: z50.string(),
|
|
2723
|
+
timestamp: z50.string()
|
|
2724
|
+
});
|
|
2725
|
+
var healthCheck = {
|
|
2726
|
+
name: "health.check",
|
|
2727
|
+
group: "health",
|
|
2728
|
+
description: "Check Elnora API health status",
|
|
2729
|
+
inputSchema: healthInput,
|
|
2730
|
+
outputSchema: healthOutput,
|
|
2731
|
+
annotations: { readOnlyHint: true },
|
|
2732
|
+
async execute(_input, _ctx) {
|
|
2733
|
+
const response = await fetch("https://platform.elnora.ai/health", {
|
|
2734
|
+
signal: AbortSignal.timeout(5e3)
|
|
2735
|
+
});
|
|
2736
|
+
const text = await response.text();
|
|
2737
|
+
let status;
|
|
2738
|
+
try {
|
|
2739
|
+
const data = JSON.parse(text);
|
|
2740
|
+
status = data?.status ?? "ok";
|
|
2741
|
+
} catch {
|
|
2742
|
+
status = response.ok ? text.trim() || "ok" : `error (HTTP ${response.status})`;
|
|
2743
|
+
}
|
|
2744
|
+
return { status, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2745
|
+
},
|
|
2746
|
+
formatOutput(output, format) {
|
|
2747
|
+
if (format === "compact") return output.status;
|
|
2748
|
+
return JSON.stringify(output, null, 2);
|
|
2749
|
+
}
|
|
2750
|
+
};
|
|
2751
|
+
|
|
2752
|
+
// src/commands/library/create-folder.ts
|
|
2753
|
+
import { z as z51 } from "zod";
|
|
2754
|
+
var inputSchema49 = z51.object({
|
|
2755
|
+
org: z51.string().uuid().describe("Organization ID"),
|
|
2756
|
+
name: z51.string().min(1).describe("Folder name"),
|
|
2757
|
+
parent: z51.string().uuid().optional().describe("Parent folder ID")
|
|
2758
|
+
});
|
|
2759
|
+
var libraryCreateFolder = {
|
|
2760
|
+
name: "library.createFolder",
|
|
2761
|
+
group: "library",
|
|
2762
|
+
description: "Create a folder in the organization library",
|
|
2763
|
+
inputSchema: inputSchema49,
|
|
2764
|
+
outputSchema: z51.any(),
|
|
2765
|
+
async execute(input, ctx) {
|
|
2766
|
+
const body = { name: input.name };
|
|
2767
|
+
if (input.parent) body.parentId = input.parent;
|
|
2768
|
+
return ctx.client.post("library_folders", body, {
|
|
2769
|
+
pathParams: { orgId: input.org }
|
|
2770
|
+
});
|
|
2771
|
+
},
|
|
2772
|
+
formatOutput(output, format) {
|
|
2773
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2774
|
+
return JSON.stringify(output, null, 2);
|
|
2775
|
+
}
|
|
2776
|
+
};
|
|
2777
|
+
|
|
2778
|
+
// src/commands/library/delete-folder.ts
|
|
2779
|
+
import { z as z52 } from "zod";
|
|
2780
|
+
var inputSchema50 = z52.object({
|
|
2781
|
+
org: z52.string().uuid().describe("Organization ID"),
|
|
2782
|
+
folderId: z52.string().uuid().describe("Folder ID to delete")
|
|
2783
|
+
});
|
|
2784
|
+
var libraryDeleteFolder = {
|
|
2785
|
+
name: "library.deleteFolder",
|
|
2786
|
+
group: "library",
|
|
2787
|
+
description: "Delete a folder from the organization library",
|
|
2788
|
+
inputSchema: inputSchema50,
|
|
2789
|
+
outputSchema: z52.any(),
|
|
2790
|
+
annotations: { destructiveHint: true },
|
|
2791
|
+
async execute(input, ctx) {
|
|
2792
|
+
await ctx.client.del("library_folder", {
|
|
2793
|
+
pathParams: { orgId: input.org, id: input.folderId }
|
|
2794
|
+
});
|
|
2795
|
+
return { deleted: true, folderId: input.folderId };
|
|
2796
|
+
},
|
|
2797
|
+
formatOutput(output, format) {
|
|
2798
|
+
if (format === "compact") return output.folderId;
|
|
2799
|
+
return JSON.stringify(output, null, 2);
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
|
|
2803
|
+
// src/commands/library/files.ts
|
|
2804
|
+
import { z as z53 } from "zod";
|
|
2805
|
+
var inputSchema51 = z53.object({
|
|
2806
|
+
org: z53.string().uuid().describe("Organization ID"),
|
|
2807
|
+
...paginationInput
|
|
2808
|
+
});
|
|
2809
|
+
var libraryFiles = {
|
|
2810
|
+
name: "library.files",
|
|
2811
|
+
group: "library",
|
|
2812
|
+
description: "List files in the organization library",
|
|
2813
|
+
inputSchema: inputSchema51,
|
|
2814
|
+
outputSchema: z53.any(),
|
|
2815
|
+
annotations: { readOnlyHint: true },
|
|
2816
|
+
async execute(input, ctx) {
|
|
2817
|
+
return ctx.client.get("library_files", {
|
|
2818
|
+
pathParams: { orgId: input.org },
|
|
2819
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
2820
|
+
});
|
|
2821
|
+
},
|
|
2822
|
+
formatOutput(output, format) {
|
|
2823
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2824
|
+
return JSON.stringify(output, null, 2);
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
|
|
2828
|
+
// src/commands/library/folders.ts
|
|
2829
|
+
import { z as z54 } from "zod";
|
|
2830
|
+
var inputSchema52 = z54.object({
|
|
2831
|
+
org: z54.string().uuid().describe("Organization ID")
|
|
2832
|
+
});
|
|
2833
|
+
var libraryFolders = {
|
|
2834
|
+
name: "library.folders",
|
|
2835
|
+
group: "library",
|
|
2836
|
+
description: "List folders in the organization library",
|
|
2837
|
+
inputSchema: inputSchema52,
|
|
2838
|
+
outputSchema: z54.any(),
|
|
2839
|
+
annotations: { readOnlyHint: true },
|
|
2840
|
+
async execute(input, ctx) {
|
|
2841
|
+
return ctx.client.get("library_folders", {
|
|
2842
|
+
pathParams: { orgId: input.org }
|
|
2843
|
+
});
|
|
2844
|
+
},
|
|
2845
|
+
formatOutput(output, format) {
|
|
2846
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2847
|
+
return JSON.stringify(output, null, 2);
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
|
|
2851
|
+
// src/commands/library/rename-folder.ts
|
|
2852
|
+
import { z as z55 } from "zod";
|
|
2853
|
+
var inputSchema53 = z55.object({
|
|
2854
|
+
org: z55.string().uuid().describe("Organization ID"),
|
|
2855
|
+
folderId: z55.string().uuid().describe("Folder ID to rename"),
|
|
2856
|
+
name: z55.string().min(1).describe("New folder name")
|
|
2857
|
+
});
|
|
2858
|
+
var libraryRenameFolder = {
|
|
2859
|
+
name: "library.renameFolder",
|
|
2860
|
+
group: "library",
|
|
2861
|
+
description: "Rename a folder in the organization library",
|
|
2862
|
+
inputSchema: inputSchema53,
|
|
2863
|
+
outputSchema: z55.any(),
|
|
2864
|
+
async execute(input, ctx) {
|
|
2865
|
+
return ctx.client.put(
|
|
2866
|
+
"library_folder",
|
|
2867
|
+
{ name: input.name },
|
|
2868
|
+
{
|
|
2869
|
+
pathParams: { orgId: input.org, id: input.folderId }
|
|
2870
|
+
}
|
|
2871
|
+
);
|
|
2872
|
+
},
|
|
2873
|
+
formatOutput(output, format) {
|
|
2874
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2875
|
+
return JSON.stringify(output, null, 2);
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
|
|
2879
|
+
// src/commands/library/index.ts
|
|
2880
|
+
function registerLibraryCommands() {
|
|
2881
|
+
return [libraryFiles, libraryFolders, libraryCreateFolder, libraryRenameFolder, libraryDeleteFolder];
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
// src/commands/mcp/serve.ts
|
|
2885
|
+
function requireEnv(name) {
|
|
2886
|
+
const value = process.env[name];
|
|
2887
|
+
if (!value) {
|
|
2888
|
+
throw new Error(`Required environment variable ${name} is not set`);
|
|
2889
|
+
}
|
|
2890
|
+
return value;
|
|
2891
|
+
}
|
|
2892
|
+
function addMcpCommands(program2, registry2) {
|
|
2893
|
+
const mcp = program2.command("mcp").description("MCP server management");
|
|
2894
|
+
mcp.command("serve").description("Start MCP server exposing all CLI commands as tools").option("--http", "HTTP transport (default, for deployment)").option("--stdio", "stdio transport (for local MCP clients)").option("--port <port>", "HTTP port", process.env.PORT ?? "3000").option("--host <host>", "HTTP bind address", process.env.HOST ?? "0.0.0.0").action(async (opts) => {
|
|
2895
|
+
if (opts.stdio) {
|
|
2896
|
+
const { startStdioServer: startStdioServer2 } = await Promise.resolve().then(() => (init_stdio_server(), stdio_server_exports));
|
|
2897
|
+
await startStdioServer2(registry2);
|
|
2898
|
+
} else {
|
|
2899
|
+
const { startHttpServer: startHttpServer2 } = await Promise.resolve().then(() => (init_http_server(), http_server_exports));
|
|
2900
|
+
await startHttpServer2(registry2, {
|
|
2901
|
+
port: Number.parseInt(opts.port, 10),
|
|
2902
|
+
host: opts.host,
|
|
2903
|
+
tokenValidationUrl: requireEnv("ELNORA_TOKEN_VALIDATION_URL"),
|
|
2904
|
+
mcpServiceKey: requireEnv("ELNORA_MCP_SERVICE_KEY")
|
|
2905
|
+
});
|
|
2906
|
+
}
|
|
2907
|
+
});
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// src/commands/open.ts
|
|
2911
|
+
import { exec } from "node:child_process";
|
|
2912
|
+
var URLS = {
|
|
2913
|
+
"": "https://platform.elnora.ai",
|
|
2914
|
+
platform: "https://platform.elnora.ai",
|
|
2915
|
+
docs: "https://docs.elnora.ai",
|
|
2916
|
+
keys: "https://platform.elnora.ai/settings/api-keys",
|
|
2917
|
+
billing: "https://platform.elnora.ai/settings/billing",
|
|
2918
|
+
github: "https://github.com/Elnora-AI/elnora-cli"
|
|
2919
|
+
};
|
|
2920
|
+
function openUrl(url) {
|
|
2921
|
+
const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "" "${url}"` : `xdg-open "${url}"`;
|
|
2922
|
+
exec(cmd, (err) => {
|
|
2923
|
+
if (err) {
|
|
2924
|
+
console.error(`Failed to open browser: ${err.message}`);
|
|
2925
|
+
console.log(url);
|
|
2926
|
+
}
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
function addOpenCommand(program2) {
|
|
2930
|
+
program2.command("open").argument("[page]", "Page to open: platform, docs, keys, billing, github").description("Open Elnora platform in the browser").action((page) => {
|
|
2931
|
+
const key = page ?? "";
|
|
2932
|
+
const url = URLS[key];
|
|
2933
|
+
if (!url) {
|
|
2934
|
+
console.error(`Unknown page: ${page}`);
|
|
2935
|
+
console.error(`Available: ${Object.keys(URLS).filter(Boolean).join(", ")}`);
|
|
2936
|
+
process.exit(1);
|
|
2937
|
+
}
|
|
2938
|
+
openUrl(url);
|
|
2939
|
+
console.log(`Opening ${url}...`);
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
// src/commands/orgs/accept-invite.ts
|
|
2944
|
+
import { z as z56 } from "zod";
|
|
2945
|
+
var inputSchema54 = z56.object({
|
|
2946
|
+
token: z56.string().min(1).describe("Invitation token")
|
|
2947
|
+
});
|
|
2948
|
+
var orgsAcceptInvite = {
|
|
2949
|
+
name: "orgs.acceptInvite",
|
|
2950
|
+
group: "orgs",
|
|
2951
|
+
description: "Accept an organization invitation by token",
|
|
2952
|
+
inputSchema: inputSchema54,
|
|
2953
|
+
outputSchema: z56.any(),
|
|
2954
|
+
async execute(input, ctx) {
|
|
2955
|
+
return ctx.client.post("invitation_accept", void 0, {
|
|
2956
|
+
pathParams: { token: input.token }
|
|
2957
|
+
});
|
|
2958
|
+
},
|
|
2959
|
+
formatOutput(output, format) {
|
|
2960
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2961
|
+
return JSON.stringify(output, null, 2);
|
|
2962
|
+
}
|
|
2963
|
+
};
|
|
2964
|
+
|
|
2965
|
+
// src/commands/orgs/billing.ts
|
|
2966
|
+
import { z as z57 } from "zod";
|
|
2967
|
+
var inputSchema55 = z57.object({
|
|
2968
|
+
orgId: z57.string().uuid().describe("Organization ID")
|
|
2969
|
+
});
|
|
2970
|
+
var orgsBilling = {
|
|
2971
|
+
name: "orgs.billing",
|
|
2972
|
+
group: "orgs",
|
|
2973
|
+
description: "Get billing status for an organization",
|
|
2974
|
+
inputSchema: inputSchema55,
|
|
2975
|
+
outputSchema: z57.any(),
|
|
2976
|
+
annotations: { readOnlyHint: true },
|
|
2977
|
+
async execute(input, ctx) {
|
|
2978
|
+
return ctx.client.get("organization_billing", {
|
|
2979
|
+
pathParams: { id: input.orgId }
|
|
2980
|
+
});
|
|
2981
|
+
},
|
|
2982
|
+
formatOutput(output, format) {
|
|
2983
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
2984
|
+
return JSON.stringify(output, null, 2);
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
|
|
2988
|
+
// src/commands/orgs/cancel-invite.ts
|
|
2989
|
+
import { z as z58 } from "zod";
|
|
2990
|
+
var inputSchema56 = z58.object({
|
|
2991
|
+
orgId: z58.string().uuid().describe("Organization ID"),
|
|
2992
|
+
invitationId: z58.string().uuid().describe("Invitation ID to cancel")
|
|
2993
|
+
});
|
|
2994
|
+
var orgsCancelInvite = {
|
|
2995
|
+
name: "orgs.cancelInvite",
|
|
2996
|
+
group: "orgs",
|
|
2997
|
+
description: "Cancel a pending organization invitation",
|
|
2998
|
+
inputSchema: inputSchema56,
|
|
2999
|
+
outputSchema: z58.any(),
|
|
3000
|
+
annotations: { destructiveHint: true },
|
|
3001
|
+
async execute(input, ctx) {
|
|
3002
|
+
await ctx.client.del("org_invitation", {
|
|
3003
|
+
pathParams: { orgId: input.orgId, invId: input.invitationId }
|
|
3004
|
+
});
|
|
3005
|
+
return { cancelled: true, invitationId: input.invitationId };
|
|
3006
|
+
},
|
|
3007
|
+
formatOutput(output, format) {
|
|
3008
|
+
if (format === "compact") return output.invitationId;
|
|
3009
|
+
return JSON.stringify(output, null, 2);
|
|
3010
|
+
}
|
|
3011
|
+
};
|
|
3012
|
+
|
|
3013
|
+
// src/commands/orgs/create.ts
|
|
3014
|
+
import { z as z59 } from "zod";
|
|
3015
|
+
var inputSchema57 = z59.object({
|
|
3016
|
+
name: z59.string().min(1).describe("Organization name"),
|
|
3017
|
+
description: z59.string().optional().describe("Organization description")
|
|
3018
|
+
});
|
|
3019
|
+
var orgsCreate = {
|
|
3020
|
+
name: "orgs.create",
|
|
3021
|
+
group: "orgs",
|
|
3022
|
+
description: "Create a new organization",
|
|
3023
|
+
inputSchema: inputSchema57,
|
|
3024
|
+
outputSchema: z59.any(),
|
|
3025
|
+
async execute(input, ctx) {
|
|
3026
|
+
return ctx.client.post("organizations", input);
|
|
3027
|
+
},
|
|
3028
|
+
formatOutput(output, format) {
|
|
3029
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3030
|
+
return JSON.stringify(output, null, 2);
|
|
3031
|
+
}
|
|
3032
|
+
};
|
|
3033
|
+
|
|
3034
|
+
// src/commands/orgs/delete.ts
|
|
3035
|
+
import { z as z60 } from "zod";
|
|
3036
|
+
var inputSchema58 = z60.object({
|
|
3037
|
+
orgId: z60.string().uuid().describe("Organization ID to delete"),
|
|
3038
|
+
yes: z60.boolean().optional().default(false).describe("Skip confirmation prompt")
|
|
3039
|
+
});
|
|
3040
|
+
var orgsDelete = {
|
|
3041
|
+
name: "orgs.delete",
|
|
3042
|
+
group: "orgs",
|
|
3043
|
+
description: "Delete an organization",
|
|
3044
|
+
inputSchema: inputSchema58,
|
|
3045
|
+
outputSchema: z60.any(),
|
|
3046
|
+
annotations: { destructiveHint: true },
|
|
3047
|
+
async execute(input, ctx) {
|
|
3048
|
+
if (!input.yes) {
|
|
3049
|
+
const confirmed = await confirmDestructive(`Are you sure you want to delete organization ${input.orgId}? [y/N] `);
|
|
3050
|
+
if (!confirmed) {
|
|
3051
|
+
return { deleted: false, orgId: input.orgId };
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
await ctx.client.del("organization", {
|
|
3055
|
+
pathParams: { id: input.orgId }
|
|
3056
|
+
});
|
|
3057
|
+
return { deleted: true, orgId: input.orgId };
|
|
3058
|
+
},
|
|
3059
|
+
formatOutput(output, format) {
|
|
3060
|
+
if (format === "compact") return output.orgId;
|
|
3061
|
+
return JSON.stringify(output, null, 2);
|
|
3062
|
+
}
|
|
3063
|
+
};
|
|
3064
|
+
|
|
3065
|
+
// src/commands/orgs/files.ts
|
|
3066
|
+
import { z as z61 } from "zod";
|
|
3067
|
+
var inputSchema59 = z61.object({
|
|
3068
|
+
orgId: z61.string().uuid().describe("Organization ID"),
|
|
3069
|
+
...paginationInput
|
|
3070
|
+
});
|
|
3071
|
+
var orgsFiles = {
|
|
3072
|
+
name: "orgs.files",
|
|
3073
|
+
group: "orgs",
|
|
3074
|
+
description: "List files belonging to an organization",
|
|
3075
|
+
inputSchema: inputSchema59,
|
|
3076
|
+
outputSchema: z61.any(),
|
|
3077
|
+
annotations: { readOnlyHint: true },
|
|
3078
|
+
async execute(input, ctx) {
|
|
3079
|
+
return ctx.client.get("org_files", {
|
|
3080
|
+
pathParams: { orgId: input.orgId },
|
|
3081
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
3082
|
+
});
|
|
3083
|
+
},
|
|
3084
|
+
formatOutput(output, format) {
|
|
3085
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3086
|
+
return JSON.stringify(output, null, 2);
|
|
3087
|
+
}
|
|
3088
|
+
};
|
|
3089
|
+
|
|
3090
|
+
// src/commands/orgs/get.ts
|
|
3091
|
+
import { z as z62 } from "zod";
|
|
3092
|
+
var inputSchema60 = z62.object({
|
|
3093
|
+
orgId: z62.string().uuid().describe("Organization ID")
|
|
3094
|
+
});
|
|
3095
|
+
var orgsGet = {
|
|
3096
|
+
name: "orgs.get",
|
|
3097
|
+
group: "orgs",
|
|
3098
|
+
description: "Get details of a specific organization",
|
|
3099
|
+
inputSchema: inputSchema60,
|
|
3100
|
+
outputSchema: z62.any(),
|
|
3101
|
+
async execute(input, ctx) {
|
|
3102
|
+
return ctx.client.get("organization", {
|
|
3103
|
+
pathParams: { id: input.orgId }
|
|
3104
|
+
});
|
|
3105
|
+
},
|
|
3106
|
+
formatOutput(output, format) {
|
|
3107
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3108
|
+
return JSON.stringify(output, null, 2);
|
|
3109
|
+
}
|
|
3110
|
+
};
|
|
3111
|
+
|
|
3112
|
+
// src/commands/orgs/invitation-info.ts
|
|
3113
|
+
import { z as z63 } from "zod";
|
|
3114
|
+
var inputSchema61 = z63.object({
|
|
3115
|
+
token: z63.string().min(1).describe("Invitation token")
|
|
3116
|
+
});
|
|
3117
|
+
var orgsInvitationInfo = {
|
|
3118
|
+
name: "orgs.invitationInfo",
|
|
3119
|
+
group: "orgs",
|
|
3120
|
+
description: "Get information about an invitation by token",
|
|
3121
|
+
inputSchema: inputSchema61,
|
|
3122
|
+
outputSchema: z63.any(),
|
|
3123
|
+
annotations: { readOnlyHint: true },
|
|
3124
|
+
async execute(input, ctx) {
|
|
3125
|
+
return ctx.client.get("invitation_info", {
|
|
3126
|
+
pathParams: { token: input.token }
|
|
3127
|
+
});
|
|
3128
|
+
},
|
|
3129
|
+
formatOutput(output, format) {
|
|
3130
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3131
|
+
return JSON.stringify(output, null, 2);
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
|
|
3135
|
+
// src/commands/orgs/invitations.ts
|
|
3136
|
+
import { z as z64 } from "zod";
|
|
3137
|
+
var inputSchema62 = z64.object({
|
|
3138
|
+
orgId: z64.string().uuid().describe("Organization ID")
|
|
3139
|
+
});
|
|
3140
|
+
var orgsInvitations = {
|
|
3141
|
+
name: "orgs.invitations",
|
|
3142
|
+
group: "orgs",
|
|
3143
|
+
description: "List pending invitations for an organization",
|
|
3144
|
+
inputSchema: inputSchema62,
|
|
3145
|
+
outputSchema: z64.any(),
|
|
3146
|
+
annotations: { readOnlyHint: true },
|
|
3147
|
+
async execute(input, ctx) {
|
|
3148
|
+
return ctx.client.get("org_invitations", {
|
|
3149
|
+
pathParams: { orgId: input.orgId }
|
|
3150
|
+
});
|
|
3151
|
+
},
|
|
3152
|
+
formatOutput(output, format) {
|
|
3153
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3154
|
+
return JSON.stringify(output, null, 2);
|
|
3155
|
+
}
|
|
3156
|
+
};
|
|
3157
|
+
|
|
3158
|
+
// src/commands/orgs/invite.ts
|
|
3159
|
+
import { z as z65 } from "zod";
|
|
3160
|
+
var inputSchema63 = z65.object({
|
|
3161
|
+
orgId: z65.string().uuid().describe("Organization ID"),
|
|
3162
|
+
email: z65.string().email().describe("Email address to invite"),
|
|
3163
|
+
role: z65.string().optional().default("Member").describe("Role to assign (default: Member)")
|
|
3164
|
+
});
|
|
3165
|
+
var orgsInvite = {
|
|
3166
|
+
name: "orgs.invite",
|
|
3167
|
+
group: "orgs",
|
|
3168
|
+
description: "Invite a user to an organization by email",
|
|
3169
|
+
inputSchema: inputSchema63,
|
|
3170
|
+
outputSchema: z65.any(),
|
|
3171
|
+
annotations: { idempotentHint: true },
|
|
3172
|
+
async execute(input, ctx) {
|
|
3173
|
+
const existing = await ctx.client.get("org_invitations", {
|
|
3174
|
+
pathParams: { orgId: input.orgId }
|
|
3175
|
+
});
|
|
3176
|
+
const items = Array.isArray(existing) ? existing : existing?.items ?? [];
|
|
3177
|
+
const match = items.find(
|
|
3178
|
+
(inv) => inv.email?.toLowerCase() === input.email.toLowerCase() && inv.status === "Pending"
|
|
3179
|
+
);
|
|
3180
|
+
if (match) return match;
|
|
3181
|
+
return ctx.client.post(
|
|
3182
|
+
"org_invitations",
|
|
3183
|
+
{ email: input.email, role: input.role ?? "Member" },
|
|
3184
|
+
{ pathParams: { orgId: input.orgId } }
|
|
3185
|
+
);
|
|
3186
|
+
},
|
|
3187
|
+
formatOutput(output, format) {
|
|
3188
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3189
|
+
return JSON.stringify(output, null, 2);
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
|
|
3193
|
+
// src/commands/orgs/list.ts
|
|
3194
|
+
import { z as z66 } from "zod";
|
|
3195
|
+
var inputSchema64 = z66.object({});
|
|
3196
|
+
var orgsList = {
|
|
3197
|
+
name: "orgs.list",
|
|
3198
|
+
group: "orgs",
|
|
3199
|
+
description: "List all organizations the current user belongs to",
|
|
3200
|
+
inputSchema: inputSchema64,
|
|
3201
|
+
outputSchema: z66.any(),
|
|
3202
|
+
annotations: { readOnlyHint: true },
|
|
3203
|
+
async execute(_input, ctx) {
|
|
3204
|
+
return ctx.client.get("organizations");
|
|
3205
|
+
},
|
|
3206
|
+
formatOutput(output, format) {
|
|
3207
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3208
|
+
return JSON.stringify(output, null, 2);
|
|
3209
|
+
}
|
|
3210
|
+
};
|
|
3211
|
+
|
|
3212
|
+
// src/commands/orgs/list-all.ts
|
|
3213
|
+
import { z as z67 } from "zod";
|
|
3214
|
+
var inputSchema65 = z67.object({});
|
|
3215
|
+
var orgsListAll = {
|
|
3216
|
+
name: "orgs.listAll",
|
|
3217
|
+
group: "orgs",
|
|
3218
|
+
description: "List all organizations (admin)",
|
|
3219
|
+
inputSchema: inputSchema65,
|
|
3220
|
+
outputSchema: z67.any(),
|
|
3221
|
+
annotations: { readOnlyHint: true },
|
|
3222
|
+
async execute(_input, ctx) {
|
|
3223
|
+
return ctx.client.get("organizations_all");
|
|
3224
|
+
},
|
|
3225
|
+
formatOutput(output, format) {
|
|
3226
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3227
|
+
return JSON.stringify(output, null, 2);
|
|
3228
|
+
}
|
|
3229
|
+
};
|
|
3230
|
+
|
|
3231
|
+
// src/commands/orgs/members.ts
|
|
3232
|
+
import { z as z68 } from "zod";
|
|
3233
|
+
var inputSchema66 = z68.object({
|
|
3234
|
+
orgId: z68.string().uuid().describe("Organization ID")
|
|
3235
|
+
});
|
|
3236
|
+
var orgsMembers = {
|
|
3237
|
+
name: "orgs.members",
|
|
3238
|
+
group: "orgs",
|
|
3239
|
+
description: "List members of an organization",
|
|
3240
|
+
inputSchema: inputSchema66,
|
|
3241
|
+
outputSchema: z68.any(),
|
|
3242
|
+
annotations: { readOnlyHint: true },
|
|
3243
|
+
async execute(input, ctx) {
|
|
3244
|
+
return ctx.client.get("organization_members", {
|
|
3245
|
+
pathParams: { id: input.orgId }
|
|
3246
|
+
});
|
|
3247
|
+
},
|
|
3248
|
+
formatOutput(output, format) {
|
|
3249
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3250
|
+
return JSON.stringify(output, null, 2);
|
|
3251
|
+
}
|
|
3252
|
+
};
|
|
3253
|
+
|
|
3254
|
+
// src/commands/orgs/remove-member.ts
|
|
3255
|
+
import { z as z69 } from "zod";
|
|
3256
|
+
var inputSchema67 = z69.object({
|
|
3257
|
+
orgId: z69.string().uuid().describe("Organization ID"),
|
|
3258
|
+
membershipId: z69.string().uuid().describe("Membership ID of the member to remove")
|
|
3259
|
+
});
|
|
3260
|
+
var orgsRemoveMember = {
|
|
3261
|
+
name: "orgs.removeMember",
|
|
3262
|
+
group: "orgs",
|
|
3263
|
+
description: "Remove a member from an organization",
|
|
3264
|
+
inputSchema: inputSchema67,
|
|
3265
|
+
outputSchema: z69.any(),
|
|
3266
|
+
annotations: { destructiveHint: true },
|
|
3267
|
+
async execute(input, ctx) {
|
|
3268
|
+
await ctx.client.del("organization_member", {
|
|
3269
|
+
pathParams: { id: input.orgId, mid: input.membershipId }
|
|
3270
|
+
});
|
|
3271
|
+
return { removed: true };
|
|
3272
|
+
},
|
|
3273
|
+
formatOutput(output, format) {
|
|
3274
|
+
if (format === "compact") return String(output.removed);
|
|
3275
|
+
return JSON.stringify(output, null, 2);
|
|
3276
|
+
}
|
|
3277
|
+
};
|
|
3278
|
+
|
|
3279
|
+
// src/commands/orgs/set-default.ts
|
|
3280
|
+
import { z as z70 } from "zod";
|
|
3281
|
+
var inputSchema68 = z70.object({
|
|
3282
|
+
orgId: z70.string().uuid().describe("Organization ID to set as default")
|
|
3283
|
+
});
|
|
3284
|
+
var orgsSetDefault = {
|
|
3285
|
+
name: "orgs.setDefault",
|
|
3286
|
+
group: "orgs",
|
|
3287
|
+
description: "Set an organization as the default",
|
|
3288
|
+
inputSchema: inputSchema68,
|
|
3289
|
+
outputSchema: z70.any(),
|
|
3290
|
+
annotations: { idempotentHint: true },
|
|
3291
|
+
async execute(input, ctx) {
|
|
3292
|
+
return ctx.client.put("organization_set_default", void 0, {
|
|
3293
|
+
pathParams: { id: input.orgId }
|
|
3294
|
+
});
|
|
3295
|
+
},
|
|
3296
|
+
formatOutput(output, format) {
|
|
3297
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3298
|
+
return JSON.stringify(output, null, 2);
|
|
3299
|
+
}
|
|
3300
|
+
};
|
|
3301
|
+
|
|
3302
|
+
// src/commands/orgs/set-stripe.ts
|
|
3303
|
+
import { z as z71 } from "zod";
|
|
3304
|
+
var inputSchema69 = z71.object({
|
|
3305
|
+
orgId: z71.string().uuid().describe("Organization ID"),
|
|
3306
|
+
customerId: z71.string().min(1).describe("Stripe customer ID (e.g. cus_xxx)")
|
|
3307
|
+
});
|
|
3308
|
+
var orgsSetStripe = {
|
|
3309
|
+
name: "orgs.setStripe",
|
|
3310
|
+
group: "orgs",
|
|
3311
|
+
description: "Set the Stripe customer ID for an organization",
|
|
3312
|
+
inputSchema: inputSchema69,
|
|
3313
|
+
outputSchema: z71.any(),
|
|
3314
|
+
annotations: { idempotentHint: true },
|
|
3315
|
+
async execute(input, ctx) {
|
|
3316
|
+
return ctx.client.put(
|
|
3317
|
+
"organization_stripe_customer",
|
|
3318
|
+
{ customerId: input.customerId },
|
|
3319
|
+
{ pathParams: { id: input.orgId } }
|
|
3320
|
+
);
|
|
3321
|
+
},
|
|
3322
|
+
formatOutput(output, format) {
|
|
3323
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3324
|
+
return JSON.stringify(output, null, 2);
|
|
3325
|
+
}
|
|
3326
|
+
};
|
|
3327
|
+
|
|
3328
|
+
// src/commands/orgs/update.ts
|
|
3329
|
+
init_errors();
|
|
3330
|
+
import { z as z72 } from "zod";
|
|
3331
|
+
var inputSchema70 = z72.object({
|
|
3332
|
+
orgId: z72.string().uuid().describe("Organization ID"),
|
|
3333
|
+
name: z72.string().min(1).optional().describe("New organization name"),
|
|
3334
|
+
description: z72.string().optional().describe("New organization description")
|
|
3335
|
+
});
|
|
3336
|
+
var orgsUpdate = {
|
|
3337
|
+
name: "orgs.update",
|
|
3338
|
+
group: "orgs",
|
|
3339
|
+
description: "Update an existing organization",
|
|
3340
|
+
inputSchema: inputSchema70,
|
|
3341
|
+
outputSchema: z72.any(),
|
|
3342
|
+
annotations: { idempotentHint: true },
|
|
3343
|
+
async execute(input, ctx) {
|
|
3344
|
+
const { orgId, ...fields } = input;
|
|
3345
|
+
if (!fields.name && !fields.description) {
|
|
3346
|
+
throw new ValidationError(
|
|
3347
|
+
"At least one field (name, description) must be provided.",
|
|
3348
|
+
"Provide at least one field to update."
|
|
3349
|
+
);
|
|
3350
|
+
}
|
|
3351
|
+
return ctx.client.patch("organization", fields, {
|
|
3352
|
+
pathParams: { id: orgId }
|
|
3353
|
+
});
|
|
3354
|
+
},
|
|
3355
|
+
formatOutput(output, format) {
|
|
3356
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3357
|
+
return JSON.stringify(output, null, 2);
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
|
|
3361
|
+
// src/commands/orgs/update-role.ts
|
|
3362
|
+
import { z as z73 } from "zod";
|
|
3363
|
+
var inputSchema71 = z73.object({
|
|
3364
|
+
orgId: z73.string().uuid().describe("Organization ID"),
|
|
3365
|
+
membershipId: z73.string().uuid().describe("Membership ID of the member"),
|
|
3366
|
+
role: z73.string().min(1).describe("New role to assign")
|
|
3367
|
+
});
|
|
3368
|
+
var orgsUpdateRole = {
|
|
3369
|
+
name: "orgs.updateRole",
|
|
3370
|
+
group: "orgs",
|
|
3371
|
+
description: "Update an organization member's role",
|
|
3372
|
+
inputSchema: inputSchema71,
|
|
3373
|
+
outputSchema: z73.any(),
|
|
3374
|
+
annotations: { idempotentHint: true },
|
|
3375
|
+
async execute(input, ctx) {
|
|
3376
|
+
return ctx.client.put(
|
|
3377
|
+
"organization_member_role",
|
|
3378
|
+
{ role: input.role },
|
|
3379
|
+
{ pathParams: { id: input.orgId, mid: input.membershipId } }
|
|
3380
|
+
);
|
|
3381
|
+
},
|
|
3382
|
+
formatOutput(output, format) {
|
|
3383
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3384
|
+
return JSON.stringify(output, null, 2);
|
|
3385
|
+
}
|
|
3386
|
+
};
|
|
3387
|
+
|
|
3388
|
+
// src/commands/orgs/index.ts
|
|
3389
|
+
function registerOrgCommands() {
|
|
3390
|
+
return [
|
|
3391
|
+
orgsList,
|
|
3392
|
+
orgsGet,
|
|
3393
|
+
orgsCreate,
|
|
3394
|
+
orgsUpdate,
|
|
3395
|
+
orgsDelete,
|
|
3396
|
+
orgsMembers,
|
|
3397
|
+
orgsUpdateRole,
|
|
3398
|
+
orgsRemoveMember,
|
|
3399
|
+
orgsBilling,
|
|
3400
|
+
orgsSetStripe,
|
|
3401
|
+
orgsSetDefault,
|
|
3402
|
+
orgsInvite,
|
|
3403
|
+
orgsInvitations,
|
|
3404
|
+
orgsCancelInvite,
|
|
3405
|
+
orgsInvitationInfo,
|
|
3406
|
+
orgsAcceptInvite,
|
|
3407
|
+
orgsFiles,
|
|
3408
|
+
orgsListAll
|
|
3409
|
+
];
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
// src/commands/projects/add-member.ts
|
|
3413
|
+
import { z as z74 } from "zod";
|
|
3414
|
+
var inputSchema72 = z74.object({
|
|
3415
|
+
projectId: z74.string().uuid().describe("Project ID"),
|
|
3416
|
+
userId: z74.string().uuid().describe("User ID to add"),
|
|
3417
|
+
role: z74.string().default("Member").describe("Role to assign (default: Member)")
|
|
3418
|
+
});
|
|
3419
|
+
var projectsAddMember = {
|
|
3420
|
+
name: "projects.addMember",
|
|
3421
|
+
group: "projects",
|
|
3422
|
+
description: "Add a member to a project",
|
|
3423
|
+
inputSchema: inputSchema72,
|
|
3424
|
+
outputSchema: z74.any(),
|
|
3425
|
+
async execute(input, ctx) {
|
|
3426
|
+
return ctx.client.post(
|
|
3427
|
+
"project_members",
|
|
3428
|
+
{ userId: input.userId, role: input.role },
|
|
3429
|
+
{ pathParams: { id: input.projectId } }
|
|
3430
|
+
);
|
|
3431
|
+
},
|
|
3432
|
+
formatOutput(output, format) {
|
|
3433
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3434
|
+
return JSON.stringify(output, null, 2);
|
|
3435
|
+
}
|
|
3436
|
+
};
|
|
3437
|
+
|
|
3438
|
+
// src/commands/projects/archive.ts
|
|
3439
|
+
import { z as z75 } from "zod";
|
|
3440
|
+
var inputSchema73 = z75.object({
|
|
3441
|
+
projectId: z75.string().uuid().describe("Project ID to archive")
|
|
3442
|
+
});
|
|
3443
|
+
var projectsArchive = {
|
|
3444
|
+
name: "projects.archive",
|
|
3445
|
+
group: "projects",
|
|
3446
|
+
description: "Archive (delete) a project",
|
|
3447
|
+
inputSchema: inputSchema73,
|
|
3448
|
+
outputSchema: z75.any(),
|
|
3449
|
+
annotations: { destructiveHint: true },
|
|
3450
|
+
async execute(input, ctx) {
|
|
3451
|
+
await ctx.client.del("project", {
|
|
3452
|
+
pathParams: { id: input.projectId }
|
|
3453
|
+
});
|
|
3454
|
+
return { archived: true, projectId: input.projectId };
|
|
3455
|
+
},
|
|
3456
|
+
formatOutput(output, format) {
|
|
3457
|
+
if (format === "compact") return output.projectId;
|
|
3458
|
+
return JSON.stringify(output, null, 2);
|
|
3459
|
+
}
|
|
3460
|
+
};
|
|
3461
|
+
|
|
3462
|
+
// src/commands/projects/create.ts
|
|
3463
|
+
import { z as z76 } from "zod";
|
|
3464
|
+
var inputSchema74 = z76.object({
|
|
3465
|
+
name: z76.string().min(1).describe("Project name"),
|
|
3466
|
+
description: z76.string().optional().describe("Project description"),
|
|
3467
|
+
icon: z76.string().optional().describe("Project icon")
|
|
3468
|
+
});
|
|
3469
|
+
var projectsCreate = {
|
|
3470
|
+
name: "projects.create",
|
|
3471
|
+
group: "projects",
|
|
3472
|
+
description: "Create a new project",
|
|
3473
|
+
inputSchema: inputSchema74,
|
|
3474
|
+
outputSchema: z76.any(),
|
|
3475
|
+
async execute(input, ctx) {
|
|
3476
|
+
return ctx.client.post("projects", input);
|
|
3477
|
+
},
|
|
3478
|
+
formatOutput(output, format) {
|
|
3479
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3480
|
+
return JSON.stringify(output, null, 2);
|
|
3481
|
+
}
|
|
3482
|
+
};
|
|
3483
|
+
|
|
3484
|
+
// src/commands/projects/get.ts
|
|
3485
|
+
import { z as z77 } from "zod";
|
|
3486
|
+
var inputSchema75 = z77.object({
|
|
3487
|
+
projectId: z77.string().uuid().describe("Project ID")
|
|
3488
|
+
});
|
|
3489
|
+
var projectsGet = {
|
|
3490
|
+
name: "projects.get",
|
|
3491
|
+
group: "projects",
|
|
3492
|
+
description: "Get details of a specific project",
|
|
3493
|
+
inputSchema: inputSchema75,
|
|
3494
|
+
outputSchema: z77.any(),
|
|
3495
|
+
annotations: { readOnlyHint: true },
|
|
3496
|
+
async execute(input, ctx) {
|
|
3497
|
+
return ctx.client.get("project", {
|
|
3498
|
+
pathParams: { id: input.projectId }
|
|
3499
|
+
});
|
|
3500
|
+
},
|
|
3501
|
+
formatOutput(output, format) {
|
|
3502
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3503
|
+
return JSON.stringify(output, null, 2);
|
|
3504
|
+
}
|
|
3505
|
+
};
|
|
3506
|
+
|
|
3507
|
+
// src/commands/projects/leave.ts
|
|
3508
|
+
import { z as z78 } from "zod";
|
|
3509
|
+
var inputSchema76 = z78.object({
|
|
3510
|
+
projectId: z78.string().uuid().describe("Project ID to leave")
|
|
3511
|
+
});
|
|
3512
|
+
var projectsLeave = {
|
|
3513
|
+
name: "projects.leave",
|
|
3514
|
+
group: "projects",
|
|
3515
|
+
description: "Leave a project",
|
|
3516
|
+
inputSchema: inputSchema76,
|
|
3517
|
+
outputSchema: z78.any(),
|
|
3518
|
+
annotations: { destructiveHint: true },
|
|
3519
|
+
async execute(input, ctx) {
|
|
3520
|
+
return ctx.client.post("project_leave", void 0, {
|
|
3521
|
+
pathParams: { id: input.projectId }
|
|
3522
|
+
});
|
|
3523
|
+
},
|
|
3524
|
+
formatOutput(output, format) {
|
|
3525
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3526
|
+
return JSON.stringify(output, null, 2);
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
// src/commands/projects/list.ts
|
|
3531
|
+
import { z as z79 } from "zod";
|
|
3532
|
+
var inputSchema77 = z79.object({
|
|
3533
|
+
...paginationInput
|
|
3534
|
+
});
|
|
3535
|
+
var projectsList = {
|
|
3536
|
+
name: "projects.list",
|
|
3537
|
+
group: "projects",
|
|
3538
|
+
description: "List all projects accessible to the current user",
|
|
3539
|
+
inputSchema: inputSchema77,
|
|
3540
|
+
outputSchema: z79.any(),
|
|
3541
|
+
annotations: { readOnlyHint: true },
|
|
3542
|
+
async execute(input, ctx) {
|
|
3543
|
+
return ctx.client.get("projects", {
|
|
3544
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
3545
|
+
});
|
|
3546
|
+
},
|
|
3547
|
+
formatOutput(output, format) {
|
|
3548
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3549
|
+
return JSON.stringify(output, null, 2);
|
|
3550
|
+
}
|
|
3551
|
+
};
|
|
3552
|
+
|
|
3553
|
+
// src/commands/projects/members.ts
|
|
3554
|
+
import { z as z80 } from "zod";
|
|
3555
|
+
var inputSchema78 = z80.object({
|
|
3556
|
+
projectId: z80.string().uuid().describe("Project ID")
|
|
3557
|
+
});
|
|
3558
|
+
var projectsMembers = {
|
|
3559
|
+
name: "projects.members",
|
|
3560
|
+
group: "projects",
|
|
3561
|
+
description: "List members of a project",
|
|
3562
|
+
inputSchema: inputSchema78,
|
|
3563
|
+
outputSchema: z80.any(),
|
|
3564
|
+
annotations: { readOnlyHint: true },
|
|
3565
|
+
async execute(input, ctx) {
|
|
3566
|
+
return ctx.client.get("project_members", {
|
|
3567
|
+
pathParams: { id: input.projectId }
|
|
3568
|
+
});
|
|
3569
|
+
},
|
|
3570
|
+
formatOutput(output, format) {
|
|
3571
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3572
|
+
return JSON.stringify(output, null, 2);
|
|
3573
|
+
}
|
|
3574
|
+
};
|
|
3575
|
+
|
|
3576
|
+
// src/commands/projects/remove-member.ts
|
|
3577
|
+
import { z as z81 } from "zod";
|
|
3578
|
+
var inputSchema79 = z81.object({
|
|
3579
|
+
projectId: z81.string().uuid().describe("Project ID"),
|
|
3580
|
+
userId: z81.string().uuid().describe("User ID to remove")
|
|
3581
|
+
});
|
|
3582
|
+
var projectsRemoveMember = {
|
|
3583
|
+
name: "projects.removeMember",
|
|
3584
|
+
group: "projects",
|
|
3585
|
+
description: "Remove a member from a project",
|
|
3586
|
+
inputSchema: inputSchema79,
|
|
3587
|
+
outputSchema: z81.any(),
|
|
3588
|
+
annotations: { destructiveHint: true },
|
|
3589
|
+
async execute(input, ctx) {
|
|
3590
|
+
await ctx.client.del("project_member", {
|
|
3591
|
+
pathParams: { id: input.projectId, uid: input.userId }
|
|
3592
|
+
});
|
|
3593
|
+
return { removed: true };
|
|
3594
|
+
},
|
|
3595
|
+
formatOutput(output, format) {
|
|
3596
|
+
if (format === "compact") return String(output.removed);
|
|
3597
|
+
return JSON.stringify(output, null, 2);
|
|
3598
|
+
}
|
|
3599
|
+
};
|
|
3600
|
+
|
|
3601
|
+
// src/commands/projects/update.ts
|
|
3602
|
+
init_errors();
|
|
3603
|
+
import { z as z82 } from "zod";
|
|
3604
|
+
var inputSchema80 = z82.object({
|
|
3605
|
+
projectId: z82.string().uuid().describe("Project ID"),
|
|
3606
|
+
name: z82.string().min(1).optional().describe("New project name"),
|
|
3607
|
+
description: z82.string().optional().describe("New project description"),
|
|
3608
|
+
icon: z82.string().optional().describe("New project icon")
|
|
3609
|
+
});
|
|
3610
|
+
var projectsUpdate = {
|
|
3611
|
+
name: "projects.update",
|
|
3612
|
+
group: "projects",
|
|
3613
|
+
description: "Update an existing project",
|
|
3614
|
+
inputSchema: inputSchema80,
|
|
3615
|
+
outputSchema: z82.any(),
|
|
3616
|
+
annotations: { idempotentHint: true },
|
|
3617
|
+
async execute(input, ctx) {
|
|
3618
|
+
const { projectId, ...fields } = input;
|
|
3619
|
+
if (!fields.name && !fields.description && !fields.icon) {
|
|
3620
|
+
throw new ValidationError(
|
|
3621
|
+
"At least one field (name, description, icon) must be provided.",
|
|
3622
|
+
"Provide at least one field to update."
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
return ctx.client.patch("project", fields, {
|
|
3626
|
+
pathParams: { id: projectId }
|
|
3627
|
+
});
|
|
3628
|
+
},
|
|
3629
|
+
formatOutput(output, format) {
|
|
3630
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3631
|
+
return JSON.stringify(output, null, 2);
|
|
3632
|
+
}
|
|
3633
|
+
};
|
|
3634
|
+
|
|
3635
|
+
// src/commands/projects/update-role.ts
|
|
3636
|
+
import { z as z83 } from "zod";
|
|
3637
|
+
var inputSchema81 = z83.object({
|
|
3638
|
+
projectId: z83.string().uuid().describe("Project ID"),
|
|
3639
|
+
userId: z83.string().uuid().describe("User ID of the member"),
|
|
3640
|
+
role: z83.string().min(1).describe("New role to assign")
|
|
3641
|
+
});
|
|
3642
|
+
var projectsUpdateRole = {
|
|
3643
|
+
name: "projects.updateRole",
|
|
3644
|
+
group: "projects",
|
|
3645
|
+
description: "Update a project member's role",
|
|
3646
|
+
inputSchema: inputSchema81,
|
|
3647
|
+
outputSchema: z83.any(),
|
|
3648
|
+
annotations: { idempotentHint: true },
|
|
3649
|
+
async execute(input, ctx) {
|
|
3650
|
+
return ctx.client.put(
|
|
3651
|
+
"project_member_role",
|
|
3652
|
+
{ role: input.role },
|
|
3653
|
+
{ pathParams: { id: input.projectId, uid: input.userId } }
|
|
3654
|
+
);
|
|
3655
|
+
},
|
|
3656
|
+
formatOutput(output, format) {
|
|
3657
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3658
|
+
return JSON.stringify(output, null, 2);
|
|
3659
|
+
}
|
|
3660
|
+
};
|
|
3661
|
+
|
|
3662
|
+
// src/commands/projects/index.ts
|
|
3663
|
+
function registerProjectCommands() {
|
|
3664
|
+
return [
|
|
3665
|
+
projectsList,
|
|
3666
|
+
projectsGet,
|
|
3667
|
+
projectsCreate,
|
|
3668
|
+
projectsUpdate,
|
|
3669
|
+
projectsArchive,
|
|
3670
|
+
projectsMembers,
|
|
3671
|
+
projectsAddMember,
|
|
3672
|
+
projectsUpdateRole,
|
|
3673
|
+
projectsRemoveMember,
|
|
3674
|
+
projectsLeave
|
|
3675
|
+
];
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
// src/commands/search/all.ts
|
|
3679
|
+
import { z as z84 } from "zod";
|
|
3680
|
+
var inputSchema82 = z84.object({
|
|
3681
|
+
query: z84.string().min(1).describe("Search query"),
|
|
3682
|
+
...paginationInput
|
|
3683
|
+
});
|
|
3684
|
+
var searchAll = {
|
|
3685
|
+
name: "search.all",
|
|
3686
|
+
group: "search",
|
|
3687
|
+
description: "Search across all entities",
|
|
3688
|
+
inputSchema: inputSchema82,
|
|
3689
|
+
outputSchema: z84.any(),
|
|
3690
|
+
annotations: { readOnlyHint: true },
|
|
3691
|
+
async execute(input, ctx) {
|
|
3692
|
+
return ctx.client.get("search_all", {
|
|
3693
|
+
queryParams: { q: input.query, page: input.page, pageSize: input.pageSize }
|
|
3694
|
+
});
|
|
3695
|
+
},
|
|
3696
|
+
formatOutput(output, format) {
|
|
3697
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3698
|
+
return JSON.stringify(output, null, 2);
|
|
3699
|
+
}
|
|
3700
|
+
};
|
|
3701
|
+
|
|
3702
|
+
// src/commands/search/file-content.ts
|
|
3703
|
+
import { z as z85 } from "zod";
|
|
3704
|
+
var inputSchema83 = z85.object({
|
|
3705
|
+
query: z85.string().min(1).describe("Search query"),
|
|
3706
|
+
projectId: z85.string().uuid().optional().describe("Filter by project ID"),
|
|
3707
|
+
...paginationInput
|
|
3708
|
+
});
|
|
3709
|
+
var searchFileContent = {
|
|
3710
|
+
name: "search.fileContent",
|
|
3711
|
+
group: "search",
|
|
3712
|
+
description: "Search within file contents",
|
|
3713
|
+
inputSchema: inputSchema83,
|
|
3714
|
+
outputSchema: z85.any(),
|
|
3715
|
+
annotations: { readOnlyHint: true },
|
|
3716
|
+
async execute(input, ctx) {
|
|
3717
|
+
const queryParams = {
|
|
3718
|
+
q: input.query,
|
|
3719
|
+
page: input.page,
|
|
3720
|
+
pageSize: input.pageSize
|
|
3721
|
+
};
|
|
3722
|
+
if (input.projectId) queryParams.project = input.projectId;
|
|
3723
|
+
return ctx.client.get("search_file_content", { queryParams });
|
|
3724
|
+
},
|
|
3725
|
+
formatOutput(output, format) {
|
|
3726
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3727
|
+
return JSON.stringify(output, null, 2);
|
|
3728
|
+
}
|
|
3729
|
+
};
|
|
3730
|
+
|
|
3731
|
+
// src/commands/search/files.ts
|
|
3732
|
+
import { z as z86 } from "zod";
|
|
3733
|
+
var inputSchema84 = z86.object({
|
|
3734
|
+
query: z86.string().min(1).describe("Search query"),
|
|
3735
|
+
...paginationInput
|
|
3736
|
+
});
|
|
3737
|
+
var searchFiles = {
|
|
3738
|
+
name: "search.files",
|
|
3739
|
+
group: "search",
|
|
3740
|
+
description: "Search files by query",
|
|
3741
|
+
inputSchema: inputSchema84,
|
|
3742
|
+
outputSchema: z86.any(),
|
|
3743
|
+
annotations: { readOnlyHint: true },
|
|
3744
|
+
async execute(input, ctx) {
|
|
3745
|
+
return ctx.client.get("search_files", {
|
|
3746
|
+
queryParams: { q: input.query, page: input.page, pageSize: input.pageSize }
|
|
3747
|
+
});
|
|
3748
|
+
},
|
|
3749
|
+
formatOutput(output, format) {
|
|
3750
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3751
|
+
return JSON.stringify(output, null, 2);
|
|
3752
|
+
}
|
|
3753
|
+
};
|
|
3754
|
+
|
|
3755
|
+
// src/commands/search/tasks.ts
|
|
3756
|
+
import { z as z87 } from "zod";
|
|
3757
|
+
var inputSchema85 = z87.object({
|
|
3758
|
+
query: z87.string().min(1).describe("Search query"),
|
|
3759
|
+
...paginationInput
|
|
3760
|
+
});
|
|
3761
|
+
var searchTasks = {
|
|
3762
|
+
name: "search.tasks",
|
|
3763
|
+
group: "search",
|
|
3764
|
+
description: "Search tasks by query",
|
|
3765
|
+
inputSchema: inputSchema85,
|
|
3766
|
+
outputSchema: z87.any(),
|
|
3767
|
+
annotations: { readOnlyHint: true },
|
|
3768
|
+
async execute(input, ctx) {
|
|
3769
|
+
return ctx.client.get("search_tasks", {
|
|
3770
|
+
queryParams: { q: input.query, page: input.page, pageSize: input.pageSize }
|
|
3771
|
+
});
|
|
3772
|
+
},
|
|
3773
|
+
formatOutput(output, format) {
|
|
3774
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3775
|
+
return JSON.stringify(output, null, 2);
|
|
3776
|
+
}
|
|
3777
|
+
};
|
|
3778
|
+
|
|
3779
|
+
// src/commands/search/index.ts
|
|
3780
|
+
function registerSearchCommands() {
|
|
3781
|
+
return [searchTasks, searchFiles, searchAll, searchFileContent];
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
// src/commands/tasks/archive.ts
|
|
3785
|
+
import { z as z88 } from "zod";
|
|
3786
|
+
var inputSchema86 = z88.object({
|
|
3787
|
+
taskId: z88.string().uuid().describe("Task ID to archive")
|
|
3788
|
+
});
|
|
3789
|
+
var tasksArchive = {
|
|
3790
|
+
name: "tasks.archive",
|
|
3791
|
+
group: "tasks",
|
|
3792
|
+
description: "Archive (delete) a task",
|
|
3793
|
+
inputSchema: inputSchema86,
|
|
3794
|
+
outputSchema: z88.any(),
|
|
3795
|
+
annotations: { destructiveHint: true },
|
|
3796
|
+
async execute(input, ctx) {
|
|
3797
|
+
await ctx.client.del("task", {
|
|
3798
|
+
pathParams: { id: input.taskId }
|
|
3799
|
+
});
|
|
3800
|
+
return { archived: true, taskId: input.taskId };
|
|
3801
|
+
},
|
|
3802
|
+
formatOutput(output, format) {
|
|
3803
|
+
if (format === "compact") return output.taskId;
|
|
3804
|
+
return JSON.stringify(output, null, 2);
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
|
|
3808
|
+
// src/commands/tasks/create.ts
|
|
3809
|
+
import { z as z89 } from "zod";
|
|
3810
|
+
var inputSchema87 = z89.object({
|
|
3811
|
+
project: z89.string().uuid().describe("Project ID"),
|
|
3812
|
+
title: z89.string().optional().describe("Task title"),
|
|
3813
|
+
message: z89.string().optional().describe("Initial message")
|
|
3814
|
+
});
|
|
3815
|
+
var tasksCreate = {
|
|
3816
|
+
name: "tasks.create",
|
|
3817
|
+
group: "tasks",
|
|
3818
|
+
description: "Create a new task in a project",
|
|
3819
|
+
inputSchema: inputSchema87,
|
|
3820
|
+
outputSchema: z89.any(),
|
|
3821
|
+
async execute(input, ctx) {
|
|
3822
|
+
return ctx.client.post("tasks", {
|
|
3823
|
+
projectId: input.project,
|
|
3824
|
+
title: input.title,
|
|
3825
|
+
initialMessage: input.message
|
|
3826
|
+
});
|
|
3827
|
+
},
|
|
3828
|
+
formatOutput(output, format) {
|
|
3829
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3830
|
+
return JSON.stringify(output, null, 2);
|
|
3831
|
+
}
|
|
3832
|
+
};
|
|
3833
|
+
|
|
3834
|
+
// src/commands/tasks/get.ts
|
|
3835
|
+
import { z as z90 } from "zod";
|
|
3836
|
+
var inputSchema88 = z90.object({
|
|
3837
|
+
taskId: z90.string().uuid().describe("Task ID")
|
|
3838
|
+
});
|
|
3839
|
+
var tasksGet = {
|
|
3840
|
+
name: "tasks.get",
|
|
3841
|
+
group: "tasks",
|
|
3842
|
+
description: "Get details of a specific task",
|
|
3843
|
+
inputSchema: inputSchema88,
|
|
3844
|
+
outputSchema: z90.any(),
|
|
3845
|
+
annotations: { readOnlyHint: true },
|
|
3846
|
+
async execute(input, ctx) {
|
|
3847
|
+
return ctx.client.get("task", {
|
|
3848
|
+
pathParams: { id: input.taskId }
|
|
3849
|
+
});
|
|
3850
|
+
},
|
|
3851
|
+
formatOutput(output, format) {
|
|
3852
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3853
|
+
return JSON.stringify(output, null, 2);
|
|
3854
|
+
}
|
|
3855
|
+
};
|
|
3856
|
+
|
|
3857
|
+
// src/commands/tasks/list.ts
|
|
3858
|
+
import { z as z91 } from "zod";
|
|
3859
|
+
var inputSchema89 = z91.object({
|
|
3860
|
+
project: z91.string().uuid().optional().describe("Project ID to filter by"),
|
|
3861
|
+
...paginationInput
|
|
3862
|
+
});
|
|
3863
|
+
var tasksList = {
|
|
3864
|
+
name: "tasks.list",
|
|
3865
|
+
group: "tasks",
|
|
3866
|
+
description: "List tasks, optionally filtered by project",
|
|
3867
|
+
inputSchema: inputSchema89,
|
|
3868
|
+
outputSchema: z91.any(),
|
|
3869
|
+
annotations: { readOnlyHint: true },
|
|
3870
|
+
async execute(input, ctx) {
|
|
3871
|
+
if (input.project) {
|
|
3872
|
+
return ctx.client.get("project_tasks", {
|
|
3873
|
+
pathParams: { id: input.project },
|
|
3874
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
3875
|
+
});
|
|
3876
|
+
}
|
|
3877
|
+
return ctx.client.get("tasks", {
|
|
3878
|
+
queryParams: { page: input.page, pageSize: input.pageSize }
|
|
3879
|
+
});
|
|
3880
|
+
},
|
|
3881
|
+
formatOutput(output, format) {
|
|
3882
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3883
|
+
return JSON.stringify(output, null, 2);
|
|
3884
|
+
}
|
|
3885
|
+
};
|
|
3886
|
+
|
|
3887
|
+
// src/commands/tasks/messages.ts
|
|
3888
|
+
import { z as z92 } from "zod";
|
|
3889
|
+
var inputSchema90 = z92.object({
|
|
3890
|
+
taskId: z92.string().uuid().describe("Task ID"),
|
|
3891
|
+
cursor: z92.string().optional().describe("Pagination cursor"),
|
|
3892
|
+
limit: z92.number().int().min(1).max(100).default(50).describe("Results per page")
|
|
3893
|
+
});
|
|
3894
|
+
var tasksMessages = {
|
|
3895
|
+
name: "tasks.messages",
|
|
3896
|
+
group: "tasks",
|
|
3897
|
+
description: "List messages for a task",
|
|
3898
|
+
inputSchema: inputSchema90,
|
|
3899
|
+
outputSchema: z92.any(),
|
|
3900
|
+
annotations: { readOnlyHint: true },
|
|
3901
|
+
async execute(input, ctx) {
|
|
3902
|
+
const queryParams = { limit: input.limit };
|
|
3903
|
+
if (input.cursor) {
|
|
3904
|
+
queryParams.cursor = input.cursor;
|
|
3905
|
+
}
|
|
3906
|
+
return ctx.client.get("task_messages", {
|
|
3907
|
+
pathParams: { id: input.taskId },
|
|
3908
|
+
queryParams
|
|
3909
|
+
});
|
|
3910
|
+
},
|
|
3911
|
+
formatOutput(output, format) {
|
|
3912
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
3913
|
+
return JSON.stringify(output, null, 2);
|
|
3914
|
+
}
|
|
3915
|
+
};
|
|
3916
|
+
|
|
3917
|
+
// src/commands/tasks/send.ts
|
|
3918
|
+
init_errors();
|
|
3919
|
+
init_profiles();
|
|
3920
|
+
import { z as z93 } from "zod";
|
|
3921
|
+
|
|
3922
|
+
// src/lib/stream.ts
|
|
3923
|
+
init_config();
|
|
3924
|
+
var TERMINAL_EVENTS = /* @__PURE__ */ new Set(["completed", "error", "timeout"]);
|
|
3925
|
+
var STREAM_TIMEOUT_MS = 3e5;
|
|
3926
|
+
async function* streamTask(taskId, apiKey, options) {
|
|
3927
|
+
const baseUrl = options?.aiServerBaseUrl ?? AI_SERVER_URL;
|
|
3928
|
+
const url = `${baseUrl}/api/v1/tasks/${taskId}/stream`;
|
|
3929
|
+
const response = await fetch(url, {
|
|
3930
|
+
headers: {
|
|
3931
|
+
"X-API-Key": apiKey,
|
|
3932
|
+
Accept: "text/event-stream"
|
|
3933
|
+
},
|
|
3934
|
+
signal: options?.signal ?? AbortSignal.timeout(STREAM_TIMEOUT_MS)
|
|
3935
|
+
});
|
|
3936
|
+
if (!response.ok || !response.body) {
|
|
3937
|
+
yield { type: "error", content: `Stream connection failed: HTTP ${response.status}` };
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
const reader = response.body.getReader();
|
|
3941
|
+
const decoder = new TextDecoder();
|
|
3942
|
+
let buffer = "";
|
|
3943
|
+
let eventType = "";
|
|
3944
|
+
let dataLines = [];
|
|
3945
|
+
try {
|
|
3946
|
+
while (true) {
|
|
3947
|
+
const { done, value } = await reader.read();
|
|
3948
|
+
if (done) break;
|
|
3949
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3950
|
+
const lines = buffer.split("\n");
|
|
3951
|
+
buffer = lines.pop() ?? "";
|
|
3952
|
+
for (const line of lines) {
|
|
3953
|
+
if (line.startsWith("event: ")) {
|
|
3954
|
+
eventType = line.slice(7).trim();
|
|
3955
|
+
} else if (line.startsWith("data: ")) {
|
|
3956
|
+
dataLines.push(line.slice(6));
|
|
3957
|
+
} else if (line === "") {
|
|
3958
|
+
if (dataLines.length > 0) {
|
|
3959
|
+
const raw = dataLines.join("\n");
|
|
3960
|
+
try {
|
|
3961
|
+
const parsed = JSON.parse(raw);
|
|
3962
|
+
const event = eventType ? { ...parsed, type: eventType } : parsed;
|
|
3963
|
+
yield event;
|
|
3964
|
+
if (TERMINAL_EVENTS.has(event.type)) return;
|
|
3965
|
+
} catch {
|
|
3966
|
+
}
|
|
3967
|
+
dataLines = [];
|
|
3968
|
+
}
|
|
3969
|
+
eventType = "";
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
} finally {
|
|
3974
|
+
reader.releaseLock();
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
async function collectStreamResponse(taskId, apiKey, options) {
|
|
3978
|
+
let content = "";
|
|
3979
|
+
for await (const event of streamTask(taskId, apiKey, options)) {
|
|
3980
|
+
if (event.type === "token") {
|
|
3981
|
+
content += event.content;
|
|
3982
|
+
} else if (event.type === "error") {
|
|
3983
|
+
throw new Error(event.content);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
return content;
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3989
|
+
// src/lib/stream-renderer.ts
|
|
3990
|
+
import pc2 from "picocolors";
|
|
3991
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3992
|
+
var SPINNER_INTERVAL_MS = 80;
|
|
3993
|
+
var StreamRenderer = class {
|
|
3994
|
+
spinnerInterval = null;
|
|
3995
|
+
spinnerFrame = 0;
|
|
3996
|
+
currentLabel = "";
|
|
3997
|
+
useColor;
|
|
3998
|
+
constructor() {
|
|
3999
|
+
this.useColor = isColorEnabled();
|
|
4000
|
+
}
|
|
4001
|
+
renderEvent(event) {
|
|
4002
|
+
switch (event.type) {
|
|
4003
|
+
case "think":
|
|
4004
|
+
this.startSpinner("Thinking...");
|
|
4005
|
+
break;
|
|
4006
|
+
case "tool_start":
|
|
4007
|
+
this.stopSpinner();
|
|
4008
|
+
this.startSpinner(`Using ${event.tool}...`);
|
|
4009
|
+
break;
|
|
4010
|
+
case "tool_end": {
|
|
4011
|
+
this.stopSpinner();
|
|
4012
|
+
const toolEvent = event;
|
|
4013
|
+
const icon = toolEvent.success ? this.useColor ? pc2.green("\u2713") : "\u2713" : this.useColor ? pc2.red("\u2717") : "\u2717";
|
|
4014
|
+
const suffix = toolEvent.result ? ` (${toolEvent.result})` : "";
|
|
4015
|
+
process.stderr.write(` ${icon} ${toolEvent.tool}${suffix}
|
|
4016
|
+
`);
|
|
4017
|
+
break;
|
|
4018
|
+
}
|
|
4019
|
+
case "progress":
|
|
4020
|
+
this.stopSpinner();
|
|
4021
|
+
process.stderr.write(
|
|
4022
|
+
` ${this.useColor ? pc2.dim(event.content) : event.content}
|
|
4023
|
+
`
|
|
4024
|
+
);
|
|
4025
|
+
break;
|
|
4026
|
+
case "token":
|
|
4027
|
+
this.stopSpinner();
|
|
4028
|
+
process.stdout.write(event.content);
|
|
4029
|
+
break;
|
|
4030
|
+
case "completed":
|
|
4031
|
+
this.stopSpinner();
|
|
4032
|
+
process.stdout.write("\n");
|
|
4033
|
+
break;
|
|
4034
|
+
case "error":
|
|
4035
|
+
this.stopSpinner();
|
|
4036
|
+
process.stderr.write(
|
|
4037
|
+
`
|
|
4038
|
+
${this.useColor ? pc2.red("Error:") : "Error:"} ${event.content}
|
|
4039
|
+
`
|
|
4040
|
+
);
|
|
4041
|
+
break;
|
|
4042
|
+
case "timeout":
|
|
4043
|
+
this.stopSpinner();
|
|
4044
|
+
process.stderr.write(`
|
|
4045
|
+
${this.useColor ? pc2.yellow("Stream timed out.") : "Stream timed out."}
|
|
4046
|
+
`);
|
|
4047
|
+
break;
|
|
4048
|
+
}
|
|
4049
|
+
}
|
|
4050
|
+
startSpinner(label) {
|
|
4051
|
+
this.stopSpinner();
|
|
4052
|
+
this.currentLabel = label;
|
|
4053
|
+
this.spinnerFrame = 0;
|
|
4054
|
+
if (!process.stderr.isTTY) {
|
|
4055
|
+
process.stderr.write(` ${label}
|
|
4056
|
+
`);
|
|
4057
|
+
return;
|
|
4058
|
+
}
|
|
4059
|
+
this.writeSpinnerFrame();
|
|
4060
|
+
this.spinnerInterval = setInterval(() => {
|
|
4061
|
+
this.spinnerFrame = (this.spinnerFrame + 1) % SPINNER_FRAMES.length;
|
|
4062
|
+
this.writeSpinnerFrame();
|
|
4063
|
+
}, SPINNER_INTERVAL_MS);
|
|
4064
|
+
}
|
|
4065
|
+
writeSpinnerFrame() {
|
|
4066
|
+
const frame = SPINNER_FRAMES[this.spinnerFrame];
|
|
4067
|
+
const text = this.useColor ? pc2.dim(`${frame} ${this.currentLabel}`) : `${frame} ${this.currentLabel}`;
|
|
4068
|
+
process.stderr.write(`\r ${text}`);
|
|
4069
|
+
}
|
|
4070
|
+
stopSpinner() {
|
|
4071
|
+
if (this.spinnerInterval) {
|
|
4072
|
+
clearInterval(this.spinnerInterval);
|
|
4073
|
+
this.spinnerInterval = null;
|
|
4074
|
+
if (process.stderr.isTTY) {
|
|
4075
|
+
process.stderr.write("\r\x1B[K");
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
};
|
|
4080
|
+
|
|
4081
|
+
// src/commands/tasks/send.ts
|
|
4082
|
+
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4083
|
+
var inputSchema91 = z93.object({
|
|
4084
|
+
taskId: z93.string().uuid().describe("Task ID"),
|
|
4085
|
+
message: z93.string().min(1).describe("Message content"),
|
|
4086
|
+
fileRefs: z93.string().optional().describe("Comma-separated file reference UUIDs"),
|
|
4087
|
+
wait: z93.boolean().default(false).describe("Wait for agent response (polling)"),
|
|
4088
|
+
stream: z93.boolean().default(false).describe("Stream agent response in real-time (SSE)")
|
|
4089
|
+
});
|
|
4090
|
+
async function pollForResponse(client, taskId, userMessageSequence, timeoutMs = 12e4) {
|
|
4091
|
+
const deadline = Date.now() + timeoutMs;
|
|
4092
|
+
while (Date.now() < deadline) {
|
|
4093
|
+
const messages = await client.get("task_messages", {
|
|
4094
|
+
pathParams: { id: taskId },
|
|
4095
|
+
queryParams: { limit: 10 }
|
|
4096
|
+
});
|
|
4097
|
+
const items = Array.isArray(messages) ? messages : messages?.items ?? [];
|
|
4098
|
+
if (Array.isArray(items)) {
|
|
4099
|
+
const assistantMsg = items.find((m) => {
|
|
4100
|
+
const msg = m;
|
|
4101
|
+
return (msg.role === "assistant" || msg.role === "Assistant") && (msg.sequence ?? msg.sequenceNumber ?? 0) > userMessageSequence;
|
|
4102
|
+
});
|
|
4103
|
+
if (assistantMsg) return assistantMsg;
|
|
4104
|
+
}
|
|
4105
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
4106
|
+
}
|
|
4107
|
+
throw new ElnoraError("Timed out waiting for agent response (120s)", {
|
|
4108
|
+
code: "RESPONSE_TIMEOUT",
|
|
4109
|
+
suggestion: "The agent may still be processing. Check with: elnora tasks messages <task-id>"
|
|
4110
|
+
});
|
|
4111
|
+
}
|
|
4112
|
+
var tasksSend = {
|
|
4113
|
+
name: "tasks.send",
|
|
4114
|
+
group: "tasks",
|
|
4115
|
+
description: "Send a message to a task",
|
|
4116
|
+
inputSchema: inputSchema91,
|
|
4117
|
+
outputSchema: z93.any(),
|
|
4118
|
+
async execute(input, ctx) {
|
|
4119
|
+
let referencedFileIds;
|
|
4120
|
+
if (input.fileRefs) {
|
|
4121
|
+
referencedFileIds = input.fileRefs.split(",").map((s) => s.trim()).filter((s) => s.length > 0).filter((s) => UUID_RE2.test(s));
|
|
4122
|
+
}
|
|
4123
|
+
const result = await ctx.client.post(
|
|
4124
|
+
"task_messages",
|
|
4125
|
+
{
|
|
4126
|
+
content: input.message,
|
|
4127
|
+
referencedFileIds
|
|
4128
|
+
},
|
|
4129
|
+
{ pathParams: { id: input.taskId } }
|
|
4130
|
+
);
|
|
4131
|
+
if (ctx.mode === "mcp") {
|
|
4132
|
+
try {
|
|
4133
|
+
const apiKey = resolveApiKey(ctx.profileName);
|
|
4134
|
+
const content = await collectStreamResponse(input.taskId, apiKey);
|
|
4135
|
+
return { sent: true, taskId: input.taskId, response: content };
|
|
4136
|
+
} catch {
|
|
4137
|
+
const seq = result?.sequence ?? 0;
|
|
4138
|
+
return pollForResponse(ctx.client, input.taskId, seq);
|
|
4139
|
+
}
|
|
4140
|
+
}
|
|
4141
|
+
if (!input.wait && !input.stream) {
|
|
4142
|
+
return result;
|
|
4143
|
+
}
|
|
4144
|
+
if (input.stream) {
|
|
4145
|
+
const apiKey = resolveApiKey(ctx.profileName);
|
|
4146
|
+
const renderer = new StreamRenderer();
|
|
4147
|
+
for await (const event of streamTask(input.taskId, apiKey)) {
|
|
4148
|
+
renderer.renderEvent(event);
|
|
4149
|
+
}
|
|
4150
|
+
renderer.stopSpinner();
|
|
4151
|
+
return { sent: true, taskId: input.taskId, streamed: true };
|
|
4152
|
+
}
|
|
4153
|
+
const sequence = result?.sequence ?? result?.sequenceNumber ?? 0;
|
|
4154
|
+
return pollForResponse(ctx.client, input.taskId, sequence);
|
|
4155
|
+
},
|
|
4156
|
+
formatOutput(output, format) {
|
|
4157
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
4158
|
+
return JSON.stringify(output, null, 2);
|
|
4159
|
+
}
|
|
4160
|
+
};
|
|
4161
|
+
|
|
4162
|
+
// src/commands/tasks/update.ts
|
|
4163
|
+
init_errors();
|
|
4164
|
+
import { z as z94 } from "zod";
|
|
4165
|
+
var inputSchema92 = z94.object({
|
|
4166
|
+
taskId: z94.string().uuid().describe("Task ID"),
|
|
4167
|
+
title: z94.string().min(1).optional().describe("New task title"),
|
|
4168
|
+
status: z94.string().optional().describe("New task status")
|
|
4169
|
+
});
|
|
4170
|
+
var tasksUpdate = {
|
|
4171
|
+
name: "tasks.update",
|
|
4172
|
+
group: "tasks",
|
|
4173
|
+
description: "Update an existing task",
|
|
4174
|
+
inputSchema: inputSchema92,
|
|
4175
|
+
outputSchema: z94.any(),
|
|
4176
|
+
annotations: { idempotentHint: true },
|
|
4177
|
+
async execute(input, ctx) {
|
|
4178
|
+
const { taskId, ...fields } = input;
|
|
4179
|
+
if (!fields.title && !fields.status) {
|
|
4180
|
+
throw new ValidationError(
|
|
4181
|
+
"At least one field (title, status) must be provided.",
|
|
4182
|
+
"Provide at least one field to update."
|
|
4183
|
+
);
|
|
4184
|
+
}
|
|
4185
|
+
return ctx.client.patch("task", fields, {
|
|
4186
|
+
pathParams: { id: taskId }
|
|
4187
|
+
});
|
|
4188
|
+
},
|
|
4189
|
+
formatOutput(output, format) {
|
|
4190
|
+
if (format === "compact") return output.id ?? JSON.stringify(output);
|
|
4191
|
+
return JSON.stringify(output, null, 2);
|
|
4192
|
+
}
|
|
4193
|
+
};
|
|
4194
|
+
|
|
4195
|
+
// src/commands/tasks/index.ts
|
|
4196
|
+
function registerTaskCommands() {
|
|
4197
|
+
return [tasksList, tasksGet, tasksCreate, tasksSend, tasksMessages, tasksUpdate, tasksArchive];
|
|
4198
|
+
}
|
|
4199
|
+
|
|
4200
|
+
// src/commands/whoami.ts
|
|
4201
|
+
init_client();
|
|
4202
|
+
init_profiles();
|
|
4203
|
+
function addWhoamiCommand(program2) {
|
|
4204
|
+
program2.command("whoami").description("Show current authentication context").action(async () => {
|
|
4205
|
+
const parentOpts = program2.opts();
|
|
4206
|
+
const profileName = parentOpts.profile ?? "default";
|
|
4207
|
+
try {
|
|
4208
|
+
const key = resolveApiKey(profileName);
|
|
4209
|
+
const masked = key.length > 20 ? `${key.slice(0, 16)}...${key.slice(-4)}` : `${key.slice(0, 4)}...`;
|
|
4210
|
+
let orgName = "unknown";
|
|
4211
|
+
try {
|
|
4212
|
+
const client = new ElnoraApiClient(key);
|
|
4213
|
+
const orgs = await client.get("organizations");
|
|
4214
|
+
const orgList = Array.isArray(orgs) ? orgs : orgs?.items ?? [];
|
|
4215
|
+
if (orgList.length > 0) orgName = orgList[0]?.name ?? "unknown";
|
|
4216
|
+
} catch {
|
|
4217
|
+
}
|
|
4218
|
+
if (parentOpts.json) {
|
|
4219
|
+
console.log(JSON.stringify({ profile: profileName, apiKey: masked, orgName }, null, 2));
|
|
4220
|
+
} else {
|
|
4221
|
+
console.log(`
|
|
4222
|
+
Profile: ${profileName}`);
|
|
4223
|
+
console.log(`API Key: ${masked}`);
|
|
4224
|
+
console.log(`Org: ${orgName}`);
|
|
4225
|
+
console.log("");
|
|
4226
|
+
}
|
|
4227
|
+
} catch (e) {
|
|
4228
|
+
console.error(e instanceof Error ? e.message : "Not authenticated");
|
|
4229
|
+
console.error("Run: elnora auth login");
|
|
4230
|
+
process.exit(3);
|
|
4231
|
+
}
|
|
4232
|
+
});
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
// src/core/registry.ts
|
|
4236
|
+
var CommandRegistry = class {
|
|
4237
|
+
commands = /* @__PURE__ */ new Map();
|
|
4238
|
+
register(command) {
|
|
4239
|
+
if (this.commands.has(command.name)) {
|
|
4240
|
+
throw new Error(`Duplicate command: ${command.name}`);
|
|
4241
|
+
}
|
|
4242
|
+
this.commands.set(command.name, command);
|
|
4243
|
+
}
|
|
4244
|
+
get(name) {
|
|
4245
|
+
return this.commands.get(name);
|
|
4246
|
+
}
|
|
4247
|
+
all() {
|
|
4248
|
+
return Array.from(this.commands.values());
|
|
4249
|
+
}
|
|
4250
|
+
byGroup(group) {
|
|
4251
|
+
return this.all().filter((cmd) => cmd.group === group);
|
|
4252
|
+
}
|
|
4253
|
+
groups() {
|
|
4254
|
+
const groups = /* @__PURE__ */ new Set();
|
|
4255
|
+
for (const cmd of this.commands.values()) {
|
|
4256
|
+
groups.add(cmd.group);
|
|
4257
|
+
}
|
|
4258
|
+
return Array.from(groups).sort();
|
|
4259
|
+
}
|
|
4260
|
+
};
|
|
4261
|
+
|
|
4262
|
+
// src/cli.ts
|
|
4263
|
+
init_errors();
|
|
4264
|
+
|
|
4265
|
+
// src/lib/update-check.ts
|
|
4266
|
+
init_config();
|
|
4267
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "node:fs";
|
|
4268
|
+
import { homedir as homedir2 } from "node:os";
|
|
4269
|
+
import { join as join3 } from "node:path";
|
|
4270
|
+
import pc3 from "picocolors";
|
|
4271
|
+
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@elnora-ai/cli/latest";
|
|
4272
|
+
var CACHE_DIR = join3(homedir2(), ".elnora");
|
|
4273
|
+
var CACHE_FILE = join3(CACHE_DIR, ".update-check");
|
|
4274
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
4275
|
+
var FETCH_TIMEOUT_MS = 3e3;
|
|
4276
|
+
function shouldSkip() {
|
|
4277
|
+
if (process.env.CI || process.env.GITHUB_ACTIONS) return true;
|
|
4278
|
+
if (process.env.ELNORA_NO_UPDATE_CHECK) return true;
|
|
4279
|
+
if (!process.stderr.isTTY) return true;
|
|
4280
|
+
return false;
|
|
4281
|
+
}
|
|
4282
|
+
function readCache() {
|
|
4283
|
+
try {
|
|
4284
|
+
if (!existsSync3(CACHE_FILE)) return null;
|
|
4285
|
+
const raw = readFileSync5(CACHE_FILE, "utf-8");
|
|
4286
|
+
return JSON.parse(raw);
|
|
4287
|
+
} catch {
|
|
4288
|
+
return null;
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
function writeCache(entry) {
|
|
4292
|
+
try {
|
|
4293
|
+
mkdirSync2(CACHE_DIR, { recursive: true });
|
|
4294
|
+
writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
|
|
4295
|
+
} catch {
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
function showUpdateNotice(latest) {
|
|
4299
|
+
const color = isColorEnabled();
|
|
4300
|
+
const msg = color ? `
|
|
4301
|
+
${pc3.yellow(`Update available: v${VERSION} \u2192 v${latest}`)}
|
|
4302
|
+
Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
|
|
4303
|
+
` : `
|
|
4304
|
+
Update available: v${VERSION} \u2192 v${latest}
|
|
4305
|
+
Run: curl -fsSL https://cli.elnora.ai/install.sh | bash
|
|
4306
|
+
`;
|
|
4307
|
+
process.stderr.write(msg);
|
|
4308
|
+
}
|
|
4309
|
+
function registerUpdateCheck() {
|
|
4310
|
+
if (shouldSkip()) return;
|
|
4311
|
+
const cached = readCache();
|
|
4312
|
+
if (cached) {
|
|
4313
|
+
const elapsed = Date.now() - new Date(cached.checkedAt).getTime();
|
|
4314
|
+
if (elapsed < CHECK_INTERVAL_MS) {
|
|
4315
|
+
if (cached.latest && cached.latest !== VERSION && cached.latest !== "unknown") {
|
|
4316
|
+
if (cached.latest > VERSION) {
|
|
4317
|
+
showUpdateNotice(cached.latest);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return;
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
setTimeout(async () => {
|
|
4324
|
+
try {
|
|
4325
|
+
const response = await fetch(NPM_REGISTRY_URL, {
|
|
4326
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
4327
|
+
});
|
|
4328
|
+
if (!response.ok) return;
|
|
4329
|
+
const data = await response.json();
|
|
4330
|
+
const latest = data.version ?? "unknown";
|
|
4331
|
+
writeCache({ checkedAt: (/* @__PURE__ */ new Date()).toISOString(), latest });
|
|
4332
|
+
if (latest !== VERSION && latest !== "unknown" && latest > VERSION) {
|
|
4333
|
+
showUpdateNotice(latest);
|
|
4334
|
+
}
|
|
4335
|
+
} catch {
|
|
4336
|
+
}
|
|
4337
|
+
}, 0);
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
// src/cli.ts
|
|
4341
|
+
process.on("uncaughtException", (err) => {
|
|
4342
|
+
const payload = formatErrorPayload(err);
|
|
4343
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
4344
|
+
`);
|
|
4345
|
+
process.exit(getExitCode(err));
|
|
4346
|
+
});
|
|
4347
|
+
process.on("unhandledRejection", (reason) => {
|
|
4348
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
4349
|
+
const payload = formatErrorPayload(err);
|
|
4350
|
+
process.stderr.write(`${JSON.stringify(payload, null, 2)}
|
|
4351
|
+
`);
|
|
4352
|
+
process.exit(1);
|
|
4353
|
+
});
|
|
4354
|
+
var registry = new CommandRegistry();
|
|
4355
|
+
var commandGroups = [
|
|
4356
|
+
[healthCheck],
|
|
4357
|
+
registerAuthCommands(),
|
|
4358
|
+
registerProjectCommands(),
|
|
4359
|
+
registerTaskCommands(),
|
|
4360
|
+
registerFileCommands(),
|
|
4361
|
+
registerOrgCommands(),
|
|
4362
|
+
registerFolderCommands(),
|
|
4363
|
+
registerSearchCommands(),
|
|
4364
|
+
registerLibraryCommands(),
|
|
4365
|
+
registerAccountCommands(),
|
|
4366
|
+
registerApiKeyCommands(),
|
|
4367
|
+
registerAuditCommands(),
|
|
4368
|
+
registerFeedbackCommands(),
|
|
4369
|
+
registerFlagCommands()
|
|
4370
|
+
];
|
|
4371
|
+
for (const commands of commandGroups) {
|
|
4372
|
+
for (const cmd of commands) {
|
|
4373
|
+
registry.register(cmd);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
var program = buildProgram(registry);
|
|
4377
|
+
addMcpCommands(program, registry);
|
|
4378
|
+
addDoctorCommand(program);
|
|
4379
|
+
addWhoamiCommand(program);
|
|
4380
|
+
addOpenCommand(program);
|
|
4381
|
+
addCompletionCommand(program);
|
|
4382
|
+
registerUpdateCheck();
|
|
4383
|
+
program.parseAsync(process.argv);
|