@arcships/morula-runtime 0.1.0-alpha.1 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -19
- package/bin/morula-runtime.js +0 -0
- package/bin/morula.js +9 -0
- package/dist/index.js +9768 -3651
- package/dist/morula.js +4393 -0
- package/dist/plugin/plugin.json +10 -0
- package/dist/plugin/skills/morula-cli/SKILL.md +217 -0
- package/dist/plugin/skills/morula-cli/references/authentication.md +21 -0
- package/dist/plugin/skills/morula-cli/references/commands.md +1680 -0
- package/dist/plugin/skills/morula-cli/references/eval-scenarios.md +42 -0
- package/dist/plugin/skills/morula-working-on-issues/SKILL.md +390 -0
- package/dist/plugin/skills/morula-working-on-issues/references/working-on-issues-source-map.md +80 -0
- package/package.json +12 -5
- package/scripts/postinstall.mjs +38 -0
package/dist/morula.js
ADDED
|
@@ -0,0 +1,4393 @@
|
|
|
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
|
+
const valueOccurrences = {};
|
|
45
|
+
const recordValue = (key, value) => {
|
|
46
|
+
flags[key] = value;
|
|
47
|
+
(valueOccurrences[key] ??= []).push(value);
|
|
48
|
+
};
|
|
49
|
+
let positionalOnly = false;
|
|
50
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
51
|
+
const value = argv[index];
|
|
52
|
+
if (positionalOnly) {
|
|
53
|
+
positionals.push(value);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (value === "--") {
|
|
57
|
+
positionalOnly = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (!value.startsWith("--")) {
|
|
61
|
+
if (value.length === 2 && value.startsWith("-") && SHORT_FLAG_ALIASES[value[1]]) {
|
|
62
|
+
flags[SHORT_FLAG_ALIASES[value[1]]] = true;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
positionals.push(value);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const equal = value.indexOf("=");
|
|
69
|
+
if (equal > 2) {
|
|
70
|
+
recordValue(value.slice(2, equal), value.slice(equal + 1));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const key = value.slice(2);
|
|
74
|
+
if (!key)
|
|
75
|
+
throw usage("invalid empty flag");
|
|
76
|
+
const next = argv[index + 1];
|
|
77
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
78
|
+
recordValue(key, next);
|
|
79
|
+
index += 1;
|
|
80
|
+
} else {
|
|
81
|
+
flags[key] = true;
|
|
82
|
+
bareFlags.push(key);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const repeatedFlags = {};
|
|
86
|
+
for (const [key, values] of Object.entries(valueOccurrences)) {
|
|
87
|
+
if (values.length > 1)
|
|
88
|
+
repeatedFlags[key] = values;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
command: positionals.shift(),
|
|
92
|
+
action: positionals.shift(),
|
|
93
|
+
positionals,
|
|
94
|
+
flags,
|
|
95
|
+
bareFlags,
|
|
96
|
+
repeatedFlags
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function flagStrings(parsed, name) {
|
|
100
|
+
const repeated = parsed.repeatedFlags[name];
|
|
101
|
+
if (repeated?.length)
|
|
102
|
+
return repeated;
|
|
103
|
+
const single = flagString(parsed.flags, name);
|
|
104
|
+
return single === void 0 ? [] : [single];
|
|
105
|
+
}
|
|
106
|
+
function flagString(flags, ...names) {
|
|
107
|
+
for (const name of names) {
|
|
108
|
+
const value = flags[name];
|
|
109
|
+
if (typeof value === "string" && value.trim())
|
|
110
|
+
return value.trim();
|
|
111
|
+
}
|
|
112
|
+
return void 0;
|
|
113
|
+
}
|
|
114
|
+
function flagBoolean(flags, name) {
|
|
115
|
+
const value = flags[name];
|
|
116
|
+
return value === true || value === "true" || value === "1";
|
|
117
|
+
}
|
|
118
|
+
function flagNumber(flags, name) {
|
|
119
|
+
const raw = flagString(flags, name);
|
|
120
|
+
if (raw === void 0)
|
|
121
|
+
return void 0;
|
|
122
|
+
const value = Number(raw);
|
|
123
|
+
if (!Number.isFinite(value))
|
|
124
|
+
throw usage(`--${name} must be a number`);
|
|
125
|
+
return value;
|
|
126
|
+
}
|
|
127
|
+
function requireFlag(flags, name) {
|
|
128
|
+
const value = flagString(flags, name);
|
|
129
|
+
if (!value)
|
|
130
|
+
throw usage(`missing required flag --${name}`);
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
function rejectUnknownFlags(flags, allowed) {
|
|
134
|
+
const known = /* @__PURE__ */ new Set([...GLOBAL_FLAG_NAMES, ...allowed]);
|
|
135
|
+
const unknown = Object.keys(flags).find((flag) => !known.has(flag));
|
|
136
|
+
if (unknown)
|
|
137
|
+
throw usage(`unknown flag --${unknown}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ../morula-cli-core/dist/auth/browser.js
|
|
141
|
+
import { spawn } from "node:child_process";
|
|
142
|
+
function openBrowser(url, env = process.env) {
|
|
143
|
+
if (env.MORULA_NO_BROWSER === "1")
|
|
144
|
+
return false;
|
|
145
|
+
const platform = process.platform;
|
|
146
|
+
let command;
|
|
147
|
+
let args;
|
|
148
|
+
let detached = true;
|
|
149
|
+
if (platform === "win32") {
|
|
150
|
+
const statement = `Start-Process '${url.replace(/'/g, "''")}'`;
|
|
151
|
+
command = `${env.SYSTEMROOT ?? env.windir ?? "C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
|
|
152
|
+
args = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand", Buffer.from(statement, "utf16le").toString("base64")];
|
|
153
|
+
detached = false;
|
|
154
|
+
} else {
|
|
155
|
+
command = platform === "darwin" ? "open" : "xdg-open";
|
|
156
|
+
args = [url];
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const child = spawn(command, args, {
|
|
160
|
+
detached,
|
|
161
|
+
stdio: "ignore",
|
|
162
|
+
windowsHide: true,
|
|
163
|
+
windowsVerbatimArguments: platform === "win32"
|
|
164
|
+
});
|
|
165
|
+
child.on("error", () => {
|
|
166
|
+
});
|
|
167
|
+
child.unref();
|
|
168
|
+
return true;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ../morula-cli-core/dist/auth/callback-server.js
|
|
175
|
+
import { createServer } from "node:http";
|
|
176
|
+
async function startCallbackServer(expectedState) {
|
|
177
|
+
let settleCode;
|
|
178
|
+
let rejectCode;
|
|
179
|
+
let settled = false;
|
|
180
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
181
|
+
settleCode = resolve;
|
|
182
|
+
rejectCode = reject;
|
|
183
|
+
});
|
|
184
|
+
const server = createServer((request, response) => {
|
|
185
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
186
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
187
|
+
response.setHeader("cache-control", "no-store");
|
|
188
|
+
response.setHeader("x-content-type-options", "nosniff");
|
|
189
|
+
if (url.pathname !== "/callback") {
|
|
190
|
+
response.statusCode = 404;
|
|
191
|
+
response.end("Not found");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const state = url.searchParams.get("state");
|
|
195
|
+
if (state !== expectedState) {
|
|
196
|
+
response.statusCode = 400;
|
|
197
|
+
response.end(page(false, "\u767B\u5F55\u56DE\u8C03\u6821\u9A8C\u5931\u8D25\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u91CD\u8BD5\u3002"));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (settled) {
|
|
201
|
+
response.statusCode = 409;
|
|
202
|
+
response.end(page(false, "\u8BE5\u767B\u5F55\u56DE\u8C03\u5DF2\u5904\u7406\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u3002"));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const error = url.searchParams.get("error");
|
|
206
|
+
const code = url.searchParams.get("code");
|
|
207
|
+
settled = true;
|
|
208
|
+
if (error) {
|
|
209
|
+
response.statusCode = 400;
|
|
210
|
+
response.end(page(false, "SkyDoor \u767B\u5F55\u672A\u5B8C\u6210\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u91CD\u8BD5\u3002"));
|
|
211
|
+
rejectCode(new CliError("LOGIN_FAILED", EXIT.OTHER, "SkyDoor login failed"));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (!code) {
|
|
215
|
+
response.statusCode = 400;
|
|
216
|
+
response.end(page(false, "\u767B\u5F55\u56DE\u8C03\u7F3A\u5C11\u6388\u6743\u7801\uFF0C\u8BF7\u8FD4\u56DE\u7EC8\u7AEF\u91CD\u8BD5\u3002"));
|
|
217
|
+
rejectCode(new CliError("LOGIN_FAILED", EXIT.OTHER, "invalid login callback (missing code)"));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
response.statusCode = 200;
|
|
221
|
+
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"));
|
|
222
|
+
settleCode(code);
|
|
223
|
+
});
|
|
224
|
+
await new Promise((resolve, reject) => {
|
|
225
|
+
server.once("error", reject);
|
|
226
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
227
|
+
});
|
|
228
|
+
const port = server.address().port;
|
|
229
|
+
return {
|
|
230
|
+
redirectUri: `http://127.0.0.1:${port}/callback`,
|
|
231
|
+
waitForCode(timeoutMs = 3e5) {
|
|
232
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
233
|
+
return Promise.reject(new CliError("LOGIN_TIMEOUT", EXIT.OTHER, "login timeout must be a positive number"));
|
|
234
|
+
return new Promise((resolve, reject) => {
|
|
235
|
+
const timer = setTimeout(() => reject(new CliError("LOGIN_TIMEOUT", EXIT.OTHER, "timed out waiting for browser login")), timeoutMs);
|
|
236
|
+
timer.unref?.();
|
|
237
|
+
codePromise.then((code) => {
|
|
238
|
+
clearTimeout(timer);
|
|
239
|
+
resolve(code);
|
|
240
|
+
}, (error) => {
|
|
241
|
+
clearTimeout(timer);
|
|
242
|
+
reject(error);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
},
|
|
246
|
+
close() {
|
|
247
|
+
return new Promise((resolve) => server.close(() => resolve()));
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
function escapeHtml(value) {
|
|
252
|
+
const entities = {
|
|
253
|
+
"&": "&",
|
|
254
|
+
"<": "<",
|
|
255
|
+
">": ">",
|
|
256
|
+
'"': """,
|
|
257
|
+
"'": "'"
|
|
258
|
+
};
|
|
259
|
+
return value.replace(/[&<>"']/g, (character) => entities[character] ?? character);
|
|
260
|
+
}
|
|
261
|
+
function page(ok, message) {
|
|
262
|
+
const safeMessage = escapeHtml(message);
|
|
263
|
+
const statusLight = ok ? "oklch(0.42 0.085 183)" : "oklch(0.577 0.245 27.325)";
|
|
264
|
+
const statusDark = ok ? "oklch(0.72 0.09 183)" : "oklch(0.704 0.191 22.216)";
|
|
265
|
+
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>';
|
|
266
|
+
const terminalIcon = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="m5 7 4 4-4 4"/><path d="M11 17h8"/></svg>';
|
|
267
|
+
return `<!doctype html>
|
|
268
|
+
<html lang="zh-CN">
|
|
269
|
+
<head>
|
|
270
|
+
<meta charset="utf-8">
|
|
271
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
272
|
+
<meta name="color-scheme" content="light dark">
|
|
273
|
+
<title>${ok ? "\u6D4F\u89C8\u5668\u9A8C\u8BC1\u5B8C\u6210" : "\u767B\u5F55\u9A8C\u8BC1\u672A\u5B8C\u6210"} \xB7 Morula CLI</title>
|
|
274
|
+
<style>
|
|
275
|
+
:root {
|
|
276
|
+
color-scheme: light;
|
|
277
|
+
--background: oklch(0.982 0.006 95);
|
|
278
|
+
--foreground: oklch(0.205 0.012 248);
|
|
279
|
+
--card: oklch(0.997 0.002 95);
|
|
280
|
+
--primary: oklch(0.42 0.085 183);
|
|
281
|
+
--primary-foreground: oklch(0.985 0.006 95);
|
|
282
|
+
--muted: oklch(0.944 0.009 95);
|
|
283
|
+
--muted-foreground: oklch(0.48 0.018 248);
|
|
284
|
+
--accent: oklch(0.925 0.018 183);
|
|
285
|
+
--border: oklch(0.91 0.005 95);
|
|
286
|
+
--status: ${statusLight};
|
|
287
|
+
--status-soft: color-mix(in oklch, var(--status) 10%, var(--card));
|
|
288
|
+
--shadow-card: 0 0 0 1px hsl(210 18% 16% / 0.06), 0 14px 28px -18px hsl(210 18% 16% / 0.28);
|
|
289
|
+
--radius: 8px;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
293
|
+
* { margin: 0; }
|
|
294
|
+
|
|
295
|
+
html, body { min-height: 100%; }
|
|
296
|
+
|
|
297
|
+
body {
|
|
298
|
+
min-width: 320px;
|
|
299
|
+
background: var(--background);
|
|
300
|
+
color: var(--foreground);
|
|
301
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif;
|
|
302
|
+
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
|
303
|
+
text-rendering: optimizeLegibility;
|
|
304
|
+
-webkit-font-smoothing: antialiased;
|
|
305
|
+
-moz-osx-font-smoothing: grayscale;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.shell {
|
|
309
|
+
display: grid;
|
|
310
|
+
grid-template-columns: minmax(420px, 1.08fr) minmax(480px, 0.92fr);
|
|
311
|
+
min-height: 100vh;
|
|
312
|
+
min-height: 100svh;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
.brand-panel {
|
|
316
|
+
position: relative;
|
|
317
|
+
display: flex;
|
|
318
|
+
min-height: 100vh;
|
|
319
|
+
min-height: 100svh;
|
|
320
|
+
flex-direction: column;
|
|
321
|
+
justify-content: space-between;
|
|
322
|
+
overflow: hidden;
|
|
323
|
+
border-right: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
|
|
324
|
+
background: var(--card);
|
|
325
|
+
padding: clamp(40px, 4vw, 64px);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.brand-panel::before {
|
|
329
|
+
position: absolute;
|
|
330
|
+
inset: 0;
|
|
331
|
+
background: linear-gradient(180deg, color-mix(in oklch, var(--accent) 45%, transparent), transparent 42%);
|
|
332
|
+
content: "";
|
|
333
|
+
pointer-events: none;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.brand-panel::after {
|
|
337
|
+
position: absolute;
|
|
338
|
+
top: 40px;
|
|
339
|
+
right: 0;
|
|
340
|
+
bottom: 40px;
|
|
341
|
+
width: 1px;
|
|
342
|
+
background: linear-gradient(to bottom, transparent, var(--border), transparent);
|
|
343
|
+
content: "";
|
|
344
|
+
pointer-events: none;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.brand, .brand-copy, .brand-footer { position: relative; z-index: 1; }
|
|
348
|
+
|
|
349
|
+
.brand {
|
|
350
|
+
display: inline-flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
gap: 12px;
|
|
353
|
+
width: fit-content;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.brand-logo {
|
|
357
|
+
display: grid;
|
|
358
|
+
width: 40px;
|
|
359
|
+
height: 40px;
|
|
360
|
+
place-items: center;
|
|
361
|
+
border-radius: var(--radius);
|
|
362
|
+
background: var(--primary);
|
|
363
|
+
color: var(--primary-foreground);
|
|
364
|
+
font-size: 18px;
|
|
365
|
+
font-weight: 800;
|
|
366
|
+
letter-spacing: -0.02em;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.brand-name { font-size: 20px; font-weight: 600; line-height: 1.15; }
|
|
370
|
+
|
|
371
|
+
.brand-product {
|
|
372
|
+
margin-top: 4px;
|
|
373
|
+
color: var(--muted-foreground);
|
|
374
|
+
font-size: 12px;
|
|
375
|
+
font-weight: 500;
|
|
376
|
+
letter-spacing: 0.12em;
|
|
377
|
+
text-transform: uppercase;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.brand-copy { width: min(100%, 580px); }
|
|
381
|
+
|
|
382
|
+
.brand-kicker {
|
|
383
|
+
display: inline-flex;
|
|
384
|
+
align-items: center;
|
|
385
|
+
min-height: 32px;
|
|
386
|
+
border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
|
|
387
|
+
border-radius: 6px;
|
|
388
|
+
background: color-mix(in oklch, var(--muted) 45%, transparent);
|
|
389
|
+
padding: 6px 12px;
|
|
390
|
+
color: var(--muted-foreground);
|
|
391
|
+
font-size: 14px;
|
|
392
|
+
font-weight: 550;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.brand-headline {
|
|
396
|
+
margin-top: 28px;
|
|
397
|
+
max-width: 560px;
|
|
398
|
+
font-size: clamp(42px, 4vw, 60px);
|
|
399
|
+
font-weight: 600;
|
|
400
|
+
letter-spacing: -0.035em;
|
|
401
|
+
line-height: 1.12;
|
|
402
|
+
text-wrap: balance;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.brand-description {
|
|
406
|
+
margin-top: 20px;
|
|
407
|
+
max-width: 530px;
|
|
408
|
+
color: var(--muted-foreground);
|
|
409
|
+
font-size: 17px;
|
|
410
|
+
line-height: 1.8;
|
|
411
|
+
text-wrap: pretty;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.connection {
|
|
415
|
+
display: flex;
|
|
416
|
+
width: min(100%, 530px);
|
|
417
|
+
align-items: center;
|
|
418
|
+
gap: 12px;
|
|
419
|
+
margin-top: 32px;
|
|
420
|
+
border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
|
|
421
|
+
border-radius: var(--radius);
|
|
422
|
+
background: var(--card);
|
|
423
|
+
padding: 14px 16px;
|
|
424
|
+
box-shadow: 0 1px 3px hsl(210 18% 16% / 0.05);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.connection-icon, .next-step-icon {
|
|
428
|
+
display: grid;
|
|
429
|
+
flex: 0 0 auto;
|
|
430
|
+
place-items: center;
|
|
431
|
+
border-radius: 6px;
|
|
432
|
+
background: var(--muted);
|
|
433
|
+
color: var(--muted-foreground);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.connection-icon { width: 36px; height: 36px; }
|
|
437
|
+
.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; }
|
|
438
|
+
|
|
439
|
+
.connection-copy { min-width: 0; }
|
|
440
|
+
.connection-copy span { display: block; color: var(--muted-foreground); font-size: 11px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
441
|
+
.connection-copy strong { display: block; margin-top: 3px; font-size: 14px; font-weight: 600; }
|
|
442
|
+
|
|
443
|
+
.secure-label {
|
|
444
|
+
margin-left: auto;
|
|
445
|
+
border-radius: 999px;
|
|
446
|
+
background: var(--accent);
|
|
447
|
+
padding: 5px 9px;
|
|
448
|
+
color: var(--primary);
|
|
449
|
+
font-size: 12px;
|
|
450
|
+
font-weight: 600;
|
|
451
|
+
white-space: nowrap;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.brand-footer { color: var(--muted-foreground); font-size: 13px; font-weight: 500; }
|
|
455
|
+
|
|
456
|
+
.result-panel {
|
|
457
|
+
display: grid;
|
|
458
|
+
min-height: 100vh;
|
|
459
|
+
min-height: 100svh;
|
|
460
|
+
place-items: center;
|
|
461
|
+
background: var(--background);
|
|
462
|
+
padding: clamp(28px, 5vw, 72px);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.result-wrap { width: min(100%, 448px); }
|
|
466
|
+
|
|
467
|
+
.mobile-brand {
|
|
468
|
+
display: none;
|
|
469
|
+
align-items: center;
|
|
470
|
+
gap: 10px;
|
|
471
|
+
margin-bottom: 24px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
.mobile-brand .brand-logo { width: 36px; height: 36px; font-size: 16px; }
|
|
475
|
+
.mobile-brand .brand-name { font-size: 17px; }
|
|
476
|
+
|
|
477
|
+
.status-card {
|
|
478
|
+
width: 100%;
|
|
479
|
+
overflow: hidden;
|
|
480
|
+
border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
|
|
481
|
+
border-radius: var(--radius);
|
|
482
|
+
background: var(--card);
|
|
483
|
+
box-shadow: var(--shadow-card);
|
|
484
|
+
animation: card-in 420ms cubic-bezier(0.23, 1, 0.32, 1) both;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.status-content { padding: 32px; }
|
|
488
|
+
|
|
489
|
+
.status-header {
|
|
490
|
+
display: flex;
|
|
491
|
+
align-items: center;
|
|
492
|
+
gap: 14px;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.status-icon {
|
|
496
|
+
display: grid;
|
|
497
|
+
width: 52px;
|
|
498
|
+
height: 52px;
|
|
499
|
+
flex: 0 0 auto;
|
|
500
|
+
place-items: center;
|
|
501
|
+
border-radius: 10px;
|
|
502
|
+
background: var(--status-soft);
|
|
503
|
+
color: var(--status);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.status-icon svg {
|
|
507
|
+
width: 26px;
|
|
508
|
+
height: 26px;
|
|
509
|
+
fill: none;
|
|
510
|
+
stroke: currentColor;
|
|
511
|
+
stroke-width: 2.25;
|
|
512
|
+
stroke-linecap: round;
|
|
513
|
+
stroke-linejoin: round;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
.status-path {
|
|
517
|
+
stroke-dasharray: 30;
|
|
518
|
+
stroke-dashoffset: 30;
|
|
519
|
+
animation: draw-path 380ms 180ms cubic-bezier(0.23, 1, 0.32, 1) forwards;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.status-path-delay { animation-delay: 260ms; }
|
|
523
|
+
|
|
524
|
+
.status-eyebrow {
|
|
525
|
+
color: var(--muted-foreground);
|
|
526
|
+
font-size: 11px;
|
|
527
|
+
font-weight: 600;
|
|
528
|
+
letter-spacing: 0.1em;
|
|
529
|
+
text-transform: uppercase;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.status-label { margin-top: 5px; color: var(--status); font-size: 14px; font-weight: 600; }
|
|
533
|
+
|
|
534
|
+
h1 {
|
|
535
|
+
margin-top: 28px;
|
|
536
|
+
font-size: 28px;
|
|
537
|
+
font-weight: 600;
|
|
538
|
+
letter-spacing: -0.022em;
|
|
539
|
+
line-height: 1.25;
|
|
540
|
+
text-wrap: balance;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
.message {
|
|
544
|
+
margin-top: 12px;
|
|
545
|
+
color: var(--muted-foreground);
|
|
546
|
+
font-size: 15px;
|
|
547
|
+
line-height: 1.75;
|
|
548
|
+
text-wrap: pretty;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
.next-step {
|
|
552
|
+
display: flex;
|
|
553
|
+
gap: 12px;
|
|
554
|
+
margin-top: 28px;
|
|
555
|
+
border: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
|
|
556
|
+
border-radius: var(--radius);
|
|
557
|
+
background: color-mix(in oklch, var(--muted) 52%, transparent);
|
|
558
|
+
padding: 16px;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.next-step-icon { width: 36px; height: 36px; background: var(--card); color: var(--foreground); }
|
|
562
|
+
.next-step-copy { min-width: 0; }
|
|
563
|
+
.next-step-copy span, .next-step-copy strong { display: block; }
|
|
564
|
+
.next-step-copy span { color: var(--muted-foreground); font-size: 12px; line-height: 1.55; }
|
|
565
|
+
.next-step-copy strong { margin: 2px 0 4px; font-size: 14px; font-weight: 600; }
|
|
566
|
+
|
|
567
|
+
.status-footer {
|
|
568
|
+
display: flex;
|
|
569
|
+
align-items: center;
|
|
570
|
+
gap: 8px;
|
|
571
|
+
border-top: 1px solid color-mix(in oklch, var(--border) 60%, transparent);
|
|
572
|
+
padding: 16px 32px;
|
|
573
|
+
color: var(--muted-foreground);
|
|
574
|
+
font-size: 12px;
|
|
575
|
+
font-weight: 500;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--status); box-shadow: 0 0 0 4px var(--status-soft); }
|
|
579
|
+
|
|
580
|
+
@keyframes card-in {
|
|
581
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
582
|
+
to { opacity: 1; transform: translateY(0); }
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
@keyframes draw-path { to { stroke-dashoffset: 0; } }
|
|
586
|
+
|
|
587
|
+
@media (max-width: 1023px) {
|
|
588
|
+
.shell { display: block; }
|
|
589
|
+
.brand-panel { display: none; }
|
|
590
|
+
.result-panel { padding: 32px 20px; }
|
|
591
|
+
.mobile-brand { display: flex; }
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
@media (max-width: 420px) {
|
|
595
|
+
.result-panel { align-items: center; padding: 24px 16px; }
|
|
596
|
+
.status-content { padding: 26px 24px; }
|
|
597
|
+
.status-footer { padding: 15px 24px; }
|
|
598
|
+
h1 { font-size: 25px; }
|
|
599
|
+
.message { font-size: 14px; }
|
|
600
|
+
.next-step { margin-top: 24px; padding: 14px; }
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
@media (prefers-color-scheme: dark) {
|
|
604
|
+
:root {
|
|
605
|
+
color-scheme: dark;
|
|
606
|
+
--background: oklch(0.165 0.008 248);
|
|
607
|
+
--foreground: oklch(0.948 0.006 95);
|
|
608
|
+
--card: oklch(0.205 0.01 248);
|
|
609
|
+
--primary: oklch(0.72 0.09 183);
|
|
610
|
+
--primary-foreground: oklch(0.16 0.014 248);
|
|
611
|
+
--muted: oklch(0.255 0.012 248);
|
|
612
|
+
--muted-foreground: oklch(0.69 0.012 248);
|
|
613
|
+
--accent: oklch(0.29 0.025 183);
|
|
614
|
+
--border: oklch(0.275 0.01 248);
|
|
615
|
+
--status: ${statusDark};
|
|
616
|
+
--status-soft: color-mix(in oklch, var(--status) 12%, var(--card));
|
|
617
|
+
--shadow-card: 0 0 0 1px hsl(0 0% 100% / 0.085);
|
|
618
|
+
}
|
|
619
|
+
.connection { box-shadow: 0 0 0 1px hsl(0 0% 100% / 0.04); }
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
@media (prefers-reduced-motion: reduce) {
|
|
623
|
+
*, *::before, *::after {
|
|
624
|
+
scroll-behavior: auto !important;
|
|
625
|
+
animation-duration: 0.01ms !important;
|
|
626
|
+
animation-delay: 0ms !important;
|
|
627
|
+
animation-iteration-count: 1 !important;
|
|
628
|
+
transition-duration: 0.01ms !important;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
</style>
|
|
632
|
+
</head>
|
|
633
|
+
<body>
|
|
634
|
+
<div class="shell">
|
|
635
|
+
<aside class="brand-panel" aria-label="Morula \u54C1\u724C\u4ECB\u7ECD">
|
|
636
|
+
<div class="brand">
|
|
637
|
+
<div class="brand-logo" aria-hidden="true">S</div>
|
|
638
|
+
<div>
|
|
639
|
+
<div class="brand-name">Morula</div>
|
|
640
|
+
<div class="brand-product">Project OS</div>
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
|
|
644
|
+
<div class="brand-copy">
|
|
645
|
+
<div class="brand-kicker">\u9762\u5411\u56E2\u961F\u7684\u8F7B\u91CF\u9879\u76EE\u534F\u540C\u5E73\u53F0</div>
|
|
646
|
+
<p class="brand-headline">\u8BA9\u9879\u76EE\u63A8\u8FDB<br>\u66F4\u6E05\u6670\u3001\u66F4\u4ECE\u5BB9</p>
|
|
647
|
+
<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>
|
|
648
|
+
<div class="connection">
|
|
649
|
+
<div class="connection-icon">${terminalIcon}</div>
|
|
650
|
+
<div class="connection-copy">
|
|
651
|
+
<span>\u8FDE\u63A5\u65B9\u5F0F</span>
|
|
652
|
+
<strong>Morula CLI</strong>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="secure-label">\u5B89\u5168\u8BA4\u8BC1</div>
|
|
655
|
+
</div>
|
|
656
|
+
</div>
|
|
657
|
+
|
|
658
|
+
<div class="brand-footer">\xA9 ${(/* @__PURE__ */ new Date()).getFullYear()} Morula \u9879\u76EE\u534F\u540C\u5E73\u53F0</div>
|
|
659
|
+
</aside>
|
|
660
|
+
|
|
661
|
+
<main class="result-panel" aria-labelledby="result-title">
|
|
662
|
+
<div class="result-wrap">
|
|
663
|
+
<div class="mobile-brand">
|
|
664
|
+
<div class="brand-logo" aria-hidden="true">S</div>
|
|
665
|
+
<div>
|
|
666
|
+
<div class="brand-name">Morula</div>
|
|
667
|
+
<div class="brand-product">\u9879\u76EE\u534F\u540C\u5E73\u53F0</div>
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
|
|
671
|
+
<section class="status-card">
|
|
672
|
+
<div class="status-content">
|
|
673
|
+
<div class="status-header">
|
|
674
|
+
<div class="status-icon">${statusIcon}</div>
|
|
675
|
+
<div>
|
|
676
|
+
<div class="status-eyebrow">Morula CLI</div>
|
|
677
|
+
<div class="status-label">${ok ? "\u8EAB\u4EFD\u8BA4\u8BC1\u5DF2\u786E\u8BA4" : "\u8EAB\u4EFD\u8BA4\u8BC1\u672A\u5B8C\u6210"}</div>
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
|
|
681
|
+
<h1 id="result-title">${ok ? "\u6D4F\u89C8\u5668\u9A8C\u8BC1\u5B8C\u6210" : "\u672A\u80FD\u5B8C\u6210\u9A8C\u8BC1"}</h1>
|
|
682
|
+
<p class="message">${safeMessage}</p>
|
|
683
|
+
|
|
684
|
+
<div class="next-step">
|
|
685
|
+
<div class="next-step-icon">${terminalIcon}</div>
|
|
686
|
+
<div class="next-step-copy">
|
|
687
|
+
<span>\u4E0B\u4E00\u6B65</span>
|
|
688
|
+
<strong>${ok ? "\u8FD4\u56DE Morula CLI" : "\u56DE\u5230\u7EC8\u7AEF\u91CD\u65B0\u767B\u5F55"}</strong>
|
|
689
|
+
<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>
|
|
690
|
+
</div>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
|
|
694
|
+
<div class="status-footer">
|
|
695
|
+
<span class="status-dot" aria-hidden="true"></span>
|
|
696
|
+
${ok ? "\u6388\u6743\u56DE\u8C03\u5DF2\u8FD4\u56DE\u672C\u5730\u7EC8\u7AEF" : "\u672C\u6B21\u6388\u6743\u56DE\u8C03\u672A\u88AB\u63A5\u53D7"}
|
|
697
|
+
</div>
|
|
698
|
+
</section>
|
|
699
|
+
</div>
|
|
700
|
+
</main>
|
|
701
|
+
</div>
|
|
702
|
+
</body>
|
|
703
|
+
</html>`;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// ../morula-cli-core/dist/build-env.js
|
|
707
|
+
var BUILD_VERSION = "0.1.0-alpha.10";
|
|
708
|
+
var BUILD_ID = "20260922054915.d5e2cc87";
|
|
709
|
+
var BUILD_CONTEXTS = {
|
|
710
|
+
"local": "http://localhost:8080/api",
|
|
711
|
+
"test": "https://morula.basecastle.com/api",
|
|
712
|
+
"prod": "https://morula.fazhiplus.com/api"
|
|
713
|
+
};
|
|
714
|
+
var BUILD_DEFAULT_CONTEXT = "prod";
|
|
715
|
+
|
|
716
|
+
// ../morula-api-client/dist/agent-task.js
|
|
717
|
+
var AgentTaskApi = class {
|
|
718
|
+
client;
|
|
719
|
+
constructor(client) {
|
|
720
|
+
this.client = client;
|
|
721
|
+
}
|
|
722
|
+
/** 列出 AgentTask(objectId 必填;status/page/pageSize 可选) */
|
|
723
|
+
list(query = {}) {
|
|
724
|
+
return this.client.get("/agent-tasks", query);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* 取消执行。
|
|
728
|
+
*
|
|
729
|
+
* 服务端语义(`agent-task.controller.ts` cancel,2026-09-20 起):
|
|
730
|
+
* - 成功 → 200 `{ ok: true, message: 'Task cancelled' }`;
|
|
731
|
+
* - 任务不存在 → **404** `NOT_FOUND`;
|
|
732
|
+
* - 状态不允许(`completed` / `cancelled`)→ **409** `CONFLICT`(带可读原因)。
|
|
733
|
+
*
|
|
734
|
+
* 即:**失败会 reject**(不再 200 假成功,见 issues/agent-task-cancel-returns-200-on-failure.md)——
|
|
735
|
+
* 调用方按异常/退出码判断,不要假定「调用返回了就是已取消」。
|
|
736
|
+
*/
|
|
737
|
+
cancel(id) {
|
|
738
|
+
return this.client.post(`/agent-tasks/${encodeURIComponent(requiredId(id))}/cancel`);
|
|
739
|
+
}
|
|
740
|
+
/** 重跑失败/已取消的执行(服务端复制原任务重新入队,返回**新** taskId) */
|
|
741
|
+
rerun(id) {
|
|
742
|
+
return this.client.post(`/agent-tasks/${encodeURIComponent(requiredId(id))}/rerun`);
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
function requiredId(id) {
|
|
746
|
+
const normalized = id.trim();
|
|
747
|
+
if (!normalized)
|
|
748
|
+
throw new TypeError("agent task id is required");
|
|
749
|
+
return normalized;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
// ../morula-api-client/dist/attachment.js
|
|
753
|
+
var AttachmentApi = class {
|
|
754
|
+
client;
|
|
755
|
+
constructor(client) {
|
|
756
|
+
this.client = client;
|
|
757
|
+
}
|
|
758
|
+
/** 获取 TUS 上传签名 */
|
|
759
|
+
getUploadSignature() {
|
|
760
|
+
return this.client.post("/attachments/upload-signature");
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* 智能体通用文件上传:上传本机文件字节并挂附件。
|
|
764
|
+
* 走 multipart/form-data,服务端接收后上传存储并创建附件记录。
|
|
765
|
+
* 智能归并:不传 resourceType/resourceId 时,服务端按任务绑定对象决定——有 object 挂 object,纯对话挂 Chat 会话最新消息(ag_message)。
|
|
766
|
+
*/
|
|
767
|
+
upload(input) {
|
|
768
|
+
const form = new FormData();
|
|
769
|
+
const blob = input.file instanceof Blob ? input.file : new Blob([input.file], { type: input.fileType });
|
|
770
|
+
form.append("file", blob, input.fileName);
|
|
771
|
+
if (input.resourceType)
|
|
772
|
+
form.append("resourceType", input.resourceType);
|
|
773
|
+
if (input.resourceId)
|
|
774
|
+
form.append("resourceId", input.resourceId);
|
|
775
|
+
if (input.taskId)
|
|
776
|
+
form.append("taskId", input.taskId);
|
|
777
|
+
if (input.isInline !== void 0)
|
|
778
|
+
form.append("isInline", input.isInline ? "true" : "false");
|
|
779
|
+
return this.client.post("/agent/attachments/upload", form);
|
|
780
|
+
}
|
|
781
|
+
/** 创建附件记录(上传完成后调用) */
|
|
782
|
+
create(input) {
|
|
783
|
+
return this.client.post("/attachments", input);
|
|
784
|
+
}
|
|
785
|
+
/** 按资源查询附件列表 */
|
|
786
|
+
list(query) {
|
|
787
|
+
return this.client.get("/attachments", query);
|
|
788
|
+
}
|
|
789
|
+
/** 获取附件详情 */
|
|
790
|
+
get(id) {
|
|
791
|
+
return this.client.get(`/attachments/${encodeURIComponent(id)}`);
|
|
792
|
+
}
|
|
793
|
+
/** 获取附件下载 URL */
|
|
794
|
+
getDownloadUrl(id) {
|
|
795
|
+
return this.client.get(`/attachments/${encodeURIComponent(id)}/download-url`);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* 读取附件的解析产物全文(MinerU markdown)。
|
|
799
|
+
* 未解析/解析中/解析失败由服务端在返回体里说明,不当作 HTTP 错误。
|
|
800
|
+
*/
|
|
801
|
+
content(id, maxChars) {
|
|
802
|
+
return this.client.get(`/attachments/${encodeURIComponent(id)}/content`, { maxChars });
|
|
803
|
+
}
|
|
804
|
+
/** 更新附件关联的资源 ID */
|
|
805
|
+
updateResourceId(id, resourceId) {
|
|
806
|
+
return this.client.patch(`/attachments/${encodeURIComponent(id)}`, { resourceId });
|
|
807
|
+
}
|
|
808
|
+
/** 删除附件 */
|
|
809
|
+
delete(id) {
|
|
810
|
+
return this.client.delete(`/attachments/${encodeURIComponent(id)}`);
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// ../morula-api-client/dist/auth.js
|
|
815
|
+
var AuthApi = class {
|
|
816
|
+
client;
|
|
817
|
+
constructor(client) {
|
|
818
|
+
this.client = client;
|
|
819
|
+
}
|
|
820
|
+
authorize(query) {
|
|
821
|
+
return this.client.get("/auth/cli/authorize", query);
|
|
822
|
+
}
|
|
823
|
+
exchangeToken(input) {
|
|
824
|
+
return this.client.post("/auth/cli/token", input);
|
|
825
|
+
}
|
|
826
|
+
revokeCliToken() {
|
|
827
|
+
return this.client.post("/auth/cli/logout");
|
|
828
|
+
}
|
|
829
|
+
profile() {
|
|
830
|
+
return this.client.get("/auth/profile");
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
// ../morula-api-client/dist/autopilot.js
|
|
835
|
+
var AutopilotApi = class {
|
|
836
|
+
client;
|
|
837
|
+
constructor(client) {
|
|
838
|
+
this.client = client;
|
|
839
|
+
}
|
|
840
|
+
list(query = {}) {
|
|
841
|
+
return this.client.get("/agent-autopilots", query);
|
|
842
|
+
}
|
|
843
|
+
create(input) {
|
|
844
|
+
return this.client.post("/agent-autopilots", input);
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
// ../morula-api-client/dist/ci.js
|
|
849
|
+
var CiApi = class {
|
|
850
|
+
client;
|
|
851
|
+
constructor(client) {
|
|
852
|
+
this.client = client;
|
|
853
|
+
}
|
|
854
|
+
/** 任务面 CI 视图(本任务关联的流水线运行 + 部署记录 + 归属依据) */
|
|
855
|
+
taskView(taskId, query = {}) {
|
|
856
|
+
return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId2(taskId, "task id"))}/vcs/ci`, query);
|
|
857
|
+
}
|
|
858
|
+
/** 任务面部署记录(D3:仅展示,不驱动流转) */
|
|
859
|
+
taskDeployments(taskId) {
|
|
860
|
+
return this.client.get(`/agent-tasks/${encodeURIComponent(requiredId2(taskId, "task id"))}/vcs/ci/deployments`);
|
|
861
|
+
}
|
|
862
|
+
/** 空间面 CI 巡检视图(管家主入口;`ref` / `failed` / `limit` 为服务端过滤) */
|
|
863
|
+
spaceView(spaceId, query = {}) {
|
|
864
|
+
return this.client.get(`/vcs/spaces/${encodeURIComponent(requiredId2(spaceId, "space id"))}/ci`, query);
|
|
865
|
+
}
|
|
866
|
+
/** 空间面部署记录(D3:仅展示) */
|
|
867
|
+
spaceDeployments(spaceId) {
|
|
868
|
+
return this.client.get(`/vcs/spaces/${encodeURIComponent(requiredId2(spaceId, "space id"))}/ci/deployments`);
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* job 日志(按 job id;服务端反查所属运行)。
|
|
872
|
+
*
|
|
873
|
+
* 两个面共用本端点:任务凭证按 tenantId 收敛,会话凭证按 `connection.spaceId === scopeId` 收敛。
|
|
874
|
+
* `truncated` 显式返回——日志被尾部截断时调用方必须知道(不能把截断当全文)。
|
|
875
|
+
*/
|
|
876
|
+
jobLog(jobId, query = {}) {
|
|
877
|
+
return this.client.get(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/log`, query);
|
|
878
|
+
}
|
|
879
|
+
/** 失败用例清单(平台可选能力:GitLab test_report 有、GitHub 无 → `CAPABILITY_UNSUPPORTED`) */
|
|
880
|
+
testReport(runId) {
|
|
881
|
+
return this.client.get(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/test-report`);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* 重跑整条流水线的失败 job(Phase 3;开放面 = 人 + 管家)。
|
|
885
|
+
*
|
|
886
|
+
* 只允许 failure / cancelled 的运行,其余状态服务端回 `CI_RETRY_NOT_ALLOWED`(不是 4xx)。
|
|
887
|
+
* **非幂等**:每次调用都会在平台上产生一次新的重跑,重复调用不会去重。
|
|
888
|
+
*/
|
|
889
|
+
retryRun(runId) {
|
|
890
|
+
return this.client.post(`/vcs/runs/${encodeURIComponent(requiredId2(runId, "run id"))}/retry`);
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* 重跑单个 job(Phase 3;开放面 = 人 + 管家)。
|
|
894
|
+
*
|
|
895
|
+
* 服务端按 jobId 反查所属运行(`CI_JOB_NOT_FOUND` 覆盖「job 不存在」与「反查不到运行」两种情况);
|
|
896
|
+
* 状态门禁与 `retryRun` 相同。非幂等。
|
|
897
|
+
*/
|
|
898
|
+
retryJob(jobId) {
|
|
899
|
+
return this.client.post(`/vcs/jobs/${encodeURIComponent(requiredId2(jobId, "job id"))}/retry`);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
function requiredId2(id, label) {
|
|
903
|
+
const normalized = id.trim();
|
|
904
|
+
if (!normalized)
|
|
905
|
+
throw new TypeError(`${label} is required`);
|
|
906
|
+
return normalized;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// ../morula-api-client/dist/client.js
|
|
910
|
+
var EXIT_CODE = {
|
|
911
|
+
OK: 0,
|
|
912
|
+
OTHER: 1,
|
|
913
|
+
USAGE: 2,
|
|
914
|
+
NOT_FOUND: 3,
|
|
915
|
+
UNAUTHENTICATED: 4,
|
|
916
|
+
FORBIDDEN: 5,
|
|
917
|
+
CONFLICT: 6,
|
|
918
|
+
UPSTREAM: 7
|
|
919
|
+
};
|
|
920
|
+
var MorulaApiError = class extends Error {
|
|
921
|
+
code;
|
|
922
|
+
exitCode;
|
|
923
|
+
status;
|
|
924
|
+
retryable;
|
|
925
|
+
details;
|
|
926
|
+
errorCode;
|
|
927
|
+
name = "MorulaApiError";
|
|
928
|
+
constructor(code, message, exitCode, status2, retryable = false, details, errorCode) {
|
|
929
|
+
super(message);
|
|
930
|
+
this.code = code;
|
|
931
|
+
this.exitCode = exitCode;
|
|
932
|
+
this.status = status2;
|
|
933
|
+
this.retryable = retryable;
|
|
934
|
+
this.details = details;
|
|
935
|
+
this.errorCode = errorCode;
|
|
936
|
+
}
|
|
937
|
+
};
|
|
938
|
+
var CLI_BUILD_HEADER = "X-Morula-Cli-Build";
|
|
939
|
+
var CLI_CAPS_HEADER = "X-Morula-Cli-Caps";
|
|
940
|
+
var CLI_BUILD_MAX_LENGTH = 64;
|
|
941
|
+
var CLI_CAPS_MAX_LENGTH = 255;
|
|
942
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
943
|
+
var CAUSE_DETAIL_MAX_LENGTH = 200;
|
|
944
|
+
function describeFetchFailure(error) {
|
|
945
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
946
|
+
const detail = formatFetchCause(error?.cause);
|
|
947
|
+
return detail ? `${message} (cause: ${detail})` : message;
|
|
948
|
+
}
|
|
949
|
+
function formatFetchCause(cause) {
|
|
950
|
+
if (cause === void 0 || cause === null)
|
|
951
|
+
return "";
|
|
952
|
+
if (cause instanceof Error) {
|
|
953
|
+
const detail = cause;
|
|
954
|
+
const parts = [
|
|
955
|
+
typeof detail.code === "string" ? detail.code : "",
|
|
956
|
+
typeof detail.errno === "number" || typeof detail.errno === "string" ? `errno=${detail.errno}` : "",
|
|
957
|
+
typeof detail.syscall === "string" ? `syscall=${detail.syscall}` : "",
|
|
958
|
+
// cause 的 message 常与外层重复('fetch failed'),只有不同才带
|
|
959
|
+
detail.message && detail.message !== "fetch failed" ? detail.message : ""
|
|
960
|
+
].filter(Boolean);
|
|
961
|
+
return parts.join(" ").slice(0, CAUSE_DETAIL_MAX_LENGTH);
|
|
962
|
+
}
|
|
963
|
+
return String(cause).slice(0, CAUSE_DETAIL_MAX_LENGTH);
|
|
964
|
+
}
|
|
965
|
+
var MorulaApiClient = class {
|
|
966
|
+
baseUrl;
|
|
967
|
+
token;
|
|
968
|
+
timeoutMs;
|
|
969
|
+
fetchImpl;
|
|
970
|
+
defaultHeaders;
|
|
971
|
+
constructor(options) {
|
|
972
|
+
const baseUrl = options.baseUrl.trim().replace(/\/+$/, "");
|
|
973
|
+
if (!baseUrl) {
|
|
974
|
+
throw new TypeError("baseUrl is required");
|
|
975
|
+
}
|
|
976
|
+
let parsed;
|
|
977
|
+
try {
|
|
978
|
+
parsed = new URL(baseUrl);
|
|
979
|
+
} catch {
|
|
980
|
+
throw new TypeError(`baseUrl must be an absolute URL: ${baseUrl}`);
|
|
981
|
+
}
|
|
982
|
+
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
983
|
+
throw new TypeError("baseUrl must use http or https");
|
|
984
|
+
}
|
|
985
|
+
this.baseUrl = parsed.toString().replace(/\/+$/, "");
|
|
986
|
+
this.token = options.token;
|
|
987
|
+
this.timeoutMs = positiveTimeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
988
|
+
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
989
|
+
if (typeof this.fetchImpl !== "function") {
|
|
990
|
+
throw new TypeError("fetch is unavailable; provide options.fetch");
|
|
991
|
+
}
|
|
992
|
+
const build = sanitizeHeaderValue(options.clientBuild, CLI_BUILD_MAX_LENGTH);
|
|
993
|
+
const caps = sanitizeHeaderValue(options.clientCaps, CLI_CAPS_MAX_LENGTH);
|
|
994
|
+
this.defaultHeaders = {
|
|
995
|
+
// CLI 身份头(P0-③):仅当调用方声明时注入——未声明的调用方(runtime/测试/第三方复用)
|
|
996
|
+
// 发出的请求不带这两个头,服务端按「未知」处理,不误报陈旧构建。
|
|
997
|
+
...build ? { [CLI_BUILD_HEADER]: build } : {},
|
|
998
|
+
...caps ? { [CLI_CAPS_HEADER]: caps } : {},
|
|
999
|
+
// 显式 headers 优先级最高(更具体的调用方可以覆盖默认值)
|
|
1000
|
+
...options.headers
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
buildUrl(path, query) {
|
|
1004
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
1005
|
+
const url = new URL(`${this.baseUrl}${normalizedPath}`);
|
|
1006
|
+
if (!query)
|
|
1007
|
+
return url;
|
|
1008
|
+
for (const [key, raw] of Object.entries(query)) {
|
|
1009
|
+
const values = Array.isArray(raw) ? raw : [raw];
|
|
1010
|
+
for (const value of values) {
|
|
1011
|
+
if (value !== void 0 && value !== null && value !== "") {
|
|
1012
|
+
url.searchParams.append(key, String(value));
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return url;
|
|
1017
|
+
}
|
|
1018
|
+
async request(path, options = {}) {
|
|
1019
|
+
const method = options.method ?? "GET";
|
|
1020
|
+
const url = this.buildUrl(path, options.query);
|
|
1021
|
+
const headers = {
|
|
1022
|
+
Accept: "application/json",
|
|
1023
|
+
...this.defaultHeaders,
|
|
1024
|
+
...options.headers
|
|
1025
|
+
};
|
|
1026
|
+
const token = options.token === void 0 ? this.token : options.token;
|
|
1027
|
+
if (token)
|
|
1028
|
+
headers.Authorization = `Bearer ${token}`;
|
|
1029
|
+
let body;
|
|
1030
|
+
if (options.body !== void 0) {
|
|
1031
|
+
if (typeof FormData !== "undefined" && options.body instanceof FormData) {
|
|
1032
|
+
delete headers["Content-Type"];
|
|
1033
|
+
body = options.body;
|
|
1034
|
+
} else {
|
|
1035
|
+
headers["Content-Type"] ??= "application/json";
|
|
1036
|
+
body = JSON.stringify(options.body);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
const controller = new AbortController();
|
|
1040
|
+
const timeoutMs = positiveTimeout(options.timeoutMs ?? this.timeoutMs);
|
|
1041
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1042
|
+
timer.unref?.();
|
|
1043
|
+
try {
|
|
1044
|
+
const response = await this.fetchImpl(url, {
|
|
1045
|
+
method,
|
|
1046
|
+
headers,
|
|
1047
|
+
body,
|
|
1048
|
+
signal: controller.signal
|
|
1049
|
+
});
|
|
1050
|
+
const raw = await response.text();
|
|
1051
|
+
const parsed = parseBody(raw);
|
|
1052
|
+
if (!response.ok) {
|
|
1053
|
+
throw errorFromResponse(response.status, parsed, raw);
|
|
1054
|
+
}
|
|
1055
|
+
return unwrapEnvelope(parsed, response.status);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
if (error instanceof MorulaApiError)
|
|
1058
|
+
throw error;
|
|
1059
|
+
if (controller.signal.aborted) {
|
|
1060
|
+
throw new MorulaApiError("TIMEOUT", `request timed out after ${timeoutMs}ms`, EXIT_CODE.UPSTREAM, void 0, true);
|
|
1061
|
+
}
|
|
1062
|
+
throw new MorulaApiError("NETWORK", `request failed: ${describeFetchFailure(error)}`, EXIT_CODE.UPSTREAM, void 0, true);
|
|
1063
|
+
} finally {
|
|
1064
|
+
clearTimeout(timer);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
get(path, query) {
|
|
1068
|
+
return this.request(path, { query });
|
|
1069
|
+
}
|
|
1070
|
+
post(path, body, query) {
|
|
1071
|
+
return this.request(path, { method: "POST", body, query });
|
|
1072
|
+
}
|
|
1073
|
+
patch(path, body, query) {
|
|
1074
|
+
return this.request(path, { method: "PATCH", body, query });
|
|
1075
|
+
}
|
|
1076
|
+
delete(path, query) {
|
|
1077
|
+
return this.request(path, { method: "DELETE", query });
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
1080
|
+
function sanitizeHeaderValue(raw, maxLength) {
|
|
1081
|
+
if (!raw)
|
|
1082
|
+
return void 0;
|
|
1083
|
+
let value = "";
|
|
1084
|
+
for (const char of raw) {
|
|
1085
|
+
const code = char.codePointAt(0) ?? 0;
|
|
1086
|
+
if (code <= 31 || code === 127)
|
|
1087
|
+
continue;
|
|
1088
|
+
value += char;
|
|
1089
|
+
}
|
|
1090
|
+
value = value.trim();
|
|
1091
|
+
return value ? value.slice(0, maxLength) : void 0;
|
|
1092
|
+
}
|
|
1093
|
+
function positiveTimeout(value) {
|
|
1094
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1095
|
+
throw new TypeError("timeoutMs must be a positive number");
|
|
1096
|
+
}
|
|
1097
|
+
return value;
|
|
1098
|
+
}
|
|
1099
|
+
function parseBody(raw) {
|
|
1100
|
+
if (!raw)
|
|
1101
|
+
return void 0;
|
|
1102
|
+
try {
|
|
1103
|
+
return JSON.parse(raw);
|
|
1104
|
+
} catch {
|
|
1105
|
+
return raw;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
function unwrapEnvelope(value, status2 = 200) {
|
|
1109
|
+
if (isEnvelope(value)) {
|
|
1110
|
+
if (value.success === false || value.code >= 400) {
|
|
1111
|
+
throw errorFromResponse(value.code || status2, value, JSON.stringify(value));
|
|
1112
|
+
}
|
|
1113
|
+
return value.data;
|
|
1114
|
+
}
|
|
1115
|
+
return value;
|
|
1116
|
+
}
|
|
1117
|
+
function isEnvelope(value) {
|
|
1118
|
+
return !!value && typeof value === "object" && "data" in value && "code" in value && "message" in value && "success" in value;
|
|
1119
|
+
}
|
|
1120
|
+
function errorFromResponse(status2, parsed, raw) {
|
|
1121
|
+
const message = extractMessage(parsed, raw, status2);
|
|
1122
|
+
const details = isEnvelope(parsed) ? parsed.data : parsed;
|
|
1123
|
+
const errorCode = extractErrorCode(parsed);
|
|
1124
|
+
if (status2 === 400 || status2 === 422) {
|
|
1125
|
+
return new MorulaApiError("BAD_REQUEST", message, EXIT_CODE.USAGE, status2, false, details, errorCode);
|
|
1126
|
+
}
|
|
1127
|
+
if (status2 === 401) {
|
|
1128
|
+
return new MorulaApiError("UNAUTHENTICATED", message, EXIT_CODE.UNAUTHENTICATED, status2, false, details, errorCode);
|
|
1129
|
+
}
|
|
1130
|
+
if (status2 === 403) {
|
|
1131
|
+
return new MorulaApiError("FORBIDDEN", message, EXIT_CODE.FORBIDDEN, status2, false, details, errorCode);
|
|
1132
|
+
}
|
|
1133
|
+
if (status2 === 404) {
|
|
1134
|
+
return new MorulaApiError("NOT_FOUND", message, EXIT_CODE.NOT_FOUND, status2, false, details, errorCode);
|
|
1135
|
+
}
|
|
1136
|
+
if (status2 === 409) {
|
|
1137
|
+
return new MorulaApiError("CONFLICT", message, EXIT_CODE.CONFLICT, status2, false, details, errorCode);
|
|
1138
|
+
}
|
|
1139
|
+
if (status2 === 429) {
|
|
1140
|
+
return new MorulaApiError("RATE_LIMITED", message, EXIT_CODE.UPSTREAM, status2, true, details, errorCode);
|
|
1141
|
+
}
|
|
1142
|
+
if (status2 >= 500) {
|
|
1143
|
+
return new MorulaApiError("UPSTREAM", message, EXIT_CODE.UPSTREAM, status2, true, details, errorCode);
|
|
1144
|
+
}
|
|
1145
|
+
return new MorulaApiError("HTTP_ERROR", message, EXIT_CODE.OTHER, status2, false, details, errorCode);
|
|
1146
|
+
}
|
|
1147
|
+
function extractErrorCode(parsed) {
|
|
1148
|
+
if (!parsed || typeof parsed !== "object")
|
|
1149
|
+
return void 0;
|
|
1150
|
+
const value = parsed.errorCode;
|
|
1151
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
1152
|
+
}
|
|
1153
|
+
function extractMessage(parsed, raw, status2) {
|
|
1154
|
+
if (parsed && typeof parsed === "object" && "message" in parsed) {
|
|
1155
|
+
const message = parsed.message;
|
|
1156
|
+
if (typeof message === "string" && message)
|
|
1157
|
+
return message;
|
|
1158
|
+
if (Array.isArray(message) && message.length)
|
|
1159
|
+
return message.map(String).join(", ");
|
|
1160
|
+
}
|
|
1161
|
+
if (typeof parsed === "string" && parsed)
|
|
1162
|
+
return parsed.slice(0, 500);
|
|
1163
|
+
if (raw)
|
|
1164
|
+
return raw.slice(0, 500);
|
|
1165
|
+
return `HTTP ${status2}`;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// ../morula-api-client/dist/comment.js
|
|
1169
|
+
var CommentApi = class {
|
|
1170
|
+
client;
|
|
1171
|
+
constructor(client) {
|
|
1172
|
+
this.client = client;
|
|
1173
|
+
}
|
|
1174
|
+
list(objectId, query = {}) {
|
|
1175
|
+
const params = { ...query };
|
|
1176
|
+
if (objectId)
|
|
1177
|
+
params.objectId = objectId;
|
|
1178
|
+
return this.client.get("/comments", params);
|
|
1179
|
+
}
|
|
1180
|
+
get(id) {
|
|
1181
|
+
return this.client.get(`/comments/${encodeURIComponent(requiredId3(id))}`);
|
|
1182
|
+
}
|
|
1183
|
+
create(input) {
|
|
1184
|
+
return this.client.post("/comments", input);
|
|
1185
|
+
}
|
|
1186
|
+
update(id, input) {
|
|
1187
|
+
return this.client.patch(`/comments/${encodeURIComponent(requiredId3(id))}`, input);
|
|
1188
|
+
}
|
|
1189
|
+
delete(id) {
|
|
1190
|
+
return this.client.delete(`/comments/${encodeURIComponent(requiredId3(id))}`);
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
function requiredId3(id) {
|
|
1194
|
+
const normalized = id.trim();
|
|
1195
|
+
if (!normalized)
|
|
1196
|
+
throw new TypeError("comment id is required");
|
|
1197
|
+
return normalized;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// ../morula-api-client/dist/dashboard.js
|
|
1201
|
+
var DashboardApi = class {
|
|
1202
|
+
client;
|
|
1203
|
+
constructor(client) {
|
|
1204
|
+
this.client = client;
|
|
1205
|
+
}
|
|
1206
|
+
get(spaceId) {
|
|
1207
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/dashboard`);
|
|
1208
|
+
}
|
|
1209
|
+
statistics(spaceId) {
|
|
1210
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/statistics`);
|
|
1211
|
+
}
|
|
1212
|
+
workload(spaceId) {
|
|
1213
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId4(spaceId))}/workload`);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
function requiredId4(id) {
|
|
1217
|
+
const normalized = id.trim();
|
|
1218
|
+
if (!normalized)
|
|
1219
|
+
throw new TypeError("space id is required");
|
|
1220
|
+
return normalized;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// ../morula-api-client/dist/document.js
|
|
1224
|
+
var DocumentApi = class {
|
|
1225
|
+
client;
|
|
1226
|
+
constructor(client) {
|
|
1227
|
+
this.client = client;
|
|
1228
|
+
}
|
|
1229
|
+
list(query = {}) {
|
|
1230
|
+
return this.client.get("/documents", query);
|
|
1231
|
+
}
|
|
1232
|
+
get(id) {
|
|
1233
|
+
return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}`);
|
|
1234
|
+
}
|
|
1235
|
+
/** 已解析归档的文档内容(Markdown 正文等) */
|
|
1236
|
+
artifacts(id) {
|
|
1237
|
+
return this.client.get(`/documents/${encodeURIComponent(requiredId5(id))}/artifacts`);
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
function requiredId5(id) {
|
|
1241
|
+
const normalized = String(id).trim();
|
|
1242
|
+
if (!normalized)
|
|
1243
|
+
throw new TypeError("document id is required");
|
|
1244
|
+
return normalized;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// ../morula-api-client/dist/object-meta.js
|
|
1248
|
+
var ObjectMetaApi = class {
|
|
1249
|
+
client;
|
|
1250
|
+
constructor(client) {
|
|
1251
|
+
this.client = client;
|
|
1252
|
+
}
|
|
1253
|
+
list(objectId) {
|
|
1254
|
+
return this.client.get(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta`);
|
|
1255
|
+
}
|
|
1256
|
+
set(objectId, key, value) {
|
|
1257
|
+
return this.client.request(`/objects/${encodeURIComponent(requiredId6(objectId))}/agent-meta/${encodeURIComponent(requiredKey(key))}`, { method: "PUT", body: { value } });
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
function requiredId6(id) {
|
|
1261
|
+
const normalized = id.trim();
|
|
1262
|
+
if (!normalized)
|
|
1263
|
+
throw new TypeError("object id is required");
|
|
1264
|
+
return normalized;
|
|
1265
|
+
}
|
|
1266
|
+
function requiredKey(key) {
|
|
1267
|
+
const normalized = key.trim();
|
|
1268
|
+
if (!normalized)
|
|
1269
|
+
throw new TypeError("meta key is required");
|
|
1270
|
+
return normalized;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// ../morula-api-client/dist/repo.js
|
|
1274
|
+
var RepoApi = class {
|
|
1275
|
+
client;
|
|
1276
|
+
constructor(client) {
|
|
1277
|
+
this.client = client;
|
|
1278
|
+
}
|
|
1279
|
+
/** 目录清单(path 缺省 = 仓库根) */
|
|
1280
|
+
files(spaceId, query = {}) {
|
|
1281
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/files`, query);
|
|
1282
|
+
}
|
|
1283
|
+
/** 单文件内容(path 必填) */
|
|
1284
|
+
read(spaceId, query) {
|
|
1285
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId7(spaceId))}/repo/file`, query);
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
function requiredId7(id) {
|
|
1289
|
+
const normalized = id.trim();
|
|
1290
|
+
if (!normalized)
|
|
1291
|
+
throw new TypeError("space id is required");
|
|
1292
|
+
return normalized;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// ../morula-api-client/dist/report.js
|
|
1296
|
+
var ReportApi = class {
|
|
1297
|
+
client;
|
|
1298
|
+
constructor(client) {
|
|
1299
|
+
this.client = client;
|
|
1300
|
+
}
|
|
1301
|
+
create(input) {
|
|
1302
|
+
return this.client.post("/agent-reports", input);
|
|
1303
|
+
}
|
|
1304
|
+
get(id) {
|
|
1305
|
+
return this.client.get(`/agent-reports/${encodeURIComponent(requiredId8(id))}`);
|
|
1306
|
+
}
|
|
1307
|
+
listByAutopilot(autopilotId, query = {}) {
|
|
1308
|
+
return this.client.get(`/agent-autopilots/${encodeURIComponent(requiredId8(autopilotId, "autopilot"))}/reports`, { ...query });
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
function requiredId8(id, label = "report") {
|
|
1312
|
+
const normalized = id.trim();
|
|
1313
|
+
if (!normalized)
|
|
1314
|
+
throw new TypeError(`${label} id is required`);
|
|
1315
|
+
return normalized;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// ../morula-api-client/dist/space-member.js
|
|
1319
|
+
var SpaceMemberApi = class {
|
|
1320
|
+
client;
|
|
1321
|
+
constructor(client) {
|
|
1322
|
+
this.client = client;
|
|
1323
|
+
}
|
|
1324
|
+
list(spaceId, query = {}) {
|
|
1325
|
+
return this.client.get("/space-members", { spaceId, ...query });
|
|
1326
|
+
}
|
|
1327
|
+
get(id) {
|
|
1328
|
+
return this.client.get(`/space-members/${encodeURIComponent(requiredId9(id))}`);
|
|
1329
|
+
}
|
|
1330
|
+
create(input) {
|
|
1331
|
+
return this.client.post("/space-members", input);
|
|
1332
|
+
}
|
|
1333
|
+
update(id, input) {
|
|
1334
|
+
return this.client.patch(`/space-members/${encodeURIComponent(requiredId9(id))}`, input);
|
|
1335
|
+
}
|
|
1336
|
+
delete(id) {
|
|
1337
|
+
return this.client.delete(`/space-members/${encodeURIComponent(requiredId9(id))}`);
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
function requiredId9(id) {
|
|
1341
|
+
const normalized = id.trim();
|
|
1342
|
+
if (!normalized)
|
|
1343
|
+
throw new TypeError("space member id is required");
|
|
1344
|
+
return normalized;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// ../morula-api-client/dist/space.js
|
|
1348
|
+
var SpaceApi = class {
|
|
1349
|
+
client;
|
|
1350
|
+
constructor(client) {
|
|
1351
|
+
this.client = client;
|
|
1352
|
+
}
|
|
1353
|
+
list(query = {}) {
|
|
1354
|
+
return this.client.get("/spaces", query);
|
|
1355
|
+
}
|
|
1356
|
+
get(id) {
|
|
1357
|
+
return this.client.get(`/spaces/${encodeURIComponent(requiredId10(id))}`);
|
|
1358
|
+
}
|
|
1359
|
+
create(input) {
|
|
1360
|
+
return this.client.post("/spaces", input);
|
|
1361
|
+
}
|
|
1362
|
+
update(id, input) {
|
|
1363
|
+
return this.client.patch(`/spaces/${encodeURIComponent(requiredId10(id))}`, input);
|
|
1364
|
+
}
|
|
1365
|
+
delete(id) {
|
|
1366
|
+
return this.client.delete(`/spaces/${encodeURIComponent(requiredId10(id))}`);
|
|
1367
|
+
}
|
|
1368
|
+
};
|
|
1369
|
+
function requiredId10(id) {
|
|
1370
|
+
const normalized = id.trim();
|
|
1371
|
+
if (!normalized)
|
|
1372
|
+
throw new TypeError("space id is required");
|
|
1373
|
+
return normalized;
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// ../morula-api-client/dist/steward.js
|
|
1377
|
+
var StewardApi = class {
|
|
1378
|
+
client;
|
|
1379
|
+
constructor(client) {
|
|
1380
|
+
this.client = client;
|
|
1381
|
+
}
|
|
1382
|
+
/** 管家信息(未接管的空间返回 null) */
|
|
1383
|
+
get(projectId) {
|
|
1384
|
+
return this.client.get(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}`);
|
|
1385
|
+
}
|
|
1386
|
+
/** 派单:建任务并派发(目标状态由 state 决定,backlog 不派发执行) */
|
|
1387
|
+
dispatch(projectId, input) {
|
|
1388
|
+
return this.client.post(`/stewards/projects/${encodeURIComponent(requiredId11(projectId))}/dispatch`, input);
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
function requiredId11(id) {
|
|
1392
|
+
const normalized = id.trim();
|
|
1393
|
+
if (!normalized)
|
|
1394
|
+
throw new TypeError("project id is required");
|
|
1395
|
+
return normalized;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// ../morula-api-client/dist/task.js
|
|
1399
|
+
var TaskApi = class {
|
|
1400
|
+
client;
|
|
1401
|
+
constructor(client) {
|
|
1402
|
+
this.client = client;
|
|
1403
|
+
}
|
|
1404
|
+
list(query = {}) {
|
|
1405
|
+
return this.client.get("/objects", query);
|
|
1406
|
+
}
|
|
1407
|
+
get(id) {
|
|
1408
|
+
return this.client.get(`/objects/${encodeURIComponent(requiredId12(id))}`);
|
|
1409
|
+
}
|
|
1410
|
+
create(input) {
|
|
1411
|
+
return this.client.post("/objects", input);
|
|
1412
|
+
}
|
|
1413
|
+
update(id, input) {
|
|
1414
|
+
return this.client.patch(`/objects/${encodeURIComponent(requiredId12(id))}`, input);
|
|
1415
|
+
}
|
|
1416
|
+
delete(id) {
|
|
1417
|
+
return this.client.delete(`/objects/${encodeURIComponent(requiredId12(id))}`);
|
|
1418
|
+
}
|
|
1419
|
+
/** 查询空间字段模板(字段 key → id 映射、options) */
|
|
1420
|
+
getSchemaFields(spaceId) {
|
|
1421
|
+
return this.client.get("/schemas", { spaceId }).then((result) => {
|
|
1422
|
+
const rows = Array.isArray(result) ? result : result.data;
|
|
1423
|
+
const fields = [];
|
|
1424
|
+
for (const row of rows ?? []) {
|
|
1425
|
+
for (const f of row.fields ?? []) {
|
|
1426
|
+
if (f.status !== "archived")
|
|
1427
|
+
fields.push(f);
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return fields;
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
/** 获取对象自定义字段值 */
|
|
1434
|
+
getFieldValues(objectId) {
|
|
1435
|
+
return this.client.get(`/object-field-values?objectId=${encodeURIComponent(objectId)}`);
|
|
1436
|
+
}
|
|
1437
|
+
/** 创建或更新对象自定义字段值(upsert) */
|
|
1438
|
+
upsertFieldValue(input) {
|
|
1439
|
+
return this.client.post("/object-field-values", input);
|
|
1440
|
+
}
|
|
1441
|
+
/** 删除对象自定义字段值(清空语义) */
|
|
1442
|
+
deleteFieldValue(objectId, schemaFieldId) {
|
|
1443
|
+
return this.client.delete(`/object-field-values`, { objectId, schemaFieldId });
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
function requiredId12(id) {
|
|
1447
|
+
const normalized = id.trim();
|
|
1448
|
+
if (!normalized)
|
|
1449
|
+
throw new TypeError("task id is required");
|
|
1450
|
+
return normalized;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// ../morula-api-client/dist/webhook.js
|
|
1454
|
+
var WebhookApi = class {
|
|
1455
|
+
client;
|
|
1456
|
+
constructor(client) {
|
|
1457
|
+
this.client = client;
|
|
1458
|
+
}
|
|
1459
|
+
list(query = {}) {
|
|
1460
|
+
return this.client.get("/webhooks", query);
|
|
1461
|
+
}
|
|
1462
|
+
create(input) {
|
|
1463
|
+
return this.client.post("/webhooks", input);
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
|
|
1467
|
+
// ../morula-api-client/dist/index.js
|
|
1468
|
+
var MorulaClient = class {
|
|
1469
|
+
http;
|
|
1470
|
+
agentTasks;
|
|
1471
|
+
attachments;
|
|
1472
|
+
auth;
|
|
1473
|
+
autopilots;
|
|
1474
|
+
ci;
|
|
1475
|
+
comments;
|
|
1476
|
+
dashboard;
|
|
1477
|
+
documents;
|
|
1478
|
+
objectMeta;
|
|
1479
|
+
reports;
|
|
1480
|
+
repos;
|
|
1481
|
+
tasks;
|
|
1482
|
+
spaces;
|
|
1483
|
+
spaceMembers;
|
|
1484
|
+
stewards;
|
|
1485
|
+
webhooks;
|
|
1486
|
+
constructor(options) {
|
|
1487
|
+
this.http = new MorulaApiClient(options);
|
|
1488
|
+
this.agentTasks = new AgentTaskApi(this.http);
|
|
1489
|
+
this.attachments = new AttachmentApi(this.http);
|
|
1490
|
+
this.auth = new AuthApi(this.http);
|
|
1491
|
+
this.autopilots = new AutopilotApi(this.http);
|
|
1492
|
+
this.ci = new CiApi(this.http);
|
|
1493
|
+
this.comments = new CommentApi(this.http);
|
|
1494
|
+
this.dashboard = new DashboardApi(this.http);
|
|
1495
|
+
this.documents = new DocumentApi(this.http);
|
|
1496
|
+
this.objectMeta = new ObjectMetaApi(this.http);
|
|
1497
|
+
this.reports = new ReportApi(this.http);
|
|
1498
|
+
this.repos = new RepoApi(this.http);
|
|
1499
|
+
this.tasks = new TaskApi(this.http);
|
|
1500
|
+
this.spaces = new SpaceApi(this.http);
|
|
1501
|
+
this.spaceMembers = new SpaceMemberApi(this.http);
|
|
1502
|
+
this.stewards = new StewardApi(this.http);
|
|
1503
|
+
this.webhooks = new WebhookApi(this.http);
|
|
1504
|
+
}
|
|
1505
|
+
};
|
|
1506
|
+
function createMorulaClient(options) {
|
|
1507
|
+
return new MorulaClient(options);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// ../morula-cli-core/dist/help.js
|
|
1511
|
+
var HELP_SCHEMA_VERSION = 1;
|
|
1512
|
+
function actionUsage(action) {
|
|
1513
|
+
const args = (action.args ?? []).map((a) => a.required ? `<${a.name}>` : `[<${a.name}>]`);
|
|
1514
|
+
const flags = (action.flags ?? []).map((f) => {
|
|
1515
|
+
if (f.type === "boolean")
|
|
1516
|
+
return `[--${f.name}]`;
|
|
1517
|
+
return `[--${f.name} ${f.valueHint ?? "<value>"}]`;
|
|
1518
|
+
});
|
|
1519
|
+
return [action.action, ...args, ...flags].join(" ");
|
|
1520
|
+
}
|
|
1521
|
+
function renderResourceHelp(base, resource) {
|
|
1522
|
+
return {
|
|
1523
|
+
// 兼容字段:commands 只含该 resource(键格式与顶层一致),消费方按 resource 取用不受影响
|
|
1524
|
+
commands: { [resource.resource]: renderCommandEntries(resource) },
|
|
1525
|
+
schemaVersion: HELP_SCHEMA_VERSION,
|
|
1526
|
+
resource: resource.resource,
|
|
1527
|
+
summary: resource.summary,
|
|
1528
|
+
actions: resource.actions.map((a) => ({
|
|
1529
|
+
action: a.action,
|
|
1530
|
+
usage: actionUsage(a),
|
|
1531
|
+
summary: a.summary,
|
|
1532
|
+
writes: a.writes ?? "none",
|
|
1533
|
+
interactive: a.interactive ?? false
|
|
1534
|
+
})),
|
|
1535
|
+
...resource.notes?.length ? { notes: resource.notes } : {},
|
|
1536
|
+
globalFlags: base.globalFlags,
|
|
1537
|
+
exitCodes: base.exitCodes
|
|
1538
|
+
};
|
|
1539
|
+
}
|
|
1540
|
+
function renderActionHelp(base, resource, action) {
|
|
1541
|
+
return {
|
|
1542
|
+
schemaVersion: HELP_SCHEMA_VERSION,
|
|
1543
|
+
resource: resource.resource,
|
|
1544
|
+
action: action.action,
|
|
1545
|
+
summary: action.summary,
|
|
1546
|
+
args: (action.args ?? []).map((a) => ({
|
|
1547
|
+
name: a.name,
|
|
1548
|
+
required: a.required ?? false,
|
|
1549
|
+
description: a.description
|
|
1550
|
+
})),
|
|
1551
|
+
flags: (action.flags ?? []).map((f) => ({
|
|
1552
|
+
name: f.name,
|
|
1553
|
+
type: f.type,
|
|
1554
|
+
description: f.description,
|
|
1555
|
+
...f.valueHint ? { valueHint: f.valueHint } : {},
|
|
1556
|
+
required: f.required ?? false,
|
|
1557
|
+
...f.enum ? { enum: f.enum } : {},
|
|
1558
|
+
...f.default !== void 0 ? { default: f.default } : {},
|
|
1559
|
+
...f.repeatable ? { repeatable: true } : {}
|
|
1560
|
+
})),
|
|
1561
|
+
examples: action.examples ?? [],
|
|
1562
|
+
writes: action.writes ?? "none",
|
|
1563
|
+
interactive: action.interactive ?? false,
|
|
1564
|
+
idempotent: action.idempotent ?? false,
|
|
1565
|
+
emits: action.emits ?? [],
|
|
1566
|
+
globalFlags: base.globalFlags,
|
|
1567
|
+
exitCodes: base.exitCodes
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
function renderCommandEntries(resource) {
|
|
1571
|
+
const entries = {};
|
|
1572
|
+
for (const action of resource.actions) {
|
|
1573
|
+
const notes = [action.summary];
|
|
1574
|
+
if (action.writes === "local")
|
|
1575
|
+
notes.push("writes local state");
|
|
1576
|
+
else if (action.writes === "remote")
|
|
1577
|
+
notes.push("writes remote state");
|
|
1578
|
+
if (action.interactive)
|
|
1579
|
+
notes.push("interactive (blocks until you finish in the browser)");
|
|
1580
|
+
entries[actionUsage(action)] = notes.join("; ");
|
|
1581
|
+
}
|
|
1582
|
+
return entries;
|
|
1583
|
+
}
|
|
1584
|
+
function renderTopLevelCommands(spec) {
|
|
1585
|
+
const commands = {};
|
|
1586
|
+
for (const resource of spec.resources)
|
|
1587
|
+
commands[resource.resource] = renderCommandEntries(resource);
|
|
1588
|
+
return commands;
|
|
1589
|
+
}
|
|
1590
|
+
function renderResourceIndex(spec) {
|
|
1591
|
+
return spec.resources.map((r) => ({
|
|
1592
|
+
resource: r.resource,
|
|
1593
|
+
summary: r.summary,
|
|
1594
|
+
actions: r.actions.map((a) => ({
|
|
1595
|
+
action: a.action,
|
|
1596
|
+
usage: actionUsage(a),
|
|
1597
|
+
summary: a.summary,
|
|
1598
|
+
writes: a.writes ?? "none",
|
|
1599
|
+
interactive: a.interactive ?? false
|
|
1600
|
+
}))
|
|
1601
|
+
}));
|
|
1602
|
+
}
|
|
1603
|
+
function renderLegacyResourceHelp(base, resource) {
|
|
1604
|
+
const entries = base.commands[resource];
|
|
1605
|
+
if (!entries)
|
|
1606
|
+
return {};
|
|
1607
|
+
return {
|
|
1608
|
+
commands: { [resource]: entries },
|
|
1609
|
+
schemaVersion: HELP_SCHEMA_VERSION,
|
|
1610
|
+
resource,
|
|
1611
|
+
actions: Object.entries(entries).map(([usage2, summary]) => ({
|
|
1612
|
+
action: usage2.split(" ")[0],
|
|
1613
|
+
usage: usage2,
|
|
1614
|
+
summary,
|
|
1615
|
+
// legacy 命令面没有契约声明:如实标 unknown,而不是假装 none(避免智能体误判只读)
|
|
1616
|
+
writes: "unknown",
|
|
1617
|
+
interactive: false
|
|
1618
|
+
})),
|
|
1619
|
+
globalFlags: base.globalFlags,
|
|
1620
|
+
exitCodes: base.exitCodes
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
// ../morula-cli-core/dist/spec.js
|
|
1625
|
+
var GLOBAL_FLAG_NAMES2 = GLOBAL_FLAG_NAMES;
|
|
1626
|
+
var GLOBAL_FLAGS = [
|
|
1627
|
+
{ name: "env", type: "string", valueHint: "<context>", description: "select test or a saved context" },
|
|
1628
|
+
{ name: "url", type: "string", valueHint: "<baseUrl>", description: "override API base URL" },
|
|
1629
|
+
{ name: "token", type: "string", valueHint: "<token>", description: "override token for this invocation (not persisted)" },
|
|
1630
|
+
{ name: "help", type: "boolean", description: "print this machine-readable command surface" },
|
|
1631
|
+
{ name: "version", type: "boolean", description: "print version JSON" },
|
|
1632
|
+
// 习惯吸收:输出本来就只有 JSON,`--json` 是 no-op 别名(智能体/脚本的第一直觉动作,此前报 unknown flag)
|
|
1633
|
+
{ name: "json", type: "boolean", description: "no-op: output is always JSON (accepted for familiarity)" }
|
|
1634
|
+
];
|
|
1635
|
+
function usageLine(resource, action) {
|
|
1636
|
+
const parts = action.args?.length ? action.args.map((a) => a.required ? `<${a.name}>` : `[<${a.name}>]`).join(" ") : "";
|
|
1637
|
+
const flagParts = (action.flags ?? []).map((f) => {
|
|
1638
|
+
if (f.type === "boolean")
|
|
1639
|
+
return `[--${f.name}]`;
|
|
1640
|
+
return `[--${f.name} ${f.valueHint ?? "<value>"}]`;
|
|
1641
|
+
});
|
|
1642
|
+
return [action.action, parts, ...flagParts].filter(Boolean).join(" ");
|
|
1643
|
+
}
|
|
1644
|
+
function suggest(input, candidates) {
|
|
1645
|
+
let best;
|
|
1646
|
+
let bestScore = Number.POSITIVE_INFINITY;
|
|
1647
|
+
for (const candidate of candidates) {
|
|
1648
|
+
const distance = editDistance(input.toLowerCase(), candidate.toLowerCase());
|
|
1649
|
+
const threshold = Math.max(1, Math.floor(candidate.length / 3));
|
|
1650
|
+
if (distance <= threshold && distance < bestScore) {
|
|
1651
|
+
bestScore = distance;
|
|
1652
|
+
best = candidate;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
return best;
|
|
1656
|
+
}
|
|
1657
|
+
function editDistance(a, b) {
|
|
1658
|
+
if (a === b)
|
|
1659
|
+
return 0;
|
|
1660
|
+
const rows = a.length + 1;
|
|
1661
|
+
const cols = b.length + 1;
|
|
1662
|
+
const d = Array.from({ length: rows }, () => Array.from({ length: cols }).fill(0));
|
|
1663
|
+
for (let i = 0; i < rows; i += 1)
|
|
1664
|
+
d[i][0] = i;
|
|
1665
|
+
for (let j = 0; j < cols; j += 1)
|
|
1666
|
+
d[0][j] = j;
|
|
1667
|
+
for (let i = 1; i < rows; i += 1) {
|
|
1668
|
+
for (let j = 1; j < cols; j += 1) {
|
|
1669
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1670
|
+
d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
|
|
1671
|
+
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1])
|
|
1672
|
+
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return d[a.length][b.length];
|
|
1676
|
+
}
|
|
1677
|
+
function validateArgs(resource, action, parsed, options = {}) {
|
|
1678
|
+
const declaredFlags = action.flags ?? [];
|
|
1679
|
+
const declaredNames = new Set(declaredFlags.map((f) => f.name));
|
|
1680
|
+
const knownNames = [...declaredNames, ...GLOBAL_FLAG_NAMES2];
|
|
1681
|
+
const usageHint = [options.cliName, resource.resource, usageLine(resource, action)].filter(Boolean).join(" ");
|
|
1682
|
+
for (const flag of Object.keys(parsed.flags)) {
|
|
1683
|
+
if (knownNames.includes(flag))
|
|
1684
|
+
continue;
|
|
1685
|
+
const near = suggest(flag, knownNames);
|
|
1686
|
+
throw usage(`unknown flag --${flag}`, {
|
|
1687
|
+
hint: usageHint,
|
|
1688
|
+
...near ? { didYouMean: `--${near}` } : {}
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
for (const flag of parsed.bareFlags) {
|
|
1692
|
+
const spec = declaredFlags.find((f) => f.name === flag);
|
|
1693
|
+
if (!spec || spec.type === "boolean")
|
|
1694
|
+
continue;
|
|
1695
|
+
const valuePart = spec.valueHint ?? "<value>";
|
|
1696
|
+
throw usage(`missing value for --${flag}`, {
|
|
1697
|
+
hint: `${usageHint} (e.g. --${flag} ${valuePart})`
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
for (const [name, values] of Object.entries(parsed.repeatedFlags)) {
|
|
1701
|
+
if (values.length < 2)
|
|
1702
|
+
continue;
|
|
1703
|
+
const spec = declaredFlags.find((f) => f.name === name);
|
|
1704
|
+
if (spec?.repeatable)
|
|
1705
|
+
continue;
|
|
1706
|
+
throw usage(`duplicate flag --${name} (given ${values.length} times; only the last value would be used)`, {
|
|
1707
|
+
hint: usageHint
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
for (const spec of declaredFlags) {
|
|
1711
|
+
if (!spec.required)
|
|
1712
|
+
continue;
|
|
1713
|
+
if (flagString(parsed.flags, spec.name) === void 0)
|
|
1714
|
+
throw usage(`missing required flag --${spec.name}`, { hint: usageHint });
|
|
1715
|
+
}
|
|
1716
|
+
for (const spec of declaredFlags) {
|
|
1717
|
+
const raw = flagString(parsed.flags, spec.name);
|
|
1718
|
+
if (raw === void 0)
|
|
1719
|
+
continue;
|
|
1720
|
+
if (spec.type === "number" && !Number.isFinite(Number(raw)))
|
|
1721
|
+
throw usage(`--${spec.name} must be a number (got "${raw}")`, { hint: usageHint });
|
|
1722
|
+
if (spec.enum && !spec.enum.includes(raw)) {
|
|
1723
|
+
throw usage(`--${spec.name} must be one of: ${spec.enum.join(" | ")} (got "${raw}")`, {
|
|
1724
|
+
hint: usageHint
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (action.allowExtraPositionals)
|
|
1729
|
+
return;
|
|
1730
|
+
const declaredArgs = action.args ?? [];
|
|
1731
|
+
const required = declaredArgs.filter((a) => a.required).length;
|
|
1732
|
+
const total = declaredArgs.length;
|
|
1733
|
+
const given = parsed.positionals.length;
|
|
1734
|
+
if (given < required) {
|
|
1735
|
+
const missing = declaredArgs.slice(given).filter((a) => a.required).map((a) => `<${a.name}>`).join(" ");
|
|
1736
|
+
throw usage(`missing required argument ${missing}`, { hint: usageHint });
|
|
1737
|
+
}
|
|
1738
|
+
if (given > total) {
|
|
1739
|
+
const extra = parsed.positionals.slice(total).join(" ");
|
|
1740
|
+
throw usage(`unexpected extra argument "${extra}"`, { hint: usageHint });
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
function findResource(spec, resource) {
|
|
1744
|
+
return spec.resources.find((r) => r.resource === resource);
|
|
1745
|
+
}
|
|
1746
|
+
function findAction(resource, action) {
|
|
1747
|
+
return resource.actions.find((a) => a.action === action);
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
// ../morula-cli-core/dist/utils/config.js
|
|
1751
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
1752
|
+
import { homedir } from "node:os";
|
|
1753
|
+
import { dirname, join } from "node:path";
|
|
1754
|
+
import { dump, load } from "js-yaml";
|
|
1755
|
+
var FileConfigStore = class {
|
|
1756
|
+
path;
|
|
1757
|
+
constructor(path, env = process.env) {
|
|
1758
|
+
this.path = path ?? env.MORULA_CONFIG ?? join(env.MORULA_CONFIG_DIR ?? join(homedir(), ".morula-cli"), "config.yaml");
|
|
1759
|
+
}
|
|
1760
|
+
load() {
|
|
1761
|
+
if (!existsSync(this.path))
|
|
1762
|
+
return {};
|
|
1763
|
+
try {
|
|
1764
|
+
const value = load(readFileSync(this.path, "utf8"));
|
|
1765
|
+
if (!isConfig(value))
|
|
1766
|
+
throw new Error("expected a context configuration object");
|
|
1767
|
+
return value;
|
|
1768
|
+
} catch (error) {
|
|
1769
|
+
throw new CliError("INVALID_CONFIG", EXIT.OTHER, `failed to read config ${this.path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
save(config) {
|
|
1773
|
+
const dir = dirname(this.path);
|
|
1774
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1775
|
+
chmodSync(dir, 448);
|
|
1776
|
+
writeFileSync(this.path, dump(config, { noRefs: true, lineWidth: -1 }), { mode: 384 });
|
|
1777
|
+
chmodSync(this.path, 384);
|
|
1778
|
+
}
|
|
1779
|
+
clear() {
|
|
1780
|
+
rmSync(this.path, { force: true });
|
|
1781
|
+
}
|
|
1782
|
+
};
|
|
1783
|
+
function isConfig(value) {
|
|
1784
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1785
|
+
return false;
|
|
1786
|
+
const config = value;
|
|
1787
|
+
if (config.currentContext !== void 0 && typeof config.currentContext !== "string")
|
|
1788
|
+
return false;
|
|
1789
|
+
if (config.contexts === void 0)
|
|
1790
|
+
return true;
|
|
1791
|
+
if (!config.contexts || typeof config.contexts !== "object" || Array.isArray(config.contexts))
|
|
1792
|
+
return false;
|
|
1793
|
+
return Object.values(config.contexts).every((context) => {
|
|
1794
|
+
return !!context && typeof context === "object" && typeof context.url === "string" && (context.token === void 0 || typeof context.token === "string");
|
|
1795
|
+
});
|
|
1796
|
+
}
|
|
1797
|
+
function resolveRuntime(flags, env, store, options = {}) {
|
|
1798
|
+
const config = store.load();
|
|
1799
|
+
const explicitUrl = flagString(flags, "url") ?? env.MORULA_API_URL;
|
|
1800
|
+
let context;
|
|
1801
|
+
if (flagString(flags, "env")) {
|
|
1802
|
+
context = flagString(flags, "env");
|
|
1803
|
+
} else if (env.MORULA_ENV) {
|
|
1804
|
+
context = env.MORULA_ENV;
|
|
1805
|
+
} else if (explicitUrl) {
|
|
1806
|
+
context = contextFromUrl(explicitUrl);
|
|
1807
|
+
} else {
|
|
1808
|
+
context = config.currentContext ?? BUILD_DEFAULT_CONTEXT;
|
|
1809
|
+
}
|
|
1810
|
+
const saved = config.contexts?.[context];
|
|
1811
|
+
const baseUrl = normalizeBaseUrl(explicitUrl ?? saved?.url ?? BUILD_CONTEXTS[context]);
|
|
1812
|
+
if (!baseUrl) {
|
|
1813
|
+
throw usage(`unknown environment "${context}"; pass --url or use one of: ${Object.keys(BUILD_CONTEXTS).join(", ")}`);
|
|
1814
|
+
}
|
|
1815
|
+
const { token, tokenSource } = resolveToken(flags, env, context, baseUrl, saved, options);
|
|
1816
|
+
return {
|
|
1817
|
+
context,
|
|
1818
|
+
baseUrl,
|
|
1819
|
+
token,
|
|
1820
|
+
tokenSource
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function resolveToken(flags, env, context, baseUrl, saved, options) {
|
|
1824
|
+
const explicitToken = flagString(flags, "token");
|
|
1825
|
+
if (explicitToken)
|
|
1826
|
+
return { token: explicitToken, tokenSource: "flag" };
|
|
1827
|
+
if (!options.excludeTaskToken && env.MORULA_TASK_TOKEN)
|
|
1828
|
+
return { token: env.MORULA_TASK_TOKEN, tokenSource: "env:MORULA_TASK_TOKEN" };
|
|
1829
|
+
if (!options.excludeTaskToken && env.MORULA_SESSION_TOKEN)
|
|
1830
|
+
return { token: env.MORULA_SESSION_TOKEN, tokenSource: "env:MORULA_SESSION_TOKEN" };
|
|
1831
|
+
if (env.MORULA_TOKEN)
|
|
1832
|
+
return { token: env.MORULA_TOKEN, tokenSource: "env:MORULA_TOKEN" };
|
|
1833
|
+
const savedToken = saved?.url && normalizeBaseUrl(saved.url) === baseUrl ? saved.token : void 0;
|
|
1834
|
+
if (savedToken)
|
|
1835
|
+
return { token: savedToken, tokenSource: `config:${context}` };
|
|
1836
|
+
return { token: void 0, tokenSource: null };
|
|
1837
|
+
}
|
|
1838
|
+
function saveContext(store, name, patch) {
|
|
1839
|
+
const current = store.load();
|
|
1840
|
+
const contexts = { ...current.contexts, [name]: { ...current.contexts?.[name], ...patch } };
|
|
1841
|
+
store.save({ ...current, currentContext: name, contexts });
|
|
1842
|
+
}
|
|
1843
|
+
function clearContextToken(store, name) {
|
|
1844
|
+
const current = store.load();
|
|
1845
|
+
const existing = current.contexts?.[name];
|
|
1846
|
+
if (!existing)
|
|
1847
|
+
return;
|
|
1848
|
+
store.save({
|
|
1849
|
+
...current,
|
|
1850
|
+
contexts: { ...current.contexts, [name]: { url: existing.url } }
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
function normalizeBaseUrl(value) {
|
|
1854
|
+
if (!value)
|
|
1855
|
+
return void 0;
|
|
1856
|
+
let url;
|
|
1857
|
+
try {
|
|
1858
|
+
url = new URL(value);
|
|
1859
|
+
} catch {
|
|
1860
|
+
throw usage("server URL must be an absolute http(s) URL");
|
|
1861
|
+
}
|
|
1862
|
+
if (!["http:", "https:"].includes(url.protocol))
|
|
1863
|
+
throw usage("server URL must use http or https");
|
|
1864
|
+
url.hash = "";
|
|
1865
|
+
url.search = "";
|
|
1866
|
+
return url.toString().replace(/\/+$/, "");
|
|
1867
|
+
}
|
|
1868
|
+
function contextFromUrl(raw) {
|
|
1869
|
+
try {
|
|
1870
|
+
const hostname = new URL(raw).hostname;
|
|
1871
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]")
|
|
1872
|
+
return "local";
|
|
1873
|
+
return hostname;
|
|
1874
|
+
} catch {
|
|
1875
|
+
return BUILD_DEFAULT_CONTEXT;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// ../morula-cli-core/dist/cli.js
|
|
1880
|
+
var CLI_CAPABILITIES = [
|
|
1881
|
+
"server-mr",
|
|
1882
|
+
"target-project",
|
|
1883
|
+
"analysis-verdict"
|
|
1884
|
+
];
|
|
1885
|
+
async function run(argv, deps = {}) {
|
|
1886
|
+
const out = deps.out ?? ((line) => console.log(line));
|
|
1887
|
+
const err = deps.err ?? ((line) => console.error(line));
|
|
1888
|
+
const help = deps.help;
|
|
1889
|
+
const commands = deps.commands ?? {};
|
|
1890
|
+
const spec = deps.spec;
|
|
1891
|
+
let requestedExit;
|
|
1892
|
+
const setExitCode = (code) => {
|
|
1893
|
+
requestedExit = code;
|
|
1894
|
+
};
|
|
1895
|
+
let parsed;
|
|
1896
|
+
try {
|
|
1897
|
+
parsed = parseArgs(argv);
|
|
1898
|
+
if (flagBoolean(parsed.flags, "version")) {
|
|
1899
|
+
out(JSON.stringify(help?.build ? { version: help.version, build: help.build } : { version: help?.version }));
|
|
1900
|
+
return EXIT.OK;
|
|
1901
|
+
}
|
|
1902
|
+
if (!parsed.command || parsed.command === "help" || flagBoolean(parsed.flags, "help")) {
|
|
1903
|
+
out(JSON.stringify(renderHelp(deps, parsed.command, parsed.action), null, 2));
|
|
1904
|
+
return EXIT.OK;
|
|
1905
|
+
}
|
|
1906
|
+
const handler = commands[parsed.command];
|
|
1907
|
+
if (!handler) {
|
|
1908
|
+
const near = suggestCommand(parsed.command, Object.keys(commands));
|
|
1909
|
+
throw new CliError("USAGE", EXIT.USAGE, `unknown command: ${parsed.command}`, false, {
|
|
1910
|
+
hint: `${help?.name ?? "cli"} --help`,
|
|
1911
|
+
...near ? { didYouMean: near } : {}
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
if (spec) {
|
|
1915
|
+
const resource = findResource(spec, parsed.command);
|
|
1916
|
+
if (resource && parsed.action) {
|
|
1917
|
+
const action = findAction(resource, parsed.action);
|
|
1918
|
+
if (action)
|
|
1919
|
+
validateArgs(resource, action, parsed, { cliName: spec.name });
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
const env = deps.env ?? process.env;
|
|
1923
|
+
const store = deps.store ?? new FileConfigStore(void 0, env);
|
|
1924
|
+
const runtime = resolveRuntime(parsed.flags, env, store);
|
|
1925
|
+
const client = createMorulaClient({
|
|
1926
|
+
baseUrl: runtime.baseUrl,
|
|
1927
|
+
token: runtime.token,
|
|
1928
|
+
fetch: deps.fetch,
|
|
1929
|
+
// CLI 身份上报(P0-③):构建标识 + 能力清单随每个请求到达服务端,
|
|
1930
|
+
// 服务端把「执行该任务的 CLI」记到任务上(陈旧构建由此可查、可告警)。
|
|
1931
|
+
clientBuild: BUILD_ID,
|
|
1932
|
+
clientCaps: CLI_CAPABILITIES.join(",")
|
|
1933
|
+
});
|
|
1934
|
+
const result = await handler({ parsed, client, store, runtime, env, err, setExitCode });
|
|
1935
|
+
out(JSON.stringify(result, null, 2));
|
|
1936
|
+
return requestedExit ?? EXIT.OK;
|
|
1937
|
+
} catch (error) {
|
|
1938
|
+
if (error instanceof CliError || error instanceof MorulaApiError) {
|
|
1939
|
+
err(JSON.stringify({
|
|
1940
|
+
code: error.code,
|
|
1941
|
+
message: error.message,
|
|
1942
|
+
...error.retryable ? { retryable: true } : {},
|
|
1943
|
+
// 服务端业务错误码(如 SESSION_SCOPE_DENIED):`code` 是按 HTTP 状态派生的粗档位,
|
|
1944
|
+
// 行为契约要判断的是业务码——不打印出来,契约就永远不可执行
|
|
1945
|
+
// (issues/api-client-drops-business-error-code.md)。退出码仍由 `error.exitCode` 决定。
|
|
1946
|
+
...error instanceof MorulaApiError && error.errorCode ? { errorCode: error.errorCode } : {},
|
|
1947
|
+
// 新增字段(可选):可执行的下一步 + typo 建议
|
|
1948
|
+
...error instanceof CliError && error.hint ? { hint: error.hint } : {},
|
|
1949
|
+
...error instanceof CliError && error.didYouMean ? { didYouMean: error.didYouMean } : {}
|
|
1950
|
+
}));
|
|
1951
|
+
return error.exitCode;
|
|
1952
|
+
}
|
|
1953
|
+
err(JSON.stringify({ code: "ERROR", message: error instanceof Error ? error.message : String(error) }));
|
|
1954
|
+
return EXIT.OTHER;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function renderHelp(deps, resource, action) {
|
|
1958
|
+
const help = deps.help;
|
|
1959
|
+
const spec = deps.spec;
|
|
1960
|
+
const scope = {};
|
|
1961
|
+
if (resource && resource !== "help")
|
|
1962
|
+
scope.resource = resource;
|
|
1963
|
+
if (action)
|
|
1964
|
+
scope.action = action;
|
|
1965
|
+
if (spec) {
|
|
1966
|
+
if (scope.resource) {
|
|
1967
|
+
const found = findResource(spec, scope.resource);
|
|
1968
|
+
if (found) {
|
|
1969
|
+
if (scope.action) {
|
|
1970
|
+
const act = findAction(found, scope.action);
|
|
1971
|
+
if (act && help)
|
|
1972
|
+
return renderActionHelp(help, found, act);
|
|
1973
|
+
}
|
|
1974
|
+
if (help)
|
|
1975
|
+
return renderResourceHelp(help, found);
|
|
1976
|
+
}
|
|
1977
|
+
const near = suggestCommand(scope.resource, spec.resources.map((r) => r.resource));
|
|
1978
|
+
return {
|
|
1979
|
+
...renderTopLevel(spec, help),
|
|
1980
|
+
error: `unknown resource: ${scope.resource}`,
|
|
1981
|
+
...near ? { didYouMean: near } : { hint: "resources \u5B57\u6BB5\u5217\u51FA\u5168\u90E8\u53EF\u7528 resource" }
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
return renderTopLevel(spec, help);
|
|
1985
|
+
}
|
|
1986
|
+
if (help && scope.resource) {
|
|
1987
|
+
const sub = renderLegacyResourceHelp(help, scope.resource);
|
|
1988
|
+
if (Object.keys(sub).length > 0)
|
|
1989
|
+
return sub;
|
|
1990
|
+
}
|
|
1991
|
+
return help;
|
|
1992
|
+
}
|
|
1993
|
+
function renderTopLevel(spec, help) {
|
|
1994
|
+
return {
|
|
1995
|
+
...help ?? {},
|
|
1996
|
+
// spec 路径下 commands 由契约渲染(键格式与 legacy 完全一致),其余字段沿用入口注入的 HELP
|
|
1997
|
+
commands: renderTopLevelCommands(spec),
|
|
1998
|
+
globalFlags: help?.globalFlags ?? flagsToRecord(GLOBAL_FLAGS),
|
|
1999
|
+
schemaVersion: 1,
|
|
2000
|
+
resources: renderResourceIndex(spec)
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
function flagsToRecord(flags) {
|
|
2004
|
+
return Object.fromEntries(flags.map((f) => [`--${f.name}`, f.description]));
|
|
2005
|
+
}
|
|
2006
|
+
function suggestCommand(input, candidates) {
|
|
2007
|
+
return suggest(input, candidates);
|
|
2008
|
+
}
|
|
2009
|
+
async function runEntry(deps = {}) {
|
|
2010
|
+
const argv = process.argv.slice(2);
|
|
2011
|
+
if (argv[0] === "--")
|
|
2012
|
+
argv.shift();
|
|
2013
|
+
run(argv, deps).then((code) => {
|
|
2014
|
+
process.exitCode = code;
|
|
2015
|
+
}).catch((error) => {
|
|
2016
|
+
console.error(JSON.stringify({ code: "ERROR", message: error instanceof Error ? error.message : String(error) }));
|
|
2017
|
+
process.exitCode = 1;
|
|
2018
|
+
});
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// ../morula-cli-core/dist/commands/auth.js
|
|
2022
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2023
|
+
async function authCommand(context) {
|
|
2024
|
+
const { action } = context.parsed;
|
|
2025
|
+
if (action === "login")
|
|
2026
|
+
return login(context);
|
|
2027
|
+
if (action === "logout")
|
|
2028
|
+
return logout(context);
|
|
2029
|
+
if (action === "status")
|
|
2030
|
+
return status(context);
|
|
2031
|
+
throw usage("usage: morula auth <login|logout|status>");
|
|
2032
|
+
}
|
|
2033
|
+
async function login(context) {
|
|
2034
|
+
rejectUnknownFlags(context.parsed.flags, ["url"]);
|
|
2035
|
+
const verifier = randomBytes(32).toString("base64url");
|
|
2036
|
+
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
2037
|
+
const state = randomBytes(16).toString("base64url");
|
|
2038
|
+
const loopback = await startCallbackServer(state);
|
|
2039
|
+
try {
|
|
2040
|
+
const response = await context.client.auth.authorize({
|
|
2041
|
+
response_type: "code",
|
|
2042
|
+
code_challenge: challenge,
|
|
2043
|
+
code_challenge_method: "S256",
|
|
2044
|
+
redirect_uri: loopback.redirectUri,
|
|
2045
|
+
state
|
|
2046
|
+
});
|
|
2047
|
+
if (!response.authorizeUrl)
|
|
2048
|
+
throw new CliError("LOGIN_FAILED", EXIT.OTHER, "server returned no authorizeUrl");
|
|
2049
|
+
context.err(`Opening browser for SkyDoor login\u2026
|
|
2050
|
+
If it does not open, visit:
|
|
2051
|
+
${response.authorizeUrl}`);
|
|
2052
|
+
openBrowser(response.authorizeUrl, context.env);
|
|
2053
|
+
const timeout = Number(context.env.MORULA_LOGIN_TIMEOUT_MS) || 3e5;
|
|
2054
|
+
const code = await loopback.waitForCode(timeout);
|
|
2055
|
+
const token = await context.client.auth.exchangeToken({ code, code_verifier: verifier });
|
|
2056
|
+
if (!token.access_token)
|
|
2057
|
+
throw new CliError("LOGIN_FAILED", EXIT.OTHER, "server returned no access_token");
|
|
2058
|
+
saveContext(context.store, context.runtime.context, { url: context.runtime.baseUrl, token: token.access_token });
|
|
2059
|
+
return { loggedIn: true, context: context.runtime.context, baseUrl: context.runtime.baseUrl, expiresIn: token.expires_in };
|
|
2060
|
+
} finally {
|
|
2061
|
+
await loopback.close();
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
async function logout(context) {
|
|
2065
|
+
rejectUnknownFlags(context.parsed.flags, []);
|
|
2066
|
+
if (context.runtime.token) {
|
|
2067
|
+
try {
|
|
2068
|
+
await context.client.auth.revokeCliToken();
|
|
2069
|
+
} catch (error) {
|
|
2070
|
+
if (!(error instanceof MorulaApiError) || error.exitCode !== EXIT.UNAUTHENTICATED)
|
|
2071
|
+
throw error;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
clearContextToken(context.store, context.runtime.context);
|
|
2075
|
+
return { loggedIn: false, context: context.runtime.context };
|
|
2076
|
+
}
|
|
2077
|
+
async function status(context) {
|
|
2078
|
+
rejectUnknownFlags(context.parsed.flags, []);
|
|
2079
|
+
if (!context.runtime.token) {
|
|
2080
|
+
return { loggedIn: false, context: context.runtime.context, baseUrl: context.runtime.baseUrl };
|
|
2081
|
+
}
|
|
2082
|
+
try {
|
|
2083
|
+
const user = await context.client.auth.profile();
|
|
2084
|
+
return { loggedIn: true, context: context.runtime.context, baseUrl: context.runtime.baseUrl, user };
|
|
2085
|
+
} catch (error) {
|
|
2086
|
+
if (typeof error === "object" && error && "exitCode" in error && error.exitCode === EXIT.UNAUTHENTICATED) {
|
|
2087
|
+
return { loggedIn: false, context: context.runtime.context, baseUrl: context.runtime.baseUrl, expired: true };
|
|
2088
|
+
}
|
|
2089
|
+
throw error;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// ../morula-cli/src/commands/attachment.ts
|
|
2094
|
+
import { readFile } from "node:fs/promises";
|
|
2095
|
+
import { basename, extname } from "node:path";
|
|
2096
|
+
var MIME_BY_EXT = {
|
|
2097
|
+
".pdf": "application/pdf",
|
|
2098
|
+
".doc": "application/msword",
|
|
2099
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
2100
|
+
".xls": "application/vnd.ms-excel",
|
|
2101
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
2102
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
2103
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
2104
|
+
".md": "text/markdown",
|
|
2105
|
+
".txt": "text/plain",
|
|
2106
|
+
".csv": "text/csv",
|
|
2107
|
+
".json": "application/json",
|
|
2108
|
+
".png": "image/png",
|
|
2109
|
+
".jpg": "image/jpeg",
|
|
2110
|
+
".jpeg": "image/jpeg",
|
|
2111
|
+
".gif": "image/gif",
|
|
2112
|
+
".webp": "image/webp",
|
|
2113
|
+
".svg": "image/svg+xml",
|
|
2114
|
+
".zip": "application/zip"
|
|
2115
|
+
};
|
|
2116
|
+
function inferMimeType(filePath) {
|
|
2117
|
+
const ext = extname(filePath).toLowerCase();
|
|
2118
|
+
return MIME_BY_EXT[ext] ?? "application/octet-stream";
|
|
2119
|
+
}
|
|
2120
|
+
async function attachmentCommand(context) {
|
|
2121
|
+
const { action, positionals, flags } = context.parsed;
|
|
2122
|
+
if (!context.runtime.token)
|
|
2123
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2124
|
+
if (action === "upload") {
|
|
2125
|
+
rejectUnknownFlags(flags, ["object", "comment", "ag-message", "task", "inline"]);
|
|
2126
|
+
const filePath = positionals[0]?.trim();
|
|
2127
|
+
if (!filePath)
|
|
2128
|
+
throw usage("usage: morula attachment upload <file> [--object <objectId>] [--comment <commentId>] [--ag-message] [--task <agentTaskId>] [--inline]");
|
|
2129
|
+
const objectId = flagString(flags, "object");
|
|
2130
|
+
const commentId = flagString(flags, "comment");
|
|
2131
|
+
const isAgMessage = flagBoolean(flags, "ag-message");
|
|
2132
|
+
const isInline = flagBoolean(flags, "inline");
|
|
2133
|
+
const taskId = flagString(flags, "task");
|
|
2134
|
+
const targets = [objectId, commentId, isAgMessage ? "ag-message" : void 0].filter(Boolean);
|
|
2135
|
+
if (targets.length > 1)
|
|
2136
|
+
throw usage("--object / --comment / --ag-message \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A");
|
|
2137
|
+
const buffer = await readFile(filePath);
|
|
2138
|
+
const resourceType = objectId ? "object" : commentId ? "comment" : isAgMessage ? "ag_message" : void 0;
|
|
2139
|
+
return context.client.attachments.upload({
|
|
2140
|
+
file: new Blob([buffer], { type: inferMimeType(filePath) }),
|
|
2141
|
+
fileName: basename(filePath),
|
|
2142
|
+
fileType: inferMimeType(filePath),
|
|
2143
|
+
resourceType,
|
|
2144
|
+
resourceId: objectId ?? commentId,
|
|
2145
|
+
taskId,
|
|
2146
|
+
isInline
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
if (action === "list") {
|
|
2150
|
+
rejectUnknownFlags(flags, ["object"]);
|
|
2151
|
+
const objectId = requireFlag(flags, "object");
|
|
2152
|
+
return context.client.attachments.list({ resourceType: "object", resourceId: objectId });
|
|
2153
|
+
}
|
|
2154
|
+
if (action === "get") {
|
|
2155
|
+
rejectUnknownFlags(flags, []);
|
|
2156
|
+
return context.client.attachments.get(requireId(positionals, "attachment get"));
|
|
2157
|
+
}
|
|
2158
|
+
if (action === "content") {
|
|
2159
|
+
rejectUnknownFlags(flags, ["max-chars"]);
|
|
2160
|
+
return context.client.attachments.content(
|
|
2161
|
+
requireId(positionals, "attachment content"),
|
|
2162
|
+
flagNumber(flags, "max-chars")
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
if (action === "download-url") {
|
|
2166
|
+
rejectUnknownFlags(flags, []);
|
|
2167
|
+
return context.client.attachments.getDownloadUrl(requireId(positionals, "attachment download-url"));
|
|
2168
|
+
}
|
|
2169
|
+
if (action === "delete") {
|
|
2170
|
+
rejectUnknownFlags(flags, []);
|
|
2171
|
+
return context.client.attachments.delete(requireId(positionals, "attachment delete"));
|
|
2172
|
+
}
|
|
2173
|
+
throw usage("usage: morula attachment <upload|list|get|download-url|delete>");
|
|
2174
|
+
}
|
|
2175
|
+
function requireId(positionals, command) {
|
|
2176
|
+
const id = positionals[0]?.trim();
|
|
2177
|
+
if (!id)
|
|
2178
|
+
throw usage(`usage: morula ${command} <id>`);
|
|
2179
|
+
return id;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
// ../morula-cli/src/commands/autopilot.ts
|
|
2183
|
+
async function autopilotCommand(context) {
|
|
2184
|
+
if (!context.runtime.token)
|
|
2185
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2186
|
+
const { action, flags } = context.parsed;
|
|
2187
|
+
if (action === "list") {
|
|
2188
|
+
rejectUnknownFlags(flags, ["space", "page", "page-size"]);
|
|
2189
|
+
return context.client.autopilots.list({
|
|
2190
|
+
spaceId: flagString(flags, "space"),
|
|
2191
|
+
page: flagNumber(flags, "page"),
|
|
2192
|
+
pageSize: flagNumber(flags, "page-size")
|
|
2193
|
+
});
|
|
2194
|
+
}
|
|
2195
|
+
if (action === "create") {
|
|
2196
|
+
rejectUnknownFlags(flags, ["space", "name", "runbook", "executor", "description", "cron", "vcs-connection"]);
|
|
2197
|
+
const spaceId = requireFlag(flags, "space");
|
|
2198
|
+
const name = requireFlag(flags, "name");
|
|
2199
|
+
const runbook = requireFlag(flags, "runbook");
|
|
2200
|
+
const executorKey = requireFlag(flags, "executor");
|
|
2201
|
+
const description = flagString(flags, "description");
|
|
2202
|
+
const cronExpr = flagString(flags, "cron");
|
|
2203
|
+
const vcsConnectionId = flagString(flags, "vcs-connection");
|
|
2204
|
+
return context.client.autopilots.create({
|
|
2205
|
+
spaceId,
|
|
2206
|
+
name,
|
|
2207
|
+
runbook,
|
|
2208
|
+
executorKey,
|
|
2209
|
+
...description !== void 0 ? { description } : {},
|
|
2210
|
+
...cronExpr !== void 0 ? { cronExpr } : {},
|
|
2211
|
+
...vcsConnectionId !== void 0 ? { vcsConnectionId } : {}
|
|
2212
|
+
});
|
|
2213
|
+
}
|
|
2214
|
+
throw usage("usage: morula autopilot <list|create>");
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
// ../morula-cli/src/commands/ci.ts
|
|
2218
|
+
async function ciCommand(context) {
|
|
2219
|
+
if (!context.runtime.token)
|
|
2220
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2221
|
+
const { action } = context.parsed;
|
|
2222
|
+
if (action === "status")
|
|
2223
|
+
return ciStatus(context);
|
|
2224
|
+
if (action === "log")
|
|
2225
|
+
return ciLog(context);
|
|
2226
|
+
if (action === "deployments")
|
|
2227
|
+
return ciDeployments(context);
|
|
2228
|
+
if (action === "test-report")
|
|
2229
|
+
return ciTestReport(context);
|
|
2230
|
+
if (action === "retry")
|
|
2231
|
+
return ciRetry(context);
|
|
2232
|
+
throw usage("usage: morula ci <status|log|deployments|test-report|retry>");
|
|
2233
|
+
}
|
|
2234
|
+
async function ciStatus(context) {
|
|
2235
|
+
const { positionals, flags } = context.parsed;
|
|
2236
|
+
rejectUnknownFlags(flags, ["space", "repo", "ref", "failed", "limit"]);
|
|
2237
|
+
const repo = flagString(flags, "repo");
|
|
2238
|
+
const ref = flagString(flags, "ref");
|
|
2239
|
+
const failed = flagBoolean(flags, "failed");
|
|
2240
|
+
const limit = flagNumber(flags, "limit");
|
|
2241
|
+
if (limit != null && (!Number.isInteger(limit) || limit <= 0))
|
|
2242
|
+
throw usage("--limit \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
2243
|
+
const face = resolveFace(context, "usage: morula ci status <taskId> [--repo <owner/name>] [--limit <n>] | morula ci status --space <spaceId> [--ref <branch>] [--failed] [--limit <n>]");
|
|
2244
|
+
if (face.kind === "space") {
|
|
2245
|
+
if (repo)
|
|
2246
|
+
throw usage("--repo \u53EA\u5728\u4EFB\u52A1\u9762\u53EF\u7528\uFF1A\u7A7A\u95F4\u9762\u8BF7\u7528 --ref \u8FC7\u6EE4\u5206\u652F\uFF08\u670D\u52A1\u7AEF\u4E0D\u652F\u6301\u6309\u4ED3\u5E93\u8FC7\u6EE4\uFF09");
|
|
2247
|
+
const result2 = await context.client.ci.spaceView(face.spaceId, {
|
|
2248
|
+
...ref ? { ref } : {},
|
|
2249
|
+
...failed ? { failed: true } : {},
|
|
2250
|
+
...limit ? { limit } : {}
|
|
2251
|
+
});
|
|
2252
|
+
if (result2.ok === false)
|
|
2253
|
+
throw ciFailure(result2);
|
|
2254
|
+
return result2;
|
|
2255
|
+
}
|
|
2256
|
+
const result = await context.client.ci.taskView(face.taskId, { ...limit ? { limit } : {} });
|
|
2257
|
+
if (result.ok === false)
|
|
2258
|
+
throw ciFailure(result);
|
|
2259
|
+
if (!repo)
|
|
2260
|
+
return result;
|
|
2261
|
+
const runs = result.runs.filter((run2) => run2.repoFullName === repo);
|
|
2262
|
+
return { ...result, runs, repoFilter: { repo, total: result.runs.length, matched: runs.length } };
|
|
2263
|
+
}
|
|
2264
|
+
async function ciLog(context) {
|
|
2265
|
+
const { flags } = context.parsed;
|
|
2266
|
+
rejectUnknownFlags(flags, ["space", "job", "tail"]);
|
|
2267
|
+
const jobId = flagString(flags, "job");
|
|
2268
|
+
if (!jobId)
|
|
2269
|
+
throw usage("usage: morula ci log <taskId> --job <jobId> [--tail <n>] | morula ci log --space <spaceId> --job <jobId> [--tail <n>]");
|
|
2270
|
+
const tail = flagNumber(flags, "tail");
|
|
2271
|
+
if (tail != null && (!Number.isInteger(tail) || tail <= 0))
|
|
2272
|
+
throw usage("--tail \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08\u4FDD\u7559\u7684\u65E5\u5FD7\u5C3E\u90E8\u5B57\u8282\u6570\uFF09");
|
|
2273
|
+
resolveFace(context, "usage: morula ci log <taskId> --job <jobId> [--tail <n>] | morula ci log --space <spaceId> --job <jobId> [--tail <n>]");
|
|
2274
|
+
const result = await context.client.ci.jobLog(jobId, { ...tail ? { tail } : {} });
|
|
2275
|
+
if (result.ok === false)
|
|
2276
|
+
throw ciFailure(result);
|
|
2277
|
+
return result;
|
|
2278
|
+
}
|
|
2279
|
+
async function ciDeployments(context) {
|
|
2280
|
+
const { flags } = context.parsed;
|
|
2281
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
2282
|
+
const face = resolveFace(context, "usage: morula ci deployments <taskId> | morula ci deployments --space <spaceId>");
|
|
2283
|
+
const result = face.kind === "space" ? await context.client.ci.spaceDeployments(face.spaceId) : await context.client.ci.taskDeployments(face.taskId);
|
|
2284
|
+
if (result.ok === false)
|
|
2285
|
+
throw ciFailure(result);
|
|
2286
|
+
return result;
|
|
2287
|
+
}
|
|
2288
|
+
async function ciTestReport(context) {
|
|
2289
|
+
const { flags } = context.parsed;
|
|
2290
|
+
rejectUnknownFlags(flags, ["space", "run"]);
|
|
2291
|
+
const runFlag = flagString(flags, "run");
|
|
2292
|
+
const face = resolveFace(context, "usage: morula ci test-report <taskId> [--run <runId>] | morula ci test-report --space <spaceId> [--run <runId>]");
|
|
2293
|
+
let runId = runFlag;
|
|
2294
|
+
if (!runId) {
|
|
2295
|
+
const view = face.kind === "space" ? await context.client.ci.spaceView(face.spaceId) : await context.client.ci.taskView(face.taskId);
|
|
2296
|
+
if (view.ok === false)
|
|
2297
|
+
throw ciFailure(view);
|
|
2298
|
+
runId = view.runs[0]?.id;
|
|
2299
|
+
if (!runId) {
|
|
2300
|
+
throw new CliError(
|
|
2301
|
+
"CI_RUN_NOT_FOUND",
|
|
2302
|
+
EXIT.NOT_FOUND,
|
|
2303
|
+
face.kind === "space" ? "\u8BE5\u7A7A\u95F4\u6CA1\u6709\u6D41\u6C34\u7EBF\u8FD0\u884C\u8BB0\u5F55\uFF0C\u53D6\u4E0D\u5230\u5931\u8D25\u7528\u4F8B\u6E05\u5355\uFF08\u53EF\u5148\u7528 morula ci status --space <id> \u786E\u8BA4\uFF09" : "\u672C\u4EFB\u52A1\u6CA1\u6709\u6D41\u6C34\u7EBF\u8FD0\u884C\u8BB0\u5F55\uFF0C\u53D6\u4E0D\u5230\u5931\u8D25\u7528\u4F8B\u6E05\u5355\uFF08\u53EF\u5148\u7528 morula ci status <taskId> \u786E\u8BA4\uFF09"
|
|
2304
|
+
);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
const result = await context.client.ci.testReport(runId);
|
|
2308
|
+
if (result.ok === false)
|
|
2309
|
+
throw ciFailure(result);
|
|
2310
|
+
return result;
|
|
2311
|
+
}
|
|
2312
|
+
async function ciRetry(context) {
|
|
2313
|
+
const { positionals, flags } = context.parsed;
|
|
2314
|
+
rejectUnknownFlags(flags, ["space", "run", "job"]);
|
|
2315
|
+
const usageHint = "usage: morula ci retry --space <spaceId> [--run <runId>] [--job <jobId>]";
|
|
2316
|
+
if (positionals.length)
|
|
2317
|
+
throw usage(`\u91CD\u8DD1\u53EA\u5728\u7A7A\u95F4\u9762\u5F00\u653E\uFF1A\u8BF7\u7528 --space <spaceId>\uFF08\u4EFB\u52A1\u9762\u4E0D\u63D0\u4F9B retry\uFF09\u3002${usageHint}`);
|
|
2318
|
+
const runId = flagString(flags, "run");
|
|
2319
|
+
const jobId = flagString(flags, "job");
|
|
2320
|
+
if (runId && jobId)
|
|
2321
|
+
throw usage(`--run \u4E0E --job \u4E92\u65A5\uFF0C\u53EA\u80FD\u7ED9\u4E00\u4E2A\uFF08${usageHint}\uFF09`);
|
|
2322
|
+
const spaceId = flagString(flags, "space");
|
|
2323
|
+
if (!spaceId)
|
|
2324
|
+
throw usage(usageHint);
|
|
2325
|
+
if (jobId) {
|
|
2326
|
+
const result2 = await context.client.ci.retryJob(jobId);
|
|
2327
|
+
if (result2.ok === false)
|
|
2328
|
+
throw ciFailure(result2, "\u64CD\u4F5C");
|
|
2329
|
+
return result2;
|
|
2330
|
+
}
|
|
2331
|
+
let targetRunId = runId;
|
|
2332
|
+
if (!targetRunId) {
|
|
2333
|
+
const view = await context.client.ci.spaceView(spaceId, { failed: true });
|
|
2334
|
+
if (view.ok === false)
|
|
2335
|
+
throw ciFailure(view);
|
|
2336
|
+
targetRunId = view.runs[0]?.id;
|
|
2337
|
+
if (!targetRunId) {
|
|
2338
|
+
throw new CliError(
|
|
2339
|
+
"CI_RUN_NOT_FOUND",
|
|
2340
|
+
EXIT.NOT_FOUND,
|
|
2341
|
+
`\u7A7A\u95F4 ${spaceId} \u6CA1\u6709\u5931\u8D25\u7684\u6D41\u6C34\u7EBF\u8FD0\u884C\u53EF\u91CD\u8DD1\uFF08\u53EF\u5148\u7528 morula ci status --space ${spaceId} --failed \u786E\u8BA4\uFF09`
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
const result = await context.client.ci.retryRun(targetRunId);
|
|
2346
|
+
if (result.ok === false)
|
|
2347
|
+
throw ciFailure(result, "\u64CD\u4F5C");
|
|
2348
|
+
return result;
|
|
2349
|
+
}
|
|
2350
|
+
function resolveFace(context, usageHint) {
|
|
2351
|
+
const taskId = context.parsed.positionals[0]?.trim();
|
|
2352
|
+
const spaceId = flagString(context.parsed.flags, "space");
|
|
2353
|
+
if (taskId && spaceId)
|
|
2354
|
+
throw usage(`<taskId> \u4E0E --space \u4E92\u65A5\uFF0C\u53EA\u80FD\u7ED9\u4E00\u4E2A\uFF08${usageHint}\uFF09`);
|
|
2355
|
+
if (!taskId && !spaceId)
|
|
2356
|
+
throw usage(usageHint);
|
|
2357
|
+
return taskId ? { kind: "task", taskId } : { kind: "space", spaceId };
|
|
2358
|
+
}
|
|
2359
|
+
function ciFailure(failure, verb = "\u67E5\u8BE2") {
|
|
2360
|
+
return new CliError(
|
|
2361
|
+
failure.code,
|
|
2362
|
+
exitForCiCode(failure.code),
|
|
2363
|
+
`CI ${verb}\u5931\u8D25: ${failure.message || "\u672A\u77E5\u539F\u56E0"}\uFF08${failure.code}\uFF09`
|
|
2364
|
+
);
|
|
2365
|
+
}
|
|
2366
|
+
function exitForCiCode(code) {
|
|
2367
|
+
if (code === "CI_RETRY_NOT_ALLOWED" || code === "CI_JOB_NOT_MANUAL")
|
|
2368
|
+
return EXIT.CONFLICT;
|
|
2369
|
+
if (code === "CI_JOB_NOT_FOUND" || code === "CI_RUN_NOT_FOUND" || code === "TASK_NOT_FOUND" || code === "OBJECT_NOT_FOUND")
|
|
2370
|
+
return EXIT.NOT_FOUND;
|
|
2371
|
+
return EXIT.OTHER;
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// ../morula-cli/src/commands/comment.ts
|
|
2375
|
+
function detectCommentFormat(text) {
|
|
2376
|
+
if (!text)
|
|
2377
|
+
return "plain";
|
|
2378
|
+
if (/<[a-z][\s\S]*>/i.test(text))
|
|
2379
|
+
return "richtext";
|
|
2380
|
+
if (/^(?:#{1,6}\s|[-*+]\s|\d+\.\s|>\s|```|~~)/m.test(text))
|
|
2381
|
+
return "markdown";
|
|
2382
|
+
if (/\*\*.+\*\*/.test(text))
|
|
2383
|
+
return "markdown";
|
|
2384
|
+
if (/\[[^\]]+\]\([^)]+\)/.test(text.trim()))
|
|
2385
|
+
return "markdown";
|
|
2386
|
+
if (/\|.+\|.+\|/.test(text))
|
|
2387
|
+
return "markdown";
|
|
2388
|
+
return "plain";
|
|
2389
|
+
}
|
|
2390
|
+
async function commentCommand(context) {
|
|
2391
|
+
if (!context.runtime.token)
|
|
2392
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2393
|
+
const { action, positionals, flags } = context.parsed;
|
|
2394
|
+
if (action === "list") {
|
|
2395
|
+
rejectUnknownFlags(flags, ["object"]);
|
|
2396
|
+
const objectId = requireFlag(flags, "object");
|
|
2397
|
+
return context.client.comments.list(objectId);
|
|
2398
|
+
}
|
|
2399
|
+
if (action === "get") {
|
|
2400
|
+
rejectUnknownFlags(flags, []);
|
|
2401
|
+
const id = positionals[0]?.trim();
|
|
2402
|
+
if (!id)
|
|
2403
|
+
throw usage("usage: morula comment get <id>");
|
|
2404
|
+
return context.client.comments.get(id);
|
|
2405
|
+
}
|
|
2406
|
+
if (action === "add") {
|
|
2407
|
+
rejectUnknownFlags(flags, ["object", "content", "parent", "format", "mention"]);
|
|
2408
|
+
const objectId = requireFlag(flags, "object");
|
|
2409
|
+
const content = requireFlag(flags, "content");
|
|
2410
|
+
const formatFlag = flagString(flags, "format");
|
|
2411
|
+
const format = formatFlag === "markdown" || formatFlag === "plain" || formatFlag === "richtext" ? formatFlag : detectCommentFormat(content);
|
|
2412
|
+
const mentions = parseMentions(flagStrings(context.parsed, "mention"));
|
|
2413
|
+
const input = compact({
|
|
2414
|
+
objectId,
|
|
2415
|
+
content,
|
|
2416
|
+
parentCommentId: flagString(flags, "parent"),
|
|
2417
|
+
contentFormat: format,
|
|
2418
|
+
// 无 --mention 时**不带** mentions 字段(服务端按缺省处理,不发任何通知)
|
|
2419
|
+
...mentions.length ? { mentions } : {}
|
|
2420
|
+
});
|
|
2421
|
+
return context.client.comments.create(input);
|
|
2422
|
+
}
|
|
2423
|
+
if (action === "update") {
|
|
2424
|
+
rejectUnknownFlags(flags, ["content"]);
|
|
2425
|
+
const id = positionals[0]?.trim();
|
|
2426
|
+
if (!id)
|
|
2427
|
+
throw usage("usage: morula comment update <id>");
|
|
2428
|
+
const content = flagString(flags, "content");
|
|
2429
|
+
if (!content)
|
|
2430
|
+
throw usage("comment update requires --content");
|
|
2431
|
+
return context.client.comments.update(id, { content });
|
|
2432
|
+
}
|
|
2433
|
+
if (action === "delete") {
|
|
2434
|
+
rejectUnknownFlags(flags, []);
|
|
2435
|
+
const id = positionals[0]?.trim();
|
|
2436
|
+
if (!id)
|
|
2437
|
+
throw usage("usage: morula comment delete <id>");
|
|
2438
|
+
return context.client.comments.delete(id);
|
|
2439
|
+
}
|
|
2440
|
+
throw usage("usage: morula comment <list|get|add|update|delete>");
|
|
2441
|
+
}
|
|
2442
|
+
function compact(value) {
|
|
2443
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
2444
|
+
}
|
|
2445
|
+
function parseMentions(rawValues) {
|
|
2446
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2447
|
+
const mentions = [];
|
|
2448
|
+
for (const raw of rawValues) {
|
|
2449
|
+
for (const part of raw.split(",")) {
|
|
2450
|
+
const principalId = part.trim();
|
|
2451
|
+
if (!principalId || seen.has(principalId))
|
|
2452
|
+
continue;
|
|
2453
|
+
seen.add(principalId);
|
|
2454
|
+
mentions.push({ type: "member", principalId });
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
return mentions;
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
// ../morula-cli/src/commands/dashboard.ts
|
|
2461
|
+
async function dashboardCommand(context) {
|
|
2462
|
+
if (!context.runtime.token)
|
|
2463
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2464
|
+
const { action, flags } = context.parsed;
|
|
2465
|
+
if (action === "get") {
|
|
2466
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
2467
|
+
return context.client.dashboard.get(requireFlag(flags, "space"));
|
|
2468
|
+
}
|
|
2469
|
+
if (action === "statistics") {
|
|
2470
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
2471
|
+
return context.client.dashboard.statistics(requireFlag(flags, "space"));
|
|
2472
|
+
}
|
|
2473
|
+
if (action === "workload") {
|
|
2474
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
2475
|
+
return context.client.dashboard.workload(requireFlag(flags, "space"));
|
|
2476
|
+
}
|
|
2477
|
+
throw usage("usage: morula dashboard <get|statistics|workload>");
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// ../morula-cli/src/commands/document.ts
|
|
2481
|
+
async function documentCommand(context) {
|
|
2482
|
+
if (!context.runtime.token)
|
|
2483
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2484
|
+
const { action, positionals, flags } = context.parsed;
|
|
2485
|
+
if (action === "list") {
|
|
2486
|
+
rejectUnknownFlags(flags, ["page", "page-size"]);
|
|
2487
|
+
return context.client.documents.list({
|
|
2488
|
+
page: flagNumber(flags, "page"),
|
|
2489
|
+
pageSize: flagNumber(flags, "page-size")
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
if (action === "get") {
|
|
2493
|
+
rejectUnknownFlags(flags, []);
|
|
2494
|
+
return context.client.documents.get(requireId2(positionals, "document get"));
|
|
2495
|
+
}
|
|
2496
|
+
if (action === "artifacts") {
|
|
2497
|
+
rejectUnknownFlags(flags, []);
|
|
2498
|
+
return context.client.documents.artifacts(requireId2(positionals, "document artifacts"));
|
|
2499
|
+
}
|
|
2500
|
+
throw usage("usage: morula document <list|get|artifacts>");
|
|
2501
|
+
}
|
|
2502
|
+
function requireId2(positionals, command) {
|
|
2503
|
+
const id = positionals[0]?.trim();
|
|
2504
|
+
if (!id)
|
|
2505
|
+
throw usage(`usage: morula ${command} <id>`);
|
|
2506
|
+
return id;
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// ../morula-cli/src/commands/object-meta.ts
|
|
2510
|
+
async function objectMetaCommand(context) {
|
|
2511
|
+
if (!context.runtime.token)
|
|
2512
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2513
|
+
const { action, flags } = context.parsed;
|
|
2514
|
+
if (action === "get") {
|
|
2515
|
+
rejectUnknownFlags(flags, ["object"]);
|
|
2516
|
+
return context.client.objectMeta.list(requireFlag(flags, "object"));
|
|
2517
|
+
}
|
|
2518
|
+
if (action === "set") {
|
|
2519
|
+
rejectUnknownFlags(flags, ["object", "key", "value"]);
|
|
2520
|
+
return context.client.objectMeta.set(
|
|
2521
|
+
requireFlag(flags, "object"),
|
|
2522
|
+
requireFlag(flags, "key"),
|
|
2523
|
+
requireFlag(flags, "value")
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2526
|
+
throw usage("usage: morula object-meta <get|set>");
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// ../morula-cli/src/commands/repo.ts
|
|
2530
|
+
import { execFileSync } from "node:child_process";
|
|
2531
|
+
async function repoCommand(context) {
|
|
2532
|
+
if (!context.runtime.token)
|
|
2533
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2534
|
+
const { action, positionals, flags } = context.parsed;
|
|
2535
|
+
if (action === "list") {
|
|
2536
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
2537
|
+
const spaceId = flagString(flags, "space");
|
|
2538
|
+
if (!spaceId)
|
|
2539
|
+
throw usage("usage: morula repo list --space <id>\uFF08\u9700\u6307\u5B9A\u4EFB\u52A1\u6240\u5728\u7A7A\u95F4\uFF09");
|
|
2540
|
+
const conns = await context.client.http.get(
|
|
2541
|
+
"/vcs/connections",
|
|
2542
|
+
{ spaceId }
|
|
2543
|
+
);
|
|
2544
|
+
return conns.filter((c) => c.status === "active").map((c) => ({ id: c.id, repoFullName: c.repoFullName, provider: c.provider }));
|
|
2545
|
+
}
|
|
2546
|
+
if (action === "checkout") {
|
|
2547
|
+
rejectUnknownFlags(flags, ["ref"]);
|
|
2548
|
+
const taskId = positionals[0]?.trim();
|
|
2549
|
+
const repoFullName = positionals[1]?.trim();
|
|
2550
|
+
if (!taskId || !repoFullName)
|
|
2551
|
+
throw usage("usage: morula repo checkout <taskId> <repoFullName> [--ref <branch|sha>]");
|
|
2552
|
+
const local = await tryLocalCheckout(taskId, repoFullName, flagString(flags, "ref"));
|
|
2553
|
+
if (local)
|
|
2554
|
+
return local;
|
|
2555
|
+
const runtimeId = context.env.MORULA_RUNTIME_ID;
|
|
2556
|
+
if (!runtimeId)
|
|
2557
|
+
throw usage("repo checkout \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09");
|
|
2558
|
+
const result = await context.client.http.post(
|
|
2559
|
+
`/agent-tasks/${taskId}/repo-checkout`,
|
|
2560
|
+
{
|
|
2561
|
+
repoFullName,
|
|
2562
|
+
ref: flagString(flags, "ref"),
|
|
2563
|
+
runtimeId
|
|
2564
|
+
}
|
|
2565
|
+
);
|
|
2566
|
+
if (result?.ok === false)
|
|
2567
|
+
throw new CliError(result.code ?? "REPO_CHECKOUT_FAILED", EXIT.OTHER, `repo checkout \u5931\u8D25: ${result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
2568
|
+
return result;
|
|
2569
|
+
}
|
|
2570
|
+
if (action === "prepare") {
|
|
2571
|
+
rejectUnknownFlags(flags, []);
|
|
2572
|
+
const taskId = positionals[0]?.trim();
|
|
2573
|
+
const repoFullName = positionals[1]?.trim();
|
|
2574
|
+
if (!taskId || !repoFullName)
|
|
2575
|
+
throw usage("usage: morula repo prepare <taskId> <repoFullName>");
|
|
2576
|
+
const local = await tryLocalPrepare(taskId, repoFullName);
|
|
2577
|
+
if (local)
|
|
2578
|
+
return local;
|
|
2579
|
+
const runtimeId = context.env.MORULA_RUNTIME_ID;
|
|
2580
|
+
if (!runtimeId)
|
|
2581
|
+
throw usage("repo prepare \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09");
|
|
2582
|
+
const result = await context.client.http.post(
|
|
2583
|
+
`/agent-tasks/${taskId}/repos/prepare`,
|
|
2584
|
+
{ repoFullName, runtimeId }
|
|
2585
|
+
);
|
|
2586
|
+
if (result?.ok === false)
|
|
2587
|
+
throw new CliError(result.code ?? "REPO_PREPARE_FAILED", EXIT.OTHER, `repo prepare \u5931\u8D25: ${result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
2588
|
+
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" };
|
|
2589
|
+
}
|
|
2590
|
+
if (action === "mr" && positionals[0] === "create") {
|
|
2591
|
+
return mrCreate(context, flags);
|
|
2592
|
+
}
|
|
2593
|
+
if (action === "mr" && positionals[0] === "list") {
|
|
2594
|
+
return mrList(context, flags);
|
|
2595
|
+
}
|
|
2596
|
+
if (action === "mr" && positionals[0] === "comment") {
|
|
2597
|
+
return mrComment(context, flags);
|
|
2598
|
+
}
|
|
2599
|
+
if (action === "mr" && positionals[0] === "get") {
|
|
2600
|
+
return mrGet(context, flags);
|
|
2601
|
+
}
|
|
2602
|
+
if (action === "mr" && positionals[0] === "diff") {
|
|
2603
|
+
return mrDiff(context, flags);
|
|
2604
|
+
}
|
|
2605
|
+
if (action === "files") {
|
|
2606
|
+
rejectUnknownFlags(flags, ["space", "repo", "path", "ref"]);
|
|
2607
|
+
const spaceId = flagString(flags, "space");
|
|
2608
|
+
if (!spaceId)
|
|
2609
|
+
throw usage("usage: morula repo files --space <id> [--repo <owner/name>] [--path <p>] [--ref <r>]");
|
|
2610
|
+
return context.client.repos.files(spaceId, {
|
|
2611
|
+
repo: flagString(flags, "repo"),
|
|
2612
|
+
path: flagString(flags, "path"),
|
|
2613
|
+
ref: flagString(flags, "ref")
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
if (action === "read") {
|
|
2617
|
+
rejectUnknownFlags(flags, ["space", "repo", "path", "ref"]);
|
|
2618
|
+
const spaceId = flagString(flags, "space");
|
|
2619
|
+
const path = flagString(flags, "path");
|
|
2620
|
+
if (!spaceId || !path)
|
|
2621
|
+
throw usage("usage: morula repo read --space <id> --path <p> [--repo <owner/name>] [--ref <r>]");
|
|
2622
|
+
return context.client.repos.read(spaceId, {
|
|
2623
|
+
path,
|
|
2624
|
+
repo: flagString(flags, "repo"),
|
|
2625
|
+
ref: flagString(flags, "ref")
|
|
2626
|
+
});
|
|
2627
|
+
}
|
|
2628
|
+
throw usage("usage: morula repo <list|checkout|prepare|files|read|mr create|mr list|mr comment|mr get|mr diff>");
|
|
2629
|
+
}
|
|
2630
|
+
function resolveMrBase(flagBase, env = process.env) {
|
|
2631
|
+
return env.MORULA_BASE_BRANCH?.trim() || flagBase?.trim() || "main";
|
|
2632
|
+
}
|
|
2633
|
+
async function mrCreate(context, flags) {
|
|
2634
|
+
rejectUnknownFlags(flags, ["branch", "base", "task-code"]);
|
|
2635
|
+
const repoFullName = context.parsed.positionals[1]?.trim();
|
|
2636
|
+
const branch = flagString(flags, "branch");
|
|
2637
|
+
const base = resolveMrBase(flagString(flags, "base"));
|
|
2638
|
+
const taskCode = flagString(flags, "task-code");
|
|
2639
|
+
if (!repoFullName || !branch)
|
|
2640
|
+
throw usage("usage: morula repo mr create <repoFullName> --branch <branch> [--base <base>] [--task-code <code>]");
|
|
2641
|
+
const taskId = await resolveTaskId(context);
|
|
2642
|
+
if (taskId)
|
|
2643
|
+
return mrCreateViaServer(context, { taskId, repoFullName, branch, base, taskCode });
|
|
2644
|
+
return mrCreateViaPushOptions({ repoFullName, branch, base, taskCode });
|
|
2645
|
+
}
|
|
2646
|
+
async function resolveCheckoutConfigPath(startDir) {
|
|
2647
|
+
const { existsSync: existsSync2 } = await import("node:fs");
|
|
2648
|
+
const { dirname: dirname2, join: join2, resolve } = await import("node:path");
|
|
2649
|
+
let current = resolve(startDir ?? process.cwd());
|
|
2650
|
+
while (true) {
|
|
2651
|
+
const candidate = join2(current, ".morula", "checkout.json");
|
|
2652
|
+
if (existsSync2(candidate))
|
|
2653
|
+
return candidate;
|
|
2654
|
+
const parent = dirname2(current);
|
|
2655
|
+
if (parent === current)
|
|
2656
|
+
return null;
|
|
2657
|
+
current = parent;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
async function resolveTaskId(context) {
|
|
2661
|
+
const fromEnv = context.env.MORULA_TASK_ID?.trim();
|
|
2662
|
+
if (fromEnv)
|
|
2663
|
+
return fromEnv;
|
|
2664
|
+
try {
|
|
2665
|
+
const { readFileSync: readFileSync2 } = await import("node:fs");
|
|
2666
|
+
const cfgPath = await resolveCheckoutConfigPath();
|
|
2667
|
+
if (!cfgPath)
|
|
2668
|
+
return null;
|
|
2669
|
+
const raw = JSON.parse(readFileSync2(cfgPath, "utf8"));
|
|
2670
|
+
return raw.taskId?.trim() || null;
|
|
2671
|
+
} catch {
|
|
2672
|
+
return null;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
async function mrCreateViaServer(context, input) {
|
|
2676
|
+
const title = input.taskCode ? `${input.taskCode}: ${input.branch}` : void 0;
|
|
2677
|
+
const result = await context.client.http.post(`/agent-tasks/${input.taskId}/mr`, {
|
|
2678
|
+
repoFullName: input.repoFullName,
|
|
2679
|
+
branch: input.branch,
|
|
2680
|
+
base: input.base,
|
|
2681
|
+
...title ? { title } : {}
|
|
2682
|
+
});
|
|
2683
|
+
if (result?.ok === false) {
|
|
2684
|
+
throw new CliError(
|
|
2685
|
+
result.code ?? "MR_CREATE_FAILED",
|
|
2686
|
+
EXIT.UPSTREAM,
|
|
2687
|
+
`\u670D\u52A1\u7AEF\u5EFA MR \u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`
|
|
2688
|
+
);
|
|
2689
|
+
}
|
|
2690
|
+
return {
|
|
2691
|
+
ok: true,
|
|
2692
|
+
repoFullName: result.repoFullName ?? input.repoFullName,
|
|
2693
|
+
branch: result.branch ?? input.branch,
|
|
2694
|
+
base: result.base ?? input.base,
|
|
2695
|
+
prNumber: result.prNumber,
|
|
2696
|
+
prUrl: result.prUrl,
|
|
2697
|
+
reused: result.reused ?? false,
|
|
2698
|
+
linked: result.linked ?? false
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
async function mrCreateViaPushOptions(input) {
|
|
2702
|
+
const { repoFullName, branch, base, taskCode } = input;
|
|
2703
|
+
const title = taskCode ? `${taskCode}: ${branch}` : branch;
|
|
2704
|
+
const description = taskCode ? `Closes ${taskCode}` : "\uFF08morula \u672C\u5730\u521B\u5EFA\uFF09";
|
|
2705
|
+
const pushOpts = [
|
|
2706
|
+
"-o",
|
|
2707
|
+
`merge_request.create`,
|
|
2708
|
+
"-o",
|
|
2709
|
+
`merge_request.target=${base}`,
|
|
2710
|
+
// 显式目标项目:fork 仓库默认会把 MR 建到上游,必须锁定到绑定仓库
|
|
2711
|
+
"-o",
|
|
2712
|
+
`merge_request.target_project=${repoFullName}`,
|
|
2713
|
+
"-o",
|
|
2714
|
+
`merge_request.title=${title}`,
|
|
2715
|
+
"-o",
|
|
2716
|
+
`merge_request.description=${description}`
|
|
2717
|
+
];
|
|
2718
|
+
let pushOutput = "";
|
|
2719
|
+
try {
|
|
2720
|
+
pushOutput = execFileSync("git", ["push", "origin", branch, ...pushOpts], { encoding: "utf8" });
|
|
2721
|
+
} catch (error) {
|
|
2722
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2723
|
+
throw new CliError("MR_CREATE_FAILED", EXIT.UPSTREAM, `git push \u521B\u5EFA MR \u5931\u8D25: ${msg.slice(0, 300)}`);
|
|
2724
|
+
}
|
|
2725
|
+
const mrUrlMatch = pushOutput.match(/View merge request for \S+:\s*\n?(\S*merge_requests\/\d+)/);
|
|
2726
|
+
const url2 = pushOutput.match(/(https?:\/\/\S+\/merge_requests\/\d+)/)?.[1];
|
|
2727
|
+
const mrUrl = mrUrlMatch?.[1]?.trim() || url2;
|
|
2728
|
+
if (mrUrl)
|
|
2729
|
+
return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl };
|
|
2730
|
+
const gitlabToken = process.env.MORULA_GITLAB_TOKEN;
|
|
2731
|
+
const gitlabBase = process.env.MORULA_GITLAB_URL;
|
|
2732
|
+
if (gitlabToken && gitlabBase) {
|
|
2733
|
+
try {
|
|
2734
|
+
const projectId = encodeURIComponent(repoFullName);
|
|
2735
|
+
const q = new URLSearchParams({ source_branch: branch, state: "opened" });
|
|
2736
|
+
const listRes = await fetch(`${gitlabBase}/api/v4/projects/${projectId}/merge_requests?${q}`, {
|
|
2737
|
+
headers: { "PRIVATE-TOKEN": gitlabToken }
|
|
2738
|
+
});
|
|
2739
|
+
const list = listRes.ok ? await listRes.json().catch(() => null) : null;
|
|
2740
|
+
const existing = Array.isArray(list) ? list[0] : null;
|
|
2741
|
+
if (existing?.iid) {
|
|
2742
|
+
process.stderr.write("[morula-cli] mr create \u5206\u652F\u5DF2\u6709 opened MR\uFF0C\u590D\u7528\u73B0\u6709 MR\n");
|
|
2743
|
+
return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl: existing.web_url ?? `${gitlabBase}/${repoFullName}/-/merge_requests/${existing.iid}` };
|
|
2744
|
+
}
|
|
2745
|
+
if (listRes.ok) {
|
|
2746
|
+
const created = await fetch(`${gitlabBase}/api/v4/projects/${projectId}/merge_requests`, {
|
|
2747
|
+
method: "POST",
|
|
2748
|
+
headers: { "PRIVATE-TOKEN": gitlabToken, "Content-Type": "application/json" },
|
|
2749
|
+
body: JSON.stringify({
|
|
2750
|
+
source_branch: branch,
|
|
2751
|
+
target_branch: base,
|
|
2752
|
+
title,
|
|
2753
|
+
description,
|
|
2754
|
+
remove_source_branch: false
|
|
2755
|
+
})
|
|
2756
|
+
});
|
|
2757
|
+
if (created.ok) {
|
|
2758
|
+
const mr = await created.json().catch(() => null);
|
|
2759
|
+
if (mr?.iid) {
|
|
2760
|
+
return { ok: true, repoFullName, branch, base, taskCode, title, mrUrl: mr.web_url ?? `${gitlabBase}/${repoFullName}/-/merge_requests/${mr.iid}` };
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
} catch {
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const hint = pushOutput.split("\n").filter((l) => l.trim()).slice(-3).join(" | ").slice(0, 300);
|
|
2768
|
+
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}`);
|
|
2769
|
+
}
|
|
2770
|
+
async function mrList(context, flags) {
|
|
2771
|
+
rejectUnknownFlags(flags, ["state", "limit", "author", "repo"]);
|
|
2772
|
+
const taskId = context.parsed.positionals[1]?.trim();
|
|
2773
|
+
if (!taskId)
|
|
2774
|
+
throw usage("usage: morula repo mr list <taskId> [--state opened|all|merged|closed] [--limit <n>] [--author <username>] [--repo <owner/name>]");
|
|
2775
|
+
const state = flagString(flags, "state") ?? "opened";
|
|
2776
|
+
const limit = flagNumber(flags, "limit");
|
|
2777
|
+
const author = flagString(flags, "author");
|
|
2778
|
+
const repo = flagString(flags, "repo");
|
|
2779
|
+
const result = await context.client.http.get(
|
|
2780
|
+
`/agent-tasks/${taskId}/vcs/mr-list`,
|
|
2781
|
+
{
|
|
2782
|
+
state,
|
|
2783
|
+
...limit ? { limit } : {},
|
|
2784
|
+
...author ? { author } : {},
|
|
2785
|
+
...repo ? { repoFullName: repo } : {}
|
|
2786
|
+
}
|
|
2787
|
+
);
|
|
2788
|
+
if (result?.ok === false)
|
|
2789
|
+
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"}`);
|
|
2790
|
+
return result;
|
|
2791
|
+
}
|
|
2792
|
+
async function mrComment(context, flags) {
|
|
2793
|
+
rejectUnknownFlags(flags, ["mr", "repo", "connection", "content", "content-file"]);
|
|
2794
|
+
const taskId = context.parsed.positionals[1]?.trim();
|
|
2795
|
+
if (!taskId)
|
|
2796
|
+
throw usage("usage: morula repo mr comment <taskId> --mr <iid> [--repo <owner/name>] [--connection <id>] (--content <text> | --content-file <path>)");
|
|
2797
|
+
const mrIid = flagNumber(flags, "mr");
|
|
2798
|
+
if (mrIid == null || !Number.isInteger(mrIid) || mrIid <= 0)
|
|
2799
|
+
throw usage("--mr \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08MR/PR \u7F16\u53F7\uFF09");
|
|
2800
|
+
const repo = flagString(flags, "repo");
|
|
2801
|
+
const connection = flagString(flags, "connection");
|
|
2802
|
+
const inline = flagString(flags, "content");
|
|
2803
|
+
const filePath = flagString(flags, "content-file");
|
|
2804
|
+
if (inline && filePath)
|
|
2805
|
+
throw usage("--content \u4E0E --content-file \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A");
|
|
2806
|
+
if (!inline && !filePath)
|
|
2807
|
+
throw usage("\u8BC4\u8BBA\u6B63\u6587\u5FC5\u586B\uFF1A--content <text> \u6216 --content-file <path>");
|
|
2808
|
+
let body = inline ?? "";
|
|
2809
|
+
if (filePath) {
|
|
2810
|
+
try {
|
|
2811
|
+
const { readFile: readFile3 } = await import("node:fs/promises");
|
|
2812
|
+
body = await readFile3(filePath, "utf8");
|
|
2813
|
+
} catch (error) {
|
|
2814
|
+
throw new CliError(
|
|
2815
|
+
"CONTENT_FILE_UNREADABLE",
|
|
2816
|
+
EXIT.OTHER,
|
|
2817
|
+
`\u8BFB\u53D6 --content-file \u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
if (!body?.trim())
|
|
2822
|
+
throw usage("\u8BC4\u8BBA\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
|
|
2823
|
+
if (body.length > 2e4)
|
|
2824
|
+
throw usage(`\u8BC4\u8BBA\u6B63\u6587\u8D85\u8FC7 20000 \u5B57\u7B26\u4E0A\u9650\uFF1A\u957F\u5185\u5BB9\u8BF7\u7528 morula report create --content-file \u5199\u6210\u62A5\u544A\uFF0C\u8BC4\u8BBA\u91CC\u7ED9\u6458\u8981`);
|
|
2825
|
+
const result = await context.client.http.post(`/agent-tasks/${taskId}/vcs/mr-comment`, {
|
|
2826
|
+
mrIid,
|
|
2827
|
+
body,
|
|
2828
|
+
...repo ? { repoFullName: repo } : {},
|
|
2829
|
+
...connection ? { connectionId: connection } : {}
|
|
2830
|
+
});
|
|
2831
|
+
if (result?.ok === false) {
|
|
2832
|
+
throw new CliError(
|
|
2833
|
+
result.code ?? "MR_COMMENT_FAILED",
|
|
2834
|
+
exitFor(result.code),
|
|
2835
|
+
`MR \u8BC4\u8BBA\u5199\u5165\u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`
|
|
2836
|
+
);
|
|
2837
|
+
}
|
|
2838
|
+
return result;
|
|
2839
|
+
}
|
|
2840
|
+
function exitFor(code) {
|
|
2841
|
+
if (code === "TASK_NOT_FOUND" || code === "NO_CONNECTION")
|
|
2842
|
+
return EXIT.NOT_FOUND;
|
|
2843
|
+
if (code === "MR_NOT_ALLOWED" || code === "CONTENT_TOO_LARGE")
|
|
2844
|
+
return EXIT.USAGE;
|
|
2845
|
+
if (code === "MR_COMMENT_FAILED")
|
|
2846
|
+
return EXIT.UPSTREAM;
|
|
2847
|
+
return EXIT.OTHER;
|
|
2848
|
+
}
|
|
2849
|
+
async function mrGet(context, flags) {
|
|
2850
|
+
rejectUnknownFlags(flags, ["mr", "repo", "ci", "notes"]);
|
|
2851
|
+
const taskId = context.parsed.positionals[1]?.trim();
|
|
2852
|
+
if (!taskId)
|
|
2853
|
+
throw usage("usage: morula repo mr get <taskId> --mr <iid> [--repo <owner/name>] [--ci] [--notes]");
|
|
2854
|
+
const mrIid = flagNumber(flags, "mr");
|
|
2855
|
+
if (mrIid == null || !Number.isInteger(mrIid) || mrIid <= 0)
|
|
2856
|
+
throw usage("--mr \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08MR/PR \u7F16\u53F7\uFF09");
|
|
2857
|
+
const repo = flagString(flags, "repo");
|
|
2858
|
+
const ci = !!flags["ci"];
|
|
2859
|
+
const notes = !!flags["notes"];
|
|
2860
|
+
const result = await context.client.http.get(
|
|
2861
|
+
`/agent-tasks/${taskId}/vcs/mr-detail`,
|
|
2862
|
+
{
|
|
2863
|
+
mrIid,
|
|
2864
|
+
...repo ? { repoFullName: repo } : {},
|
|
2865
|
+
...ci ? { ci: 1 } : {},
|
|
2866
|
+
...notes ? { notes: 1 } : {}
|
|
2867
|
+
}
|
|
2868
|
+
);
|
|
2869
|
+
if (result?.ok === false)
|
|
2870
|
+
throw new CliError(result.code ?? "MR_QUERY_FAILED", EXIT.OTHER, `MR \u8BE6\u60C5\u67E5\u8BE2\u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
2871
|
+
return result;
|
|
2872
|
+
}
|
|
2873
|
+
async function mrDiff(context, flags) {
|
|
2874
|
+
rejectUnknownFlags(flags, ["mr", "repo", "max-files", "max-chars"]);
|
|
2875
|
+
const taskId = context.parsed.positionals[1]?.trim();
|
|
2876
|
+
if (!taskId)
|
|
2877
|
+
throw usage("usage: morula repo mr diff <taskId> --mr <iid> [--repo <owner/name>] [--max-files <n>] [--max-chars <n>]");
|
|
2878
|
+
const mrIid = flagNumber(flags, "mr");
|
|
2879
|
+
if (mrIid == null || !Number.isInteger(mrIid) || mrIid <= 0)
|
|
2880
|
+
throw usage("--mr \u5FC5\u987B\u662F\u6B63\u6574\u6570\uFF08MR/PR \u7F16\u53F7\uFF09");
|
|
2881
|
+
const repo = flagString(flags, "repo");
|
|
2882
|
+
const maxFiles = flagNumber(flags, "max-files");
|
|
2883
|
+
const maxChars = flagNumber(flags, "max-chars");
|
|
2884
|
+
if (maxFiles != null && maxFiles <= 0)
|
|
2885
|
+
throw usage("--max-files \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
2886
|
+
if (maxChars != null && maxChars <= 0)
|
|
2887
|
+
throw usage("--max-chars \u5FC5\u987B\u662F\u6B63\u6574\u6570");
|
|
2888
|
+
const result = await context.client.http.get(
|
|
2889
|
+
`/agent-tasks/${taskId}/vcs/mr-diff`,
|
|
2890
|
+
{
|
|
2891
|
+
mrIid,
|
|
2892
|
+
...repo ? { repoFullName: repo } : {},
|
|
2893
|
+
...maxFiles ? { maxFiles } : {},
|
|
2894
|
+
...maxChars ? { maxChars } : {}
|
|
2895
|
+
}
|
|
2896
|
+
);
|
|
2897
|
+
if (result?.ok === false)
|
|
2898
|
+
throw new CliError(result.code ?? "MR_QUERY_FAILED", EXIT.OTHER, `MR diff \u67E5\u8BE2\u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
2899
|
+
return result;
|
|
2900
|
+
}
|
|
2901
|
+
async function tryLocalCheckout(taskId, repoFullName, ref) {
|
|
2902
|
+
try {
|
|
2903
|
+
const { readFileSync: readFileSync2 } = await import("node:fs");
|
|
2904
|
+
const cfgPath = await resolveCheckoutConfigPath();
|
|
2905
|
+
if (!cfgPath)
|
|
2906
|
+
return null;
|
|
2907
|
+
const raw = JSON.parse(readFileSync2(cfgPath, "utf8"));
|
|
2908
|
+
if (raw.taskId !== taskId || typeof raw.port !== "number" || !raw.checkToken)
|
|
2909
|
+
return null;
|
|
2910
|
+
const res = await fetch(`http://127.0.0.1:${raw.port}/repo/checkout`, {
|
|
2911
|
+
method: "POST",
|
|
2912
|
+
headers: { "Content-Type": "application/json" },
|
|
2913
|
+
body: JSON.stringify({ taskId, repoFullName, ref, checkToken: raw.checkToken })
|
|
2914
|
+
});
|
|
2915
|
+
const body = await res.json().catch(() => null);
|
|
2916
|
+
if (!body)
|
|
2917
|
+
return null;
|
|
2918
|
+
if (body?.ok === false)
|
|
2919
|
+
throw new CliError(body.code ?? "REPO_CHECKOUT_FAILED", EXIT.OTHER, `\u672C\u5730 repo checkout \u5931\u8D25: ${body.error ?? body.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
2920
|
+
return { ok: true, local: true, path: body?.path, headSha: body?.headSha, branchName: body?.branchName ?? null };
|
|
2921
|
+
} catch (error) {
|
|
2922
|
+
if (error instanceof CliError)
|
|
2923
|
+
throw error;
|
|
2924
|
+
return null;
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
async function tryLocalPrepare(taskId, repoFullName) {
|
|
2928
|
+
try {
|
|
2929
|
+
const { readFileSync: readFileSync2 } = await import("node:fs");
|
|
2930
|
+
const cfgPath = await resolveCheckoutConfigPath();
|
|
2931
|
+
if (!cfgPath)
|
|
2932
|
+
return null;
|
|
2933
|
+
const raw = JSON.parse(readFileSync2(cfgPath, "utf8"));
|
|
2934
|
+
if (raw.taskId !== taskId || typeof raw.port !== "number" || !raw.checkToken)
|
|
2935
|
+
return null;
|
|
2936
|
+
const res = await fetch(`http://127.0.0.1:${raw.port}/repo/prepare`, {
|
|
2937
|
+
method: "POST",
|
|
2938
|
+
headers: { "Content-Type": "application/json" },
|
|
2939
|
+
body: JSON.stringify({ taskId, repoFullName, checkToken: raw.checkToken })
|
|
2940
|
+
});
|
|
2941
|
+
const body = await res.json().catch(() => null);
|
|
2942
|
+
if (!body)
|
|
2943
|
+
return null;
|
|
2944
|
+
if (body?.ok === false)
|
|
2945
|
+
throw new CliError(body.code ?? "REPO_PREPARE_FAILED", EXIT.OTHER, `\u672C\u5730 repo prepare \u5931\u8D25: ${body.error ?? body.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
2946
|
+
return { ok: true, local: true, path: body?.path, headSha: body?.headSha, baseBranch: body?.baseBranch ?? null, workContextId: body?.workContextId, created: body?.created ?? false };
|
|
2947
|
+
} catch (error) {
|
|
2948
|
+
if (error instanceof CliError)
|
|
2949
|
+
throw error;
|
|
2950
|
+
return null;
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2954
|
+
// ../morula-cli/src/commands/report.ts
|
|
2955
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
2956
|
+
async function reportCommand(context) {
|
|
2957
|
+
if (!context.runtime.token)
|
|
2958
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
2959
|
+
const { action, positionals, flags } = context.parsed;
|
|
2960
|
+
if (action === "create") {
|
|
2961
|
+
rejectUnknownFlags(flags, ["title", "content", "content-file", "space", "autopilot"]);
|
|
2962
|
+
const title = requireFlag(flags, "title");
|
|
2963
|
+
const content = await resolveContent(flags);
|
|
2964
|
+
const input = { title, content };
|
|
2965
|
+
const spaceId = flagString(flags, "space");
|
|
2966
|
+
if (spaceId)
|
|
2967
|
+
input.spaceId = spaceId;
|
|
2968
|
+
const autopilotId = flagString(flags, "autopilot");
|
|
2969
|
+
if (autopilotId)
|
|
2970
|
+
input.autopilotId = autopilotId;
|
|
2971
|
+
return context.client.reports.create(input);
|
|
2972
|
+
}
|
|
2973
|
+
if (action === "get") {
|
|
2974
|
+
rejectUnknownFlags(flags, []);
|
|
2975
|
+
const id = positionals[0]?.trim();
|
|
2976
|
+
if (!id)
|
|
2977
|
+
throw usage("usage: morula report get <id>");
|
|
2978
|
+
return context.client.reports.get(id);
|
|
2979
|
+
}
|
|
2980
|
+
if (action === "list") {
|
|
2981
|
+
rejectUnknownFlags(flags, ["autopilot", "page", "page-size"]);
|
|
2982
|
+
const autopilotId = flagString(flags, "autopilot");
|
|
2983
|
+
if (!autopilotId)
|
|
2984
|
+
throw usage("usage: morula report list --autopilot <id> [--page <n>] [--page-size <n>]");
|
|
2985
|
+
const query = {
|
|
2986
|
+
page: flagNumber(flags, "page"),
|
|
2987
|
+
pageSize: flagNumber(flags, "page-size")
|
|
2988
|
+
};
|
|
2989
|
+
return context.client.reports.listByAutopilot(autopilotId, query);
|
|
2990
|
+
}
|
|
2991
|
+
throw usage(`usage: morula report <create|get|list>
|
|
2992
|
+
|
|
2993
|
+
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
|
|
2994
|
+
get \u67E5\u770B\u62A5\u544A\u8BE6\u60C5\uFF08\u542B Markdown \u6B63\u6587\uFF09
|
|
2995
|
+
list \u5217\u51FA\u67D0\u81EA\u52A8\u5316\u7684\u62A5\u544A`);
|
|
2996
|
+
}
|
|
2997
|
+
async function resolveContent(flags) {
|
|
2998
|
+
const inline = flagString(flags, "content");
|
|
2999
|
+
const filePath = flagString(flags, "content-file");
|
|
3000
|
+
if (inline && filePath)
|
|
3001
|
+
throw usage("--content \u4E0E --content-file \u53EA\u80FD\u6307\u5B9A\u4E00\u4E2A");
|
|
3002
|
+
if (!inline && !filePath)
|
|
3003
|
+
throw usage("\u62A5\u544A\u6B63\u6587\u5FC5\u586B\uFF1A--content <markdown> \u6216 --content-file <path>");
|
|
3004
|
+
let content = inline;
|
|
3005
|
+
if (filePath) {
|
|
3006
|
+
try {
|
|
3007
|
+
content = await readFile2(filePath, "utf8");
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
throw new CliError(
|
|
3010
|
+
"CONTENT_FILE_UNREADABLE",
|
|
3011
|
+
EXIT.OTHER,
|
|
3012
|
+
`\u8BFB\u53D6 --content-file \u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
|
|
3013
|
+
);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
if (!content?.trim())
|
|
3017
|
+
throw usage("\u62A5\u544A\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
|
|
3018
|
+
return content;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
// ../morula-cli/src/commands/review.ts
|
|
3022
|
+
async function reviewCommand(context) {
|
|
3023
|
+
if (!context.runtime.token)
|
|
3024
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
3025
|
+
const { action, positionals, flags } = context.parsed;
|
|
3026
|
+
if (action === "submit") {
|
|
3027
|
+
rejectUnknownFlags(flags, ["verdict", "details", "pr-number"]);
|
|
3028
|
+
const objectId = positionals[0]?.trim();
|
|
3029
|
+
const verdict = flagString(flags, "verdict");
|
|
3030
|
+
if (!objectId)
|
|
3031
|
+
throw usage(`usage: morula review submit <objectId> --verdict <approved|changes_requested|commented> [--details <text>] [--pr-number <n>]
|
|
3032
|
+
|
|
3033
|
+
\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
|
|
3034
|
+
--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
|
|
3035
|
+
--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
|
|
3036
|
+
--details \u8BC4\u5BA1\u610F\u89C1\u6587\u672C\uFF08changes_requested \u5FC5\u586B\uFF1A\u6587\u4EF6/\u884C/\u95EE\u9898/\u5EFA\u8BAE\uFF09
|
|
3037
|
+
--pr-number \u8BC4\u5BA1\u5BF9\u8C61\u7684 PR \u7F16\u53F7\uFF08\u6709\u5219\u586B\uFF09
|
|
3038
|
+
\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`);
|
|
3039
|
+
if (!["approved", "changes_requested", "commented"].includes(verdict ?? ""))
|
|
3040
|
+
throw usage("--verdict \u5FC5\u987B\u662F approved / changes_requested / commented");
|
|
3041
|
+
const details = flagString(flags, "details");
|
|
3042
|
+
if (verdict === "changes_requested" && !details?.trim())
|
|
3043
|
+
throw usage("changes_requested \u5FC5\u987B\u63D0\u4F9B --details\uFF08\u5177\u4F53\u4FEE\u6539\u610F\u89C1\uFF09");
|
|
3044
|
+
const body = { objectId, verdict };
|
|
3045
|
+
if (details?.trim())
|
|
3046
|
+
body.details = details.trim();
|
|
3047
|
+
const prNumber = flagNumber(flags, "pr-number");
|
|
3048
|
+
if (prNumber)
|
|
3049
|
+
body.prNumber = prNumber;
|
|
3050
|
+
const result = await context.client.http.post(
|
|
3051
|
+
"/agent-collaborations/review/submit",
|
|
3052
|
+
body
|
|
3053
|
+
);
|
|
3054
|
+
if (result?.ok === false)
|
|
3055
|
+
throw new CliError(result.code ?? "REVIEW_SUBMIT_FAILED", EXIT.OTHER, `\u8BC4\u5BA1\u63D0\u4EA4\u5931\u8D25: ${result.message ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
3056
|
+
return { ok: true, reviewRound: result.reviewRound, verdict: result.verdict, reverted: result.reverted };
|
|
3057
|
+
}
|
|
3058
|
+
throw usage(`usage: morula review <submit>
|
|
3059
|
+
|
|
3060
|
+
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`);
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
// ../morula-cli/src/commands/space-member.ts
|
|
3064
|
+
async function spaceMemberCommand(context) {
|
|
3065
|
+
if (!context.runtime.token)
|
|
3066
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
3067
|
+
const { action, positionals, flags } = context.parsed;
|
|
3068
|
+
if (action === "list") {
|
|
3069
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
3070
|
+
const spaceId = requireFlag(flags, "space");
|
|
3071
|
+
return context.client.spaceMembers.list(spaceId);
|
|
3072
|
+
}
|
|
3073
|
+
if (action === "add") {
|
|
3074
|
+
rejectUnknownFlags(flags, ["space", "principal", "role"]);
|
|
3075
|
+
const spaceId = requireFlag(flags, "space");
|
|
3076
|
+
const principalId = requireFlag(flags, "principal");
|
|
3077
|
+
const input = compact2({
|
|
3078
|
+
spaceId,
|
|
3079
|
+
principalId,
|
|
3080
|
+
roleCode: flagString(flags, "role")
|
|
3081
|
+
});
|
|
3082
|
+
return context.client.spaceMembers.create(input);
|
|
3083
|
+
}
|
|
3084
|
+
if (action === "remove") {
|
|
3085
|
+
rejectUnknownFlags(flags, []);
|
|
3086
|
+
const id = positionals[0]?.trim();
|
|
3087
|
+
if (!id)
|
|
3088
|
+
throw usage("usage: morula space-member remove <member-id>");
|
|
3089
|
+
return context.client.spaceMembers.delete(id);
|
|
3090
|
+
}
|
|
3091
|
+
if (action === "update") {
|
|
3092
|
+
rejectUnknownFlags(flags, ["role"]);
|
|
3093
|
+
const id = positionals[0]?.trim();
|
|
3094
|
+
if (!id)
|
|
3095
|
+
throw usage("usage: morula space-member update <member-id>");
|
|
3096
|
+
const role = flagString(flags, "role");
|
|
3097
|
+
if (!role)
|
|
3098
|
+
throw usage("space-member update requires --role");
|
|
3099
|
+
return context.client.spaceMembers.update(id, { roleCode: role });
|
|
3100
|
+
}
|
|
3101
|
+
throw usage("usage: morula space-member <list|add|update|remove>");
|
|
3102
|
+
}
|
|
3103
|
+
function compact2(value) {
|
|
3104
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// ../morula-cli/src/commands/space.ts
|
|
3108
|
+
async function spaceCommand(context) {
|
|
3109
|
+
if (!context.runtime.token)
|
|
3110
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
3111
|
+
const { action, positionals, flags } = context.parsed;
|
|
3112
|
+
if (action === "list") {
|
|
3113
|
+
rejectUnknownFlags(flags, ["page", "page-size", "keyword"]);
|
|
3114
|
+
return context.client.spaces.list({
|
|
3115
|
+
page: flagNumber(flags, "page"),
|
|
3116
|
+
pageSize: flagNumber(flags, "page-size"),
|
|
3117
|
+
keyword: flagString(flags, "keyword")
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
if (action === "get") {
|
|
3121
|
+
rejectUnknownFlags(flags, []);
|
|
3122
|
+
const id = positionals[0]?.trim();
|
|
3123
|
+
if (!id)
|
|
3124
|
+
throw usage("usage: morula space get <id>");
|
|
3125
|
+
return context.client.spaces.get(id);
|
|
3126
|
+
}
|
|
3127
|
+
if (action === "create") {
|
|
3128
|
+
rejectUnknownFlags(flags, ["name", "description", "code", "visibility"]);
|
|
3129
|
+
return context.client.spaces.create({
|
|
3130
|
+
name: requireFlag(flags, "name"),
|
|
3131
|
+
description: flagString(flags, "description"),
|
|
3132
|
+
code: flagString(flags, "code"),
|
|
3133
|
+
visibility: flagString(flags, "visibility")
|
|
3134
|
+
});
|
|
3135
|
+
}
|
|
3136
|
+
if (action === "update") {
|
|
3137
|
+
rejectUnknownFlags(flags, ["name", "description", "visibility"]);
|
|
3138
|
+
const id = requireId3(positionals, "space update");
|
|
3139
|
+
const input = {
|
|
3140
|
+
name: flagString(flags, "name"),
|
|
3141
|
+
description: flagString(flags, "description"),
|
|
3142
|
+
visibility: flagString(flags, "visibility")
|
|
3143
|
+
};
|
|
3144
|
+
if (Object.values(input).every((value) => value === void 0))
|
|
3145
|
+
throw usage("space update \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B57\u6BB5\u65D7\u6807\uFF08--name / --description / --visibility\uFF09");
|
|
3146
|
+
return context.client.spaces.update(id, input);
|
|
3147
|
+
}
|
|
3148
|
+
if (action === "delete") {
|
|
3149
|
+
rejectUnknownFlags(flags, []);
|
|
3150
|
+
return context.client.spaces.delete(requireId3(positionals, "space delete"));
|
|
3151
|
+
}
|
|
3152
|
+
throw usage("usage: morula space <list|get|create|update|delete>");
|
|
3153
|
+
}
|
|
3154
|
+
function requireId3(positionals, command) {
|
|
3155
|
+
const id = positionals[0]?.trim();
|
|
3156
|
+
if (!id)
|
|
3157
|
+
throw usage(`usage: morula ${command} <id>`);
|
|
3158
|
+
return id;
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
// ../morula-cli/src/commands/spec.ts
|
|
3162
|
+
var SPEC = {
|
|
3163
|
+
name: "morula",
|
|
3164
|
+
summary: "Agent-first CLI for Morula task management (plugin surface, injected into task workspaces). Success output is JSON on stdout; errors are JSON on stderr.",
|
|
3165
|
+
resources: [
|
|
3166
|
+
{
|
|
3167
|
+
resource: "auth",
|
|
3168
|
+
summary: "SkyDoor authentication (shared config with morula-runtime)",
|
|
3169
|
+
actions: [
|
|
3170
|
+
{
|
|
3171
|
+
action: "login",
|
|
3172
|
+
summary: "browser SkyDoor SSO using PKCE and loopback callback (writes ~/.morula-cli/config.yaml)",
|
|
3173
|
+
flags: [{ name: "url", type: "string", valueHint: "<baseUrl>", description: "server base URL to log in to" }],
|
|
3174
|
+
examples: ["morula auth login"],
|
|
3175
|
+
writes: "local",
|
|
3176
|
+
interactive: true,
|
|
3177
|
+
emits: ["loggedIn", "context", "baseUrl", "expiresIn"]
|
|
3178
|
+
},
|
|
3179
|
+
{
|
|
3180
|
+
action: "logout",
|
|
3181
|
+
summary: "remove the current context token",
|
|
3182
|
+
flags: [],
|
|
3183
|
+
examples: ["morula auth logout"],
|
|
3184
|
+
writes: "local",
|
|
3185
|
+
idempotent: true,
|
|
3186
|
+
emits: ["loggedIn", "context"]
|
|
3187
|
+
},
|
|
3188
|
+
{
|
|
3189
|
+
action: "status",
|
|
3190
|
+
summary: "show context, endpoint, and authentication state",
|
|
3191
|
+
flags: [],
|
|
3192
|
+
examples: ["morula auth status"],
|
|
3193
|
+
writes: "none",
|
|
3194
|
+
idempotent: true,
|
|
3195
|
+
emits: ["loggedIn", "context", "baseUrl", "user", "expired"]
|
|
3196
|
+
}
|
|
3197
|
+
]
|
|
3198
|
+
},
|
|
3199
|
+
{
|
|
3200
|
+
resource: "task",
|
|
3201
|
+
summary: "task (business Object) read/write + AgentTask machine protocol (branch/state/analysis-verdict/inbox)",
|
|
3202
|
+
actions: [
|
|
3203
|
+
{
|
|
3204
|
+
action: "list",
|
|
3205
|
+
summary: "list tasks (--mine: \u4EC5\u6211\u8D1F\u8D23\u7684\uFF1B--field key=value \u81EA\u5B9A\u4E49\u5B57\u6BB5\u7B5B\u9009\uFF0C\u9700 --space)",
|
|
3206
|
+
flags: [
|
|
3207
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "space id" },
|
|
3208
|
+
{ name: "mine", type: "boolean", description: "\u4EC5\u6211\u8D1F\u8D23\u7684\uFF08\u4E0E --assignee \u4E92\u65A5\uFF09" },
|
|
3209
|
+
{ name: "assignee", type: "string", valueHint: "<principalId>", description: "\u6309\u8D1F\u8D23\u4EBA\u8FC7\u6EE4" },
|
|
3210
|
+
{ name: "principal", type: "string", valueHint: "<id>", description: "\u6309\u521B\u5EFA\u4EBA\u8FC7\u6EE4" },
|
|
3211
|
+
{ name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
|
|
3212
|
+
{ name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" },
|
|
3213
|
+
{ name: "search", type: "string", valueHint: "<text>", description: "\u5173\u952E\u8BCD\u641C\u7D22" },
|
|
3214
|
+
{ name: "priority", type: "string", valueHint: "<low|medium|high|urgent>", description: "\u4F18\u5148\u7EA7\u8FC7\u6EE4" },
|
|
3215
|
+
{ name: "state", type: "string", valueHint: "<id>", description: "\u72B6\u6001\u8FC7\u6EE4" },
|
|
3216
|
+
{ name: "sort", type: "string", valueHint: "<field>", description: "\u6392\u5E8F\u5B57\u6BB5" },
|
|
3217
|
+
{ name: "order", type: "string", valueHint: "<asc|desc>", description: "\u6392\u5E8F\u65B9\u5411" },
|
|
3218
|
+
{ name: "field", type: "string", valueHint: "key=value[,key=value]", description: "\u81EA\u5B9A\u4E49\u5B57\u6BB5\u7B49\u503C\u7B5B\u9009\uFF08\u591A\u6761\u4EF6 AND\uFF1B\u9700 --space \u89E3\u6790\u5B57\u6BB5\u6A21\u677F\uFF09" }
|
|
3219
|
+
],
|
|
3220
|
+
examples: ["morula task list --space <id>", "morula task list --mine", "morula task list --space <id> --field priority=p1"],
|
|
3221
|
+
writes: "none",
|
|
3222
|
+
idempotent: true
|
|
3223
|
+
},
|
|
3224
|
+
{
|
|
3225
|
+
action: "create",
|
|
3226
|
+
summary: "create task (\u5FC5\u586B --space/--title\uFF1B\u843D backlog \u7531\u670D\u52A1\u7AEF\u51B3\u5B9A\u521D\u59CB\u72B6\u6001)",
|
|
3227
|
+
flags: [
|
|
3228
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3229
|
+
{ name: "title", type: "string", valueHint: "<text>", description: "\u6807\u9898\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3230
|
+
{ name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0\uFF08\u683C\u5F0F\u81EA\u52A8\u68C0\u6D4B\uFF09" },
|
|
3231
|
+
{ name: "priority", type: "string", valueHint: "<low|medium|high|urgent>", description: "\u4F18\u5148\u7EA7" },
|
|
3232
|
+
{ name: "state", type: "string", valueHint: "<id>", description: "\u521D\u59CB\u72B6\u6001\uFF08\u4E1A\u52A1 Object state id\uFF09" },
|
|
3233
|
+
{ name: "owner", type: "string", valueHint: "<principalId>", description: "\u8D1F\u8D23\u4EBA\uFF08\u4EC5\u521B\u5EFA\u65F6\uFF09" },
|
|
3234
|
+
{ name: "assignee", type: "string", valueHint: "<principalId>", description: "\u7ECF\u529E\u4EBA" },
|
|
3235
|
+
{ name: "visibility", type: "string", valueHint: "<value>", description: "\u53EF\u89C1\u6027" },
|
|
3236
|
+
{ name: "estimate-hours", type: "number", valueHint: "<n>", description: "\u9884\u4F30\u5DE5\u65F6" },
|
|
3237
|
+
{ name: "schema", type: "string", valueHint: "<id>", description: "\u5B57\u6BB5\u6A21\u677F" },
|
|
3238
|
+
{ name: "sort-order", type: "number", valueHint: "<n>", description: "\u6392\u5E8F\u53F7" },
|
|
3239
|
+
{ name: "field", type: "string", valueHint: "key=value[,key=value]", description: "\u81EA\u5B9A\u4E49\u5B57\u6BB5 upsert\uFF08\u521B\u5EFA\u540E\u5199\u5165\uFF09" }
|
|
3240
|
+
],
|
|
3241
|
+
examples: ["morula task create --space <id> --title <text>"],
|
|
3242
|
+
writes: "remote"
|
|
3243
|
+
},
|
|
3244
|
+
{
|
|
3245
|
+
action: "get",
|
|
3246
|
+
summary: "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)",
|
|
3247
|
+
args: [{ name: "objectId", required: false, description: "\u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09ID\uFF1B\u7701\u7565 = \u5F53\u524D AgentTask \u8BE6\u60C5" }],
|
|
3248
|
+
flags: [],
|
|
3249
|
+
examples: ["morula task get", "morula task get <objectId>"],
|
|
3250
|
+
writes: "none",
|
|
3251
|
+
idempotent: true
|
|
3252
|
+
},
|
|
3253
|
+
{
|
|
3254
|
+
action: "update",
|
|
3255
|
+
summary: "update task (--state here is the business Object state id, NOT the AgentTask state code \u2014 see `task state`)",
|
|
3256
|
+
args: [{ name: "id", required: true, description: "\u4EFB\u52A1\uFF08Object\uFF09ID" }],
|
|
3257
|
+
flags: [
|
|
3258
|
+
{ name: "title", type: "string", valueHint: "<text>", description: "\u6807\u9898" },
|
|
3259
|
+
{ name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
|
|
3260
|
+
{ name: "priority", type: "string", valueHint: "<low|medium|high|urgent>", description: "\u4F18\u5148\u7EA7" },
|
|
3261
|
+
{ name: "state", type: "string", valueHint: "<id>", description: "\u72B6\u6001\uFF08\u4E1A\u52A1 Object state id\uFF09" },
|
|
3262
|
+
{ name: "assignee", type: "string", valueHint: "<principalId>", description: "\u7ECF\u529E\u4EBA" },
|
|
3263
|
+
{ name: "visibility", type: "string", valueHint: "<value>", description: "\u53EF\u89C1\u6027" },
|
|
3264
|
+
{ name: "estimate-hours", type: "number", valueHint: "<n>", description: "\u9884\u4F30\u5DE5\u65F6" },
|
|
3265
|
+
{ name: "schema", type: "string", valueHint: "<id>", description: "\u5B57\u6BB5\u6A21\u677F" },
|
|
3266
|
+
{ name: "sort-order", type: "number", valueHint: "<n>", description: "\u6392\u5E8F\u53F7" },
|
|
3267
|
+
{ name: "field", type: "string", valueHint: "key=value[,key=value]", description: "\u81EA\u5B9A\u4E49\u5B57\u6BB5 upsert\uFF08\u7A7A value = \u6E05\u7A7A\uFF09" }
|
|
3268
|
+
],
|
|
3269
|
+
examples: ["morula task update <id> --title <text>"],
|
|
3270
|
+
writes: "remote"
|
|
3271
|
+
},
|
|
3272
|
+
{
|
|
3273
|
+
action: "delete",
|
|
3274
|
+
summary: "hard delete task",
|
|
3275
|
+
args: [{ name: "id", required: true, description: "\u4EFB\u52A1\uFF08Object\uFF09ID" }],
|
|
3276
|
+
flags: [],
|
|
3277
|
+
examples: ["morula task delete <id>"],
|
|
3278
|
+
writes: "remote"
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
action: "branch",
|
|
3282
|
+
summary: "report the final branch name for an agent task (machine protocol; agent naming, runtimeId from env; --repo: secondary repo in a multi-repo task)",
|
|
3283
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08morula task get \u65E0\u53C2\u8FD4\u56DE\u7684 id\uFF09" }],
|
|
3284
|
+
flags: [
|
|
3285
|
+
{ name: "name", type: "string", valueHint: "<branch>", description: "\u5206\u652F\u540D\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3286
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u6B21\u4ED3\u5E93\uFF08\u591A\u4ED3\u5E93\u4EFB\u52A1\u7684\u5206\u652F\u9010\u4ED3\u4E0A\u62A5\uFF09" }
|
|
3287
|
+
],
|
|
3288
|
+
examples: ["morula task branch <taskId> --name <type>/<code>-<slug>"],
|
|
3289
|
+
writes: "remote"
|
|
3290
|
+
},
|
|
3291
|
+
{
|
|
3292
|
+
action: "state",
|
|
3293
|
+
summary: "self-transition task state (machine protocol; agent drives todo\u2192in_progress\u2192review, runtimeId from env)",
|
|
3294
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
|
|
3295
|
+
flags: [{ name: "state", type: "string", valueHint: "<in_progress|review>", description: "\u76EE\u6807\u72B6\u6001 code\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3296
|
+
examples: ["morula task state <taskId> --state in_progress"],
|
|
3297
|
+
writes: "remote"
|
|
3298
|
+
},
|
|
3299
|
+
{
|
|
3300
|
+
action: "analysis-verdict",
|
|
3301
|
+
summary: "report the task-analysis verdict (machine protocol; ready \u2192 platform auto-starts development, blocked \u2192 task stays in analysis)",
|
|
3302
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
|
|
3303
|
+
flags: [
|
|
3304
|
+
{ name: "verdict", type: "string", valueHint: "<ready|blocked>", enum: ["ready", "blocked"], description: "\u5206\u6790\u7ED3\u8BBA\uFF08\u5FC5\u586B\uFF1B\u4EC5\u4EFB\u52A1\u5206\u6790\u4EFB\u52A1\u53EF\u4E0A\u62A5\uFF09", required: true },
|
|
3305
|
+
{ name: "reason", type: "string", valueHint: "<text>", description: "blocked \u65F6\u7684\u963B\u585E/\u5F85\u6F84\u6E05\u539F\u56E0" }
|
|
3306
|
+
],
|
|
3307
|
+
examples: ["morula task analysis-verdict <taskId> --verdict ready"],
|
|
3308
|
+
writes: "remote"
|
|
3309
|
+
},
|
|
3310
|
+
{
|
|
3311
|
+
action: "inbox",
|
|
3312
|
+
summary: "checkpoint: pull this thread's unconsumed messages into the current turn (machine protocol; --peek reads without marking them consumed)",
|
|
3313
|
+
flags: [
|
|
3314
|
+
{ name: "peek", type: "boolean", description: "\u53EA\u8BFB\uFF0C\u4E0D\u6807\u8BB0\u6D88\u8D39" },
|
|
3315
|
+
{ name: "after-seq", type: "number", valueHint: "<n>", description: "\u4ECE\u8BE5 seq \u4E4B\u540E\u62C9\u53D6" }
|
|
3316
|
+
],
|
|
3317
|
+
examples: ["morula task inbox", "morula task inbox --peek"],
|
|
3318
|
+
writes: "remote"
|
|
3319
|
+
},
|
|
3320
|
+
{
|
|
3321
|
+
action: "agent-tasks",
|
|
3322
|
+
summary: "list the AgentTask executions of a business task (Object). AgentTask UUID \u2260 \u4E1A\u52A1\u4EFB\u52A1 id \u2014 cancel/rerun \u8981\u7684\u662F\u8FD9\u91CC\u7684 id",
|
|
3323
|
+
flags: [
|
|
3324
|
+
{ name: "object", type: "string", valueHint: "<objectId>", description: "\u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3325
|
+
{ name: "status", type: "string", valueHint: "<queued|dispatched|running|awaiting_approval|completed|failed|cancelled>", description: "\u6309\u5355\u4E2A\u72B6\u6001\u8FC7\u6EE4\uFF08\u7F3A\u7701 = \u5168\u90E8\uFF1B\u670D\u52A1\u7AEF\u4E0D\u505A\u679A\u4E3E\u6821\u9A8C\uFF0C\u975E\u6CD5\u503C\u53EA\u8FD4\u56DE\u7A7A\uFF09" },
|
|
3326
|
+
{ name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
|
|
3327
|
+
{ name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570\uFF08\u670D\u52A1\u7AEF\u7F3A\u7701 20\uFF09" }
|
|
3328
|
+
],
|
|
3329
|
+
examples: ["morula task agent-tasks --object <objectId>", "morula task agent-tasks --object <objectId> --status failed"],
|
|
3330
|
+
writes: "none",
|
|
3331
|
+
idempotent: true
|
|
3332
|
+
},
|
|
3333
|
+
{
|
|
3334
|
+
action: "cancel",
|
|
3335
|
+
summary: "cancel a running execution (POST /agent-tasks/:id/cancel). queued/dispatched/running/awaiting_approval/failed \u53EF\u53D6\u6D88\uFF1Bcompleted/cancelled \u4E0D\u53EF\uFF08409 CONFLICT\uFF09\uFF1B\u4EFB\u52A1\u4E0D\u5B58\u5728 404 NOT_FOUND\u3002\u6210\u529F\u8FD4\u56DE { ok: true }\u2014\u2014**\u975E\u96F6\u9000\u51FA\u5373\u672A\u53D6\u6D88**\uFF0C\u4E0D\u8981\u5411\u7528\u6237\u6C47\u62A5\u300C\u5DF2\u505C\u6389\u300D",
|
|
3336
|
+
args: [{ name: "taskId", required: true, description: "AgentTask UUID\uFF08\u6765\u81EA morula task agent-tasks\uFF09" }],
|
|
3337
|
+
flags: [],
|
|
3338
|
+
examples: ["morula task cancel <taskId>"],
|
|
3339
|
+
writes: "remote"
|
|
3340
|
+
},
|
|
3341
|
+
{
|
|
3342
|
+
action: "rerun",
|
|
3343
|
+
summary: "rerun a failed/cancelled execution (POST /agent-tasks/:id/rerun; copies the task and re-queues it \u2192 returns a NEW taskId). \u4EC5 failed/cancelled \u53EF\u91CD\u8DD1\uFF0C\u5176\u4ED6\u72B6\u6001 ok:false + code CONFLICT\uFF1B10s \u5185\u5DF2\u6709\u6D3B\u8DC3\u91CD\u8DD1\u4E3A ALREADY_QUEUED",
|
|
3344
|
+
args: [{ name: "taskId", required: true, description: "AgentTask UUID\uFF08\u6765\u81EA morula task agent-tasks\uFF09" }],
|
|
3345
|
+
flags: [],
|
|
3346
|
+
examples: ["morula task rerun <taskId>"],
|
|
3347
|
+
writes: "remote"
|
|
3348
|
+
}
|
|
3349
|
+
]
|
|
3350
|
+
},
|
|
3351
|
+
{
|
|
3352
|
+
resource: "space",
|
|
3353
|
+
summary: "space (project) read/write",
|
|
3354
|
+
actions: [
|
|
3355
|
+
{
|
|
3356
|
+
action: "list",
|
|
3357
|
+
summary: "list visible spaces",
|
|
3358
|
+
flags: [
|
|
3359
|
+
{ name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
|
|
3360
|
+
{ name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" },
|
|
3361
|
+
{ name: "keyword", type: "string", valueHint: "<text>", description: "\u5173\u952E\u8BCD" }
|
|
3362
|
+
],
|
|
3363
|
+
examples: ["morula space list"],
|
|
3364
|
+
writes: "none",
|
|
3365
|
+
idempotent: true
|
|
3366
|
+
},
|
|
3367
|
+
{
|
|
3368
|
+
action: "get",
|
|
3369
|
+
summary: "get space details",
|
|
3370
|
+
args: [{ name: "id", required: true, description: "\u7A7A\u95F4 ID" }],
|
|
3371
|
+
flags: [],
|
|
3372
|
+
examples: ["morula space get <id>"],
|
|
3373
|
+
writes: "none",
|
|
3374
|
+
idempotent: true
|
|
3375
|
+
},
|
|
3376
|
+
{
|
|
3377
|
+
action: "create",
|
|
3378
|
+
summary: "create space (project)",
|
|
3379
|
+
flags: [
|
|
3380
|
+
{ name: "name", type: "string", valueHint: "<text>", description: "\u7A7A\u95F4\u540D\u79F0\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3381
|
+
{ name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
|
|
3382
|
+
{ name: "code", type: "string", valueHint: "<code>", description: "\u7A7A\u95F4\u7F16\u53F7\uFF08\u7F3A\u7701\u7531\u670D\u52A1\u7AEF\u751F\u6210\uFF09" },
|
|
3383
|
+
{ name: "visibility", type: "string", valueHint: "<private|internal|public>", enum: ["private", "internal", "public"], description: "\u53EF\u89C1\u6027" }
|
|
3384
|
+
],
|
|
3385
|
+
examples: ["morula space create --name <text>"],
|
|
3386
|
+
writes: "remote"
|
|
3387
|
+
},
|
|
3388
|
+
{
|
|
3389
|
+
action: "update",
|
|
3390
|
+
summary: "update space (\u81F3\u5C11\u4E00\u4E2A\u5B57\u6BB5\u65D7\u6807)",
|
|
3391
|
+
args: [{ name: "id", required: true, description: "\u7A7A\u95F4 ID" }],
|
|
3392
|
+
flags: [
|
|
3393
|
+
{ name: "name", type: "string", valueHint: "<text>", description: "\u7A7A\u95F4\u540D\u79F0" },
|
|
3394
|
+
{ name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
|
|
3395
|
+
{ name: "visibility", type: "string", valueHint: "<private|internal|public>", enum: ["private", "internal", "public"], description: "\u53EF\u89C1\u6027" }
|
|
3396
|
+
],
|
|
3397
|
+
examples: ["morula space update <id> --name <text>"],
|
|
3398
|
+
writes: "remote"
|
|
3399
|
+
},
|
|
3400
|
+
{
|
|
3401
|
+
action: "delete",
|
|
3402
|
+
summary: "delete space",
|
|
3403
|
+
args: [{ name: "id", required: true, description: "\u7A7A\u95F4 ID" }],
|
|
3404
|
+
flags: [],
|
|
3405
|
+
examples: ["morula space delete <id>"],
|
|
3406
|
+
writes: "remote"
|
|
3407
|
+
}
|
|
3408
|
+
]
|
|
3409
|
+
},
|
|
3410
|
+
{
|
|
3411
|
+
resource: "space-member",
|
|
3412
|
+
summary: "space member management (admin operations)",
|
|
3413
|
+
actions: [
|
|
3414
|
+
{
|
|
3415
|
+
action: "list",
|
|
3416
|
+
summary: "list space members",
|
|
3417
|
+
flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3418
|
+
examples: ["morula space-member list --space <id>"],
|
|
3419
|
+
writes: "none",
|
|
3420
|
+
idempotent: true
|
|
3421
|
+
},
|
|
3422
|
+
{
|
|
3423
|
+
action: "add",
|
|
3424
|
+
summary: "add member to space",
|
|
3425
|
+
flags: [
|
|
3426
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3427
|
+
{ name: "principal", type: "string", valueHint: "<id>", description: "\u6210\u5458 principalId\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3428
|
+
{ name: "role", type: "string", valueHint: "<admin|member>", enum: ["admin", "member"], description: "\u89D2\u8272\uFF08\u7F3A\u7701 member\uFF09" }
|
|
3429
|
+
],
|
|
3430
|
+
examples: ["morula space-member add --space <id> --principal <id>"],
|
|
3431
|
+
writes: "remote"
|
|
3432
|
+
},
|
|
3433
|
+
{
|
|
3434
|
+
action: "update",
|
|
3435
|
+
summary: "update member role",
|
|
3436
|
+
args: [{ name: "memberId", required: true, description: "\u6210\u5458\u884C ID" }],
|
|
3437
|
+
flags: [{ name: "role", type: "string", valueHint: "<admin|member>", enum: ["admin", "member"], description: "\u65B0\u89D2\u8272\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3438
|
+
examples: ["morula space-member update <memberId> --role admin"],
|
|
3439
|
+
writes: "remote"
|
|
3440
|
+
},
|
|
3441
|
+
{
|
|
3442
|
+
action: "remove",
|
|
3443
|
+
summary: "remove member from space",
|
|
3444
|
+
args: [{ name: "memberId", required: true, description: "\u6210\u5458\u884C ID" }],
|
|
3445
|
+
flags: [],
|
|
3446
|
+
examples: ["morula space-member remove <memberId>"],
|
|
3447
|
+
writes: "remote"
|
|
3448
|
+
}
|
|
3449
|
+
]
|
|
3450
|
+
},
|
|
3451
|
+
{
|
|
3452
|
+
resource: "steward",
|
|
3453
|
+
summary: "space steward (project-level agent): read takeover state / dispatch a task with an explicit target state",
|
|
3454
|
+
actions: [
|
|
3455
|
+
{
|
|
3456
|
+
action: "get",
|
|
3457
|
+
summary: "get the steward record of a space (null = \u672A\u63A5\u7BA1)",
|
|
3458
|
+
flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4\uFF08\u9879\u76EE\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3459
|
+
examples: ["morula steward get --space <id>"],
|
|
3460
|
+
writes: "none",
|
|
3461
|
+
idempotent: true
|
|
3462
|
+
},
|
|
3463
|
+
{
|
|
3464
|
+
action: "dispatch",
|
|
3465
|
+
summary: "dispatch a task: create the task and hand it to the pipeline. --state backlog = \u843D\u5F85\u89C4\u5212\uFF08\u4E0D\u6D3E\u53D1\u6267\u884C\uFF0C\u670D\u52A1\u7AEF\u4E0D\u5EFA AgentTask\uFF09\uFF1B\u7F3A\u7701 todo\uFF08\u5F85\u5206\u6790\uFF09",
|
|
3466
|
+
flags: [
|
|
3467
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4\uFF08\u9879\u76EE\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3468
|
+
{ name: "title", type: "string", valueHint: "<text>", description: "\u4EFB\u52A1\u6807\u9898\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3469
|
+
{ name: "description", type: "string", valueHint: "<text>", description: "\u4EFB\u52A1\u63CF\u8FF0" },
|
|
3470
|
+
{ name: "state", type: "string", valueHint: "<backlog|todo|in_progress>", enum: ["backlog", "todo", "in_progress"], description: "\u76EE\u6807\u521D\u59CB\u72B6\u6001\uFF08\u7F3A\u7701 todo\uFF09" },
|
|
3471
|
+
{ name: "assignee", type: "string", valueHint: "<principalId>", description: "\u6307\u5B9A\u6267\u884C\u8005" },
|
|
3472
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u6307\u5B9A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u7531\u670D\u52A1\u7AEF\u89E3\u6790\uFF09" },
|
|
3473
|
+
{ name: "client-message-id", type: "string", valueHint: "<id>", description: "\u5E42\u7B49\u952E\uFF1A\u540C\u4E00\u952E\u91CD\u590D\u6D3E\u5355\u8FD4\u56DE\u539F\u7ED3\u679C" }
|
|
3474
|
+
],
|
|
3475
|
+
examples: ["morula steward dispatch --space <id> --title <text>", "morula steward dispatch --space <id> --title <text> --state backlog"],
|
|
3476
|
+
writes: "remote"
|
|
3477
|
+
}
|
|
3478
|
+
]
|
|
3479
|
+
},
|
|
3480
|
+
{
|
|
3481
|
+
resource: "autopilot",
|
|
3482
|
+
summary: "agent autopilots (scheduled/webhook-triggered automated runs; list only shows your own)",
|
|
3483
|
+
actions: [
|
|
3484
|
+
{
|
|
3485
|
+
action: "list",
|
|
3486
|
+
summary: "list autopilots created by the current user",
|
|
3487
|
+
flags: [
|
|
3488
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u6309\u7A7A\u95F4\u8FC7\u6EE4" },
|
|
3489
|
+
{ name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
|
|
3490
|
+
{ name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" }
|
|
3491
|
+
],
|
|
3492
|
+
examples: ["morula autopilot list", "morula autopilot list --space <id>"],
|
|
3493
|
+
writes: "none",
|
|
3494
|
+
idempotent: true
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
action: "create",
|
|
3498
|
+
summary: "create an autopilot (--executor is a runtime key; the server currently only accepts `dim`)",
|
|
3499
|
+
flags: [
|
|
3500
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u5F52\u5C5E\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3501
|
+
{ name: "name", type: "string", valueHint: "<text>", description: "\u540D\u79F0\uFF08\u540C\u79DF\u6237\u552F\u4E00\uFF0C\u5FC5\u586B\uFF09", required: true },
|
|
3502
|
+
{ name: "runbook", type: "string", valueHint: "<text>", description: "Runbook\uFF1A\u6BCF\u6B21\u89E6\u53D1\u7ED9\u8FD0\u884C\u65F6\u7684\u76EE\u6807/\u80CC\u666F/\u6B65\u9AA4\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3503
|
+
{ name: "executor", type: "string", valueHint: "<key>", description: "\u6267\u884C\u8005\u8FD0\u884C\u65F6 key\uFF08\u5FC5\u586B\uFF1B\u5F53\u524D\u4EC5 dim\uFF09", required: true },
|
|
3504
|
+
{ name: "description", type: "string", valueHint: "<text>", description: "\u63CF\u8FF0" },
|
|
3505
|
+
{ name: "cron", type: "string", valueHint: "<expr>", description: "cron \u8868\u8FBE\u5F0F\uFF085 \u5B57\u6BB5\uFF09\uFF1B\u7F3A\u7701 = \u4EC5 webhook \u89E6\u53D1" },
|
|
3506
|
+
{ name: "vcs-connection", type: "string", valueHint: "<id>", description: "\u9650\u5B9A\u4ED3\u5E93\u8FDE\u63A5\uFF08\u7F3A\u7701\u4E0D\u9650\uFF09" }
|
|
3507
|
+
],
|
|
3508
|
+
examples: ["morula autopilot create --space <id> --name <text> --runbook <text> --executor dim"],
|
|
3509
|
+
writes: "remote"
|
|
3510
|
+
}
|
|
3511
|
+
]
|
|
3512
|
+
},
|
|
3513
|
+
{
|
|
3514
|
+
resource: "dashboard",
|
|
3515
|
+
summary: "space dashboard / statistics / member workload (read-only aggregates)",
|
|
3516
|
+
actions: [
|
|
3517
|
+
{
|
|
3518
|
+
action: "get",
|
|
3519
|
+
summary: "get the space dashboard aggregate",
|
|
3520
|
+
flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3521
|
+
examples: ["morula dashboard get --space <id>"],
|
|
3522
|
+
writes: "none",
|
|
3523
|
+
idempotent: true
|
|
3524
|
+
},
|
|
3525
|
+
{
|
|
3526
|
+
action: "statistics",
|
|
3527
|
+
summary: "get space task statistics",
|
|
3528
|
+
flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3529
|
+
examples: ["morula dashboard statistics --space <id>"],
|
|
3530
|
+
writes: "none",
|
|
3531
|
+
idempotent: true
|
|
3532
|
+
},
|
|
3533
|
+
{
|
|
3534
|
+
action: "workload",
|
|
3535
|
+
summary: "get per-member workload in a space",
|
|
3536
|
+
flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3537
|
+
examples: ["morula dashboard workload --space <id>"],
|
|
3538
|
+
writes: "none",
|
|
3539
|
+
idempotent: true
|
|
3540
|
+
}
|
|
3541
|
+
]
|
|
3542
|
+
},
|
|
3543
|
+
{
|
|
3544
|
+
resource: "object-meta",
|
|
3545
|
+
summary: "task-level agent metadata (D-06 high-signal keys only)",
|
|
3546
|
+
actions: [
|
|
3547
|
+
{
|
|
3548
|
+
action: "get",
|
|
3549
|
+
summary: "read all agent metadata of a task",
|
|
3550
|
+
flags: [{ name: "object", type: "string", valueHint: "<id>", description: "\u4EFB\u52A1\uFF08Object\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3551
|
+
examples: ["morula object-meta get --object <id>"],
|
|
3552
|
+
writes: "none",
|
|
3553
|
+
idempotent: true
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
action: "set",
|
|
3557
|
+
summary: "write one agent metadata key. Key must be in the high-signal whitelist: pr_url / pr_number / pipeline_status / deploy_url / waiting_on / blocked_reason / decision",
|
|
3558
|
+
flags: [
|
|
3559
|
+
{ name: "object", type: "string", valueHint: "<id>", description: "\u4EFB\u52A1\uFF08Object\uFF09ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3560
|
+
{ name: "key", type: "string", valueHint: "<key>", description: "\u5143\u6570\u636E\u952E\uFF08\u5FC5\u586B\uFF0C\u767D\u540D\u5355\uFF09", required: true },
|
|
3561
|
+
{ name: "value", type: "string", valueHint: "<text>", description: "\u503C\uFF08\u5FC5\u586B\uFF0C\u975E\u7A7A\uFF09", required: true }
|
|
3562
|
+
],
|
|
3563
|
+
examples: ["morula object-meta set --object <id> --key pr_url --value <url>"],
|
|
3564
|
+
writes: "remote"
|
|
3565
|
+
}
|
|
3566
|
+
]
|
|
3567
|
+
},
|
|
3568
|
+
{
|
|
3569
|
+
resource: "document",
|
|
3570
|
+
summary: "document artifacts (ag_artifact; id is a numeric auto-increment id, not a uuid)",
|
|
3571
|
+
actions: [
|
|
3572
|
+
{
|
|
3573
|
+
action: "list",
|
|
3574
|
+
summary: "list document artifacts",
|
|
3575
|
+
flags: [
|
|
3576
|
+
{ name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
|
|
3577
|
+
{ name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" }
|
|
3578
|
+
],
|
|
3579
|
+
examples: ["morula document list"],
|
|
3580
|
+
writes: "none",
|
|
3581
|
+
idempotent: true
|
|
3582
|
+
},
|
|
3583
|
+
{
|
|
3584
|
+
action: "get",
|
|
3585
|
+
summary: "get document metadata (\u4E0D\u542B\u5B8C\u6574\u6B63\u6587)",
|
|
3586
|
+
args: [{ name: "id", required: true, description: "\u6587\u6863 ID\uFF08\u6570\u5B57\uFF0C\u6765\u81EA document list\uFF09" }],
|
|
3587
|
+
flags: [],
|
|
3588
|
+
examples: ["morula document get <id>"],
|
|
3589
|
+
writes: "none",
|
|
3590
|
+
idempotent: true
|
|
3591
|
+
},
|
|
3592
|
+
{
|
|
3593
|
+
action: "artifacts",
|
|
3594
|
+
summary: "read the parsed document content (markdown)",
|
|
3595
|
+
args: [{ name: "id", required: true, description: "\u6587\u6863 ID\uFF08\u6570\u5B57\uFF0C\u6765\u81EA document list\uFF09" }],
|
|
3596
|
+
flags: [],
|
|
3597
|
+
examples: ["morula document artifacts <id>"],
|
|
3598
|
+
writes: "none",
|
|
3599
|
+
idempotent: true
|
|
3600
|
+
}
|
|
3601
|
+
]
|
|
3602
|
+
},
|
|
3603
|
+
{
|
|
3604
|
+
resource: "comment",
|
|
3605
|
+
summary: "object comments (agent summaries live here)",
|
|
3606
|
+
actions: [
|
|
3607
|
+
{
|
|
3608
|
+
action: "list",
|
|
3609
|
+
summary: "list comments for an object",
|
|
3610
|
+
flags: [{ name: "object", type: "string", valueHint: "<id>", description: "\u5BF9\u8C61 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3611
|
+
examples: ["morula comment list --object <id>"],
|
|
3612
|
+
writes: "none",
|
|
3613
|
+
idempotent: true
|
|
3614
|
+
},
|
|
3615
|
+
{
|
|
3616
|
+
action: "get",
|
|
3617
|
+
summary: "get comment details",
|
|
3618
|
+
args: [{ name: "id", required: true, description: "\u8BC4\u8BBA ID" }],
|
|
3619
|
+
flags: [],
|
|
3620
|
+
examples: ["morula comment get <id>"],
|
|
3621
|
+
writes: "none",
|
|
3622
|
+
idempotent: true
|
|
3623
|
+
},
|
|
3624
|
+
{
|
|
3625
|
+
action: "add",
|
|
3626
|
+
summary: "add comment (format auto-detected; use markdown for agent final summaries). --mention \u53EF @ \u4EBA\uFF08\u89E6\u53D1\u901A\u77E5/\u50AC\u529E\uFF09",
|
|
3627
|
+
flags: [
|
|
3628
|
+
{ name: "object", type: "string", valueHint: "<id>", description: "\u5BF9\u8C61 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3629
|
+
{ name: "content", type: "string", valueHint: "<text>", description: "\u8BC4\u8BBA\u5185\u5BB9\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3630
|
+
{ name: "parent", type: "string", valueHint: "<id>", description: "\u7236\u8BC4\u8BBA\uFF08\u56DE\u590D\uFF09" },
|
|
3631
|
+
{ name: "format", type: "string", valueHint: "<markdown|plain|richtext>", enum: ["markdown", "plain", "richtext"], description: "\u5185\u5BB9\u683C\u5F0F\uFF08\u7F3A\u7701\u81EA\u52A8\u68C0\u6D4B\uFF09" },
|
|
3632
|
+
{ name: "mention", type: "string", valueHint: "<principalId[,principalId]>", repeatable: true, description: "@ \u4EBA\uFF1A\u88AB\u63D0\u53CA\u8005\u7684 principalId\uFF0C\u53EF\u91CD\u590D\u4F20\uFF08--mention a --mention b\uFF09\u6216\u7528\u9017\u53F7\u5206\u9694\u591A\u4E2A\uFF08\u4E24\u79CD\u6DF7\u7528\u81EA\u52A8\u5408\u5E76\u53BB\u91CD\u4FDD\u5E8F\uFF09\uFF1B\u53EA\u652F\u6301\u4EBA\uFF0C\u4E0D\u652F\u6301 @ \u8FD0\u884C\u65F6/\u667A\u80FD\u4F53" }
|
|
3633
|
+
],
|
|
3634
|
+
examples: ["morula comment add --object <id> --content <text>", "morula comment add --object <id> --content <text> --mention <principalId>", "morula comment add --object <id> --content <text> --mention <principalIdA> --mention <principalIdB>"],
|
|
3635
|
+
writes: "remote"
|
|
3636
|
+
},
|
|
3637
|
+
{
|
|
3638
|
+
action: "update",
|
|
3639
|
+
summary: "update comment",
|
|
3640
|
+
args: [{ name: "id", required: true, description: "\u8BC4\u8BBA ID" }],
|
|
3641
|
+
flags: [{ name: "content", type: "string", valueHint: "<text>", description: "\u65B0\u5185\u5BB9\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3642
|
+
examples: ["morula comment update <id> --content <text>"],
|
|
3643
|
+
writes: "remote"
|
|
3644
|
+
},
|
|
3645
|
+
{
|
|
3646
|
+
action: "delete",
|
|
3647
|
+
summary: "hard delete comment",
|
|
3648
|
+
args: [{ name: "id", required: true, description: "\u8BC4\u8BBA ID" }],
|
|
3649
|
+
flags: [],
|
|
3650
|
+
examples: ["morula comment delete <id>"],
|
|
3651
|
+
writes: "remote"
|
|
3652
|
+
}
|
|
3653
|
+
]
|
|
3654
|
+
},
|
|
3655
|
+
{
|
|
3656
|
+
resource: "review",
|
|
3657
|
+
summary: "Code Review verdict submission (review task machine protocol)",
|
|
3658
|
+
actions: [
|
|
3659
|
+
{
|
|
3660
|
+
action: "submit",
|
|
3661
|
+
summary: "submit Code Review verdict (machine protocol; review task token)",
|
|
3662
|
+
args: [{ name: "objectId", required: true, description: "\u8BC4\u5BA1\u5BF9\u8C61 ID\uFF08\u7CFB\u7EDF\u63D0\u793A\u300C\u8BC4\u5BA1\u5BF9\u8C61\u300D\uFF09" }],
|
|
3663
|
+
flags: [
|
|
3664
|
+
{ name: "verdict", type: "string", valueHint: "<approved|changes_requested|commented>", enum: ["approved", "changes_requested", "commented"], description: "\u8BC4\u5BA1\u7ED3\u8BBA\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3665
|
+
{ name: "details", type: "string", valueHint: "<text>", description: "\u8BC4\u5BA1\u610F\u89C1\uFF08changes_requested \u5FC5\u586B\uFF1A\u6587\u4EF6/\u884C/\u95EE\u9898/\u5EFA\u8BAE\uFF09" },
|
|
3666
|
+
{ name: "pr-number", type: "number", valueHint: "<n>", description: "\u8BC4\u5BA1\u5BF9\u8C61\u7684 PR \u7F16\u53F7\uFF08\u6709\u5219\u586B\uFF09" }
|
|
3667
|
+
],
|
|
3668
|
+
examples: ["morula review submit <objectId> --verdict approved"],
|
|
3669
|
+
writes: "remote"
|
|
3670
|
+
}
|
|
3671
|
+
]
|
|
3672
|
+
},
|
|
3673
|
+
{
|
|
3674
|
+
resource: "report",
|
|
3675
|
+
summary: "autopilot reports (conclusion-heavy output of automated runs)",
|
|
3676
|
+
actions: [
|
|
3677
|
+
{
|
|
3678
|
+
action: "create",
|
|
3679
|
+
summary: "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)",
|
|
3680
|
+
flags: [
|
|
3681
|
+
{ name: "title", type: "string", valueHint: "<text>", description: "\u6807\u9898\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3682
|
+
{ name: "content", type: "string", valueHint: "<markdown>", description: "\u77ED\u6B63\u6587\uFF08\u4E0E --content-file \u4E8C\u9009\u4E00\uFF09" },
|
|
3683
|
+
{ name: "content-file", type: "string", valueHint: "<path>", description: "\u957F Markdown \u62A5\u544A\u6587\u4EF6\uFF08\u4E0E --content \u4E8C\u9009\u4E00\uFF09" },
|
|
3684
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u5F52\u5C5E\u7A7A\u95F4" },
|
|
3685
|
+
{ name: "autopilot", type: "string", valueHint: "<id>", description: "\u6240\u5C5E\u81EA\u52A8\u5316\uFF08\u7701\u7565\u65F6\u6309\u5F53\u524D\u4EFB\u52A1\u53CD\u67E5\uFF09" }
|
|
3686
|
+
],
|
|
3687
|
+
examples: ["morula report create --title <t> --content-file <path>"],
|
|
3688
|
+
writes: "remote"
|
|
3689
|
+
},
|
|
3690
|
+
{
|
|
3691
|
+
action: "get",
|
|
3692
|
+
summary: "get report details (\u542B Markdown \u6B63\u6587)",
|
|
3693
|
+
args: [{ name: "id", required: true, description: "\u62A5\u544A ID" }],
|
|
3694
|
+
flags: [],
|
|
3695
|
+
examples: ["morula report get <id>"],
|
|
3696
|
+
writes: "none",
|
|
3697
|
+
idempotent: true
|
|
3698
|
+
},
|
|
3699
|
+
{
|
|
3700
|
+
action: "list",
|
|
3701
|
+
summary: "list reports of an autopilot",
|
|
3702
|
+
flags: [
|
|
3703
|
+
{ name: "autopilot", type: "string", valueHint: "<id>", description: "\u81EA\u52A8\u5316 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3704
|
+
{ name: "page", type: "number", valueHint: "<n>", description: "\u9875\u7801" },
|
|
3705
|
+
{ name: "page-size", type: "number", valueHint: "<n>", description: "\u6BCF\u9875\u6761\u6570" }
|
|
3706
|
+
],
|
|
3707
|
+
examples: ["morula report list --autopilot <id>"],
|
|
3708
|
+
writes: "none",
|
|
3709
|
+
idempotent: true
|
|
3710
|
+
}
|
|
3711
|
+
]
|
|
3712
|
+
},
|
|
3713
|
+
{
|
|
3714
|
+
resource: "repo",
|
|
3715
|
+
summary: "task-space VCS repos: materialize checkout / MR read & comment (server channel; never approves/merges)",
|
|
3716
|
+
actions: [
|
|
3717
|
+
{
|
|
3718
|
+
action: "list",
|
|
3719
|
+
summary: "list task-space VCS connections (the space repo list \u2014 the task prompt does NOT list repos; query this when a task may span multiple repos)",
|
|
3720
|
+
flags: [{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3721
|
+
examples: ["morula repo list --space <id>"],
|
|
3722
|
+
writes: "none",
|
|
3723
|
+
idempotent: true
|
|
3724
|
+
},
|
|
3725
|
+
{
|
|
3726
|
+
action: "checkout",
|
|
3727
|
+
summary: "materialize a repo into the task workdir (local daemon endpoint preferred, works same-turn; falls back to server machine protocol which resolves scope + workContext)",
|
|
3728
|
+
args: [
|
|
3729
|
+
{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" },
|
|
3730
|
+
{ name: "repoFullName", required: true, description: "\u4ED3\u5E93\u5168\u540D\uFF08owner/name\uFF09" }
|
|
3731
|
+
],
|
|
3732
|
+
flags: [{ name: "ref", type: "string", valueHint: "<branch|sha>", description: "\u68C0\u51FA\u5F15\u7528\uFF08\u7F3A\u7701 = \u7ED1\u5B9A\u5206\u652F\uFF09" }],
|
|
3733
|
+
examples: ["morula repo checkout <taskId> <owner/name>"],
|
|
3734
|
+
writes: "local",
|
|
3735
|
+
idempotent: true
|
|
3736
|
+
},
|
|
3737
|
+
{
|
|
3738
|
+
action: "prepare",
|
|
3739
|
+
summary: "materialize a secondary repo into the task workdir (same level as the primary repo dir)",
|
|
3740
|
+
args: [
|
|
3741
|
+
{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" },
|
|
3742
|
+
{ name: "repoFullName", required: true, description: "\u6B21\u4ED3\u5E93\u5168\u540D\uFF08owner/name\uFF09" }
|
|
3743
|
+
],
|
|
3744
|
+
flags: [],
|
|
3745
|
+
examples: ["morula repo prepare <taskId> <owner/name>"],
|
|
3746
|
+
writes: "local",
|
|
3747
|
+
idempotent: true
|
|
3748
|
+
},
|
|
3749
|
+
{
|
|
3750
|
+
action: "files",
|
|
3751
|
+
summary: "list files in a space repo via the server (read-only, non-task: no local checkout, no AgentTask context)",
|
|
3752
|
+
flags: [
|
|
3753
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3754
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u4ED3\u5E93\u5168\u540D\uFF08\u7F3A\u7701 = \u7A7A\u95F4\u4E3B\u4ED3\u5E93\uFF09" },
|
|
3755
|
+
{ name: "path", type: "string", valueHint: "<path>", description: "\u76EE\u5F55\u8DEF\u5F84\uFF08\u7F3A\u7701 = \u4ED3\u5E93\u6839\uFF09" },
|
|
3756
|
+
{ name: "ref", type: "string", valueHint: "<branch|sha>", description: "\u5206\u652F / tag / commit\uFF08\u7F3A\u7701 = \u9ED8\u8BA4\u5206\u652F\uFF09" }
|
|
3757
|
+
],
|
|
3758
|
+
examples: ["morula repo files --space <id>", "morula repo files --space <id> --path src"],
|
|
3759
|
+
writes: "none",
|
|
3760
|
+
idempotent: true
|
|
3761
|
+
},
|
|
3762
|
+
{
|
|
3763
|
+
action: "read",
|
|
3764
|
+
summary: "read a single file from a space repo via the server (read-only, non-task)",
|
|
3765
|
+
flags: [
|
|
3766
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3767
|
+
{ name: "path", type: "string", valueHint: "<path>", description: "\u6587\u4EF6\u8DEF\u5F84\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3768
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u4ED3\u5E93\u5168\u540D\uFF08\u7F3A\u7701 = \u7A7A\u95F4\u4E3B\u4ED3\u5E93\uFF09" },
|
|
3769
|
+
{ name: "ref", type: "string", valueHint: "<branch|sha>", description: "\u5206\u652F / tag / commit\uFF08\u7F3A\u7701 = \u9ED8\u8BA4\u5206\u652F\uFF09" }
|
|
3770
|
+
],
|
|
3771
|
+
examples: ["morula repo read --space <id> --path src/index.ts"],
|
|
3772
|
+
writes: "none",
|
|
3773
|
+
idempotent: true
|
|
3774
|
+
},
|
|
3775
|
+
{
|
|
3776
|
+
action: "mr create",
|
|
3777
|
+
summary: "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)",
|
|
3778
|
+
args: [{ name: "repoFullName", required: true, description: "\u4ED3\u5E93\u5168\u540D\uFF08owner/name\uFF09" }],
|
|
3779
|
+
flags: [
|
|
3780
|
+
{ name: "branch", type: "string", valueHint: "<branch>", description: "\u6E90\u5206\u652F\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3781
|
+
{ name: "base", type: "string", valueHint: "<base>", description: "\u76EE\u6807\u5206\u652F\uFF08\u7F3A\u7701 MORULA_BASE_BRANCH / main\uFF09" },
|
|
3782
|
+
{ name: "task-code", type: "string", valueHint: "<code>", description: "\u4EFB\u52A1\u7F16\u53F7\uFF08MR \u6807\u9898/\u6B63\u6587\u56DE\u94FE\uFF09" }
|
|
3783
|
+
],
|
|
3784
|
+
examples: ["morula repo mr create <owner/name> --branch <b> --task-code <code>"],
|
|
3785
|
+
writes: "remote"
|
|
3786
|
+
},
|
|
3787
|
+
{
|
|
3788
|
+
action: "mr list",
|
|
3789
|
+
summary: "list the task-space repos' merge requests (read-only; includes createdAt/updatedAt/author; never approves/merges)",
|
|
3790
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
|
|
3791
|
+
flags: [
|
|
3792
|
+
{ name: "state", type: "string", valueHint: "<opened|closed|merged>", enum: ["opened", "closed", "merged"], description: "MR \u72B6\u6001\u8FC7\u6EE4\uFF08\u7F3A\u7701 opened\uFF09" },
|
|
3793
|
+
{ name: "limit", type: "number", valueHint: "<n>", description: "\u6761\u6570\u4E0A\u9650" },
|
|
3794
|
+
{ name: "author", type: "string", valueHint: "<username>", description: "\u6309\u4F5C\u8005\u8FC7\u6EE4" },
|
|
3795
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" }
|
|
3796
|
+
],
|
|
3797
|
+
examples: ["morula repo mr list <taskId> --state opened"],
|
|
3798
|
+
writes: "none",
|
|
3799
|
+
idempotent: true
|
|
3800
|
+
},
|
|
3801
|
+
{
|
|
3802
|
+
action: "mr comment",
|
|
3803
|
+
summary: "write a review comment on an MR/PR (write-only: never approves or merges). \u5199\u4E4B\u524D\u5FC5\u987B\u5148 `morula repo mr get <taskId> --mr <iid> --notes` \u8BFB\u5DF2\u6709\u8BA8\u8BBA\uFF1A\u82E5\u672C\u81EA\u52A8\u5316\u5DF2\u5C31\u540C\u4E00\u95EE\u9898\u7559\u8FC7\u7ED3\u8BBA\u4E14\u7ED3\u8BBA\u672A\u53D8\uFF0C\u4E0D\u8981\u518D\u8BC4\u8BBA\u3002\u5185\u5BB9\u4E3A\u7ED3\u8BBA + \u5173\u952E\u95EE\u9898\uFF08\u6587\u4EF6:\u884C + \u5EFA\u8BAE\uFF09\uFF0C\u672B\u5C3E\u52A0\u7F72\u540D\u884C\uFF1B\u957F\u5185\u5BB9\u5199\u6210\u62A5\u544A\u540E\u7528\u6458\u8981\u5F15\u7528\uFF08morula report create \u2192 \u8BC4\u8BBA\u91CC\u7ED9\u8981\u70B9\uFF09",
|
|
3804
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
|
|
3805
|
+
flags: [
|
|
3806
|
+
{ name: "mr", type: "number", valueHint: "<iid>", description: "MR \u7F16\u53F7\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3807
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" },
|
|
3808
|
+
{ name: "connection", type: "string", valueHint: "<id>", description: "\u8FDE\u63A5 ID\uFF08\u591A\u8FDE\u63A5\u573A\u666F\uFF09" },
|
|
3809
|
+
{ name: "content", type: "string", valueHint: "<text>", description: "\u8BC4\u8BBA\u6B63\u6587\uFF08\u4E0E --content-file \u4E8C\u9009\u4E00\uFF09" },
|
|
3810
|
+
{ name: "content-file", type: "string", valueHint: "<path>", description: "\u8BC4\u8BBA\u6B63\u6587\u6587\u4EF6\uFF08\u4E0E --content \u4E8C\u9009\u4E00\uFF09" }
|
|
3811
|
+
],
|
|
3812
|
+
examples: ["morula repo mr comment <taskId> --mr <iid> --content-file <path>"],
|
|
3813
|
+
writes: "remote"
|
|
3814
|
+
},
|
|
3815
|
+
{
|
|
3816
|
+
action: "mr get",
|
|
3817
|
+
summary: "get a single MR detail (read-only; --ci attaches pipeline status where none = no pipeline, not an error; --notes attaches existing comments \u2014 read this before writing a comment to avoid duplicates)",
|
|
3818
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
|
|
3819
|
+
flags: [
|
|
3820
|
+
{ name: "mr", type: "number", valueHint: "<iid>", description: "MR \u7F16\u53F7\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3821
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" },
|
|
3822
|
+
{ name: "ci", type: "boolean", description: "\u9644\u5E26 pipeline \u72B6\u6001" },
|
|
3823
|
+
{ name: "notes", type: "boolean", description: "\u9644\u5E26\u5DF2\u6709\u8BC4\u8BBA\uFF08\u5199\u8BC4\u8BBA\u524D\u5148\u8BFB\uFF0C\u9632\u91CD\u590D\uFF09" }
|
|
3824
|
+
],
|
|
3825
|
+
examples: ["morula repo mr get <taskId> --mr <iid> --notes"],
|
|
3826
|
+
writes: "none",
|
|
3827
|
+
idempotent: true
|
|
3828
|
+
},
|
|
3829
|
+
{
|
|
3830
|
+
action: "mr diff",
|
|
3831
|
+
summary: "read an MR diff via the server (read-only; works without a local checkout; truncated flag is set when output was capped)",
|
|
3832
|
+
args: [{ name: "taskId", required: true, description: "\u672C\u4EFB\u52A1 AgentTask UUID" }],
|
|
3833
|
+
flags: [
|
|
3834
|
+
{ name: "mr", type: "number", valueHint: "<iid>", description: "MR \u7F16\u53F7\uFF08\u5FC5\u586B\uFF09", required: true },
|
|
3835
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u76EE\u6807\u4ED3\u5E93\uFF08\u7F3A\u7701\u4E3B\u4ED3\u5E93\uFF09" },
|
|
3836
|
+
{ name: "max-files", type: "number", valueHint: "<n>", description: "\u6587\u4EF6\u6570\u4E0A\u9650" },
|
|
3837
|
+
{ name: "max-chars", type: "number", valueHint: "<n>", description: "\u5B57\u7B26\u6570\u4E0A\u9650" }
|
|
3838
|
+
],
|
|
3839
|
+
examples: ["morula repo mr diff <taskId> --mr <iid>"],
|
|
3840
|
+
writes: "none",
|
|
3841
|
+
idempotent: true
|
|
3842
|
+
}
|
|
3843
|
+
],
|
|
3844
|
+
notes: [
|
|
3845
|
+
"mr \u662F\u591A\u7EA7\u5B50\u547D\u4EE4\u5F62\u6001\uFF08action=mr\u3001\u5B50\u547D\u4EE4\u4F4D\u5728 positionals[0]\uFF09\uFF1A\u5951\u7EA6\u6309\u5B50\u547D\u4EE4\u62C6\u5206\u58F0\u660E\uFF08mr create / mr list / ...\uFF09\uFF0C\u4E25\u683C\u6821\u9A8C\u53EA\u5BF9\u5355\u7EA7 action \u751F\u6548"
|
|
3846
|
+
]
|
|
3847
|
+
},
|
|
3848
|
+
{
|
|
3849
|
+
resource: "ci",
|
|
3850
|
+
summary: "CI/CD pipeline facts and pipeline operations: pipeline runs / job logs / deployments / failed test cases (read-only) plus retry of a failed pipeline or job (Phase 3 write, space face only). Read faces share one command surface \u2014 task face `<taskId>` (AgentTask credential, self-check your MR pipeline) vs space face `--space <spaceId>` (steward/session credential, space-wide inspection); the two are mutually exclusive. Retry is space-face ONLY (person + steward); trigger-deploy (play a manual job) stays person-only and is intentionally absent from the CLI",
|
|
3851
|
+
actions: [
|
|
3852
|
+
{
|
|
3853
|
+
action: "status",
|
|
3854
|
+
summary: 'pipeline runs + deployments for a task (or a space); hasCiConfig:false comes with a notice explaining the repo has no CI config (not an error); on the space face repos[] answers per repo (a multi-repo space never answers hasCiConfig for "the space" \u2014 it is null there); on the task face --repo filters runs client-side by repoFullName and echoes repoFilter (the server does NOT support repo filtering)',
|
|
3855
|
+
args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
|
|
3856
|
+
flags: [
|
|
3857
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
|
|
3858
|
+
{ name: "repo", type: "string", valueHint: "<owner/name>", description: "\u591A\u4ED3\u5E93\u7A7A\u95F4\uFF1A\u53EA\u770B\u8BE5\u4ED3\u5E93\u7684\u8FD0\u884C\uFF08\u4EFB\u52A1\u9762\u4E13\u7528\uFF0C\u5BA2\u6237\u7AEF\u8FC7\u6EE4\uFF09" },
|
|
3859
|
+
{ name: "ref", type: "string", valueHint: "<branch>", description: "\u6309\u5206\u652F/tag \u8FC7\u6EE4\uFF08\u7A7A\u95F4\u9762\u4E13\u7528\uFF09" },
|
|
3860
|
+
{ name: "failed", type: "boolean", description: "\u53EA\u770B\u5931\u8D25\u7684\u8FD0\u884C\uFF08\u7A7A\u95F4\u9762\u4E13\u7528\uFF09" },
|
|
3861
|
+
{ name: "limit", type: "number", valueHint: "<n>", description: "\u8FD0\u884C\u6761\u6570\u4E0A\u9650\uFF08\u670D\u52A1\u7AEF\u9ED8\u8BA4 20\uFF0C\u4E0A\u9650 100\uFF09" }
|
|
3862
|
+
],
|
|
3863
|
+
examples: ["morula ci status <taskId>", "morula ci status --space <id> --failed"],
|
|
3864
|
+
writes: "none",
|
|
3865
|
+
idempotent: true,
|
|
3866
|
+
emits: ["ok", "taskId", "spaceId", "runs", "deployments", "repos", "hasCiConfig", "notice", "repoFilter"]
|
|
3867
|
+
},
|
|
3868
|
+
{
|
|
3869
|
+
action: "log",
|
|
3870
|
+
summary: "job log by job id (the server resolves the owning run; both faces use this same endpoint \u2014 the task/space flag only declares which face you are calling from). `truncated:true` means the log was tail-cut, not complete",
|
|
3871
|
+
args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
|
|
3872
|
+
flags: [
|
|
3873
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
|
|
3874
|
+
{ name: "job", type: "string", valueHint: "<jobId>", description: "job ID\uFF08\u5FC5\u586B\uFF1B\u6765\u81EA ci status \u7684 jobs[].id\uFF09", required: true },
|
|
3875
|
+
{ name: "tail", type: "number", valueHint: "<n>", description: "\u4FDD\u7559\u7684\u65E5\u5FD7\u5C3E\u90E8\u5B57\u8282\u6570\uFF08\u670D\u52A1\u7AEF\u9ED8\u8BA4 64KB\uFF09" }
|
|
3876
|
+
],
|
|
3877
|
+
examples: ["morula ci log <taskId> --job <jobId> --tail 20000"],
|
|
3878
|
+
writes: "none",
|
|
3879
|
+
idempotent: true,
|
|
3880
|
+
emits: ["ok", "runId", "jobId", "log", "truncated"]
|
|
3881
|
+
},
|
|
3882
|
+
{
|
|
3883
|
+
action: "deployments",
|
|
3884
|
+
summary: "deployment records for a task (or a space) \u2014 display only: deployments do NOT drive lifecycle state (released is driven solely by a successful production-branch pipeline run)",
|
|
3885
|
+
args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
|
|
3886
|
+
flags: [
|
|
3887
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" }
|
|
3888
|
+
],
|
|
3889
|
+
examples: ["morula ci deployments <taskId>"],
|
|
3890
|
+
writes: "none",
|
|
3891
|
+
idempotent: true,
|
|
3892
|
+
emits: ["ok", "spaceId", "deployments"]
|
|
3893
|
+
},
|
|
3894
|
+
{
|
|
3895
|
+
action: "test-report",
|
|
3896
|
+
summary: "failed test cases of a run; --run omitted resolves to the newest run in the CI view \u2014 no run at all is CI_RUN_NOT_FOUND (exit 3), never an empty report; provider without this capability (GitHub) returns CAPABILITY_UNSUPPORTED",
|
|
3897
|
+
args: [{ name: "taskId", description: "\u672C\u4EFB\u52A1 AgentTask UUID\uFF08\u4E0E --space \u4E92\u65A5\uFF09" }],
|
|
3898
|
+
flags: [
|
|
3899
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u7BA1\u5BB6/\u7A7A\u95F4\u9762\uFF1B\u4E0E <taskId> \u4E92\u65A5\uFF09" },
|
|
3900
|
+
{ name: "run", type: "string", valueHint: "<runId>", description: "\u8FD0\u884C ID\uFF08\u7F3A\u7701 = CI \u89C6\u56FE\u91CC\u6700\u65B0\u4E00\u6761\u8FD0\u884C\uFF09" }
|
|
3901
|
+
],
|
|
3902
|
+
examples: ["morula ci test-report <taskId>"],
|
|
3903
|
+
writes: "none",
|
|
3904
|
+
idempotent: true,
|
|
3905
|
+
emits: ["ok", "runId", "report"]
|
|
3906
|
+
},
|
|
3907
|
+
{
|
|
3908
|
+
action: "retry",
|
|
3909
|
+
summary: "re-run a failed pipeline (Phase 3; space face ONLY \u2014 a positional <taskId> is a usage error, retry is not open to the task face; person + steward, and a steward must go through an elicitation confirm card). Omit both --run and --job to retry the newest FAILED run of the space (no failed run at all \u2192 CI_RUN_NOT_FOUND exit 3). Only failure/cancelled runs or jobs can be retried (CI_RETRY_NOT_ALLOWED exit 6). NOT idempotent: every call starts another platform re-run",
|
|
3910
|
+
flags: [
|
|
3911
|
+
{ name: "space", type: "string", valueHint: "<id>", description: "\u7A7A\u95F4 ID\uFF08\u5FC5\u586B\uFF1Bretry \u53EA\u6709\u7A7A\u95F4\u9762\uFF0C\u4EFB\u52A1\u9762\u4E0D\u5F00\u653E\uFF09", required: true },
|
|
3912
|
+
{ name: "run", type: "string", valueHint: "<runId>", description: "\u8981\u91CD\u8DD1\u7684\u8FD0\u884C ID\uFF08\u4E0E --job \u4E92\u65A5\uFF1B\u7F3A\u7701 = \u8BE5\u7A7A\u95F4\u6700\u65B0\u4E00\u6761\u5931\u8D25\u8FD0\u884C\uFF09" },
|
|
3913
|
+
{ name: "job", type: "string", valueHint: "<jobId>", description: "\u8981\u91CD\u8DD1\u7684\u5355\u4E2A job ID\uFF08\u4E0E --run \u4E92\u65A5\uFF1B\u6765\u81EA ci status \u7684 jobs[].id\uFF09" }
|
|
3914
|
+
],
|
|
3915
|
+
examples: ["morula ci retry --space <id>", "morula ci retry --space <id> --job <jobId>"],
|
|
3916
|
+
writes: "remote",
|
|
3917
|
+
idempotent: false,
|
|
3918
|
+
emits: ["ok", "action", "runId", "jobId", "externalId", "ref", "acceptedAt"]
|
|
3919
|
+
}
|
|
3920
|
+
],
|
|
3921
|
+
notes: [
|
|
3922
|
+
"\u4E24\u4E2A\u9762\uFF08\u4EFB\u52A1\u9762 <taskId> / \u7A7A\u95F4\u9762 --space <spaceId>\uFF09\u4E92\u65A5\uFF1A\u540C\u65F6\u7ED9 \u2192 usage error(2)\uFF0C\u90FD\u4E0D\u7ED9\u540C\u6837 usage error\u3002\u51ED\u8BC1\u4E0E\u4F5C\u7528\u57DF\u4E0D\u540C\uFF0C\u731C\u9519\u4F1A\u53D8\u6210\u9759\u9ED8\u8D8A\u6743\u6216\u9759\u9ED8\u5931\u8D25",
|
|
3923
|
+
"\u53EA\u8BFB\u9762\uFF1Astatus / log / deployments / test-report \u53EA\u8BFB\uFF08writes: none\uFF09\uFF1Bretry \u662F Phase 3 \u7684\u552F\u4E00\u5199\u9762\uFF08writes: remote\uFF0C\u975E\u5E42\u7B49\uFF09\uFF0C\u4E14**\u53EA\u6709\u7A7A\u95F4\u9762**\u2014\u2014\u7ED9\u4F4D\u7F6E\u53C2\u6570 <taskId> \u2192 usage error(2)",
|
|
3924
|
+
"\u89E6\u53D1\u624B\u52A8 job\uFF08play\uFF0C\u90E8\u7F72\uFF09\u4E0D\u8FDB CLI\uFF1A\u4EC5\u4EBA\u9762\uFF08D5/D6\uFF09\uFF0C\u5165\u53E3\u53EA\u5728 web \u786E\u8BA4\u5F39\u7A97",
|
|
3925
|
+
"\u670D\u52A1\u7AEF\u4E1A\u52A1\u5931\u8D25\u662F HTTP 200 + `{ ok:false, code, message }`\uFF1ACI_JOB_NOT_FOUND / CI_RUN_NOT_FOUND / TASK_NOT_FOUND / OBJECT_NOT_FOUND \u2192 exit 3\uFF1BCI_RETRY_NOT_ALLOWED / CI_JOB_NOT_MANUAL \u2192 exit 6\uFF1BCAPABILITY_UNSUPPORTED / NO_CONNECTION / \u5176\u4F59 \u2192 exit 1"
|
|
3926
|
+
]
|
|
3927
|
+
},
|
|
3928
|
+
{
|
|
3929
|
+
resource: "attachment",
|
|
3930
|
+
summary: "file attachments for objects / comments / chat messages (local file upload)",
|
|
3931
|
+
actions: [
|
|
3932
|
+
{
|
|
3933
|
+
action: "upload",
|
|
3934
|
+
summary: "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)",
|
|
3935
|
+
args: [{ name: "file", required: true, description: "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84" }],
|
|
3936
|
+
flags: [
|
|
3937
|
+
{ name: "object", type: "string", valueHint: "<id>", description: "\u6302\u5230\u5BF9\u8C61" },
|
|
3938
|
+
{ name: "comment", type: "string", valueHint: "<id>", description: "\u6302\u5230\u8BC4\u8BBA" },
|
|
3939
|
+
{ name: "ag-message", type: "boolean", description: "\u6302\u5230 Chat \u4F1A\u8BDD\u6700\u65B0\u6D88\u606F" },
|
|
3940
|
+
{ name: "task", type: "string", valueHint: "<agentTaskId>", description: "\u7EAF Chat \u4EFB\u52A1\u5F52\u5E76\u5230\u4F1A\u8BDD\u6D88\u606F" },
|
|
3941
|
+
{ name: "inline", type: "boolean", description: "\u5185\u8054\u9644\u4EF6" }
|
|
3942
|
+
],
|
|
3943
|
+
examples: ["morula attachment upload <file> --object <id>"],
|
|
3944
|
+
writes: "remote"
|
|
3945
|
+
},
|
|
3946
|
+
{
|
|
3947
|
+
action: "list",
|
|
3948
|
+
summary: "list attachments for an object",
|
|
3949
|
+
flags: [{ name: "object", type: "string", valueHint: "<id>", description: "\u5BF9\u8C61 ID\uFF08\u5FC5\u586B\uFF09", required: true }],
|
|
3950
|
+
examples: ["morula attachment list --object <id>"],
|
|
3951
|
+
writes: "none",
|
|
3952
|
+
idempotent: true
|
|
3953
|
+
},
|
|
3954
|
+
{
|
|
3955
|
+
action: "get",
|
|
3956
|
+
summary: "get attachment details",
|
|
3957
|
+
args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
|
|
3958
|
+
flags: [],
|
|
3959
|
+
examples: ["morula attachment get <id>"],
|
|
3960
|
+
writes: "none",
|
|
3961
|
+
idempotent: true
|
|
3962
|
+
},
|
|
3963
|
+
{
|
|
3964
|
+
action: "content",
|
|
3965
|
+
summary: "read the parsed text content (MinerU markdown) of an attachment; unparsed/parsing/failed is reported in the body, not as an HTTP error",
|
|
3966
|
+
args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
|
|
3967
|
+
flags: [{ name: "max-chars", type: "number", valueHint: "<n>", description: "\u6700\u5927\u8FD4\u56DE\u5B57\u7B26\u6570\uFF08\u670D\u52A1\u7AEF\u7F3A\u7701 8000\uFF09" }],
|
|
3968
|
+
examples: ["morula attachment content <id>", "morula attachment content <id> --max-chars 20000"],
|
|
3969
|
+
writes: "none",
|
|
3970
|
+
idempotent: true
|
|
3971
|
+
},
|
|
3972
|
+
{
|
|
3973
|
+
action: "download-url",
|
|
3974
|
+
summary: "get signed download URL for an attachment",
|
|
3975
|
+
args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
|
|
3976
|
+
flags: [],
|
|
3977
|
+
examples: ["morula attachment download-url <id>"],
|
|
3978
|
+
writes: "none",
|
|
3979
|
+
idempotent: true
|
|
3980
|
+
},
|
|
3981
|
+
{
|
|
3982
|
+
action: "delete",
|
|
3983
|
+
summary: "delete attachment",
|
|
3984
|
+
args: [{ name: "id", required: true, description: "\u9644\u4EF6 ID" }],
|
|
3985
|
+
flags: [],
|
|
3986
|
+
examples: ["morula attachment delete <id>"],
|
|
3987
|
+
writes: "remote"
|
|
3988
|
+
}
|
|
3989
|
+
]
|
|
3990
|
+
}
|
|
3991
|
+
]
|
|
3992
|
+
};
|
|
3993
|
+
|
|
3994
|
+
// ../morula-cli/src/commands/steward.ts
|
|
3995
|
+
async function stewardCommand(context) {
|
|
3996
|
+
if (!context.runtime.token)
|
|
3997
|
+
throw new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
3998
|
+
const { action, flags } = context.parsed;
|
|
3999
|
+
if (action === "get") {
|
|
4000
|
+
rejectUnknownFlags(flags, ["space"]);
|
|
4001
|
+
return context.client.stewards.get(requireFlag(flags, "space"));
|
|
4002
|
+
}
|
|
4003
|
+
if (action === "dispatch") {
|
|
4004
|
+
rejectUnknownFlags(flags, ["space", "title", "description", "state", "assignee", "repo", "client-message-id"]);
|
|
4005
|
+
const projectId = requireFlag(flags, "space");
|
|
4006
|
+
const title = requireFlag(flags, "title");
|
|
4007
|
+
const state = flagString(flags, "state");
|
|
4008
|
+
const description = flagString(flags, "description");
|
|
4009
|
+
const assigneePrincipalId = flagString(flags, "assignee");
|
|
4010
|
+
const repoFullName = flagString(flags, "repo");
|
|
4011
|
+
const clientMessageId = flagString(flags, "client-message-id");
|
|
4012
|
+
return context.client.stewards.dispatch(projectId, {
|
|
4013
|
+
title,
|
|
4014
|
+
...description !== void 0 ? { description } : {},
|
|
4015
|
+
...state !== void 0 ? { state } : {},
|
|
4016
|
+
...assigneePrincipalId !== void 0 ? { assigneePrincipalId } : {},
|
|
4017
|
+
...repoFullName !== void 0 ? { repoFullName } : {},
|
|
4018
|
+
...clientMessageId !== void 0 ? { clientMessageId } : {}
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
throw usage("usage: morula steward <get|dispatch>");
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
// ../morula-cli/src/commands/task.ts
|
|
4025
|
+
var LIST_FLAGS = ["space", "page", "page-size", "search", "priority", "state", "assignee", "principal", "sort", "order", "mine", "field"];
|
|
4026
|
+
var WRITE_FLAGS = ["title", "description", "space", "priority", "state", "owner", "assignee", "visibility", "estimate-hours", "schema", "sort-order", "field"];
|
|
4027
|
+
function parseFieldFlags(raw) {
|
|
4028
|
+
if (!raw)
|
|
4029
|
+
return [];
|
|
4030
|
+
const specs = [];
|
|
4031
|
+
for (const pair of raw.split(",")) {
|
|
4032
|
+
const eq = pair.indexOf("=");
|
|
4033
|
+
if (eq > 0) {
|
|
4034
|
+
specs.push({ key: pair.slice(0, eq).trim(), value: pair.slice(eq + 1).trim() });
|
|
4035
|
+
} else {
|
|
4036
|
+
throw usage(`--field \u683C\u5F0F\u5E94\u4E3A key=value\uFF0C\u6536\u5230 "${pair}"`);
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
return specs.filter((f) => f.key);
|
|
4040
|
+
}
|
|
4041
|
+
async function resolveFieldIds(context, spaceId, specs) {
|
|
4042
|
+
if (specs.length === 0)
|
|
4043
|
+
return [];
|
|
4044
|
+
const fields = await context.client.tasks.getSchemaFields(spaceId);
|
|
4045
|
+
const resolved = [];
|
|
4046
|
+
for (const spec of specs) {
|
|
4047
|
+
const field = fields.find((f) => f.fieldKey === spec.key || f.fieldName === spec.key);
|
|
4048
|
+
if (!field)
|
|
4049
|
+
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`);
|
|
4050
|
+
resolved.push({ schemaFieldId: field.id, value: spec.value });
|
|
4051
|
+
}
|
|
4052
|
+
return resolved;
|
|
4053
|
+
}
|
|
4054
|
+
function detectDescriptionFormat(text) {
|
|
4055
|
+
if (!text)
|
|
4056
|
+
return "plain";
|
|
4057
|
+
if (/<[a-z][\s\S]*>/i.test(text))
|
|
4058
|
+
return "richtext";
|
|
4059
|
+
if (/^(?:#{1,6}\s|[-*+]\s|\d+\.\s|>\s|```|~~)/m.test(text))
|
|
4060
|
+
return "markdown";
|
|
4061
|
+
if (/\*\*.+\*\*/.test(text))
|
|
4062
|
+
return "markdown";
|
|
4063
|
+
if (/\[[^\]]+\]\([^)]+\)/.test(text.trim()))
|
|
4064
|
+
return "markdown";
|
|
4065
|
+
if (/\|.+\|.+\|/.test(text))
|
|
4066
|
+
return "markdown";
|
|
4067
|
+
return "plain";
|
|
4068
|
+
}
|
|
4069
|
+
async function taskCommand(context) {
|
|
4070
|
+
const { action, positionals, flags } = context.parsed;
|
|
4071
|
+
if (!context.runtime.token)
|
|
4072
|
+
throw unauthenticated();
|
|
4073
|
+
if (action === "list") {
|
|
4074
|
+
rejectUnknownFlags(flags, LIST_FLAGS);
|
|
4075
|
+
const mine = flagBoolean(flags, "mine");
|
|
4076
|
+
if (mine && flagString(flags, "assignee"))
|
|
4077
|
+
throw usage("--mine \u4E0E --assignee \u4E92\u65A5\uFF0C\u8BF7\u4E8C\u9009\u4E00");
|
|
4078
|
+
let assigneePrincipalId = flagString(flags, "assignee");
|
|
4079
|
+
if (mine) {
|
|
4080
|
+
const profile = await context.client.auth.profile();
|
|
4081
|
+
if (!profile.principalId)
|
|
4082
|
+
throw usage("--mine \u65E0\u6CD5\u89E3\u6790\u5F53\u524D\u7528\u6237 principalId\uFF08profile \u7F3A\u5931\uFF09");
|
|
4083
|
+
assigneePrincipalId = profile.principalId;
|
|
4084
|
+
}
|
|
4085
|
+
const query = compact3({
|
|
4086
|
+
spaceId: flagString(flags, "space"),
|
|
4087
|
+
page: flagNumber(flags, "page"),
|
|
4088
|
+
pageSize: flagNumber(flags, "page-size"),
|
|
4089
|
+
search: flagString(flags, "search"),
|
|
4090
|
+
priority: flagString(flags, "priority"),
|
|
4091
|
+
currentStateId: flagString(flags, "state"),
|
|
4092
|
+
assigneePrincipalId,
|
|
4093
|
+
principalId: flagString(flags, "principal"),
|
|
4094
|
+
sort: flagString(flags, "sort"),
|
|
4095
|
+
order: flagString(flags, "order"),
|
|
4096
|
+
includeFieldValues: true
|
|
4097
|
+
});
|
|
4098
|
+
const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
|
|
4099
|
+
if (fieldSpecs.length > 0) {
|
|
4100
|
+
const spaceId = flagString(flags, "space");
|
|
4101
|
+
if (!spaceId)
|
|
4102
|
+
throw usage("--field \u9700\u8981\u914D\u5408 --space \u6307\u5B9A\u7A7A\u95F4\uFF08\u7528\u4E8E\u89E3\u6790\u5B57\u6BB5\u6A21\u677F\uFF09");
|
|
4103
|
+
const resolved = await resolveFieldIds(context, spaceId, fieldSpecs);
|
|
4104
|
+
query.fieldFilters = JSON.stringify(resolved.map((r) => ({ schemaFieldId: r.schemaFieldId, op: "eq", value: r.value })));
|
|
4105
|
+
}
|
|
4106
|
+
return context.client.tasks.list(query);
|
|
4107
|
+
}
|
|
4108
|
+
if (action === "get") {
|
|
4109
|
+
rejectUnknownFlags(flags, []);
|
|
4110
|
+
if (!positionals[0]?.trim()) {
|
|
4111
|
+
const taskId = await resolveTaskId(context);
|
|
4112
|
+
if (!taskId)
|
|
4113
|
+
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
|
|
4114
|
+
|
|
4115
|
+
\u7528\u6CD5:
|
|
4116
|
+
morula task get # \u5F53\u524D AgentTask \u8BE6\u60C5\uFF08daemon \u4EFB\u52A1\u5DE5\u4F5C\u533A\u5185\uFF1B\u8FD4\u56DE code/objectId/taskPrompt/contextSnapshot\uFF09
|
|
4117
|
+
morula task get <objectId> # \u4E1A\u52A1\u4EFB\u52A1\uFF08Object\uFF09\u8BE6\u60C5\uFF08\u542B\u81EA\u5B9A\u4E49\u5B57\u6BB5\u503C\uFF09`);
|
|
4118
|
+
return context.client.http.get(`/agent-tasks/${encodeURIComponent(taskId)}`);
|
|
4119
|
+
}
|
|
4120
|
+
const id = requireId4(positionals, "task get");
|
|
4121
|
+
const task = await context.client.tasks.get(id);
|
|
4122
|
+
if (!task)
|
|
4123
|
+
return null;
|
|
4124
|
+
try {
|
|
4125
|
+
const fieldValues = await context.client.tasks.getFieldValues(id);
|
|
4126
|
+
task.fieldValues = fieldValues;
|
|
4127
|
+
} catch {
|
|
4128
|
+
}
|
|
4129
|
+
return task;
|
|
4130
|
+
}
|
|
4131
|
+
if (action === "create") {
|
|
4132
|
+
rejectUnknownFlags(flags, WRITE_FLAGS);
|
|
4133
|
+
const input = taskInput(flags, true);
|
|
4134
|
+
const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
|
|
4135
|
+
const created = await context.client.tasks.create(input);
|
|
4136
|
+
if (fieldSpecs.length > 0 && created) {
|
|
4137
|
+
const spaceId = input.spaceId ?? created.spaceId;
|
|
4138
|
+
const resolved = await resolveFieldIds(context, spaceId, fieldSpecs);
|
|
4139
|
+
for (const r of resolved) {
|
|
4140
|
+
if (r.value) {
|
|
4141
|
+
await context.client.tasks.upsertFieldValue({
|
|
4142
|
+
objectId: created.id,
|
|
4143
|
+
schemaFieldId: r.schemaFieldId,
|
|
4144
|
+
valueText: r.value
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
;
|
|
4149
|
+
created.fieldValues = resolved.filter((r) => r.value).map((r) => ({ schemaFieldId: r.schemaFieldId, valueText: r.value }));
|
|
4150
|
+
}
|
|
4151
|
+
return created;
|
|
4152
|
+
}
|
|
4153
|
+
if (action === "update") {
|
|
4154
|
+
rejectUnknownFlags(flags, WRITE_FLAGS.filter((flag) => flag !== "space" && flag !== "owner"));
|
|
4155
|
+
const id = requireId4(positionals, "task update");
|
|
4156
|
+
const input = taskInput(flags, false);
|
|
4157
|
+
const fieldSpecs = parseFieldFlags(flagString(flags, "field"));
|
|
4158
|
+
if (!Object.keys(input).length && !flagString(flags, "field"))
|
|
4159
|
+
throw usage("task update requires at least one field flag");
|
|
4160
|
+
const updated = await context.client.tasks.update(id, input);
|
|
4161
|
+
if (fieldSpecs.length > 0) {
|
|
4162
|
+
const task = updated ?? await context.client.tasks.get(id);
|
|
4163
|
+
const spaceId = task?.spaceId;
|
|
4164
|
+
if (!spaceId)
|
|
4165
|
+
throw usage(`--field \u65E0\u6CD5\u89E3\u6790\u4EFB\u52A1 ${id} \u6240\u5C5E\u7A7A\u95F4`);
|
|
4166
|
+
const resolved = await resolveFieldIds(context, spaceId, fieldSpecs);
|
|
4167
|
+
for (const r of resolved) {
|
|
4168
|
+
if (r.value) {
|
|
4169
|
+
await context.client.tasks.upsertFieldValue({
|
|
4170
|
+
objectId: id,
|
|
4171
|
+
schemaFieldId: r.schemaFieldId,
|
|
4172
|
+
valueText: r.value
|
|
4173
|
+
});
|
|
4174
|
+
} else {
|
|
4175
|
+
await context.client.tasks.deleteFieldValue(id, r.schemaFieldId).catch(() => void 0);
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
const values = await context.client.tasks.getFieldValues(id).catch(() => []);
|
|
4179
|
+
if (updated)
|
|
4180
|
+
updated.fieldValues = values;
|
|
4181
|
+
}
|
|
4182
|
+
return updated;
|
|
4183
|
+
}
|
|
4184
|
+
if (action === "delete") {
|
|
4185
|
+
rejectUnknownFlags(flags, []);
|
|
4186
|
+
const id = requireId4(positionals, "task delete");
|
|
4187
|
+
return context.client.tasks.delete(id);
|
|
4188
|
+
}
|
|
4189
|
+
if (action === "branch") {
|
|
4190
|
+
rejectUnknownFlags(flags, ["name", "repo"]);
|
|
4191
|
+
const id = requireId4(positionals, "task branch");
|
|
4192
|
+
const branchName = requireFlag(flags, "name");
|
|
4193
|
+
const runtimeId = context.env.MORULA_RUNTIME_ID;
|
|
4194
|
+
if (!runtimeId)
|
|
4195
|
+
throw usage("task branch \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09");
|
|
4196
|
+
const repoFullName = flagString(flags, "repo")?.trim();
|
|
4197
|
+
return context.client.http.post(`/agent-tasks/${id}/branch`, {
|
|
4198
|
+
branchName,
|
|
4199
|
+
runtimeId,
|
|
4200
|
+
...repoFullName ? { repoFullName } : {}
|
|
4201
|
+
});
|
|
4202
|
+
}
|
|
4203
|
+
if (action === "state") {
|
|
4204
|
+
rejectUnknownFlags(flags, ["state"]);
|
|
4205
|
+
const id = requireId4(positionals, "task state");
|
|
4206
|
+
const stateCode = requireFlag(flags, "state");
|
|
4207
|
+
const runtimeId = context.env.MORULA_RUNTIME_ID;
|
|
4208
|
+
if (!runtimeId)
|
|
4209
|
+
throw usage(`task state \u9700\u8981 MORULA_RUNTIME_ID\uFF08\u7531 daemon \u6CE8\u5165\uFF1Bagent \u4E0D\u624B\u52A8\u6307\u5B9A\uFF09
|
|
4210
|
+
|
|
4211
|
+
\u7528\u6CD5: morula task state <taskId> --state <in_progress|review>
|
|
4212
|
+
\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`);
|
|
4213
|
+
return context.client.http.post(`/agent-tasks/${id}/state`, { stateCode, runtimeId });
|
|
4214
|
+
}
|
|
4215
|
+
if (action === "analysis-verdict") {
|
|
4216
|
+
rejectUnknownFlags(flags, ["verdict", "reason"]);
|
|
4217
|
+
const id = requireId4(positionals, "task analysis-verdict");
|
|
4218
|
+
const verdict = requireFlag(flags, "verdict");
|
|
4219
|
+
if (verdict !== "ready" && verdict !== "blocked")
|
|
4220
|
+
throw usage(`--verdict \u5FC5\u987B\u662F ready / blocked
|
|
4221
|
+
|
|
4222
|
+
\u7528\u6CD5: morula task analysis-verdict <taskId> --verdict <ready|blocked> [--reason <\u6587\u672C>]
|
|
4223
|
+
\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
|
|
4224
|
+
\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`);
|
|
4225
|
+
const body = { verdict };
|
|
4226
|
+
const reason = flagString(flags, "reason");
|
|
4227
|
+
if (reason?.trim())
|
|
4228
|
+
body.reason = reason.trim();
|
|
4229
|
+
const result = await context.client.http.post(
|
|
4230
|
+
`/agent-tasks/${id}/analysis-verdict`,
|
|
4231
|
+
body
|
|
4232
|
+
);
|
|
4233
|
+
if (result?.ok === false)
|
|
4234
|
+
throw new CliError(result.code ?? "ANALYSIS_VERDICT_FAILED", EXIT.OTHER, `\u5206\u6790\u7ED3\u8BBA\u4E0A\u62A5\u5931\u8D25: ${result.message ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
4235
|
+
return { ok: true, verdict: result.verdict, alreadyReported: result.alreadyReported ?? false };
|
|
4236
|
+
}
|
|
4237
|
+
if (action === "inbox") {
|
|
4238
|
+
rejectUnknownFlags(flags, ["peek", "after-seq"]);
|
|
4239
|
+
const taskId = await resolveTaskId(context);
|
|
4240
|
+
if (!taskId) {
|
|
4241
|
+
throw usage(`morula task inbox \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
|
|
4242
|
+
|
|
4243
|
+
\u7528\u6CD5:
|
|
4244
|
+
morula task inbox # \u62C9\u53D6\u672C\u7EBF\u7A0B\u672A\u6D88\u8D39\u6D88\u606F\uFF08\u7EB3\u5165\u672C\u56DE\u5408\uFF0C\u670D\u52A1\u7AEF\u6807\u8BB0\u5DF2\u6D88\u8D39\uFF09
|
|
4245
|
+
morula task inbox --peek # \u53EA\u8BFB\uFF0C\u4E0D\u6807\u8BB0\u6D88\u8D39
|
|
4246
|
+
\u8BF4\u660E: \u4EC5\u5728\u9636\u6BB5\u8FD0\u884C\u8FC7\u7A0B\u4E2D\u3001\u4E14\u5230\u8FBE\u68C0\u67E5\u70B9\u65F6\u8C03\u7528\uFF08\u4E00\u6279\u6539\u52A8\u5B8C\u6210\u540E / git commit \u524D / git push \u524D / \u6536\u5230\u4E2D\u65AD\u4FE1\u53F7\u65F6\uFF09\uFF1B\u6CA1\u6709\u6D88\u606F\u5C31\u7ACB\u5373\u7EE7\u7EED\uFF0C\u4E0D\u8981\u53CD\u590D\u8F6E\u8BE2\u3002`);
|
|
4247
|
+
}
|
|
4248
|
+
const afterSeq = flagNumber(flags, "after-seq");
|
|
4249
|
+
const peek = flagBoolean(flags, "peek");
|
|
4250
|
+
const query = {};
|
|
4251
|
+
if (afterSeq !== void 0)
|
|
4252
|
+
query.afterSeq = afterSeq;
|
|
4253
|
+
if (peek)
|
|
4254
|
+
query.peek = 1;
|
|
4255
|
+
let result;
|
|
4256
|
+
try {
|
|
4257
|
+
result = await context.client.http.get(`/agent-tasks/${encodeURIComponent(taskId)}/thread/inbox`, query);
|
|
4258
|
+
} catch (error) {
|
|
4259
|
+
const code = error?.code ?? error?.response?.data?.code;
|
|
4260
|
+
if (code === "CHECKPOINT_UNSUPPORTED" || code === "CHECKPOINT_NO_ACTIVE_TURN") {
|
|
4261
|
+
throw new CliError(String(code), EXIT.OTHER, `${error?.message ?? "\u5F53\u524D\u4E0D\u53EF\u62C9\u53D6\u68C0\u67E5\u70B9\u6D88\u606F"}\uFF08\u6CA1\u6709\u8FDB\u884C\u4E2D\u7684\u56DE\u5408\u65F6\u65E0\u9700\u68C0\u67E5\u70B9\uFF1B\u7B49\u5F85\u672C\u8F6E\u7ED3\u675F\u540E\u7684\u7EED\u8DD1\u5373\u53EF\uFF09`);
|
|
4262
|
+
}
|
|
4263
|
+
throw error;
|
|
4264
|
+
}
|
|
4265
|
+
const messages = (result?.messages ?? []).map((m) => ({
|
|
4266
|
+
seq: m.seq,
|
|
4267
|
+
author: m.authorName ?? (m.authorType === "user" ? "\u4EBA" : m.authorType === "system" ? "\u5E73\u53F0" : "\u8FD0\u884C\u65F6"),
|
|
4268
|
+
state: m.deliveryState ?? "consumed",
|
|
4269
|
+
content: m.content
|
|
4270
|
+
}));
|
|
4271
|
+
return {
|
|
4272
|
+
taskId,
|
|
4273
|
+
turnNo: result?.turnNo ?? 1,
|
|
4274
|
+
consumed: result?.consumed === true,
|
|
4275
|
+
count: messages.length,
|
|
4276
|
+
messages,
|
|
4277
|
+
...messages.length === 0 ? { hint: "\u672C\u7EBF\u7A0B\u6CA1\u6709\u65B0\u6D88\u606F\uFF1A\u6309\u539F\u8BA1\u5212\u7EE7\u7EED\uFF0C\u4E0D\u8981\u5C55\u5F00\u8BA8\u8BBA\u3002" } : {}
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
if (action === "agent-tasks") {
|
|
4281
|
+
rejectUnknownFlags(flags, ["object", "status", "page", "page-size"]);
|
|
4282
|
+
const objectId = requireFlag(flags, "object");
|
|
4283
|
+
return context.client.agentTasks.list(compact3({
|
|
4284
|
+
objectId,
|
|
4285
|
+
status: flagString(flags, "status"),
|
|
4286
|
+
page: flagNumber(flags, "page"),
|
|
4287
|
+
pageSize: flagNumber(flags, "page-size")
|
|
4288
|
+
}));
|
|
4289
|
+
}
|
|
4290
|
+
if (action === "cancel") {
|
|
4291
|
+
rejectUnknownFlags(flags, []);
|
|
4292
|
+
const id = requireId4(positionals, "task cancel");
|
|
4293
|
+
return context.client.agentTasks.cancel(id);
|
|
4294
|
+
}
|
|
4295
|
+
if (action === "rerun") {
|
|
4296
|
+
rejectUnknownFlags(flags, []);
|
|
4297
|
+
const id = requireId4(positionals, "task rerun");
|
|
4298
|
+
const result = await context.client.agentTasks.rerun(id);
|
|
4299
|
+
if (result?.ok === false)
|
|
4300
|
+
throw new CliError(result.code ?? "RERUN_FAILED", rerunExitCode(result.code), `\u91CD\u8DD1\u5931\u8D25: ${result.message ?? result.code ?? "\u672A\u77E5\u539F\u56E0"}`);
|
|
4301
|
+
return result;
|
|
4302
|
+
}
|
|
4303
|
+
throw usage("usage: morula task <list|create|get|update|delete|branch|state|analysis-verdict|inbox|agent-tasks|cancel|rerun>");
|
|
4304
|
+
}
|
|
4305
|
+
function rerunExitCode(code) {
|
|
4306
|
+
if (code === "NOT_FOUND")
|
|
4307
|
+
return EXIT.NOT_FOUND;
|
|
4308
|
+
if (code === "CONFLICT" || code === "ALREADY_QUEUED")
|
|
4309
|
+
return EXIT.CONFLICT;
|
|
4310
|
+
return EXIT.OTHER;
|
|
4311
|
+
}
|
|
4312
|
+
function taskInput(flags, create) {
|
|
4313
|
+
const description = flagString(flags, "description");
|
|
4314
|
+
const values = compact3({
|
|
4315
|
+
spaceId: flagString(flags, "space"),
|
|
4316
|
+
title: flagString(flags, "title"),
|
|
4317
|
+
description,
|
|
4318
|
+
descriptionFormat: description !== void 0 ? detectDescriptionFormat(description) : void 0,
|
|
4319
|
+
priority: flagString(flags, "priority"),
|
|
4320
|
+
currentStateId: flagString(flags, "state"),
|
|
4321
|
+
ownerPrincipalId: flagString(flags, "owner"),
|
|
4322
|
+
assigneePrincipalId: flagString(flags, "assignee"),
|
|
4323
|
+
visibility: flagString(flags, "visibility"),
|
|
4324
|
+
estimateHours: flagNumber(flags, "estimate-hours"),
|
|
4325
|
+
schemaId: flagString(flags, "schema"),
|
|
4326
|
+
sortOrder: flagNumber(flags, "sort-order")
|
|
4327
|
+
});
|
|
4328
|
+
if (create) {
|
|
4329
|
+
values.spaceId = requireFlag(flags, "space");
|
|
4330
|
+
values.title = requireFlag(flags, "title");
|
|
4331
|
+
}
|
|
4332
|
+
return values;
|
|
4333
|
+
}
|
|
4334
|
+
function requireId4(positionals, command) {
|
|
4335
|
+
const id = positionals[0]?.trim();
|
|
4336
|
+
if (!id)
|
|
4337
|
+
throw usage(`usage: morula ${command} <id>`);
|
|
4338
|
+
return id;
|
|
4339
|
+
}
|
|
4340
|
+
function compact3(value) {
|
|
4341
|
+
return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== void 0));
|
|
4342
|
+
}
|
|
4343
|
+
function unauthenticated() {
|
|
4344
|
+
return new CliError("UNAUTHENTICATED", EXIT.UNAUTHENTICATED, "\u8BF7\u5148\u8FD0\u884C morula auth login");
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
// ../morula-cli/src/commands/registry.ts
|
|
4348
|
+
var COMMANDS = {
|
|
4349
|
+
"attachment": attachmentCommand,
|
|
4350
|
+
"auth": authCommand,
|
|
4351
|
+
"autopilot": autopilotCommand,
|
|
4352
|
+
"ci": ciCommand,
|
|
4353
|
+
"comment": commentCommand,
|
|
4354
|
+
"dashboard": dashboardCommand,
|
|
4355
|
+
"document": documentCommand,
|
|
4356
|
+
"object-meta": objectMetaCommand,
|
|
4357
|
+
"report": reportCommand,
|
|
4358
|
+
"repo": repoCommand,
|
|
4359
|
+
"review": reviewCommand,
|
|
4360
|
+
"task": taskCommand,
|
|
4361
|
+
"space": spaceCommand,
|
|
4362
|
+
"space-member": spaceMemberCommand,
|
|
4363
|
+
"steward": stewardCommand
|
|
4364
|
+
};
|
|
4365
|
+
var HELP = {
|
|
4366
|
+
name: "morula",
|
|
4367
|
+
version: BUILD_VERSION,
|
|
4368
|
+
build: BUILD_ID,
|
|
4369
|
+
summary: "Agent-first CLI for Morula task management. Success output is JSON on stdout; errors are JSON on stderr.",
|
|
4370
|
+
usage: "morula <resource> <action> [id] [--flags]",
|
|
4371
|
+
commands: renderTopLevelCommands(SPEC),
|
|
4372
|
+
globalFlags: {
|
|
4373
|
+
"--env <context>": "select test or a saved context",
|
|
4374
|
+
"--url <baseUrl>": "override API base URL",
|
|
4375
|
+
"--token <token>": "override token for this invocation (not persisted)",
|
|
4376
|
+
"--json": "no-op; output is always JSON (accepted for familiarity)",
|
|
4377
|
+
"--help": "print this machine-readable command surface",
|
|
4378
|
+
"--version": "print version JSON"
|
|
4379
|
+
},
|
|
4380
|
+
exitCodes: {
|
|
4381
|
+
0: "success",
|
|
4382
|
+
1: "other error",
|
|
4383
|
+
2: "usage error",
|
|
4384
|
+
3: "not found",
|
|
4385
|
+
4: "unauthenticated",
|
|
4386
|
+
5: "forbidden",
|
|
4387
|
+
6: "conflict",
|
|
4388
|
+
7: "upstream/network error"
|
|
4389
|
+
}
|
|
4390
|
+
};
|
|
4391
|
+
|
|
4392
|
+
// ../morula-cli/src/index.ts
|
|
4393
|
+
runEntry({ commands: COMMANDS, help: HELP, spec: SPEC });
|