@arcships/morula-runtime 0.1.0-alpha.2 → 0.1.0-alpha.3

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/dist/morula.js ADDED
@@ -0,0 +1,2749 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ../morula-cli-core/dist/errors.js
4
+ var EXIT = {
5
+ OK: 0,
6
+ OTHER: 1,
7
+ USAGE: 2,
8
+ NOT_FOUND: 3,
9
+ UNAUTHENTICATED: 4,
10
+ FORBIDDEN: 5,
11
+ CONFLICT: 6,
12
+ UPSTREAM: 7
13
+ };
14
+ var CliError = class extends Error {
15
+ code;
16
+ exitCode;
17
+ retryable;
18
+ hint;
19
+ didYouMean;
20
+ constructor(code, exitCode, message, retryable = false, details = {}) {
21
+ super(message);
22
+ this.code = code;
23
+ this.exitCode = exitCode;
24
+ this.retryable = retryable;
25
+ this.name = "CliError";
26
+ this.hint = details.hint;
27
+ this.didYouMean = details.didYouMean;
28
+ }
29
+ };
30
+ function usage(message, details = {}) {
31
+ return new CliError("USAGE", EXIT.USAGE, message, false, details);
32
+ }
33
+
34
+ // ../morula-cli-core/dist/args.js
35
+ var GLOBAL_FLAG_NAMES = ["env", "url", "token", "help", "version", "json"];
36
+ var SHORT_FLAG_ALIASES = {
37
+ h: "help",
38
+ v: "version"
39
+ };
40
+ function parseArgs(argv) {
41
+ const positionals = [];
42
+ const flags = {};
43
+ const bareFlags = [];
44
+ let positionalOnly = false;
45
+ for (let index = 0; index < argv.length; index += 1) {
46
+ const value = argv[index];
47
+ if (positionalOnly) {
48
+ positionals.push(value);
49
+ continue;
50
+ }
51
+ if (value === "--") {
52
+ positionalOnly = true;
53
+ continue;
54
+ }
55
+ if (!value.startsWith("--")) {
56
+ if (value.length === 2 && value.startsWith("-") && SHORT_FLAG_ALIASES[value[1]]) {
57
+ flags[SHORT_FLAG_ALIASES[value[1]]] = true;
58
+ continue;
59
+ }
60
+ positionals.push(value);
61
+ continue;
62
+ }
63
+ const equal = value.indexOf("=");
64
+ if (equal > 2) {
65
+ flags[value.slice(2, equal)] = value.slice(equal + 1);
66
+ continue;
67
+ }
68
+ const key = value.slice(2);
69
+ if (!key)
70
+ throw usage("invalid empty flag");
71
+ const next = argv[index + 1];
72
+ if (next !== void 0 && !next.startsWith("--")) {
73
+ flags[key] = next;
74
+ index += 1;
75
+ } else {
76
+ flags[key] = true;
77
+ bareFlags.push(key);
78
+ }
79
+ }
80
+ return {
81
+ command: positionals.shift(),
82
+ action: positionals.shift(),
83
+ positionals,
84
+ flags,
85
+ bareFlags
86
+ };
87
+ }
88
+ function flagString(flags, ...names) {
89
+ for (const name of names) {
90
+ const value = flags[name];
91
+ if (typeof value === "string" && value.trim())
92
+ return value.trim();
93
+ }
94
+ return void 0;
95
+ }
96
+ function flagBoolean(flags, name) {
97
+ const value = flags[name];
98
+ return value === true || value === "true" || value === "1";
99
+ }
100
+ function flagNumber(flags, name) {
101
+ const raw = flagString(flags, name);
102
+ if (raw === void 0)
103
+ return void 0;
104
+ const value = Number(raw);
105
+ if (!Number.isFinite(value))
106
+ throw usage(`--${name} must be a number`);
107
+ return value;
108
+ }
109
+ function requireFlag(flags, name) {
110
+ const value = flagString(flags, name);
111
+ if (!value)
112
+ throw usage(`missing required flag --${name}`);
113
+ return value;
114
+ }
115
+ function rejectUnknownFlags(flags, allowed) {
116
+ const known = /* @__PURE__ */ new Set([...GLOBAL_FLAG_NAMES, ...allowed]);
117
+ const unknown = Object.keys(flags).find((flag) => !known.has(flag));
118
+ if (unknown)
119
+ throw usage(`unknown flag --${unknown}`);
120
+ }
121
+
122
+ // ../morula-cli-core/dist/auth/browser.js
123
+ import { spawn } from "node:child_process";
124
+ function openBrowser(url, env = process.env) {
125
+ if (env.MORULA_NO_BROWSER === "1")
126
+ return false;
127
+ const platform = process.platform;
128
+ let command;
129
+ let args;
130
+ let detached = true;
131
+ if (platform === "win32") {
132
+ const statement = `Start-Process '${url.replace(/'/g, "''")}'`;
133
+ command = `${env.SYSTEMROOT ?? env.windir ?? "C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
134
+ args = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(statement, "utf16le").toString("base64")];
135
+ detached = false;
136
+ } else {
137
+ command = platform === "darwin" ? "open" : "xdg-open";
138
+ args = [url];
139
+ }
140
+ try {
141
+ const child = spawn(command, args, {
142
+ detached,
143
+ stdio: "ignore",
144
+ windowsHide: true,
145
+ windowsVerbatimArguments: platform === "win32"
146
+ });
147
+ child.on("error", () => {
148
+ });
149
+ child.unref();
150
+ return true;
151
+ } catch {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ // ../morula-cli-core/dist/auth/callback-server.js
157
+ import { createServer } from "node:http";
158
+ async function startCallbackServer(expectedState) {
159
+ let settleCode;
160
+ let rejectCode;
161
+ let settled = false;
162
+ const codePromise = new Promise((resolve, reject) => {
163
+ settleCode = resolve;
164
+ rejectCode = reject;
165
+ });
166
+ const server = createServer((request, response) => {
167
+ const url = new URL(request.url ?? "/", "http://127.0.0.1");
168
+ response.setHeader("content-type", "text/html; charset=utf-8");
169
+ response.setHeader("cache-control", "no-store");
170
+ response.setHeader("x-content-type-options", "nosniff");
171
+ if (url.pathname !== "/callback") {
172
+ response.statusCode = 404;
173
+ response.end("Not found");
174
+ return;
175
+ }
176
+ const state = url.searchParams.get("state");
177
+ if (state !== expectedState) {
178
+ response.statusCode = 400;
179
+ response.end(page(false, "\u767B\u5F55\u56DE\u8C03\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u91CD\u8BD5\u3002"));
180
+ return;
181
+ }
182
+ if (settled) {
183
+ response.statusCode = 409;
184
+ response.end(page(false, "\u8BE5\u767B\u5F55\u56DE\u8C03\u5DF2\u5904\u7406\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u3002"));
185
+ return;
186
+ }
187
+ const error = url.searchParams.get("error");
188
+ const code = url.searchParams.get("code");
189
+ settled = true;
190
+ if (error) {
191
+ response.statusCode = 400;
192
+ response.end(page(false, "SkyDoor \u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u91CD\u8BD5\u3002"));
193
+ rejectCode(new CliError("LOGIN_FAILED", EXIT.OTHER, "SkyDoor login failed"));
194
+ return;
195
+ }
196
+ if (!code) {
197
+ response.statusCode = 400;
198
+ response.end(page(false, "\u767B\u5F55\u56DE\u8C03\u7F3A\u5C11\u6388\u6743\u7801\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u91CD\u8BD5\u3002"));
199
+ rejectCode(new CliError("LOGIN_FAILED", EXIT.OTHER, "invalid login callback (missing code)"));
200
+ return;
201
+ }
202
+ response.statusCode = 200;
203
+ response.end(page(true, "\u8EAB\u4EFD\u9A8C\u8BC1\u5DF2\u5B8C\u6210\uFF0C\u6388\u6743\u7ED3\u679C\u5DF2\u7ECF\u5B89\u5168\u53D1\u9001\u81F3 Morula CLI\u3002"));
204
+ settleCode(code);
205
+ });
206
+ await new Promise((resolve, reject) => {
207
+ server.once("error", reject);
208
+ server.listen(0, "127.0.0.1", resolve);
209
+ });
210
+ const port = server.address().port;
211
+ return {
212
+ redirectUri: `http://127.0.0.1:${port}/callback`,
213
+ waitForCode(timeoutMs = 3e5) {
214
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
215
+ return Promise.reject(new CliError("LOGIN_TIMEOUT", EXIT.OTHER, "login timeout must be a positive number"));
216
+ return new Promise((resolve, reject) => {
217
+ const timer = setTimeout(() => reject(new CliError("LOGIN_TIMEOUT", EXIT.OTHER, "timed out waiting for browser login")), timeoutMs);
218
+ timer.unref?.();
219
+ codePromise.then((code) => {
220
+ clearTimeout(timer);
221
+ resolve(code);
222
+ }, (error) => {
223
+ clearTimeout(timer);
224
+ reject(error);
225
+ });
226
+ });
227
+ },
228
+ close() {
229
+ return new Promise((resolve) => server.close(() => resolve()));
230
+ }
231
+ };
232
+ }
233
+ function escapeHtml(value) {
234
+ const entities = {
235
+ "&": "&amp;",
236
+ "<": "&lt;",
237
+ ">": "&gt;",
238
+ '"': "&quot;",
239
+ "'": "&#39;"
240
+ };
241
+ return value.replace(/[&<>"']/g, (character) => entities[character] ?? character);
242
+ }
243
+ function page(ok, message) {
244
+ const safeMessage = escapeHtml(message);
245
+ const statusLight = ok ? "oklch(0.42 0.085 183)" : "oklch(0.577 0.245 27.325)";
246
+ const statusDark = ok ? "oklch(0.72 0.09 183)" : "oklch(0.704 0.191 22.216)";
247
+ const statusIcon = ok ? '<svg viewBox="0 0 24 24" aria-hidden="true"><path class="status-path" d="m5 12.5 4.25 4.25L19 7"/></svg>' : '<svg viewBox="0 0 24 24" aria-hidden="true"><path class="status-path" d="M18 6 6 18"/><path class="status-path status-path-delay" d="m6 6 12 12"/></svg>';
248
+ const terminalIcon = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 7 4 4-4 4"/><path d="M11 17h8"/></svg>';
249
+ return `<!doctype html>
250
+ <html lang="zh-CN">
251
+ <head>
252
+ <meta charset="utf-8">
253
+ <meta name="viewport" content="width=device-width,initial-scale=1">
254
+ <meta name="color-scheme" content="light dark">
255
+ <title>${ok ? "\u6D4F\u89C8\u5668\u9A8C\u8BC1\u5B8C\u6210" : "\u767B\u5F55\u9A8C\u8BC1\u672A\u5B8C\u6210"} \xB7 Morula CLI</title>
256
+ <style>
257
+ :root {
258
+ color-scheme: light;
259
+ --background: oklch(0.982 0.006 95);
260
+ --foreground: oklch(0.205 0.012 248);
261
+ --card: oklch(0.997 0.002 95);
262
+ --primary: oklch(0.42 0.085 183);
263
+ --primary-foreground: oklch(0.985 0.006 95);
264
+ --muted: oklch(0.944 0.009 95);
265
+ --muted-foreground: oklch(0.48 0.018 248);
266
+ --accent: oklch(0.925 0.018 183);
267
+ --border: oklch(0.91 0.005 95);
268
+ --status: ${statusLight};
269
+ --status-soft: color-mix(in oklch, var(--status) 10%, var(--card));
270
+ --shadow-card: 0 0 0 1px hsl(210 18% 16% / 0.06), 0 14px 28px -18px hsl(210 18% 16% / 0.28);
271
+ --radius: 8px;
272
+ }
273
+
274
+ *, *::before, *::after { box-sizing: border-box; }
275
+ * { margin: 0; }
276
+
277
+ html, body { min-height: 100%; }
278
+
279
+ body {
280
+ min-width: 320px;
281
+ background: var(--background);
282
+ color: var(--foreground);
283
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
284
+ font-feature-settings: "cv02", "cv03", "cv04", "cv11";
285
+ text-rendering: optimizeLegibility;
286
+ -webkit-font-smoothing: antialiased;
287
+ -moz-osx-font-smoothing: grayscale;
288
+ }
289
+
290
+ .shell {
291
+ display: grid;
292
+ grid-template-columns: minmax(420px, 1.08fr) minmax(480px, 0.92fr);
293
+ min-height: 100vh;
294
+ min-height: 100svh;
295
+ }
296
+
297
+ .brand-panel {
298
+ position: relative;
299
+ display: flex;
300
+ min-height: 100vh;
301
+ min-height: 100svh;
302
+ flex-direction: column;
303
+ justify-content: space-between;
304
+ overflow: hidden;
305
+ border-right: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
306
+ background: var(--card);
307
+ padding: clamp(40px, 4vw, 64px);
308
+ }
309
+
310
+ .brand-panel::before {
311
+ position: absolute;
312
+ inset: 0;
313
+ background: linear-gradient(180deg, color-mix(in oklch, var(--accent) 45%, transparent), transparent 42%);
314
+ content: "";
315
+ pointer-events: none;
316
+ }
317
+
318
+ .brand-panel::after {
319
+ position: absolute;
320
+ top: 40px;
321
+ right: 0;
322
+ bottom: 40px;
323
+ width: 1px;
324
+ background: linear-gradient(to bottom, transparent, var(--border), transparent);
325
+ content: "";
326
+ pointer-events: none;
327
+ }
328
+
329
+ .brand, .brand-copy, .brand-footer { position: relative; z-index: 1; }
330
+
331
+ .brand {
332
+ display: inline-flex;
333
+ align-items: center;
334
+ gap: 12px;
335
+ width: fit-content;
336
+ }
337
+
338
+ .brand-logo {
339
+ display: grid;
340
+ width: 40px;
341
+ height: 40px;
342
+ place-items: center;
343
+ border-radius: var(--radius);
344
+ background: var(--primary);
345
+ color: var(--primary-foreground);
346
+ font-size: 18px;
347
+ font-weight: 800;
348
+ letter-spacing: -0.02em;
349
+ }
350
+
351
+ .brand-name { font-size: 20px; font-weight: 600; line-height: 1.15; }
352
+
353
+ .brand-product {
354
+ margin-top: 4px;
355
+ color: var(--muted-foreground);
356
+ font-size: 12px;
357
+ font-weight: 500;
358
+ letter-spacing: 0.12em;
359
+ text-transform: uppercase;
360
+ }
361
+
362
+ .brand-copy { width: min(100%, 580px); }
363
+
364
+ .brand-kicker {
365
+ display: inline-flex;
366
+ align-items: center;
367
+ min-height: 32px;
368
+ border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
369
+ border-radius: 6px;
370
+ background: color-mix(in oklch, var(--muted) 45%, transparent);
371
+ padding: 6px 12px;
372
+ color: var(--muted-foreground);
373
+ font-size: 14px;
374
+ font-weight: 550;
375
+ }
376
+
377
+ .brand-headline {
378
+ margin-top: 28px;
379
+ max-width: 560px;
380
+ font-size: clamp(42px, 4vw, 60px);
381
+ font-weight: 600;
382
+ letter-spacing: -0.035em;
383
+ line-height: 1.12;
384
+ text-wrap: balance;
385
+ }
386
+
387
+ .brand-description {
388
+ margin-top: 20px;
389
+ max-width: 530px;
390
+ color: var(--muted-foreground);
391
+ font-size: 17px;
392
+ line-height: 1.8;
393
+ text-wrap: pretty;
394
+ }
395
+
396
+ .connection {
397
+ display: flex;
398
+ width: min(100%, 530px);
399
+ align-items: center;
400
+ gap: 12px;
401
+ margin-top: 32px;
402
+ border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
403
+ border-radius: var(--radius);
404
+ background: var(--card);
405
+ padding: 14px 16px;
406
+ box-shadow: 0 1px 3px hsl(210 18% 16% / 0.05);
407
+ }
408
+
409
+ .connection-icon, .next-step-icon {
410
+ display: grid;
411
+ flex: 0 0 auto;
412
+ place-items: center;
413
+ border-radius: 6px;
414
+ background: var(--muted);
415
+ color: var(--muted-foreground);
416
+ }
417
+
418
+ .connection-icon { width: 36px; height: 36px; }
419
+ .connection-icon svg, .next-step-icon svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
420
+
421
+ .connection-copy { min-width: 0; }
422
+ .connection-copy span { display: block; color: var(--muted-foreground); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; }
423
+ .connection-copy strong { display: block; margin-top: 3px; font-size: 14px; font-weight: 600; }
424
+
425
+ .secure-label {
426
+ margin-left: auto;
427
+ border-radius: 999px;
428
+ background: var(--accent);
429
+ padding: 5px 9px;
430
+ color: var(--primary);
431
+ font-size: 12px;
432
+ font-weight: 600;
433
+ white-space: nowrap;
434
+ }
435
+
436
+ .brand-footer { color: var(--muted-foreground); font-size: 13px; font-weight: 500; }
437
+
438
+ .result-panel {
439
+ display: grid;
440
+ min-height: 100vh;
441
+ min-height: 100svh;
442
+ place-items: center;
443
+ background: var(--background);
444
+ padding: clamp(28px, 5vw, 72px);
445
+ }
446
+
447
+ .result-wrap { width: min(100%, 448px); }
448
+
449
+ .mobile-brand {
450
+ display: none;
451
+ align-items: center;
452
+ gap: 10px;
453
+ margin-bottom: 24px;
454
+ }
455
+
456
+ .mobile-brand .brand-logo { width: 36px; height: 36px; font-size: 16px; }
457
+ .mobile-brand .brand-name { font-size: 17px; }
458
+
459
+ .status-card {
460
+ width: 100%;
461
+ overflow: hidden;
462
+ border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
463
+ border-radius: var(--radius);
464
+ background: var(--card);
465
+ box-shadow: var(--shadow-card);
466
+ animation: card-in 420ms cubic-bezier(0.23, 1, 0.32, 1) both;
467
+ }
468
+
469
+ .status-content { padding: 32px; }
470
+
471
+ .status-header {
472
+ display: flex;
473
+ align-items: center;
474
+ gap: 14px;
475
+ }
476
+
477
+ .status-icon {
478
+ display: grid;
479
+ width: 52px;
480
+ height: 52px;
481
+ flex: 0 0 auto;
482
+ place-items: center;
483
+ border-radius: 10px;
484
+ background: var(--status-soft);
485
+ color: var(--status);
486
+ }
487
+
488
+ .status-icon svg {
489
+ width: 26px;
490
+ height: 26px;
491
+ fill: none;
492
+ stroke: currentColor;
493
+ stroke-width: 2.25;
494
+ stroke-linecap: round;
495
+ stroke-linejoin: round;
496
+ }
497
+
498
+ .status-path {
499
+ stroke-dasharray: 30;
500
+ stroke-dashoffset: 30;
501
+ animation: draw-path 380ms 180ms cubic-bezier(0.23, 1, 0.32, 1) forwards;
502
+ }
503
+
504
+ .status-path-delay { animation-delay: 260ms; }
505
+
506
+ .status-eyebrow {
507
+ color: var(--muted-foreground);
508
+ font-size: 11px;
509
+ font-weight: 600;
510
+ letter-spacing: 0.1em;
511
+ text-transform: uppercase;
512
+ }
513
+
514
+ .status-label { margin-top: 5px; color: var(--status); font-size: 14px; font-weight: 600; }
515
+
516
+ h1 {
517
+ margin-top: 28px;
518
+ font-size: 28px;
519
+ font-weight: 600;
520
+ letter-spacing: -0.022em;
521
+ line-height: 1.25;
522
+ text-wrap: balance;
523
+ }
524
+
525
+ .message {
526
+ margin-top: 12px;
527
+ color: var(--muted-foreground);
528
+ font-size: 15px;
529
+ line-height: 1.75;
530
+ text-wrap: pretty;
531
+ }
532
+
533
+ .next-step {
534
+ display: flex;
535
+ gap: 12px;
536
+ margin-top: 28px;
537
+ border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
538
+ border-radius: var(--radius);
539
+ background: color-mix(in oklch, var(--muted) 52%, transparent);
540
+ padding: 16px;
541
+ }
542
+
543
+ .next-step-icon { width: 36px; height: 36px; background: var(--card); color: var(--foreground); }
544
+ .next-step-copy { min-width: 0; }
545
+ .next-step-copy span, .next-step-copy strong { display: block; }
546
+ .next-step-copy span { color: var(--muted-foreground); font-size: 12px; line-height: 1.55; }
547
+ .next-step-copy strong { margin: 2px 0 4px; font-size: 14px; font-weight: 600; }
548
+
549
+ .status-footer {
550
+ display: flex;
551
+ align-items: center;
552
+ gap: 8px;
553
+ border-top: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
554
+ padding: 16px 32px;
555
+ color: var(--muted-foreground);
556
+ font-size: 12px;
557
+ font-weight: 500;
558
+ }
559
+
560
+ .status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--status); box-shadow: 0 0 0 4px var(--status-soft); }
561
+
562
+ @keyframes card-in {
563
+ from { opacity: 0; transform: translateY(10px); }
564
+ to { opacity: 1; transform: translateY(0); }
565
+ }
566
+
567
+ @keyframes draw-path { to { stroke-dashoffset: 0; } }
568
+
569
+ @media (max-width: 1023px) {
570
+ .shell { display: block; }
571
+ .brand-panel { display: none; }
572
+ .result-panel { padding: 32px 20px; }
573
+ .mobile-brand { display: flex; }
574
+ }
575
+
576
+ @media (max-width: 420px) {
577
+ .result-panel { align-items: center; padding: 24px 16px; }
578
+ .status-content { padding: 26px 24px; }
579
+ .status-footer { padding: 15px 24px; }
580
+ h1 { font-size: 25px; }
581
+ .message { font-size: 14px; }
582
+ .next-step { margin-top: 24px; padding: 14px; }
583
+ }
584
+
585
+ @media (prefers-color-scheme: dark) {
586
+ :root {
587
+ color-scheme: dark;
588
+ --background: oklch(0.165 0.008 248);
589
+ --foreground: oklch(0.948 0.006 95);
590
+ --card: oklch(0.205 0.01 248);
591
+ --primary: oklch(0.72 0.09 183);
592
+ --primary-foreground: oklch(0.16 0.014 248);
593
+ --muted: oklch(0.255 0.012 248);
594
+ --muted-foreground: oklch(0.69 0.012 248);
595
+ --accent: oklch(0.29 0.025 183);
596
+ --border: oklch(0.275 0.01 248);
597
+ --status: ${statusDark};
598
+ --status-soft: color-mix(in oklch, var(--status) 12%, var(--card));
599
+ --shadow-card: 0 0 0 1px hsl(0 0% 100% / 0.085);
600
+ }
601
+ .connection { box-shadow: 0 0 0 1px hsl(0 0% 100% / 0.04); }
602
+ }
603
+
604
+ @media (prefers-reduced-motion: reduce) {
605
+ *, *::before, *::after {
606
+ scroll-behavior: auto !important;
607
+ animation-duration: 0.01ms !important;
608
+ animation-delay: 0ms !important;
609
+ animation-iteration-count: 1 !important;
610
+ transition-duration: 0.01ms !important;
611
+ }
612
+ }
613
+ </style>
614
+ </head>
615
+ <body>
616
+ <div class="shell">
617
+ <aside class="brand-panel" aria-label="Morula \u54C1\u724C\u4ECB\u7ECD">
618
+ <div class="brand">
619
+ <div class="brand-logo" aria-hidden="true">S</div>
620
+ <div>
621
+ <div class="brand-name">Morula</div>
622
+ <div class="brand-product">Project OS</div>
623
+ </div>
624
+ </div>
625
+
626
+ <div class="brand-copy">
627
+ <div class="brand-kicker">\u9762\u5411\u56E2\u961F\u7684\u8F7B\u91CF\u9879\u76EE\u534F\u540C\u5E73\u53F0</div>
628
+ <p class="brand-headline">\u8BA9\u9879\u76EE\u63A8\u8FDB<br>\u66F4\u6E05\u6670\u3001\u66F4\u4ECE\u5BB9</p>
629
+ <p class="brand-description">\u7EDF\u4E00\u7A7A\u95F4\u3001\u4EFB\u52A1\u3001\u6210\u5458\u4E0E\u8FDB\u5EA6\u4E0A\u4E0B\u6587\uFF0C\u628A\u56E2\u961F\u534F\u4F5C\u4ECE\u96F6\u6563\u6C9F\u901A\u5E26\u56DE\u53EF\u8FFD\u8E2A\u7684\u5DE5\u4F5C\u6D41\u3002</p>
630
+ <div class="connection">
631
+ <div class="connection-icon">${terminalIcon}</div>
632
+ <div class="connection-copy">
633
+ <span>\u8FDE\u63A5\u65B9\u5F0F</span>
634
+ <strong>Morula CLI</strong>
635
+ </div>
636
+ <div class="secure-label">\u5B89\u5168\u8BA4\u8BC1</div>
637
+ </div>
638
+ </div>
639
+
640
+ <div class="brand-footer">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} Morula \u9879\u76EE\u534F\u540C\u5E73\u53F0</div>
641
+ </aside>
642
+
643
+ <main class="result-panel" aria-labelledby="result-title">
644
+ <div class="result-wrap">
645
+ <div class="mobile-brand">
646
+ <div class="brand-logo" aria-hidden="true">S</div>
647
+ <div>
648
+ <div class="brand-name">Morula</div>
649
+ <div class="brand-product">\u9879\u76EE\u534F\u540C\u5E73\u53F0</div>
650
+ </div>
651
+ </div>
652
+
653
+ <section class="status-card">
654
+ <div class="status-content">
655
+ <div class="status-header">
656
+ <div class="status-icon">${statusIcon}</div>
657
+ <div>
658
+ <div class="status-eyebrow">Morula CLI</div>
659
+ <div class="status-label">${ok ? "\u8EAB\u4EFD\u8BA4\u8BC1\u5DF2\u786E\u8BA4" : "\u8EAB\u4EFD\u8BA4\u8BC1\u672A\u5B8C\u6210"}</div>
660
+ </div>
661
+ </div>
662
+
663
+ <h1 id="result-title">${ok ? "\u6D4F\u89C8\u5668\u9A8C\u8BC1\u5B8C\u6210" : "\u672A\u80FD\u5B8C\u6210\u9A8C\u8BC1"}</h1>
664
+ <p class="message">${safeMessage}</p>
665
+
666
+ <div class="next-step">
667
+ <div class="next-step-icon">${terminalIcon}</div>
668
+ <div class="next-step-copy">
669
+ <span>\u4E0B\u4E00\u6B65</span>
670
+ <strong>${ok ? "\u8FD4\u56DE Morula CLI" : "\u56DE\u5230\u7EC8\u7AEF\u91CD\u65B0\u767B\u5F55"}</strong>
671
+ <span>${ok ? "\u7EC8\u7AEF\u5C06\u7EE7\u7EED\u5B8C\u6210\u767B\u5F55\uFF0C\u5E76\u663E\u793A\u6700\u7EC8\u7ED3\u679C\u3002" : "\u8BF7\u56DE\u5230\u7EC8\u7AEF\u91CD\u65B0\u8FD0\u884C\u767B\u5F55\u547D\u4EE4\uFF08auth login\uFF09\uFF0C\u518D\u6B21\u5B8C\u6210\u6D4F\u89C8\u5668\u9A8C\u8BC1\u3002"}</span>
672
+ </div>
673
+ </div>
674
+ </div>
675
+
676
+ <div class="status-footer">
677
+ <span class="status-dot" aria-hidden="true"></span>
678
+ ${ok ? "\u6388\u6743\u56DE\u8C03\u5DF2\u8FD4\u56DE\u672C\u5730\u7EC8\u7AEF" : "\u672C\u6B21\u6388\u6743\u56DE\u8C03\u672A\u88AB\u63A5\u53D7"}
679
+ </div>
680
+ </section>
681
+ </div>
682
+ </main>
683
+ </div>
684
+ </body>
685
+ </html>`;
686
+ }
687
+
688
+ // ../morula-cli-core/dist/build-env.js
689
+ var BUILD_VERSION = "0.1.0-alpha.3";
690
+ var BUILD_ID = "20260914094501.nogit";
691
+ var BUILD_CONTEXTS = {
692
+ "local": "http://localhost:8080/api",
693
+ "test": "https://morula.basecastle.com/api",
694
+ "prod": "https://morula.fazhiplus.com/api"
695
+ };
696
+ var BUILD_DEFAULT_CONTEXT = "prod";
697
+
698
+ // ../morula-api-client/dist/attachment.js
699
+ var AttachmentApi = class {
700
+ client;
701
+ constructor(client) {
702
+ this.client = client;
703
+ }
704
+ /** 获取 TUS 上传签名 */
705
+ getUploadSignature() {
706
+ return this.client.post("/attachments/upload-signature");
707
+ }
708
+ /**
709
+ * 智能体通用文件上传:上传本机文件字节并挂附件。
710
+ * 走 multipart/form-data,服务端接收后上传存储并创建附件记录。
711
+ * 智能归并:不传 resourceType/resourceId 时,服务端按任务绑定对象决定——有 object 挂 object,纯对话挂 Chat 会话最新消息(ag_message)。
712
+ */
713
+ upload(input) {
714
+ const form = new FormData();
715
+ const blob = input.file instanceof Blob ? input.file : new Blob([input.file], { type: input.fileType });
716
+ form.append("file", blob, input.fileName);
717
+ if (input.resourceType)
718
+ form.append("resourceType", input.resourceType);
719
+ if (input.resourceId)
720
+ form.append("resourceId", input.resourceId);
721
+ if (input.taskId)
722
+ form.append("taskId", input.taskId);
723
+ if (input.isInline !== void 0)
724
+ form.append("isInline", input.isInline ? "true" : "false");
725
+ return this.client.post("/agent/attachments/upload", form);
726
+ }
727
+ /** 创建附件记录(上传完成后调用) */
728
+ create(input) {
729
+ return this.client.post("/attachments", input);
730
+ }
731
+ /** 按资源查询附件列表 */
732
+ list(query) {
733
+ return this.client.get("/attachments", query);
734
+ }
735
+ /** 获取附件详情 */
736
+ get(id) {
737
+ return this.client.get(`/attachments/${encodeURIComponent(id)}`);
738
+ }
739
+ /** 获取附件下载 URL */
740
+ getDownloadUrl(id) {
741
+ return this.client.get(`/attachments/${encodeURIComponent(id)}/download-url`);
742
+ }
743
+ /** 更新附件关联的资源 ID */
744
+ updateResourceId(id, resourceId) {
745
+ return this.client.patch(`/attachments/${encodeURIComponent(id)}`, { resourceId });
746
+ }
747
+ /** 删除附件 */
748
+ delete(id) {
749
+ return this.client.delete(`/attachments/${encodeURIComponent(id)}`);
750
+ }
751
+ };
752
+
753
+ // ../morula-api-client/dist/auth.js
754
+ var AuthApi = class {
755
+ client;
756
+ constructor(client) {
757
+ this.client = client;
758
+ }
759
+ authorize(query) {
760
+ return this.client.get("/auth/cli/authorize", query);
761
+ }
762
+ exchangeToken(input) {
763
+ return this.client.post("/auth/cli/token", input);
764
+ }
765
+ revokeCliToken() {
766
+ return this.client.post("/auth/cli/logout");
767
+ }
768
+ profile() {
769
+ return this.client.get("/auth/profile");
770
+ }
771
+ };
772
+
773
+ // ../morula-api-client/dist/client.js
774
+ var EXIT_CODE = {
775
+ OK: 0,
776
+ OTHER: 1,
777
+ USAGE: 2,
778
+ NOT_FOUND: 3,
779
+ UNAUTHENTICATED: 4,
780
+ FORBIDDEN: 5,
781
+ CONFLICT: 6,
782
+ UPSTREAM: 7
783
+ };
784
+ var MorulaApiError = class extends Error {
785
+ code;
786
+ exitCode;
787
+ status;
788
+ retryable;
789
+ details;
790
+ name = "MorulaApiError";
791
+ constructor(code, message, exitCode, status2, retryable = false, details) {
792
+ super(message);
793
+ this.code = code;
794
+ this.exitCode = exitCode;
795
+ this.status = status2;
796
+ this.retryable = retryable;
797
+ this.details = details;
798
+ }
799
+ };
800
+ var CLI_BUILD_HEADER = "X-Morula-Cli-Build";
801
+ var CLI_CAPS_HEADER = "X-Morula-Cli-Caps";
802
+ var CLI_BUILD_MAX_LENGTH = 64;
803
+ var CLI_CAPS_MAX_LENGTH = 255;
804
+ var DEFAULT_TIMEOUT_MS = 3e4;
805
+ var MorulaApiClient = class {
806
+ baseUrl;
807
+ token;
808
+ timeoutMs;
809
+ fetchImpl;
810
+ defaultHeaders;
811
+ constructor(options) {
812
+ const baseUrl = options.baseUrl.trim().replace(/\/+$/, "");
813
+ if (!baseUrl) {
814
+ throw new TypeError("baseUrl is required");
815
+ }
816
+ let parsed;
817
+ try {
818
+ parsed = new URL(baseUrl);
819
+ } catch {
820
+ throw new TypeError(`baseUrl must be an absolute URL: ${baseUrl}`);
821
+ }
822
+ if (!["http:", "https:"].includes(parsed.protocol)) {
823
+ throw new TypeError("baseUrl must use http or https");
824
+ }
825
+ this.baseUrl = parsed.toString().replace(/\/+$/, "");
826
+ this.token = options.token;
827
+ this.timeoutMs = positiveTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
828
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
829
+ if (typeof this.fetchImpl !== "function") {
830
+ throw new TypeError("fetch is unavailable; provide options.fetch");
831
+ }
832
+ const build = sanitizeHeaderValue(options.clientBuild, CLI_BUILD_MAX_LENGTH);
833
+ const caps = sanitizeHeaderValue(options.clientCaps, CLI_CAPS_MAX_LENGTH);
834
+ this.defaultHeaders = {
835
+ // CLI 身份头(P0-③):仅当调用方声明时注入——未声明的调用方(runtime/测试/第三方复用)
836
+ // 发出的请求不带这两个头,服务端按「未知」处理,不误报陈旧构建。
837
+ ...build ? { [CLI_BUILD_HEADER]: build } : {},
838
+ ...caps ? { [CLI_CAPS_HEADER]: caps } : {},
839
+ // 显式 headers 优先级最高(更具体的调用方可以覆盖默认值)
840
+ ...options.headers
841
+ };
842
+ }
843
+ buildUrl(path, query) {
844
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
845
+ const url = new URL(`${this.baseUrl}${normalizedPath}`);
846
+ if (!query)
847
+ return url;
848
+ for (const [key, raw] of Object.entries(query)) {
849
+ const values = Array.isArray(raw) ? raw : [raw];
850
+ for (const value of values) {
851
+ if (value !== void 0 && value !== null && value !== "") {
852
+ url.searchParams.append(key, String(value));
853
+ }
854
+ }
855
+ }
856
+ return url;
857
+ }
858
+ async request(path, options = {}) {
859
+ const method = options.method ?? "GET";
860
+ const url = this.buildUrl(path, options.query);
861
+ const headers = {
862
+ Accept: "application/json",
863
+ ...this.defaultHeaders,
864
+ ...options.headers
865
+ };
866
+ const token = options.token === void 0 ? this.token : options.token;
867
+ if (token)
868
+ headers.Authorization = `Bearer ${token}`;
869
+ let body;
870
+ if (options.body !== void 0) {
871
+ if (typeof FormData !== "undefined" && options.body instanceof FormData) {
872
+ delete headers["Content-Type"];
873
+ body = options.body;
874
+ } else {
875
+ headers["Content-Type"] ??= "application/json";
876
+ body = JSON.stringify(options.body);
877
+ }
878
+ }
879
+ const controller = new AbortController();
880
+ const timeoutMs = positiveTimeout(options.timeoutMs ?? this.timeoutMs);
881
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
882
+ timer.unref?.();
883
+ try {
884
+ const response = await this.fetchImpl(url, {
885
+ method,
886
+ headers,
887
+ body,
888
+ signal: controller.signal
889
+ });
890
+ const raw = await response.text();
891
+ const parsed = parseBody(raw);
892
+ if (!response.ok) {
893
+ throw errorFromResponse(response.status, parsed, raw);
894
+ }
895
+ return unwrapEnvelope(parsed, response.status);
896
+ } catch (error) {
897
+ if (error instanceof MorulaApiError)
898
+ throw error;
899
+ if (controller.signal.aborted) {
900
+ throw new MorulaApiError("TIMEOUT", `request timed out after ${timeoutMs}ms`, EXIT_CODE.UPSTREAM, void 0, true);
901
+ }
902
+ throw new MorulaApiError("NETWORK", `request failed: ${error instanceof Error ? error.message : String(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
903
+ } finally {
904
+ clearTimeout(timer);
905
+ }
906
+ }
907
+ get(path, query) {
908
+ return this.request(path, { query });
909
+ }
910
+ post(path, body, query) {
911
+ return this.request(path, { method: "POST", body, query });
912
+ }
913
+ patch(path, body, query) {
914
+ return this.request(path, { method: "PATCH", body, query });
915
+ }
916
+ delete(path, query) {
917
+ return this.request(path, { method: "DELETE", query });
918
+ }
919
+ };
920
+ function sanitizeHeaderValue(raw, maxLength) {
921
+ if (!raw)
922
+ return void 0;
923
+ let value = "";
924
+ for (const char of raw) {
925
+ const code = char.codePointAt(0) ?? 0;
926
+ if (code <= 31 || code === 127)
927
+ continue;
928
+ value += char;
929
+ }
930
+ value = value.trim();
931
+ return value ? value.slice(0, maxLength) : void 0;
932
+ }
933
+ function positiveTimeout(value) {
934
+ if (!Number.isFinite(value) || value <= 0) {
935
+ throw new TypeError("timeoutMs must be a positive number");
936
+ }
937
+ return value;
938
+ }
939
+ function parseBody(raw) {
940
+ if (!raw)
941
+ return void 0;
942
+ try {
943
+ return JSON.parse(raw);
944
+ } catch {
945
+ return raw;
946
+ }
947
+ }
948
+ function unwrapEnvelope(value, status2 = 200) {
949
+ if (isEnvelope(value)) {
950
+ if (value.success === false || value.code >= 400) {
951
+ throw errorFromResponse(value.code || status2, value, JSON.stringify(value));
952
+ }
953
+ return value.data;
954
+ }
955
+ return value;
956
+ }
957
+ function isEnvelope(value) {
958
+ return !!value && typeof value === "object" && "data" in value && "code" in value && "message" in value && "success" in value;
959
+ }
960
+ function errorFromResponse(status2, parsed, raw) {
961
+ const message = extractMessage(parsed, raw, status2);
962
+ const details = isEnvelope(parsed) ? parsed.data : parsed;
963
+ if (status2 === 400 || status2 === 422) {
964
+ return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status2, false, details);
965
+ }
966
+ if (status2 === 401) {
967
+ return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status2, false, details);
968
+ }
969
+ if (status2 === 403) {
970
+ return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status2, false, details);
971
+ }
972
+ if (status2 === 404) {
973
+ return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status2, false, details);
974
+ }
975
+ if (status2 === 409) {
976
+ return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status2, false, details);
977
+ }
978
+ if (status2 === 429) {
979
+ return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status2, true, details);
980
+ }
981
+ if (status2 >= 500) {
982
+ return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status2, true, details);
983
+ }
984
+ return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status2, false, details);
985
+ }
986
+ function extractMessage(parsed, raw, status2) {
987
+ if (parsed && typeof parsed === "object" && "message" in parsed) {
988
+ const message = parsed.message;
989
+ if (typeof message === "string" && message)
990
+ return message;
991
+ if (Array.isArray(message) && message.length)
992
+ return message.map(String).join(", ");
993
+ }
994
+ if (typeof parsed === "string" && parsed)
995
+ return parsed.slice(0, 500);
996
+ if (raw)
997
+ return raw.slice(0, 500);
998
+ return `HTTP ${status2}`;
999
+ }
1000
+
1001
+ // ../morula-api-client/dist/comment.js
1002
+ var CommentApi = class {
1003
+ client;
1004
+ constructor(client) {
1005
+ this.client = client;
1006
+ }
1007
+ list(objectId, query = {}) {
1008
+ const params = { ...query };
1009
+ if (objectId)
1010
+ params.objectId = objectId;
1011
+ return this.client.get("/comments", params);
1012
+ }
1013
+ get(id) {
1014
+ return this.client.get(`/comments/${encodeURIComponent(requiredId(id))}`);
1015
+ }
1016
+ create(input) {
1017
+ return this.client.post("/comments", input);
1018
+ }
1019
+ update(id, input) {
1020
+ return this.client.patch(`/comments/${encodeURIComponent(requiredId(id))}`, input);
1021
+ }
1022
+ delete(id) {
1023
+ return this.client.delete(`/comments/${encodeURIComponent(requiredId(id))}`);
1024
+ }
1025
+ };
1026
+ function requiredId(id) {
1027
+ const normalized = id.trim();
1028
+ if (!normalized)
1029
+ throw new TypeError("comment id is required");
1030
+ return normalized;
1031
+ }
1032
+
1033
+ // ../morula-api-client/dist/report.js
1034
+ var ReportApi = class {
1035
+ client;
1036
+ constructor(client) {
1037
+ this.client = client;
1038
+ }
1039
+ create(input) {
1040
+ return this.client.post("/agent-reports", input);
1041
+ }
1042
+ get(id) {
1043
+ return this.client.get(`/agent-reports/${encodeURIComponent(requiredId2(id))}`);
1044
+ }
1045
+ listByAutopilot(autopilotId, query = {}) {
1046
+ return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId2(autopilotId, "autopilot"))}/reports`, { ...query });
1047
+ }
1048
+ archive(id) {
1049
+ return this.client.patch(`/agent-reports/${encodeURIComponent(requiredId2(id))}`, { status: "archived" });
1050
+ }
1051
+ };
1052
+ function requiredId2(id, label = "report") {
1053
+ const normalized = id.trim();
1054
+ if (!normalized)
1055
+ throw new TypeError(`${label} id is required`);
1056
+ return normalized;
1057
+ }
1058
+
1059
+ // ../morula-api-client/dist/space-member.js
1060
+ var SpaceMemberApi = class {
1061
+ client;
1062
+ constructor(client) {
1063
+ this.client = client;
1064
+ }
1065
+ list(spaceId, query = {}) {
1066
+ return this.client.get("/space-members", { spaceId, ...query });
1067
+ }
1068
+ get(id) {
1069
+ return this.client.get(`/space-members/${encodeURIComponent(requiredId3(id))}`);
1070
+ }
1071
+ create(input) {
1072
+ return this.client.post("/space-members", input);
1073
+ }
1074
+ update(id, input) {
1075
+ return this.client.patch(`/space-members/${encodeURIComponent(requiredId3(id))}`, input);
1076
+ }
1077
+ delete(id) {
1078
+ return this.client.delete(`/space-members/${encodeURIComponent(requiredId3(id))}`);
1079
+ }
1080
+ };
1081
+ function requiredId3(id) {
1082
+ const normalized = id.trim();
1083
+ if (!normalized)
1084
+ throw new TypeError("space member id is required");
1085
+ return normalized;
1086
+ }
1087
+
1088
+ // ../morula-api-client/dist/space.js
1089
+ var SpaceApi = class {
1090
+ client;
1091
+ constructor(client) {
1092
+ this.client = client;
1093
+ }
1094
+ list(query = {}) {
1095
+ return this.client.get("/spaces", query);
1096
+ }
1097
+ get(id) {
1098
+ return this.client.get(`/spaces/${encodeURIComponent(requiredId4(id))}`);
1099
+ }
1100
+ create(input) {
1101
+ return this.client.post("/spaces", input);
1102
+ }
1103
+ update(id, input) {
1104
+ return this.client.patch(`/spaces/${encodeURIComponent(requiredId4(id))}`, input);
1105
+ }
1106
+ delete(id) {
1107
+ return this.client.delete(`/spaces/${encodeURIComponent(requiredId4(id))}`);
1108
+ }
1109
+ };
1110
+ function requiredId4(id) {
1111
+ const normalized = id.trim();
1112
+ if (!normalized)
1113
+ throw new TypeError("space id is required");
1114
+ return normalized;
1115
+ }
1116
+
1117
+ // ../morula-api-client/dist/task.js
1118
+ var TaskApi = class {
1119
+ client;
1120
+ constructor(client) {
1121
+ this.client = client;
1122
+ }
1123
+ list(query = {}) {
1124
+ return this.client.get("/objects", query);
1125
+ }
1126
+ get(id) {
1127
+ return this.client.get(`/objects/${encodeURIComponent(requiredId5(id))}`);
1128
+ }
1129
+ create(input) {
1130
+ return this.client.post("/objects", input);
1131
+ }
1132
+ update(id, input) {
1133
+ return this.client.patch(`/objects/${encodeURIComponent(requiredId5(id))}`, input);
1134
+ }
1135
+ delete(id) {
1136
+ return this.client.delete(`/objects/${encodeURIComponent(requiredId5(id))}`);
1137
+ }
1138
+ /** 查询空间字段模板(字段 key → id 映射、options) */
1139
+ getSchemaFields(spaceId) {
1140
+ return this.client.get("/schemas", { spaceId }).then((result) => {
1141
+ const rows = Array.isArray(result) ? result : result.data;
1142
+ const fields = [];
1143
+ for (const row of rows ?? []) {
1144
+ for (const f of row.fields ?? []) {
1145
+ if (f.status !== "archived")
1146
+ fields.push(f);
1147
+ }
1148
+ }
1149
+ return fields;
1150
+ });
1151
+ }
1152
+ /** 获取对象自定义字段值 */
1153
+ getFieldValues(objectId) {
1154
+ return this.client.get(`/object-field-values?objectId=${encodeURIComponent(objectId)}`);
1155
+ }
1156
+ /** 创建或更新对象自定义字段值(upsert) */
1157
+ upsertFieldValue(input) {
1158
+ return this.client.post("/object-field-values", input);
1159
+ }
1160
+ /** 删除对象自定义字段值(清空语义) */
1161
+ deleteFieldValue(objectId, schemaFieldId) {
1162
+ return this.client.delete(`/object-field-values`, { objectId, schemaFieldId });
1163
+ }
1164
+ };
1165
+ function requiredId5(id) {
1166
+ const normalized = id.trim();
1167
+ if (!normalized)
1168
+ throw new TypeError("task id is required");
1169
+ return normalized;
1170
+ }
1171
+
1172
+ // ../morula-api-client/dist/webhook.js
1173
+ var WebhookApi = class {
1174
+ client;
1175
+ constructor(client) {
1176
+ this.client = client;
1177
+ }
1178
+ list(query = {}) {
1179
+ return this.client.get("/webhooks", query);
1180
+ }
1181
+ create(input) {
1182
+ return this.client.post("/webhooks", input);
1183
+ }
1184
+ };
1185
+
1186
+ // ../morula-api-client/dist/index.js
1187
+ var MorulaClient = class {
1188
+ http;
1189
+ attachments;
1190
+ auth;
1191
+ comments;
1192
+ reports;
1193
+ tasks;
1194
+ spaces;
1195
+ spaceMembers;
1196
+ webhooks;
1197
+ constructor(options) {
1198
+ this.http = new MorulaApiClient(options);
1199
+ this.attachments = new AttachmentApi(this.http);
1200
+ this.auth = new AuthApi(this.http);
1201
+ this.comments = new CommentApi(this.http);
1202
+ this.reports = new ReportApi(this.http);
1203
+ this.tasks = new TaskApi(this.http);
1204
+ this.spaces = new SpaceApi(this.http);
1205
+ this.spaceMembers = new SpaceMemberApi(this.http);
1206
+ this.webhooks = new WebhookApi(this.http);
1207
+ }
1208
+ };
1209
+ function createMorulaClient(options) {
1210
+ return new MorulaClient(options);
1211
+ }
1212
+
1213
+ // ../morula-cli-core/dist/help.js
1214
+ var HELP_SCHEMA_VERSION = 1;
1215
+ function actionUsage(action) {
1216
+ const args = (action.args ?? []).map((a) => a.required ? `<${a.name}>` : `[<${a.name}>]`);
1217
+ const flags = (action.flags ?? []).map((f) => {
1218
+ if (f.type === "boolean")
1219
+ return `[--${f.name}]`;
1220
+ return `[--${f.name} ${f.valueHint ?? "<value>"}]`;
1221
+ });
1222
+ return [action.action, ...args, ...flags].join(" ");
1223
+ }
1224
+ function renderResourceHelp(base, resource) {
1225
+ return {
1226
+ // 兼容字段:commands 只含该 resource(键格式与顶层一致),消费方按 resource 取用不受影响
1227
+ commands: { [resource.resource]: renderCommandEntries(resource) },
1228
+ schemaVersion: HELP_SCHEMA_VERSION,
1229
+ resource: resource.resource,
1230
+ summary: resource.summary,
1231
+ actions: resource.actions.map((a) => ({
1232
+ action: a.action,
1233
+ usage: actionUsage(a),
1234
+ summary: a.summary,
1235
+ writes: a.writes ?? "none",
1236
+ interactive: a.interactive ?? false
1237
+ })),
1238
+ ...resource.notes?.length ? { notes: resource.notes } : {},
1239
+ globalFlags: base.globalFlags,
1240
+ exitCodes: base.exitCodes
1241
+ };
1242
+ }
1243
+ function renderActionHelp(base, resource, action) {
1244
+ return {
1245
+ schemaVersion: HELP_SCHEMA_VERSION,
1246
+ resource: resource.resource,
1247
+ action: action.action,
1248
+ summary: action.summary,
1249
+ args: (action.args ?? []).map((a) => ({
1250
+ name: a.name,
1251
+ required: a.required ?? false,
1252
+ description: a.description
1253
+ })),
1254
+ flags: (action.flags ?? []).map((f) => ({
1255
+ name: f.name,
1256
+ type: f.type,
1257
+ description: f.description,
1258
+ ...f.valueHint ? { valueHint: f.valueHint } : {},
1259
+ required: f.required ?? false,
1260
+ ...f.enum ? { enum: f.enum } : {},
1261
+ ...f.default !== void 0 ? { default: f.default } : {}
1262
+ })),
1263
+ examples: action.examples ?? [],
1264
+ writes: action.writes ?? "none",
1265
+ interactive: action.interactive ?? false,
1266
+ idempotent: action.idempotent ?? false,
1267
+ emits: action.emits ?? [],
1268
+ globalFlags: base.globalFlags,
1269
+ exitCodes: base.exitCodes
1270
+ };
1271
+ }
1272
+ function renderCommandEntries(resource) {
1273
+ const entries = {};
1274
+ for (const action of resource.actions) {
1275
+ const notes = [action.summary];
1276
+ if (action.writes === "local")
1277
+ notes.push("writes local state");
1278
+ else if (action.writes === "remote")
1279
+ notes.push("writes remote state");
1280
+ if (action.interactive)
1281
+ notes.push("interactive (blocks until you finish in the browser)");
1282
+ entries[actionUsage(action)] = notes.join("; ");
1283
+ }
1284
+ return entries;
1285
+ }
1286
+ function renderTopLevelCommands(spec) {
1287
+ const commands = {};
1288
+ for (const resource of spec.resources)
1289
+ commands[resource.resource] = renderCommandEntries(resource);
1290
+ return commands;
1291
+ }
1292
+ function renderResourceIndex(spec) {
1293
+ return spec.resources.map((r) => ({
1294
+ resource: r.resource,
1295
+ summary: r.summary,
1296
+ actions: r.actions.map((a) => ({
1297
+ action: a.action,
1298
+ usage: actionUsage(a),
1299
+ summary: a.summary,
1300
+ writes: a.writes ?? "none",
1301
+ interactive: a.interactive ?? false
1302
+ }))
1303
+ }));
1304
+ }
1305
+ function renderLegacyResourceHelp(base, resource) {
1306
+ const entries = base.commands[resource];
1307
+ if (!entries)
1308
+ return {};
1309
+ return {
1310
+ commands: { [resource]: entries },
1311
+ schemaVersion: HELP_SCHEMA_VERSION,
1312
+ resource,
1313
+ actions: Object.entries(entries).map(([usage2, summary]) => ({
1314
+ action: usage2.split(" ")[0],
1315
+ usage: usage2,
1316
+ summary,
1317
+ // legacy 命令面没有契约声明:如实标 unknown,而不是假装 none(避免智能体误判只读)
1318
+ writes: "unknown",
1319
+ interactive: false
1320
+ })),
1321
+ globalFlags: base.globalFlags,
1322
+ exitCodes: base.exitCodes
1323
+ };
1324
+ }
1325
+
1326
+ // ../morula-cli-core/dist/spec.js
1327
+ var GLOBAL_FLAG_NAMES2 = GLOBAL_FLAG_NAMES;
1328
+ var GLOBAL_FLAGS = [
1329
+ { name: "env", type: "string", valueHint: "<context>", description: "select test or a saved context" },
1330
+ { name: "url", type: "string", valueHint: "<baseUrl>", description: "override API base URL" },
1331
+ { name: "token", type: "string", valueHint: "<token>", description: "override token for this invocation (not persisted)" },
1332
+ { name: "help", type: "boolean", description: "print this machine-readable command surface" },
1333
+ { name: "version", type: "boolean", description: "print version JSON" },
1334
+ // 习惯吸收:输出本来就只有 JSON,`--json` 是 no-op 别名(智能体/脚本的第一直觉动作,此前报 unknown flag)
1335
+ { name: "json", type: "boolean", description: "no-op: output is always JSON (accepted for familiarity)" }
1336
+ ];
1337
+ function usageLine(resource, action) {
1338
+ const parts = action.args?.length ? action.args.map((a) => a.required ? `<${a.name}>` : `[<${a.name}>]`).join(" ") : "";
1339
+ const flagParts = (action.flags ?? []).map((f) => {
1340
+ if (f.type === "boolean")
1341
+ return `[--${f.name}]`;
1342
+ return `[--${f.name} ${f.valueHint ?? "<value>"}]`;
1343
+ });
1344
+ return [action.action, parts, ...flagParts].filter(Boolean).join(" ");
1345
+ }
1346
+ function suggest(input, candidates) {
1347
+ let best;
1348
+ let bestScore = Number.POSITIVE_INFINITY;
1349
+ for (const candidate of candidates) {
1350
+ const distance = editDistance(input.toLowerCase(), candidate.toLowerCase());
1351
+ const threshold = Math.max(1, Math.floor(candidate.length / 3));
1352
+ if (distance <= threshold && distance < bestScore) {
1353
+ bestScore = distance;
1354
+ best = candidate;
1355
+ }
1356
+ }
1357
+ return best;
1358
+ }
1359
+ function editDistance(a, b) {
1360
+ if (a === b)
1361
+ return 0;
1362
+ const rows = a.length + 1;
1363
+ const cols = b.length + 1;
1364
+ const d = Array.from({ length: rows }, () => Array.from({ length: cols }).fill(0));
1365
+ for (let i = 0; i < rows; i += 1)
1366
+ d[i][0] = i;
1367
+ for (let j = 0; j < cols; j += 1)
1368
+ d[0][j] = j;
1369
+ for (let i = 1; i < rows; i += 1) {
1370
+ for (let j = 1; j < cols; j += 1) {
1371
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1372
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
1373
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1])
1374
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
1375
+ }
1376
+ }
1377
+ return d[a.length][b.length];
1378
+ }
1379
+ function validateArgs(resource, action, parsed, options = {}) {
1380
+ const declaredFlags = action.flags ?? [];
1381
+ const declaredNames = new Set(declaredFlags.map((f) => f.name));
1382
+ const knownNames = [...declaredNames, ...GLOBAL_FLAG_NAMES2];
1383
+ const usageHint = [options.cliName, resource.resource, usageLine(resource, action)].filter(Boolean).join(" ");
1384
+ for (const flag of Object.keys(parsed.flags)) {
1385
+ if (knownNames.includes(flag))
1386
+ continue;
1387
+ const near = suggest(flag, knownNames);
1388
+ throw usage(`unknown flag --${flag}`, {
1389
+ hint: usageHint,
1390
+ ...near ? { didYouMean: `--${near}` } : {}
1391
+ });
1392
+ }
1393
+ for (const flag of parsed.bareFlags) {
1394
+ const spec = declaredFlags.find((f) => f.name === flag);
1395
+ if (!spec || spec.type === "boolean")
1396
+ continue;
1397
+ const valuePart = spec.valueHint ?? "<value>";
1398
+ throw usage(`missing value for --${flag}`, {
1399
+ hint: `${usageHint} (e.g. --${flag} ${valuePart})`
1400
+ });
1401
+ }
1402
+ for (const spec of declaredFlags) {
1403
+ if (!spec.required)
1404
+ continue;
1405
+ if (flagString(parsed.flags, spec.name) === void 0)
1406
+ throw usage(`missing required flag --${spec.name}`, { hint: usageHint });
1407
+ }
1408
+ for (const spec of declaredFlags) {
1409
+ const raw = flagString(parsed.flags, spec.name);
1410
+ if (raw === void 0)
1411
+ continue;
1412
+ if (spec.type === "number" && !Number.isFinite(Number(raw)))
1413
+ throw usage(`--${spec.name} must be a number (got "${raw}")`, { hint: usageHint });
1414
+ if (spec.enum && !spec.enum.includes(raw)) {
1415
+ throw usage(`--${spec.name} must be one of: ${spec.enum.join(" | ")} (got "${raw}")`, {
1416
+ hint: usageHint
1417
+ });
1418
+ }
1419
+ }
1420
+ if (action.allowExtraPositionals)
1421
+ return;
1422
+ const declaredArgs = action.args ?? [];
1423
+ const required = declaredArgs.filter((a) => a.required).length;
1424
+ const total = declaredArgs.length;
1425
+ const given = parsed.positionals.length;
1426
+ if (given < required) {
1427
+ const missing = declaredArgs.slice(given).filter((a) => a.required).map((a) => `<${a.name}>`).join(" ");
1428
+ throw usage(`missing required argument ${missing}`, { hint: usageHint });
1429
+ }
1430
+ if (given > total) {
1431
+ const extra = parsed.positionals.slice(total).join(" ");
1432
+ throw usage(`unexpected extra argument "${extra}"`, { hint: usageHint });
1433
+ }
1434
+ }
1435
+ function findResource(spec, resource) {
1436
+ return spec.resources.find((r) => r.resource === resource);
1437
+ }
1438
+ function findAction(resource, action) {
1439
+ return resource.actions.find((a) => a.action === action);
1440
+ }
1441
+
1442
+ // ../morula-cli-core/dist/utils/config.js
1443
+ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
1444
+ import { homedir } from "node:os";
1445
+ import { dirname, join } from "node:path";
1446
+ import { dump, load } from "js-yaml";
1447
+ var FileConfigStore = class {
1448
+ path;
1449
+ constructor(path, env = process.env) {
1450
+ this.path = path ?? env.MORULA_CONFIG ?? join(env.MORULA_CONFIG_DIR ?? join(homedir(), ".morula-cli"), "config.yaml");
1451
+ }
1452
+ load() {
1453
+ if (!existsSync(this.path))
1454
+ return {};
1455
+ try {
1456
+ const value = load(readFileSync(this.path, "utf8"));
1457
+ if (!isConfig(value))
1458
+ throw new Error("expected a context configuration object");
1459
+ return value;
1460
+ } catch (error) {
1461
+ throw new CliError("INVALID_CONFIG", EXIT.OTHER, `failed to read config ${this.path}: ${error instanceof Error ? error.message : String(error)}`);
1462
+ }
1463
+ }
1464
+ save(config) {
1465
+ const dir = dirname(this.path);
1466
+ mkdirSync(dir, { recursive: true, mode: 448 });
1467
+ chmodSync(dir, 448);
1468
+ writeFileSync(this.path, dump(config, { noRefs: true, lineWidth: -1 }), { mode: 384 });
1469
+ chmodSync(this.path, 384);
1470
+ }
1471
+ clear() {
1472
+ rmSync(this.path, { force: true });
1473
+ }
1474
+ };
1475
+ function isConfig(value) {
1476
+ if (!value || typeof value !== "object" || Array.isArray(value))
1477
+ return false;
1478
+ const config = value;
1479
+ if (config.currentContext !== void 0 && typeof config.currentContext !== "string")
1480
+ return false;
1481
+ if (config.contexts === void 0)
1482
+ return true;
1483
+ if (!config.contexts || typeof config.contexts !== "object" || Array.isArray(config.contexts))
1484
+ return false;
1485
+ return Object.values(config.contexts).every((context) => {
1486
+ return !!context && typeof context === "object" && typeof context.url === "string" && (context.token === void 0 || typeof context.token === "string");
1487
+ });
1488
+ }
1489
+ function resolveRuntime(flags, env, store, options = {}) {
1490
+ const config = store.load();
1491
+ const explicitUrl = flagString(flags, "url") ?? env.MORULA_API_URL;
1492
+ let context;
1493
+ if (flagString(flags, "env")) {
1494
+ context = flagString(flags, "env");
1495
+ } else if (env.MORULA_ENV) {
1496
+ context = env.MORULA_ENV;
1497
+ } else if (explicitUrl) {
1498
+ context = contextFromUrl(explicitUrl);
1499
+ } else {
1500
+ context = config.currentContext ?? BUILD_DEFAULT_CONTEXT;
1501
+ }
1502
+ const saved = config.contexts?.[context];
1503
+ const baseUrl = normalizeBaseUrl(explicitUrl ?? saved?.url ?? BUILD_CONTEXTS[context]);
1504
+ if (!baseUrl) {
1505
+ throw usage(`unknown environment "${context}"; pass --url or use one of: ${Object.keys(BUILD_CONTEXTS).join(", ")}`);
1506
+ }
1507
+ const { token, tokenSource } = resolveToken(flags, env, context, baseUrl, saved, options);
1508
+ return {
1509
+ context,
1510
+ baseUrl,
1511
+ token,
1512
+ tokenSource
1513
+ };
1514
+ }
1515
+ function resolveToken(flags, env, context, baseUrl, saved, options) {
1516
+ const explicitToken = flagString(flags, "token");
1517
+ if (explicitToken)
1518
+ return { token: explicitToken, tokenSource: "flag" };
1519
+ if (!options.excludeTaskToken && env.MORULA_TASK_TOKEN)
1520
+ return { token: env.MORULA_TASK_TOKEN, tokenSource: "env:MORULA_TASK_TOKEN" };
1521
+ if (env.MORULA_TOKEN)
1522
+ return { token: env.MORULA_TOKEN, tokenSource: "env:MORULA_TOKEN" };
1523
+ const savedToken = saved?.url && normalizeBaseUrl(saved.url) === baseUrl ? saved.token : void 0;
1524
+ if (savedToken)
1525
+ return { token: savedToken, tokenSource: `config:${context}` };
1526
+ return { token: void 0, tokenSource: null };
1527
+ }
1528
+ function saveContext(store, name, patch) {
1529
+ const current = store.load();
1530
+ const contexts = { ...current.contexts, [name]: { ...current.contexts?.[name], ...patch } };
1531
+ store.save({ ...current, currentContext: name, contexts });
1532
+ }
1533
+ function clearContextToken(store, name) {
1534
+ const current = store.load();
1535
+ const existing = current.contexts?.[name];
1536
+ if (!existing)
1537
+ return;
1538
+ store.save({
1539
+ ...current,
1540
+ contexts: { ...current.contexts, [name]: { url: existing.url } }
1541
+ });
1542
+ }
1543
+ function normalizeBaseUrl(value) {
1544
+ if (!value)
1545
+ return void 0;
1546
+ let url;
1547
+ try {
1548
+ url = new URL(value);
1549
+ } catch {
1550
+ throw usage("server URL must be an absolute http(s) URL");
1551
+ }
1552
+ if (!["http:", "https:"].includes(url.protocol))
1553
+ throw usage("server URL must use http or https");
1554
+ url.hash = "";
1555
+ url.search = "";
1556
+ return url.toString().replace(/\/+$/, "");
1557
+ }
1558
+ function contextFromUrl(raw) {
1559
+ try {
1560
+ const hostname = new URL(raw).hostname;
1561
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]")
1562
+ return "local";
1563
+ return hostname;
1564
+ } catch {
1565
+ return BUILD_DEFAULT_CONTEXT;
1566
+ }
1567
+ }
1568
+
1569
+ // ../morula-cli-core/dist/cli.js
1570
+ var CLI_CAPABILITIES = [
1571
+ "server-mr",
1572
+ "target-project",
1573
+ "analysis-verdict"
1574
+ ];
1575
+ async function run(argv, deps = {}) {
1576
+ const out = deps.out ?? ((line) => console.log(line));
1577
+ const err = deps.err ?? ((line) => console.error(line));
1578
+ const help = deps.help;
1579
+ const commands = deps.commands ?? {};
1580
+ const spec = deps.spec;
1581
+ let requestedExit;
1582
+ const setExitCode = (code) => {
1583
+ requestedExit = code;
1584
+ };
1585
+ let parsed;
1586
+ try {
1587
+ parsed = parseArgs(argv);
1588
+ if (flagBoolean(parsed.flags, "version")) {
1589
+ out(JSON.stringify(help?.build ? { version: help.version, build: help.build } : { version: help?.version }));
1590
+ return EXIT.OK;
1591
+ }
1592
+ if (!parsed.command || parsed.command === "help" || flagBoolean(parsed.flags, "help")) {
1593
+ out(JSON.stringify(renderHelp(deps, parsed.command, parsed.action), null, 2));
1594
+ return EXIT.OK;
1595
+ }
1596
+ const handler = commands[parsed.command];
1597
+ if (!handler) {
1598
+ const near = suggestCommand(parsed.command, Object.keys(commands));
1599
+ throw new CliError("USAGE", EXIT.USAGE, `unknown command: ${parsed.command}`, false, {
1600
+ hint: `${help?.name ?? "cli"} --help`,
1601
+ ...near ? { didYouMean: near } : {}
1602
+ });
1603
+ }
1604
+ if (spec) {
1605
+ const resource = findResource(spec, parsed.command);
1606
+ if (resource && parsed.action) {
1607
+ const action = findAction(resource, parsed.action);
1608
+ if (action)
1609
+ validateArgs(resource, action, parsed, { cliName: spec.name });
1610
+ }
1611
+ }
1612
+ const env = deps.env ?? process.env;
1613
+ const store = deps.store ?? new FileConfigStore(void 0, env);
1614
+ const runtime = resolveRuntime(parsed.flags, env, store);
1615
+ const client = createMorulaClient({
1616
+ baseUrl: runtime.baseUrl,
1617
+ token: runtime.token,
1618
+ fetch: deps.fetch,
1619
+ // CLI 身份上报(P0-③):构建标识 + 能力清单随每个请求到达服务端,
1620
+ // 服务端把「执行该任务的 CLI」记到任务上(陈旧构建由此可查、可告警)。
1621
+ clientBuild: BUILD_ID,
1622
+ clientCaps: CLI_CAPABILITIES.join(",")
1623
+ });
1624
+ const result = await handler({ parsed, client, store, runtime, env, err, setExitCode });
1625
+ out(JSON.stringify(result, null, 2));
1626
+ return requestedExit ?? EXIT.OK;
1627
+ } catch (error) {
1628
+ if (error instanceof CliError || error instanceof MorulaApiError) {
1629
+ err(JSON.stringify({
1630
+ code: error.code,
1631
+ message: error.message,
1632
+ ...error.retryable ? { retryable: true } : {},
1633
+ // 新增字段(可选):可执行的下一步 + typo 建议
1634
+ ...error instanceof CliError && error.hint ? { hint: error.hint } : {},
1635
+ ...error instanceof CliError && error.didYouMean ? { didYouMean: error.didYouMean } : {}
1636
+ }));
1637
+ return error.exitCode;
1638
+ }
1639
+ err(JSON.stringify({ code: "ERROR", message: error instanceof Error ? error.message : String(error) }));
1640
+ return EXIT.OTHER;
1641
+ }
1642
+ }
1643
+ function renderHelp(deps, resource, action) {
1644
+ const help = deps.help;
1645
+ const spec = deps.spec;
1646
+ const scope = {};
1647
+ if (resource && resource !== "help")
1648
+ scope.resource = resource;
1649
+ if (action)
1650
+ scope.action = action;
1651
+ if (spec) {
1652
+ if (scope.resource) {
1653
+ const found = findResource(spec, scope.resource);
1654
+ if (found) {
1655
+ if (scope.action) {
1656
+ const act = findAction(found, scope.action);
1657
+ if (act && help)
1658
+ return renderActionHelp(help, found, act);
1659
+ }
1660
+ if (help)
1661
+ return renderResourceHelp(help, found);
1662
+ }
1663
+ const near = suggestCommand(scope.resource, spec.resources.map((r) => r.resource));
1664
+ return {
1665
+ ...renderTopLevel(spec, help),
1666
+ error: `unknown resource: ${scope.resource}`,
1667
+ ...near ? { didYouMean: near } : { hint: "resources \u5B57\u6BB5\u5217\u51FA\u5168\u90E8\u53EF\u7528 resource" }
1668
+ };
1669
+ }
1670
+ return renderTopLevel(spec, help);
1671
+ }
1672
+ if (help && scope.resource) {
1673
+ const sub = renderLegacyResourceHelp(help, scope.resource);
1674
+ if (Object.keys(sub).length > 0)
1675
+ return sub;
1676
+ }
1677
+ return help;
1678
+ }
1679
+ function renderTopLevel(spec, help) {
1680
+ return {
1681
+ ...help ?? {},
1682
+ // spec 路径下 commands 由契约渲染(键格式与 legacy 完全一致),其余字段沿用入口注入的 HELP
1683
+ commands: renderTopLevelCommands(spec),
1684
+ globalFlags: help?.globalFlags ?? flagsToRecord(GLOBAL_FLAGS),
1685
+ schemaVersion: 1,
1686
+ resources: renderResourceIndex(spec)
1687
+ };
1688
+ }
1689
+ function flagsToRecord(flags) {
1690
+ return Object.fromEntries(flags.map((f) => [`--${f.name}`, f.description]));
1691
+ }
1692
+ function suggestCommand(input, candidates) {
1693
+ return suggest(input, candidates);
1694
+ }
1695
+ async function runEntry(deps = {}) {
1696
+ const argv = process.argv.slice(2);
1697
+ if (argv[0] === "--")
1698
+ argv.shift();
1699
+ run(argv, deps).then((code) => {
1700
+ process.exitCode = code;
1701
+ }).catch((error) => {
1702
+ console.error(JSON.stringify({ code: "ERROR", message: error instanceof Error ? error.message : String(error) }));
1703
+ process.exitCode = 1;
1704
+ });
1705
+ }
1706
+
1707
+ // ../morula-cli-core/dist/commands/auth.js
1708
+ import { createHash, randomBytes } from "node:crypto";
1709
+ async function authCommand(context) {
1710
+ const { action } = context.parsed;
1711
+ if (action === "login")
1712
+ return login(context);
1713
+ if (action === "logout")
1714
+ return logout(context);
1715
+ if (action === "status")
1716
+ return status(context);
1717
+ throw usage("usage: morula auth <login|logout|status>");
1718
+ }
1719
+ async function login(context) {
1720
+ rejectUnknownFlags(context.parsed.flags, ["url"]);
1721
+ const verifier = randomBytes(32).toString("base64url");
1722
+ const challenge = createHash("sha256").update(verifier).digest("base64url");
1723
+ const state = randomBytes(16).toString("base64url");
1724
+ const loopback = await startCallbackServer(state);
1725
+ try {
1726
+ const response = await context.client.auth.authorize({
1727
+ response_type: "code",
1728
+ code_challenge: challenge,
1729
+ code_challenge_method: "S256",
1730
+ redirect_uri: loopback.redirectUri,
1731
+ state
1732
+ });
1733
+ if (!response.authorizeUrl)
1734
+ throw new CliError("LOGIN_FAILED", EXIT.OTHER, "server returned no authorizeUrl");
1735
+ context.err(`Opening browser for SkyDoor login\u2026
1736
+ If it does not open, visit:
1737
+ ${response.authorizeUrl}`);
1738
+ openBrowser(response.authorizeUrl, context.env);
1739
+ const timeout = Number(context.env.MORULA_LOGIN_TIMEOUT_MS) || 3e5;
1740
+ const code = await loopback.waitForCode(timeout);
1741
+ const token = await context.client.auth.exchangeToken({ code, code_verifier: verifier });
1742
+ if (!token.access_token)
1743
+ throw new CliError("LOGIN_FAILED", EXIT.OTHER, "server returned no access_token");
1744
+ saveContext(context.store, context.runtime.context, { url: context.runtime.baseUrl, token: token.access_token });
1745
+ return { loggedIn: true, context: context.runtime.context, baseUrl: context.runtime.baseUrl, expiresIn: token.expires_in };
1746
+ } finally {
1747
+ await loopback.close();
1748
+ }
1749
+ }
1750
+ async function logout(context) {
1751
+ rejectUnknownFlags(context.parsed.flags, []);
1752
+ if (context.runtime.token) {
1753
+ try {
1754
+ await context.client.auth.revokeCliToken();
1755
+ } catch (error) {
1756
+ if (!(error instanceof MorulaApiError) || error.exitCode !== EXIT.UNAUTHENTICATED)
1757
+ throw error;
1758
+ }
1759
+ }
1760
+ clearContextToken(context.store, context.runtime.context);
1761
+ return { loggedIn: false, context: context.runtime.context };
1762
+ }
1763
+ async function status(context) {
1764
+ rejectUnknownFlags(context.parsed.flags, []);
1765
+ if (!context.runtime.token) {
1766
+ return { loggedIn: false, context: context.runtime.context, baseUrl: context.runtime.baseUrl };
1767
+ }
1768
+ try {
1769
+ const user = await context.client.auth.profile();
1770
+ return { loggedIn: true, context: context.runtime.context, baseUrl: context.runtime.baseUrl, user };
1771
+ } catch (error) {
1772
+ if (typeof error === "object" && error && "exitCode" in error && error.exitCode === EXIT.UNAUTHENTICATED) {
1773
+ return { loggedIn: false, context: context.runtime.context, baseUrl: context.runtime.baseUrl, expired: true };
1774
+ }
1775
+ throw error;
1776
+ }
1777
+ }
1778
+
1779
+ // ../morula-cli/src/commands/attachment.ts
1780
+ import { readFile } from "node:fs/promises";
1781
+ import { basename, extname } from "node:path";
1782
+ var MIME_BY_EXT = {
1783
+ ".pdf": "application/pdf",
1784
+ ".doc": "application/msword",
1785
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1786
+ ".xls": "application/vnd.ms-excel",
1787
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1788
+ ".ppt": "application/vnd.ms-powerpoint",
1789
+ ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
1790
+ ".md": "text/markdown",
1791
+ ".txt": "text/plain",
1792
+ ".csv": "text/csv",
1793
+ ".json": "application/json",
1794
+ ".png": "image/png",
1795
+ ".jpg": "image/jpeg",
1796
+ ".jpeg": "image/jpeg",
1797
+ ".gif": "image/gif",
1798
+ ".webp": "image/webp",
1799
+ ".svg": "image/svg+xml",
1800
+ ".zip": "application/zip"
1801
+ };
1802
+ function inferMimeType(filePath) {
1803
+ const ext = extname(filePath).toLowerCase();
1804
+ return MIME_BY_EXT[ext] ?? "application/octet-stream";
1805
+ }
1806
+ async function attachmentCommand(context) {
1807
+ const { action, positionals, flags } = context.parsed;
1808
+ if (!context.runtime.token)
1809
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
1810
+ if (action === "upload") {
1811
+ rejectUnknownFlags(flags, ["object", "comment", "ag-message", "task", "inline"]);
1812
+ const filePath = positionals[0]?.trim();
1813
+ if (!filePath)
1814
+ throw usage("usage: morula attachment upload <file> [--object <objectId>] [--comment <commentId>] [--ag-message] [--task <agentTaskId>] [--inline]");
1815
+ const objectId = flagString(flags, "object");
1816
+ const commentId = flagString(flags, "comment");
1817
+ const isAgMessage = flagBoolean(flags, "ag-message");
1818
+ const isInline = flagBoolean(flags, "inline");
1819
+ const taskId = flagString(flags, "task");
1820
+ const targets = [objectId, commentId, isAgMessage ? "ag-message" : void 0].filter(Boolean);
1821
+ if (targets.length > 1)
1822
+ throw usage("--object / --comment / --ag-message \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A");
1823
+ const buffer = await readFile(filePath);
1824
+ const resourceType = objectId ? "object" : commentId ? "comment" : isAgMessage ? "ag_message" : void 0;
1825
+ return context.client.attachments.upload({
1826
+ file: new Blob([buffer], { type: inferMimeType(filePath) }),
1827
+ fileName: basename(filePath),
1828
+ fileType: inferMimeType(filePath),
1829
+ resourceType,
1830
+ resourceId: objectId ?? commentId,
1831
+ taskId,
1832
+ isInline
1833
+ });
1834
+ }
1835
+ if (action === "list") {
1836
+ rejectUnknownFlags(flags, ["object"]);
1837
+ const objectId = requireFlag(flags, "object");
1838
+ return context.client.attachments.list({ resourceType: "object", resourceId: objectId });
1839
+ }
1840
+ if (action === "get") {
1841
+ rejectUnknownFlags(flags, []);
1842
+ return context.client.attachments.get(requireId(positionals, "attachment get"));
1843
+ }
1844
+ if (action === "download-url") {
1845
+ rejectUnknownFlags(flags, []);
1846
+ return context.client.attachments.getDownloadUrl(requireId(positionals, "attachment download-url"));
1847
+ }
1848
+ if (action === "delete") {
1849
+ rejectUnknownFlags(flags, []);
1850
+ return context.client.attachments.delete(requireId(positionals, "attachment delete"));
1851
+ }
1852
+ throw usage("usage: morula attachment <upload|list|get|download-url|delete>");
1853
+ }
1854
+ function requireId(positionals, command) {
1855
+ const id = positionals[0]?.trim();
1856
+ if (!id)
1857
+ throw usage(`usage: morula ${command} <id>`);
1858
+ return id;
1859
+ }
1860
+
1861
+ // ../morula-cli/src/commands/comment.ts
1862
+ function detectCommentFormat(text) {
1863
+ if (!text)
1864
+ return "plain";
1865
+ if (/<[a-z][\s\S]*>/i.test(text))
1866
+ return "richtext";
1867
+ if (/^(?:#{1,6}\s|[-*+]\s|\d+\.\s|>\s|```|~~)/m.test(text))
1868
+ return "markdown";
1869
+ if (/\*\*.+\*\*/.test(text))
1870
+ return "markdown";
1871
+ if (/\[[^\]]+\]\([^)]+\)/.test(text.trim()))
1872
+ return "markdown";
1873
+ if (/\|.+\|.+\|/.test(text))
1874
+ return "markdown";
1875
+ return "plain";
1876
+ }
1877
+ async function commentCommand(context) {
1878
+ if (!context.runtime.token)
1879
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
1880
+ const { action, positionals, flags } = context.parsed;
1881
+ if (action === "list") {
1882
+ rejectUnknownFlags(flags, ["object"]);
1883
+ const objectId = requireFlag(flags, "object");
1884
+ return context.client.comments.list(objectId);
1885
+ }
1886
+ if (action === "get") {
1887
+ rejectUnknownFlags(flags, []);
1888
+ const id = positionals[0]?.trim();
1889
+ if (!id)
1890
+ throw usage("usage: morula comment get <id>");
1891
+ return context.client.comments.get(id);
1892
+ }
1893
+ if (action === "add") {
1894
+ rejectUnknownFlags(flags, ["object", "content", "parent", "format"]);
1895
+ const objectId = requireFlag(flags, "object");
1896
+ const content = requireFlag(flags, "content");
1897
+ const formatFlag = flagString(flags, "format");
1898
+ const format = formatFlag === "markdown" || formatFlag === "plain" || formatFlag === "richtext" ? formatFlag : detectCommentFormat(content);
1899
+ const input = compact({
1900
+ objectId,
1901
+ content,
1902
+ parentCommentId: flagString(flags, "parent"),
1903
+ contentFormat: format
1904
+ });
1905
+ return context.client.comments.create(input);
1906
+ }
1907
+ if (action === "update") {
1908
+ rejectUnknownFlags(flags, ["content"]);
1909
+ const id = positionals[0]?.trim();
1910
+ if (!id)
1911
+ throw usage("usage: morula comment update <id>");
1912
+ const content = flagString(flags, "content");
1913
+ if (!content)
1914
+ throw usage("comment update requires --content");
1915
+ return context.client.comments.update(id, { content });
1916
+ }
1917
+ if (action === "delete") {
1918
+ rejectUnknownFlags(flags, []);
1919
+ const id = positionals[0]?.trim();
1920
+ if (!id)
1921
+ throw usage("usage: morula comment delete <id>");
1922
+ return context.client.comments.delete(id);
1923
+ }
1924
+ throw usage("usage: morula comment <list|get|add|update|delete>");
1925
+ }
1926
+ function compact(value) {
1927
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
1928
+ }
1929
+
1930
+ // ../morula-cli/src/commands/repo.ts
1931
+ import { execFileSync } from "node:child_process";
1932
+ async function repoCommand(context) {
1933
+ if (!context.runtime.token)
1934
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
1935
+ const { action, positionals, flags } = context.parsed;
1936
+ if (action === "list") {
1937
+ rejectUnknownFlags(flags, ["space"]);
1938
+ const spaceId = flagString(flags, "space");
1939
+ if (!spaceId)
1940
+ throw usage("usage: morula repo list --space <id>\uFF08\u9700\u6307\u5B9A\u4EFB\u52A1\u6240\u5728\u7A7A\u95F4\uFF09");
1941
+ const conns = await context.client.http.get(
1942
+ "/vcs/connections",
1943
+ { spaceId }
1944
+ );
1945
+ return conns.filter((c) => c.status === "active").map((c) => ({ id: c.id, repoFullName: c.repoFullName, provider: c.provider }));
1946
+ }
1947
+ if (action === "checkout") {
1948
+ rejectUnknownFlags(flags, ["ref"]);
1949
+ const taskId = positionals[0]?.trim();
1950
+ const repoFullName = positionals[1]?.trim();
1951
+ if (!taskId || !repoFullName)
1952
+ throw usage("usage: morula repo checkout <taskId> <repoFullName> [--ref <branch|sha>]");
1953
+ const local = await tryLocalCheckout(taskId, repoFullName, flagString(flags, "ref"));
1954
+ if (local)
1955
+ return local;
1956
+ const runtimeId = context.env.MORULA_RUNTIME_ID;
1957
+ if (!runtimeId)
1958
+ throw usage("repo checkout \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09");
1959
+ const result = await context.client.http.post(
1960
+ `/agent-tasks/${taskId}/repo-checkout`,
1961
+ {
1962
+ repoFullName,
1963
+ ref: flagString(flags, "ref"),
1964
+ runtimeId
1965
+ }
1966
+ );
1967
+ if (result?.ok === false)
1968
+ throw new CliError(result.code ?? "REPO_CHECKOUT_FAILED", EXIT.OTHER, `repo checkout \u5931\u8D25: ${result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
1969
+ return result;
1970
+ }
1971
+ if (action === "prepare") {
1972
+ rejectUnknownFlags(flags, []);
1973
+ const taskId = positionals[0]?.trim();
1974
+ const repoFullName = positionals[1]?.trim();
1975
+ if (!taskId || !repoFullName)
1976
+ throw usage("usage: morula repo prepare <taskId> <repoFullName>");
1977
+ const local = await tryLocalPrepare(taskId, repoFullName);
1978
+ if (local)
1979
+ return local;
1980
+ const runtimeId = context.env.MORULA_RUNTIME_ID;
1981
+ if (!runtimeId)
1982
+ throw usage("repo prepare \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09");
1983
+ const result = await context.client.http.post(
1984
+ `/agent-tasks/${taskId}/repos/prepare`,
1985
+ { repoFullName, runtimeId }
1986
+ );
1987
+ if (result?.ok === false)
1988
+ throw new CliError(result.code ?? "REPO_PREPARE_FAILED", EXIT.OTHER, `repo prepare \u5931\u8D25: ${result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
1989
+ return { ...result, hint: "\u670D\u52A1\u7AEF\u5DF2\u767B\u8BB0 workContext\uFF1B\u672C\u5730\u7269\u5316\u9700 daemon \u672C\u5730\u7AEF\u70B9\uFF08\u672C\u8F6E\u65E0 .morula/checkout.json\uFF09\uFF0C\u4E0B\u4E00\u8F6E\u4EFB\u52A1\u6267\u884C\u65F6\u751F\u6548" };
1990
+ }
1991
+ if (action === "mr" && positionals[0] === "create") {
1992
+ return mrCreate(context, flags);
1993
+ }
1994
+ if (action === "mr" && positionals[0] === "list") {
1995
+ return mrList(context, flags);
1996
+ }
1997
+ throw usage("usage: morula repo <list|checkout|prepare|mr create|mr list>");
1998
+ }
1999
+ function resolveMrBase(flagBase, env = process.env) {
2000
+ return env.MORULA_BASE_BRANCH?.trim() || flagBase?.trim() || "main";
2001
+ }
2002
+ async function mrCreate(context, flags) {
2003
+ rejectUnknownFlags(flags, ["branch", "base", "task-code"]);
2004
+ const repoFullName = context.parsed.positionals[1]?.trim();
2005
+ const branch = flagString(flags, "branch");
2006
+ const base = resolveMrBase(flagString(flags, "base"));
2007
+ const taskCode = flagString(flags, "task-code");
2008
+ if (!repoFullName || !branch)
2009
+ throw usage("usage: morula repo mr create <repoFullName> --branch <branch> [--base <base>] [--task-code <code>]");
2010
+ const taskId = await resolveTaskId(context);
2011
+ if (taskId)
2012
+ return mrCreateViaServer(context, { taskId, repoFullName, branch, base, taskCode });
2013
+ return mrCreateViaPushOptions({ repoFullName, branch, base, taskCode });
2014
+ }
2015
+ async function resolveCheckoutConfigPath(startDir) {
2016
+ const { existsSync: existsSync2 } = await import("node:fs");
2017
+ const { dirname: dirname2, join: join2, resolve } = await import("node:path");
2018
+ let current = resolve(startDir ?? process.cwd());
2019
+ while (true) {
2020
+ const candidate = join2(current, ".morula", "checkout.json");
2021
+ if (existsSync2(candidate))
2022
+ return candidate;
2023
+ const parent = dirname2(current);
2024
+ if (parent === current)
2025
+ return null;
2026
+ current = parent;
2027
+ }
2028
+ }
2029
+ async function resolveTaskId(context) {
2030
+ const fromEnv = context.env.MORULA_TASK_ID?.trim();
2031
+ if (fromEnv)
2032
+ return fromEnv;
2033
+ try {
2034
+ const { readFileSync: readFileSync2 } = await import("node:fs");
2035
+ const cfgPath = await resolveCheckoutConfigPath();
2036
+ if (!cfgPath)
2037
+ return null;
2038
+ const raw = JSON.parse(readFileSync2(cfgPath, "utf8"));
2039
+ return raw.taskId?.trim() || null;
2040
+ } catch {
2041
+ return null;
2042
+ }
2043
+ }
2044
+ async function mrCreateViaServer(context, input) {
2045
+ const title = input.taskCode ? `${input.taskCode}: ${input.branch}` : void 0;
2046
+ const result = await context.client.http.post(`/agent-tasks/${input.taskId}/mr`, {
2047
+ repoFullName: input.repoFullName,
2048
+ branch: input.branch,
2049
+ base: input.base,
2050
+ ...title ? { title } : {}
2051
+ });
2052
+ if (result?.ok === false) {
2053
+ throw new CliError(
2054
+ result.code ?? "MR_CREATE_FAILED",
2055
+ EXIT.UPSTREAM,
2056
+ `\u670D\u52A1\u7AEF\u5EFA MR \u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`
2057
+ );
2058
+ }
2059
+ return {
2060
+ ok: true,
2061
+ repoFullName: result.repoFullName ?? input.repoFullName,
2062
+ branch: result.branch ?? input.branch,
2063
+ base: result.base ?? input.base,
2064
+ prNumber: result.prNumber,
2065
+ prUrl: result.prUrl,
2066
+ reused: result.reused ?? false,
2067
+ linked: result.linked ?? false
2068
+ };
2069
+ }
2070
+ async function mrCreateViaPushOptions(input) {
2071
+ const { repoFullName, branch, base, taskCode } = input;
2072
+ const title = taskCode ? `${taskCode}: ${branch}` : branch;
2073
+ const description = taskCode ? `Closes ${taskCode}` : "\uFF08morula \u672C\u5730\u521B\u5EFA\uFF09";
2074
+ const pushOpts = [
2075
+ "-o",
2076
+ `merge_request.create`,
2077
+ "-o",
2078
+ `merge_request.target=${base}`,
2079
+ // 显式目标项目:fork 仓库默认会把 MR 建到上游,必须锁定到绑定仓库
2080
+ "-o",
2081
+ `merge_request.target_project=${repoFullName}`,
2082
+ "-o",
2083
+ `merge_request.title=${title}`,
2084
+ "-o",
2085
+ `merge_request.description=${description}`
2086
+ ];
2087
+ let pushOutput = "";
2088
+ try {
2089
+ pushOutput = execFileSync("git", ["push", "origin", branch, ...pushOpts], { encoding: "utf8" });
2090
+ } catch (error) {
2091
+ const msg = error instanceof Error ? error.message : String(error);
2092
+ throw new CliError("MR_CREATE_FAILED", EXIT.UPSTREAM, `git push \u521B\u5EFA MR \u5931\u8D25: ${msg.slice(0, 300)}`);
2093
+ }
2094
+ const mrUrlMatch = pushOutput.match(/View merge request for \S+:\s*\n?(\S*merge_requests\/\d+)/);
2095
+ const url2 = pushOutput.match(/(https?:\/\/\S+\/merge_requests\/\d+)/)?.[1];
2096
+ const mrUrl = mrUrlMatch?.[1]?.trim() || url2;
2097
+ if (mrUrl)
2098
+ return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl };
2099
+ const gitlabToken = process.env.MORULA_GITLAB_TOKEN;
2100
+ const gitlabBase = process.env.MORULA_GITLAB_URL;
2101
+ if (gitlabToken && gitlabBase) {
2102
+ try {
2103
+ const projectId = encodeURIComponent(repoFullName);
2104
+ const q = new URLSearchParams({ source_branch: branch, state: "opened" });
2105
+ const listRes = await fetch(`${gitlabBase}/api/v4/projects/${projectId}/merge_requests?${q}`, {
2106
+ headers: { "PRIVATE-TOKEN": gitlabToken }
2107
+ });
2108
+ const list = listRes.ok ? await listRes.json().catch(() => null) : null;
2109
+ const existing = Array.isArray(list) ? list[0] : null;
2110
+ if (existing?.iid) {
2111
+ console.log("[morula-cli] mr create \u5206\u652F\u5DF2\u6709 opened MR\uFF0C\u590D\u7528\u73B0\u6709 MR");
2112
+ return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl: existing.web_url ?? `${gitlabBase}/${repoFullName}/-/merge_requests/${existing.iid}` };
2113
+ }
2114
+ if (listRes.ok) {
2115
+ const created = await fetch(`${gitlabBase}/api/v4/projects/${projectId}/merge_requests`, {
2116
+ method: "POST",
2117
+ headers: { "PRIVATE-TOKEN": gitlabToken, "Content-Type": "application/json" },
2118
+ body: JSON.stringify({
2119
+ source_branch: branch,
2120
+ target_branch: base,
2121
+ title,
2122
+ description,
2123
+ remove_source_branch: false
2124
+ })
2125
+ });
2126
+ if (created.ok) {
2127
+ const mr = await created.json().catch(() => null);
2128
+ if (mr?.iid) {
2129
+ return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl: mr.web_url ?? `${gitlabBase}/${repoFullName}/-/merge_requests/${mr.iid}` };
2130
+ }
2131
+ }
2132
+ }
2133
+ } catch {
2134
+ }
2135
+ }
2136
+ const hint = pushOutput.split("\n").filter((l) => l.trim()).slice(-3).join(" | ").slice(0, 300);
2137
+ throw new CliError("MR_CREATE_FAILED", EXIT.UPSTREAM, `git push \u6210\u529F\u4F46\u672A\u68C0\u6D4B\u5230 MR \u521B\u5EFA\u8F93\u51FA\uFF08\u5206\u652F\u53EF\u80FD\u5DF2\u5B58\u5728\u6216 push options \u672A\u751F\u6548\uFF09\u3002\u8F93\u51FA\u5C3E\u90E8: ${hint}`);
2138
+ }
2139
+ async function mrList(context, flags) {
2140
+ rejectUnknownFlags(flags, ["state", "limit"]);
2141
+ const taskId = context.parsed.positionals[1]?.trim();
2142
+ if (!taskId)
2143
+ throw usage("usage: morula repo mr list <taskId> [--state opened|all|merged|closed] [--limit <n>]");
2144
+ const state = flagString(flags, "state") ?? "opened";
2145
+ const limit = flagNumber(flags, "limit");
2146
+ const result = await context.client.http.get(
2147
+ `/agent-tasks/${taskId}/vcs/mr-list`,
2148
+ {
2149
+ state,
2150
+ ...limit ? { limit } : {}
2151
+ }
2152
+ );
2153
+ if (result?.ok === false)
2154
+ throw new CliError(result.code ?? "MR_QUERY_FAILED", EXIT.OTHER, `MR \u5217\u8868\u67E5\u8BE2\u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
2155
+ return result;
2156
+ }
2157
+ async function tryLocalCheckout(taskId, repoFullName, ref) {
2158
+ try {
2159
+ const { readFileSync: readFileSync2 } = await import("node:fs");
2160
+ const cfgPath = await resolveCheckoutConfigPath();
2161
+ if (!cfgPath)
2162
+ return null;
2163
+ const raw = JSON.parse(readFileSync2(cfgPath, "utf8"));
2164
+ if (raw.taskId !== taskId || typeof raw.port !== "number" || !raw.checkToken)
2165
+ return null;
2166
+ const res = await fetch(`http://127.0.0.1:${raw.port}/repo/checkout`, {
2167
+ method: "POST",
2168
+ headers: { "Content-Type": "application/json" },
2169
+ body: JSON.stringify({ taskId, repoFullName, ref, checkToken: raw.checkToken })
2170
+ });
2171
+ const body = await res.json().catch(() => null);
2172
+ if (!body)
2173
+ return null;
2174
+ if (body?.ok === false)
2175
+ throw new CliError(body.code ?? "REPO_CHECKOUT_FAILED", EXIT.OTHER, `\u672C\u5730 repo checkout \u5931\u8D25: ${body.error ?? body.code ?? "\u672A\u77E5\u539F\u56E0"}`);
2176
+ return { ok: true, local: true, path: body?.path, headSha: body?.headSha, branchName: body?.branchName ?? null };
2177
+ } catch (error) {
2178
+ if (error instanceof CliError)
2179
+ throw error;
2180
+ return null;
2181
+ }
2182
+ }
2183
+ async function tryLocalPrepare(taskId, repoFullName) {
2184
+ try {
2185
+ const { readFileSync: readFileSync2 } = await import("node:fs");
2186
+ const cfgPath = await resolveCheckoutConfigPath();
2187
+ if (!cfgPath)
2188
+ return null;
2189
+ const raw = JSON.parse(readFileSync2(cfgPath, "utf8"));
2190
+ if (raw.taskId !== taskId || typeof raw.port !== "number" || !raw.checkToken)
2191
+ return null;
2192
+ const res = await fetch(`http://127.0.0.1:${raw.port}/repo/prepare`, {
2193
+ method: "POST",
2194
+ headers: { "Content-Type": "application/json" },
2195
+ body: JSON.stringify({ taskId, repoFullName, checkToken: raw.checkToken })
2196
+ });
2197
+ const body = await res.json().catch(() => null);
2198
+ if (!body)
2199
+ return null;
2200
+ if (body?.ok === false)
2201
+ throw new CliError(body.code ?? "REPO_PREPARE_FAILED", EXIT.OTHER, `\u672C\u5730 repo prepare \u5931\u8D25: ${body.error ?? body.code ?? "\u672A\u77E5\u539F\u56E0"}`);
2202
+ return { ok: true, local: true, path: body?.path, headSha: body?.headSha, baseBranch: body?.baseBranch ?? null, workContextId: body?.workContextId, created: body?.created ?? false };
2203
+ } catch (error) {
2204
+ if (error instanceof CliError)
2205
+ throw error;
2206
+ return null;
2207
+ }
2208
+ }
2209
+
2210
+ // ../morula-cli/src/commands/report.ts
2211
+ import { readFile as readFile2 } from "node:fs/promises";
2212
+ var REPORT_STATUSES = ["ready", "archived"];
2213
+ async function reportCommand(context) {
2214
+ if (!context.runtime.token)
2215
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2216
+ const { action, positionals, flags } = context.parsed;
2217
+ if (action === "create") {
2218
+ rejectUnknownFlags(flags, ["title", "content", "content-file", "space", "autopilot"]);
2219
+ const title = requireFlag(flags, "title");
2220
+ const content = await resolveContent(flags);
2221
+ const input = { title, content };
2222
+ const spaceId = flagString(flags, "space");
2223
+ if (spaceId)
2224
+ input.spaceId = spaceId;
2225
+ const autopilotId = flagString(flags, "autopilot");
2226
+ if (autopilotId)
2227
+ input.autopilotId = autopilotId;
2228
+ return context.client.reports.create(input);
2229
+ }
2230
+ if (action === "get") {
2231
+ rejectUnknownFlags(flags, []);
2232
+ const id = positionals[0]?.trim();
2233
+ if (!id)
2234
+ throw usage("usage: morula report get <id>");
2235
+ return context.client.reports.get(id);
2236
+ }
2237
+ if (action === "list") {
2238
+ rejectUnknownFlags(flags, ["autopilot", "page", "page-size", "status"]);
2239
+ const autopilotId = flagString(flags, "autopilot");
2240
+ if (!autopilotId)
2241
+ throw usage("usage: morula report list --autopilot <id> [--page <n>] [--page-size <n>] [--status ready|archived]");
2242
+ const status2 = flagString(flags, "status");
2243
+ const query = {
2244
+ page: flagNumber(flags, "page"),
2245
+ pageSize: flagNumber(flags, "page-size")
2246
+ };
2247
+ if (status2) {
2248
+ if (!isReportStatus(status2))
2249
+ throw usage("--status \u5FC5\u987B\u662F ready / archived");
2250
+ query.status = status2;
2251
+ }
2252
+ return context.client.reports.listByAutopilot(autopilotId, query);
2253
+ }
2254
+ if (action === "archive") {
2255
+ rejectUnknownFlags(flags, []);
2256
+ const id = positionals[0]?.trim();
2257
+ if (!id)
2258
+ throw usage("usage: morula report archive <id>");
2259
+ return context.client.reports.archive(id);
2260
+ }
2261
+ throw usage(`usage: morula report <create|get|list|archive>
2262
+
2263
+ create \u4EA7\u51FA\u62A5\u544A\uFF08\u81EA\u52A8\u5316\u6267\u884C\u7684\u7ED3\u8BBA\u578B\u5185\u5BB9\uFF1B--autopilot \u53EF\u7701\u7565\uFF0C\u670D\u52A1\u7AEF\u6309\u5F53\u524D\u4EFB\u52A1\u53CD\u67E5\u6240\u5C5E\u81EA\u52A8\u5316\uFF09
2264
+ get \u67E5\u770B\u62A5\u544A\u8BE6\u60C5\uFF08\u542B Markdown \u6B63\u6587\uFF09
2265
+ list \u5217\u51FA\u67D0\u81EA\u52A8\u5316\u7684\u62A5\u544A\uFF08\u9ED8\u8BA4\u4E0D\u542B\u5DF2\u5F52\u6863\uFF1B--status archived \u67E5\u770B\u5F52\u6863\uFF09
2266
+ archive \u5F52\u6863\u62A5\u544A\uFF08\u5F52\u6863\u540E\u9ED8\u8BA4\u5217\u8868\u4E0D\u518D\u5C55\u793A\uFF09`);
2267
+ }
2268
+ async function resolveContent(flags) {
2269
+ const inline = flagString(flags, "content");
2270
+ const filePath = flagString(flags, "content-file");
2271
+ if (inline && filePath)
2272
+ throw usage("--content \u4E0E --content-file \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A");
2273
+ if (!inline && !filePath)
2274
+ throw usage("\u62A5\u544A\u6B63\u6587\u5FC5\u586B\uFF1A--content <markdown> \u6216 --content-file <path>");
2275
+ let content = inline;
2276
+ if (filePath) {
2277
+ try {
2278
+ content = await readFile2(filePath, "utf8");
2279
+ } catch (error) {
2280
+ throw new CliError(
2281
+ "CONTENT_FILE_UNREADABLE",
2282
+ EXIT.OTHER,
2283
+ `\u8BFB\u53D6 --content-file \u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
2284
+ );
2285
+ }
2286
+ }
2287
+ if (!content?.trim())
2288
+ throw usage("\u62A5\u544A\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
2289
+ return content;
2290
+ }
2291
+ function isReportStatus(value) {
2292
+ return REPORT_STATUSES.includes(value);
2293
+ }
2294
+
2295
+ // ../morula-cli/src/commands/review.ts
2296
+ async function reviewCommand(context) {
2297
+ if (!context.runtime.token)
2298
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2299
+ const { action, positionals, flags } = context.parsed;
2300
+ if (action === "submit") {
2301
+ rejectUnknownFlags(flags, ["verdict", "details", "pr-number"]);
2302
+ const objectId = positionals[0]?.trim();
2303
+ const verdict = flagString(flags, "verdict");
2304
+ if (!objectId)
2305
+ throw usage(`usage: morula review submit <objectId> --verdict <approved|changes_requested|commented> [--details <text>] [--pr-number <n>]
2306
+
2307
+ \u8BC4\u5BA1\u4EFB\u52A1\u673A\u5668\u534F\u8BAE\uFF08\u53EA\u8BFB\u8BC4\u5BA1\u4EFB\u52A1\u4F7F\u7528\uFF09\uFF1A\u5BF9\u5F00\u53D1\u63D0\u4EA4\u7684\u6539\u52A8\u4E0B\u7ED3\u8BBA\uFF0C\u670D\u52A1\u7AEF\u6821\u9A8C\u8C03\u7528\u8005 = \u8BC4\u5BA1\u4EFB\u52A1\u540E\u843D\u5E93\u5E76\u9A71\u52A8\u56DE\u73AF\u3002
2308
+ --objectId \u8BC4\u5BA1\u5BF9\u8C61 ID\uFF08\u7CFB\u7EDF\u63D0\u793A\u300C\u8BC4\u5BA1\u5BF9\u8C61\u300D\uFF1B\u7F3A\u5931\u65F6\u7528 morula task get <taskId> \u67E5\u8BE2\uFF09
2309
+ --verdict approved=\u901A\u8FC7 / changes_requested=\u4E0D\u901A\u8FC7\uFF08\u5FC5\u987B\u7ED9 --details \u5177\u4F53\u610F\u89C1\uFF0C\u5426\u5219\u5F00\u53D1\u65E0\u6CD5\u4FEE\u590D\uFF09/ commented=\u6709\u610F\u89C1\u4F46\u4E0D\u963B\u585E
2310
+ --details \u8BC4\u5BA1\u610F\u89C1\u6587\u672C\uFF08changes_requested \u5FC5\u586B\uFF1A\u6587\u4EF6/\u884C/\u95EE\u9898/\u5EFA\u8BAE\uFF09
2311
+ --pr-number \u8BC4\u5BA1\u5BF9\u8C61\u7684 PR \u7F16\u53F7\uFF08\u6709\u5219\u586B\uFF09
2312
+ \u7981\u6B62\uFF1A\u4FEE\u6539\u4EE3\u7801\u3001\u5EFA\u5206\u652F\u3001\u5408\u5E76 MR\u3001\u8BC4\u5BA1\u81EA\u5DF1\u5F00\u53D1\u7684\u4EFB\u52A1\u3002`);
2313
+ if (!["approved", "changes_requested", "commented"].includes(verdict ?? ""))
2314
+ throw usage("--verdict \u5FC5\u987B\u662F approved / changes_requested / commented");
2315
+ const details = flagString(flags, "details");
2316
+ if (verdict === "changes_requested" && !details?.trim())
2317
+ throw usage("changes_requested \u5FC5\u987B\u63D0\u4F9B --details\uFF08\u5177\u4F53\u4FEE\u6539\u610F\u89C1\uFF09");
2318
+ const body = { objectId, verdict };
2319
+ if (details?.trim())
2320
+ body.details = details.trim();
2321
+ const prNumber = flagNumber(flags, "pr-number");
2322
+ if (prNumber)
2323
+ body.prNumber = prNumber;
2324
+ const result = await context.client.http.post(
2325
+ "/agent-collaborations/review/submit",
2326
+ body
2327
+ );
2328
+ if (result?.ok === false)
2329
+ throw new CliError(result.code ?? "REVIEW_SUBMIT_FAILED", EXIT.OTHER, `\u8BC4\u5BA1\u63D0\u4EA4\u5931\u8D25: ${result.message ?? "\u672A\u77E5\u539F\u56E0"}`);
2330
+ return { ok: true, reviewRound: result.reviewRound, verdict: result.verdict, reverted: result.reverted };
2331
+ }
2332
+ throw usage(`usage: morula review <submit>
2333
+
2334
+ submit \u63D0\u4EA4\u8BC4\u5BA1\u7ED3\u8BBA\uFF08\u8BC4\u5BA1\u4EFB\u52A1\u673A\u5668\u534F\u8BAE\uFF1B\u7528\u6CD5\u4E0E\u53C2\u6570\u89C1 morula review submit --help\uFF09`);
2335
+ }
2336
+
2337
+ // ../morula-cli/src/commands/space-member.ts
2338
+ async function spaceMemberCommand(context) {
2339
+ if (!context.runtime.token)
2340
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2341
+ const { action, positionals, flags } = context.parsed;
2342
+ if (action === "list") {
2343
+ rejectUnknownFlags(flags, ["space"]);
2344
+ const spaceId = requireFlag(flags, "space");
2345
+ return context.client.spaceMembers.list(spaceId);
2346
+ }
2347
+ if (action === "add") {
2348
+ rejectUnknownFlags(flags, ["space", "principal", "role"]);
2349
+ const spaceId = requireFlag(flags, "space");
2350
+ const principalId = requireFlag(flags, "principal");
2351
+ const input = compact2({
2352
+ spaceId,
2353
+ principalId,
2354
+ roleCode: flagString(flags, "role")
2355
+ });
2356
+ return context.client.spaceMembers.create(input);
2357
+ }
2358
+ if (action === "remove") {
2359
+ rejectUnknownFlags(flags, []);
2360
+ const id = positionals[0]?.trim();
2361
+ if (!id)
2362
+ throw usage("usage: morula space-member remove <member-id>");
2363
+ return context.client.spaceMembers.delete(id);
2364
+ }
2365
+ if (action === "update") {
2366
+ rejectUnknownFlags(flags, ["role"]);
2367
+ const id = positionals[0]?.trim();
2368
+ if (!id)
2369
+ throw usage("usage: morula space-member update <member-id>");
2370
+ const role = flagString(flags, "role");
2371
+ if (!role)
2372
+ throw usage("space-member update requires --role");
2373
+ return context.client.spaceMembers.update(id, { roleCode: role });
2374
+ }
2375
+ throw usage("usage: morula space-member <list|add|update|remove>");
2376
+ }
2377
+ function compact2(value) {
2378
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
2379
+ }
2380
+
2381
+ // ../morula-cli/src/commands/space.ts
2382
+ async function spaceCommand(context) {
2383
+ if (!context.runtime.token)
2384
+ throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2385
+ const { action, positionals, flags } = context.parsed;
2386
+ if (action === "list") {
2387
+ rejectUnknownFlags(flags, ["page", "page-size", "keyword"]);
2388
+ return context.client.spaces.list({
2389
+ page: flagNumber(flags, "page"),
2390
+ pageSize: flagNumber(flags, "page-size"),
2391
+ keyword: flagString(flags, "keyword")
2392
+ });
2393
+ }
2394
+ if (action === "get") {
2395
+ rejectUnknownFlags(flags, []);
2396
+ const id = positionals[0]?.trim();
2397
+ if (!id)
2398
+ throw usage("usage: morula space get <id>");
2399
+ return context.client.spaces.get(id);
2400
+ }
2401
+ throw usage("usage: morula space <list|get>");
2402
+ }
2403
+
2404
+ // ../morula-cli/src/commands/task.ts
2405
+ var LIST_FLAGS = ["space", "page", "page-size", "search", "priority", "state", "assignee", "principal", "sort", "order", "mine", "field"];
2406
+ var WRITE_FLAGS = ["title", "description", "space", "priority", "state", "owner", "assignee", "visibility", "estimate-hours", "schema", "sort-order", "field"];
2407
+ function parseFieldFlags(raw) {
2408
+ if (!raw)
2409
+ return [];
2410
+ const specs = [];
2411
+ for (const pair of raw.split(",")) {
2412
+ const eq = pair.indexOf("=");
2413
+ if (eq > 0) {
2414
+ specs.push({ key: pair.slice(0, eq).trim(), value: pair.slice(eq + 1).trim() });
2415
+ } else {
2416
+ throw usage(`--field \u683C\u5F0F\u5E94\u4E3A key=value\uFF0C\u6536\u5230 "${pair}"`);
2417
+ }
2418
+ }
2419
+ return specs.filter((f) => f.key);
2420
+ }
2421
+ async function resolveFieldIds(context, spaceId, specs) {
2422
+ if (specs.length === 0)
2423
+ return [];
2424
+ const fields = await context.client.tasks.getSchemaFields(spaceId);
2425
+ const resolved = [];
2426
+ for (const spec of specs) {
2427
+ const field = fields.find((f) => f.fieldKey === spec.key || f.fieldName === spec.key);
2428
+ if (!field)
2429
+ throw usage(`\u81EA\u5B9A\u4E49\u5B57\u6BB5\u4E0D\u5B58\u5728\uFF1A${spec.key}\uFF08\u7A7A\u95F4 ${spaceId} \u7684\u5B57\u6BB5\u6A21\u677F\u4E2D\u672A\u627E\u5230\uFF09`);
2430
+ resolved.push({ schemaFieldId: field.id, value: spec.value });
2431
+ }
2432
+ return resolved;
2433
+ }
2434
+ function detectDescriptionFormat(text) {
2435
+ if (!text)
2436
+ return "plain";
2437
+ if (/<[a-z][\s\S]*>/i.test(text))
2438
+ return "richtext";
2439
+ if (/^(?:#{1,6}\s|[-*+]\s|\d+\.\s|>\s|```|~~)/m.test(text))
2440
+ return "markdown";
2441
+ if (/\*\*.+\*\*/.test(text))
2442
+ return "markdown";
2443
+ if (/\[[^\]]+\]\([^)]+\)/.test(text.trim()))
2444
+ return "markdown";
2445
+ if (/\|.+\|.+\|/.test(text))
2446
+ return "markdown";
2447
+ return "plain";
2448
+ }
2449
+ async function taskCommand(context) {
2450
+ const { action, positionals, flags } = context.parsed;
2451
+ if (!context.runtime.token)
2452
+ throw unauthenticated();
2453
+ if (action === "list") {
2454
+ rejectUnknownFlags(flags, LIST_FLAGS);
2455
+ const mine = flagBoolean(flags, "mine");
2456
+ if (mine && flagString(flags, "assignee"))
2457
+ throw usage("--mine \u4E0E --assignee \u4E92\u65A5\uFF0C\u8BF7\u4E8C\u9009\u4E00");
2458
+ let assigneePrincipalId = flagString(flags, "assignee");
2459
+ if (mine) {
2460
+ const profile = await context.client.auth.profile();
2461
+ if (!profile.principalId)
2462
+ throw usage("--mine \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u7528\u6237 principalId\uFF08profile \u7F3A\u5931\uFF09");
2463
+ assigneePrincipalId = profile.principalId;
2464
+ }
2465
+ const query = compact3({
2466
+ spaceId: flagString(flags, "space"),
2467
+ page: flagNumber(flags, "page"),
2468
+ pageSize: flagNumber(flags, "page-size"),
2469
+ search: flagString(flags, "search"),
2470
+ priority: flagString(flags, "priority"),
2471
+ currentStateId: flagString(flags, "state"),
2472
+ assigneePrincipalId,
2473
+ principalId: flagString(flags, "principal"),
2474
+ sort: flagString(flags, "sort"),
2475
+ order: flagString(flags, "order"),
2476
+ includeFieldValues: true
2477
+ });
2478
+ const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
2479
+ if (fieldSpecs.length > 0) {
2480
+ const spaceId = flagString(flags, "space");
2481
+ if (!spaceId)
2482
+ throw usage("--field \u9700\u8981\u914D\u5408 --space \u6307\u5B9A\u7A7A\u95F4\uFF08\u7528\u4E8E\u89E3\u6790\u5B57\u6BB5\u6A21\u677F\uFF09");
2483
+ const resolved = await resolveFieldIds(context, spaceId, fieldSpecs);
2484
+ query.fieldFilters = JSON.stringify(resolved.map((r) => ({ schemaFieldId: r.schemaFieldId, op: "eq", value: r.value })));
2485
+ }
2486
+ return context.client.tasks.list(query);
2487
+ }
2488
+ if (action === "get") {
2489
+ rejectUnknownFlags(flags, []);
2490
+ if (!positionals[0]?.trim()) {
2491
+ const taskId = await resolveTaskId(context);
2492
+ if (!taskId)
2493
+ throw usage(`morula task get\uFF08\u65E0\u53C2\uFF09\u9700\u8981\u4EFB\u52A1\u4E0A\u4E0B\u6587\uFF1A\u5F53\u524D\u76EE\u5F55\u627E\u4E0D\u5230 .morula/checkout.json \u4E14\u672A\u8BBE\u7F6E MORULA_TASK_ID\u3002
2494
+
2495
+ \u7528\u6CD5:
2496
+ morula task get # \u5F53\u524D AgentTask \u8BE6\u60C5\uFF08daemon \u4EFB\u52A1\u5DE5\u4F5C\u533A\u5185\uFF1B\u8FD4\u56DE code/objectId/taskPrompt/contextSnapshot\uFF09
2497
+ morula task get <objectId> # \u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09\u8BE6\u60C5\uFF08\u542B\u81EA\u5B9A\u4E49\u5B57\u6BB5\u503C\uFF09`);
2498
+ return context.client.http.get(`/agent-tasks/${encodeURIComponent(taskId)}`);
2499
+ }
2500
+ const id = requireId2(positionals, "task get");
2501
+ const task = await context.client.tasks.get(id);
2502
+ if (!task)
2503
+ return null;
2504
+ try {
2505
+ const fieldValues = await context.client.tasks.getFieldValues(id);
2506
+ task.fieldValues = fieldValues;
2507
+ } catch {
2508
+ }
2509
+ return task;
2510
+ }
2511
+ if (action === "create") {
2512
+ rejectUnknownFlags(flags, WRITE_FLAGS);
2513
+ const input = taskInput(flags, true);
2514
+ const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
2515
+ const created = await context.client.tasks.create(input);
2516
+ if (fieldSpecs.length > 0 && created) {
2517
+ const spaceId = input.spaceId ?? created.spaceId;
2518
+ const resolved = await resolveFieldIds(context, spaceId, fieldSpecs);
2519
+ for (const r of resolved) {
2520
+ if (r.value) {
2521
+ await context.client.tasks.upsertFieldValue({
2522
+ objectId: created.id,
2523
+ schemaFieldId: r.schemaFieldId,
2524
+ valueText: r.value
2525
+ });
2526
+ }
2527
+ }
2528
+ ;
2529
+ created.fieldValues = resolved.filter((r) => r.value).map((r) => ({ schemaFieldId: r.schemaFieldId, valueText: r.value }));
2530
+ }
2531
+ return created;
2532
+ }
2533
+ if (action === "update") {
2534
+ rejectUnknownFlags(flags, WRITE_FLAGS.filter((flag) => flag !== "space" && flag !== "owner"));
2535
+ const id = requireId2(positionals, "task update");
2536
+ const input = taskInput(flags, false);
2537
+ const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
2538
+ if (!Object.keys(input).length && !flagString(flags, "field"))
2539
+ throw usage("task update requires at least one field flag");
2540
+ const updated = await context.client.tasks.update(id, input);
2541
+ if (fieldSpecs.length > 0) {
2542
+ const task = updated ?? await context.client.tasks.get(id);
2543
+ const spaceId = task?.spaceId;
2544
+ if (!spaceId)
2545
+ throw usage(`--field \u65E0\u6CD5\u89E3\u6790\u4EFB\u52A1 ${id} \u6240\u5C5E\u7A7A\u95F4`);
2546
+ const resolved = await resolveFieldIds(context, spaceId, fieldSpecs);
2547
+ for (const r of resolved) {
2548
+ if (r.value) {
2549
+ await context.client.tasks.upsertFieldValue({
2550
+ objectId: id,
2551
+ schemaFieldId: r.schemaFieldId,
2552
+ valueText: r.value
2553
+ });
2554
+ } else {
2555
+ await context.client.tasks.deleteFieldValue(id, r.schemaFieldId).catch(() => void 0);
2556
+ }
2557
+ }
2558
+ const values = await context.client.tasks.getFieldValues(id).catch(() => []);
2559
+ if (updated)
2560
+ updated.fieldValues = values;
2561
+ }
2562
+ return updated;
2563
+ }
2564
+ if (action === "delete") {
2565
+ rejectUnknownFlags(flags, []);
2566
+ const id = requireId2(positionals, "task delete");
2567
+ return context.client.tasks.delete(id);
2568
+ }
2569
+ if (action === "branch") {
2570
+ rejectUnknownFlags(flags, ["name", "repo"]);
2571
+ const id = requireId2(positionals, "task branch");
2572
+ const branchName = requireFlag(flags, "name");
2573
+ const runtimeId = context.env.MORULA_RUNTIME_ID;
2574
+ if (!runtimeId)
2575
+ throw usage("task branch \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09");
2576
+ const repoFullName = flagString(flags, "repo")?.trim();
2577
+ return context.client.http.post(`/agent-tasks/${id}/branch`, {
2578
+ branchName,
2579
+ runtimeId,
2580
+ ...repoFullName ? { repoFullName } : {}
2581
+ });
2582
+ }
2583
+ if (action === "state") {
2584
+ rejectUnknownFlags(flags, ["state"]);
2585
+ const id = requireId2(positionals, "task state");
2586
+ const stateCode = requireFlag(flags, "state");
2587
+ const runtimeId = context.env.MORULA_RUNTIME_ID;
2588
+ if (!runtimeId)
2589
+ throw usage(`task state \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09
2590
+
2591
+ \u7528\u6CD5: morula task state <taskId> --state <in_progress|review>
2592
+ \u8BF4\u660E: \u4EFB\u52A1\u72B6\u6001\u63A8\u8FDB\u7531\u670D\u52A1\u7AEF\u9A71\u52A8\uFF08\u6D3E\u5355\u65F6\u6309\u9636\u6BB5\u5BF9\u9F50\u3001PR \u4E8B\u4EF6\u81EA\u52A8\u6D41\u8F6C\uFF09\uFF1B\u4EC5\u5E73\u53F0\u660E\u786E\u8981\u6C42 agent \u81EA\u63A8\u72B6\u6001\u65F6\u4F7F\u7528\u3002`);
2593
+ return context.client.http.post(`/agent-tasks/${id}/state`, { stateCode, runtimeId });
2594
+ }
2595
+ if (action === "analysis-verdict") {
2596
+ rejectUnknownFlags(flags, ["verdict", "reason"]);
2597
+ const id = requireId2(positionals, "task analysis-verdict");
2598
+ const verdict = requireFlag(flags, "verdict");
2599
+ if (verdict !== "ready" && verdict !== "blocked")
2600
+ throw usage(`--verdict \u5FC5\u987B\u662F ready / blocked
2601
+
2602
+ \u7528\u6CD5: morula task analysis-verdict <taskId> --verdict <ready|blocked> [--reason <\u6587\u672C>]
2603
+ \u4EFB\u52A1\u5206\u6790\u673A\u5668\u534F\u8BAE\uFF08\u4EC5\u300C\u4EFB\u52A1\u5206\u6790\u300D\u4EFB\u52A1\u9002\u7528\uFF09: ready=\u9700\u6C42\u6E05\u6670\u53EF\u5F00\u53D1\uFF08\u670D\u52A1\u7AEF\u81EA\u52A8\u8FDB\u5165\u5F00\u53D1\u5E76\u6D3E\u53D1\uFF09\uFF1Bblocked=\u4E0D\u53EF\u505A/\u9700\u6F84\u6E05\uFF08\u4EFB\u52A1\u505C\u5206\u6790\uFF0Creason \u5199\u6E05\u963B\u585E\u70B9\u4E0E\u5F85\u6F84\u6E05\u95EE\u9898\uFF09\u3002
2604
+ \u6F0F\u62A5\u4F1A\u8BA9\u4EFB\u52A1\u505C\u5728\u300C\u4EFB\u52A1\u5206\u6790\u300D\uFF1B\u540C\u4E00\u4EFB\u52A1\u53EA\u4E0A\u62A5\u4E00\u6B21\uFF08\u91CD\u590D\u4E0A\u62A5\u4EE5\u9996\u6B21\u4E3A\u51C6\uFF09\u3002`);
2605
+ const body = { verdict };
2606
+ const reason = flagString(flags, "reason");
2607
+ if (reason?.trim())
2608
+ body.reason = reason.trim();
2609
+ const result = await context.client.http.post(
2610
+ `/agent-tasks/${id}/analysis-verdict`,
2611
+ body
2612
+ );
2613
+ if (result?.ok === false)
2614
+ throw new CliError(result.code ?? "ANALYSIS_VERDICT_FAILED", EXIT.OTHER, `\u5206\u6790\u7ED3\u8BBA\u4E0A\u62A5\u5931\u8D25: ${result.message ?? "\u672A\u77E5\u539F\u56E0"}`);
2615
+ return { ok: true, verdict: result.verdict, alreadyReported: result.alreadyReported ?? false };
2616
+ }
2617
+ throw usage("usage: morula task <list|create|get|update|delete|branch|state|analysis-verdict>");
2618
+ }
2619
+ function taskInput(flags, create) {
2620
+ const description = flagString(flags, "description");
2621
+ const values = compact3({
2622
+ spaceId: flagString(flags, "space"),
2623
+ title: flagString(flags, "title"),
2624
+ description,
2625
+ descriptionFormat: description !== void 0 ? detectDescriptionFormat(description) : void 0,
2626
+ priority: flagString(flags, "priority"),
2627
+ currentStateId: flagString(flags, "state"),
2628
+ ownerPrincipalId: flagString(flags, "owner"),
2629
+ assigneePrincipalId: flagString(flags, "assignee"),
2630
+ visibility: flagString(flags, "visibility"),
2631
+ estimateHours: flagNumber(flags, "estimate-hours"),
2632
+ schemaId: flagString(flags, "schema"),
2633
+ sortOrder: flagNumber(flags, "sort-order")
2634
+ });
2635
+ if (create) {
2636
+ values.spaceId = requireFlag(flags, "space");
2637
+ values.title = requireFlag(flags, "title");
2638
+ }
2639
+ return values;
2640
+ }
2641
+ function requireId2(positionals, command) {
2642
+ const id = positionals[0]?.trim();
2643
+ if (!id)
2644
+ throw usage(`usage: morula ${command} <id>`);
2645
+ return id;
2646
+ }
2647
+ function compact3(value) {
2648
+ return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
2649
+ }
2650
+ function unauthenticated() {
2651
+ return new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
2652
+ }
2653
+
2654
+ // ../morula-cli/src/commands/registry.ts
2655
+ var COMMANDS = {
2656
+ "attachment": attachmentCommand,
2657
+ "auth": authCommand,
2658
+ "comment": commentCommand,
2659
+ "report": reportCommand,
2660
+ "repo": repoCommand,
2661
+ "review": reviewCommand,
2662
+ "task": taskCommand,
2663
+ "space": spaceCommand,
2664
+ "space-member": spaceMemberCommand
2665
+ };
2666
+ var HELP = {
2667
+ name: "morula",
2668
+ version: BUILD_VERSION,
2669
+ build: BUILD_ID,
2670
+ summary: "Agent-first CLI for Morula task management. Success output is JSON on stdout; errors are JSON on stderr.",
2671
+ usage: "morula <resource> <action> [id] [--flags]",
2672
+ commands: {
2673
+ "auth": {
2674
+ login: "browser SkyDoor SSO using PKCE and loopback callback",
2675
+ logout: "remove the current context token",
2676
+ status: "show context, endpoint, and authentication state"
2677
+ },
2678
+ "task": {
2679
+ "list [--space <id>] [--mine] [--assignee <id>] [--page <n>] [--page-size <n>] [--search <text>] [--priority <low|medium|high|urgent>] [--state <id>]": "list tasks (--mine: \u4EC5\u6211\u8D1F\u8D23\u7684)",
2680
+ "create --space <id> --title <text> [--priority <value>] [--assignee <id>]": "create task",
2681
+ "get [id]": "get task details. No argument = the current AgentTask (id resolved from MORULA_TASK_ID / .morula/checkout.json). With an id = the business Object (title/description)",
2682
+ "update <id> [--title <text>] [--state <id>] [--priority <value>] [--sort-order <n>]": "update task (--state here is the business Object state id, NOT the AgentTask state code \u2014 see `task state`)",
2683
+ "delete <id>": "hard delete task",
2684
+ "branch <id> --name <name> [--repo <owner/name>]": "report the final branch name for an agent task (machine protocol; agent naming, runtimeId from env; --repo: secondary repo in a multi-repo task)",
2685
+ "state <id> --state <code>": "self-transition task state (machine protocol; agent drives todo\u2192in_progress\u2192review, runtimeId from env)",
2686
+ "analysis-verdict <taskId> --verdict <ready|blocked> [--reason <text>]": "report the task-analysis verdict (machine protocol; ready \u2192 platform auto-starts development, blocked \u2192 task stays in analysis)"
2687
+ },
2688
+ "space": {
2689
+ "list [--page <n>] [--page-size <n>] [--keyword <text>]": "list visible spaces",
2690
+ "get <id>": "get space details"
2691
+ },
2692
+ "space-member": {
2693
+ "list --space <id>": "list space members",
2694
+ "add --space <id> --principal <id> [--role <admin|member>]": "add member to space",
2695
+ "update <member-id> --role <admin|member>": "update member role",
2696
+ "remove <member-id>": "remove member from space"
2697
+ },
2698
+ "comment": {
2699
+ "list --object <id>": "list comments for an object",
2700
+ "get <id>": "get comment details",
2701
+ "add --object <id> --content <text> [--parent <id>] [--format markdown|plain|richtext]": "add comment (format auto-detected; use markdown for agent final summaries)",
2702
+ "update <id> --content <text>": "update comment",
2703
+ "delete <id>": "hard delete comment"
2704
+ },
2705
+ "review": {
2706
+ "submit <objectId> --verdict <approved|changes_requested|commented> [--details <text>] [--pr-number <n>]": "submit Code Review verdict (machine protocol; review task token)"
2707
+ },
2708
+ "report": {
2709
+ "create --title <text> [--content <markdown> | --content-file <path>] [--space <id>] [--autopilot <id>]": "create an autopilot report (\u6B63\u6587\u4E8C\u9009\u4E00\uFF1A--content \u77ED\u6B63\u6587 / --content-file \u957F Markdown\uFF1B--autopilot \u7701\u7565\u65F6\u6309\u5F53\u524D\u4EFB\u52A1\u53CD\u67E5\u6240\u5C5E\u81EA\u52A8\u5316)",
2710
+ "get <id>": "get report details (\u542B Markdown \u6B63\u6587)",
2711
+ "list --autopilot <id> [--page <n>] [--page-size <n>] [--status ready|archived]": "list reports of an autopilot",
2712
+ "archive <id>": "archive report (\u5F52\u6863\u540E\u9ED8\u8BA4\u5217\u8868\u4E0D\u518D\u5C55\u793A)"
2713
+ },
2714
+ "repo": {
2715
+ "list --space <id>": "list task-space VCS connections (available repos; note: the system prompt already lists them for a task \u2014 you rarely need this)",
2716
+ "checkout <taskId> <repoFullName> [--ref <branch|sha>]": "materialize a repo into the task workdir (local daemon endpoint preferred, works same-turn; falls back to server machine protocol which resolves scope + workContext)",
2717
+ "prepare <taskId> <repoFullName>": "materialize a secondary repo into the task workdir (same level as the primary repo dir)",
2718
+ "mr create <repoFullName> --branch <branch> [--base <base>] [--task-code <code>]": "create a GitLab MR for the task via the server channel (target project forced to the bound repo, PR linked immediately; push the branch first)",
2719
+ "mr list <taskId> [--state opened|closed|merged] [--limit <n>]": "list the task-space repos' merge requests (read-only; never approves/merges)"
2720
+ },
2721
+ "attachment": {
2722
+ "upload <file> [--object <id>] [--comment <id>] [--ag-message] [--task <agentTaskId>] [--inline]": "upload a local file and attach it to a task (--object) / comment (--comment) / chat message (--ag-message); for pure chat tasks pass --task <agentTaskId> to attach to the chat session; omit target to auto-merge (object if bound, else chat message)",
2723
+ "list --object <id>": "list attachments for an object",
2724
+ "get <id>": "get attachment details",
2725
+ "download-url <id>": "get signed download URL for an attachment",
2726
+ "delete <id>": "delete attachment"
2727
+ }
2728
+ },
2729
+ globalFlags: {
2730
+ "--env <context>": "select test or a saved context",
2731
+ "--url <baseUrl>": "override API base URL",
2732
+ "--token <token>": "override token for this invocation (not persisted)",
2733
+ "--help": "print this machine-readable command surface",
2734
+ "--version": "print version JSON"
2735
+ },
2736
+ exitCodes: {
2737
+ 0: "success",
2738
+ 1: "other error",
2739
+ 2: "usage error",
2740
+ 3: "not found",
2741
+ 4: "unauthenticated",
2742
+ 5: "forbidden",
2743
+ 6: "conflict",
2744
+ 7: "upstream/network error"
2745
+ }
2746
+ };
2747
+
2748
+ // ../morula-cli/src/index.ts
2749
+ runEntry({ commands: COMMANDS, help: HELP });