@moxt-ai/cli 0.3.2 → 0.3.3-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,162 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command } from "commander";
5
4
  import updateNotifier from "update-notifier";
6
5
 
6
+ // src/program.ts
7
+ import { Command } from "commander";
8
+
9
+ // src/utils/output.ts
10
+ var colors = {
11
+ bold: "\x1B[1m",
12
+ blue: "\x1B[1;34m",
13
+ green: "\x1B[32m",
14
+ red: "\x1B[31m",
15
+ cyan: "\x1B[36m",
16
+ dim: "\x1B[2m",
17
+ reset: "\x1B[0m"
18
+ };
19
+ function print(message) {
20
+ console.log(message);
21
+ }
22
+ function printError(message) {
23
+ console.error(`${colors.red}${message}${colors.reset}`);
24
+ }
25
+
26
+ // src/utils/runtime-context.ts
27
+ var DEFAULT_USER_HOST = "api.moxt.ai";
28
+ var SANDBOX_ENV_KEYS = ["BASE_API_HOST", "SANDBOX_TOOL_API_TOKEN", "MOXT_REPO_PAYLOAD", "MOXT_WORKSPACE_ID"];
29
+ var RuntimeContextError = class extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = "RuntimeContextError";
33
+ }
34
+ };
35
+ function resolveRuntimeContext(env = process.env) {
36
+ if (hasAnySandboxEnv(env)) {
37
+ return resolveSandboxRuntimeContext(env);
38
+ }
39
+ const apiKey = readNonEmptyEnv(env, "MOXT_API_KEY");
40
+ if (apiKey) {
41
+ return {
42
+ mode: "user",
43
+ host: readNonEmptyEnv(env, "MOXT_HOST") ?? DEFAULT_USER_HOST,
44
+ apiKey
45
+ };
46
+ }
47
+ throw new RuntimeContextError("Missing auth. Set sandbox environment variables or MOXT_API_KEY.");
48
+ }
49
+ function resolveSandboxRuntimeContext(env = process.env) {
50
+ const missingKeys = SANDBOX_ENV_KEYS.filter((key) => !readNonEmptyEnv(env, key));
51
+ if (missingKeys.length > 0) {
52
+ throw new RuntimeContextError(`Incomplete sandbox environment. Missing: ${missingKeys.join(", ")}.`);
53
+ }
54
+ return {
55
+ mode: "sandbox",
56
+ baseApiHost: normalizeBaseApiHost(readRequiredEnv(env, "BASE_API_HOST")),
57
+ sandboxToolApiToken: readRequiredEnv(env, "SANDBOX_TOOL_API_TOKEN"),
58
+ workspaceId: readRequiredEnv(env, "MOXT_WORKSPACE_ID"),
59
+ repoPayload: parseRepoPayload(readRequiredEnv(env, "MOXT_REPO_PAYLOAD"))
60
+ };
61
+ }
62
+ function parseRepoPayload(raw) {
63
+ let parsed;
64
+ try {
65
+ parsed = JSON.parse(raw);
66
+ } catch (error) {
67
+ throw new RuntimeContextError(`MOXT_REPO_PAYLOAD must be valid JSON: ${error instanceof Error ? error.message : String(error)}`);
68
+ }
69
+ if (!isRecord(parsed)) {
70
+ throw new RuntimeContextError("MOXT_REPO_PAYLOAD must be a JSON object.");
71
+ }
72
+ const personal = parseRepoEntry(parsed.personal, "personal");
73
+ const teamsValue = parsed.teams;
74
+ if (!Array.isArray(teamsValue)) {
75
+ throw new RuntimeContextError("MOXT_REPO_PAYLOAD.teams must be an array.");
76
+ }
77
+ const teams = teamsValue.map((entry, index) => parseRepoEntry(entry, `teams[${index}]`));
78
+ const workspace = parsed.workspace == null ? null : parseRepoEntry(parsed.workspace, "workspace");
79
+ return { personal, teams, workspace };
80
+ }
81
+ function normalizeBaseApiHost(value) {
82
+ const trimmed = value.trim();
83
+ if (!trimmed) {
84
+ throw new RuntimeContextError("BASE_API_HOST must not be empty.");
85
+ }
86
+ return trimmed.replace(/\/+$/, "");
87
+ }
88
+ function hasAnySandboxEnv(env) {
89
+ return SANDBOX_ENV_KEYS.some((key) => readNonEmptyEnv(env, key) !== void 0);
90
+ }
91
+ function readRequiredEnv(env, key) {
92
+ const value = readNonEmptyEnv(env, key);
93
+ if (!value) {
94
+ throw new RuntimeContextError(`${key} is required.`);
95
+ }
96
+ return value;
97
+ }
98
+ function readNonEmptyEnv(env, key) {
99
+ const value = env[key];
100
+ if (typeof value !== "string") return void 0;
101
+ const trimmed = value.trim();
102
+ return trimmed.length > 0 ? trimmed : void 0;
103
+ }
104
+ function parseRepoEntry(value, path2) {
105
+ if (!isRecord(value)) {
106
+ throw new RuntimeContextError(`MOXT_REPO_PAYLOAD.${path2} must be an object.`);
107
+ }
108
+ const repoId = readStringField(value, "repoId", path2);
109
+ const name = readStringField(value, "name", path2);
110
+ return { repoId, name };
111
+ }
112
+ function readStringField(record2, field, path2) {
113
+ const value = record2[field];
114
+ if (typeof value !== "string" || value.trim().length === 0) {
115
+ throw new RuntimeContextError(`MOXT_REPO_PAYLOAD.${path2}.${field} must be a non-empty string.`);
116
+ }
117
+ return value;
118
+ }
119
+ function isRecord(value) {
120
+ return typeof value === "object" && value !== null && !Array.isArray(value);
121
+ }
122
+
123
+ // src/utils/sandbox-path.ts
124
+ function listRepoEntries(repoPayload) {
125
+ return [
126
+ repoPayload.personal,
127
+ ...repoPayload.teams,
128
+ ...repoPayload.workspace ? [repoPayload.workspace] : []
129
+ ];
130
+ }
131
+
132
+ // src/commands/auth.ts
133
+ function registerAuthCommand(program2) {
134
+ const auth = program2.command("auth").description("Authentication utilities");
135
+ auth.command("status").description("Show CLI authentication mode").action(() => {
136
+ try {
137
+ const context = resolveRuntimeContext();
138
+ if (context.mode === "sandbox") {
139
+ print(`${colors.bold}Mode${colors.reset}: sandbox`);
140
+ print(`${colors.bold}Workspace${colors.reset}: ${context.workspaceId}`);
141
+ print(`${colors.bold}Base API${colors.reset}: ${context.baseApiHost}`);
142
+ print(`${colors.bold}Repos${colors.reset}:`);
143
+ for (const repo of listRepoEntries(context.repoPayload)) {
144
+ print(` ${repo.name} ${repo.repoId}`);
145
+ }
146
+ return;
147
+ }
148
+ print(`${colors.bold}Mode${colors.reset}: user`);
149
+ print(`${colors.bold}Host${colors.reset}: ${context.host}`);
150
+ } catch (error) {
151
+ if (error instanceof RuntimeContextError) {
152
+ printError(error.message);
153
+ process.exit(1);
154
+ }
155
+ throw error;
156
+ }
157
+ });
158
+ }
159
+
7
160
  // src/commands/file.ts
8
161
  import * as fs from "fs";
9
162
 
@@ -147,7 +300,7 @@ function getApiKeyOrUndefined() {
147
300
 
148
301
  // src/utils/http.ts
149
302
  function getUserAgent() {
150
- return `moxt-cli/${"0.3.2"} (${platform()}/${arch()}) node/${process.versions.node}`;
303
+ return `moxt-cli/${"0.3.3-beta.1"} (${platform()}/${arch()}) node/${process.versions.node}`;
151
304
  }
152
305
  async function fetchApi(method, path2, body) {
153
306
  const apiKey = getApiKey();
@@ -187,6 +340,9 @@ async function httpPut(path2, body) {
187
340
  async function httpPost(path2, body) {
188
341
  return request("POST", path2, body);
189
342
  }
343
+ async function httpPatch(path2, body) {
344
+ return request("PATCH", path2, body);
345
+ }
190
346
  async function httpDelete(path2, body) {
191
347
  return request("DELETE", path2, body);
192
348
  }
@@ -200,23 +356,6 @@ async function httpRaw(method, path2, body) {
200
356
  };
201
357
  }
202
358
 
203
- // src/utils/output.ts
204
- var colors = {
205
- bold: "\x1B[1m",
206
- blue: "\x1B[1;34m",
207
- green: "\x1B[32m",
208
- red: "\x1B[31m",
209
- cyan: "\x1B[36m",
210
- dim: "\x1B[2m",
211
- reset: "\x1B[0m"
212
- };
213
- function print(message) {
214
- console.log(message);
215
- }
216
- function printError(message) {
217
- console.error(`${colors.red}${message}${colors.reset}`);
218
- }
219
-
220
359
  // src/utils/search-options.ts
221
360
  var SearchOptionsError = class extends Error {
222
361
  constructor(message) {
@@ -939,6 +1078,844 @@ function registerWorkspaceCommand(program2) {
939
1078
  });
940
1079
  }
941
1080
 
1081
+ // src/commands/workflow.ts
1082
+ import * as fs3 from "fs";
1083
+ var MAX_DESC_LENGTH = 5e3;
1084
+ var WorkflowOptionsError = class extends Error {
1085
+ constructor(message) {
1086
+ super(message);
1087
+ this.name = "WorkflowOptionsError";
1088
+ }
1089
+ };
1090
+ function runOrExit3(fn) {
1091
+ try {
1092
+ return fn();
1093
+ } catch (err) {
1094
+ if (err instanceof WorkflowOptionsError || err instanceof SpaceOptionsError || err instanceof SearchOptionsError) {
1095
+ printError(err.message);
1096
+ process.exit(1);
1097
+ }
1098
+ throw err;
1099
+ }
1100
+ }
1101
+ function addWorkspaceOption(command) {
1102
+ return command.requiredOption("-w, --workspace <workspaceId>", "Workspace ID");
1103
+ }
1104
+ function addSpaceOptions2(command) {
1105
+ return addWorkspaceOption(command).option("-s, --space <name>", "Space name (personal or teamspace name)").option("--personal", "Use personal space (default)").option("--team-space-id <teamSpaceId>", "Team space ID").option("--teammate-id <teammateId>", "Teammate ID (teammate space)");
1106
+ }
1107
+ function addAssigneeOptions(command) {
1108
+ return command.option("--human-id <humanId>", "Human assignee ID").option("--assistant-id <assistantId>", "AI assistant assignee ID").option("--teammate-id <teammateId>", "AI teammate assignee ID");
1109
+ }
1110
+ function addPreferredAssigneeOptions(command) {
1111
+ return command.option("--preferred-human-id <humanId>", "Preferred human assignee ID").option("--preferred-assistant-id <assistantId>", "Preferred AI assistant assignee ID").option("--preferred-teammate-id <teammateId>", "Preferred AI teammate assignee ID");
1112
+ }
1113
+ function parsePositiveInteger(raw, label) {
1114
+ const parsed = Number.parseInt(raw, 10);
1115
+ if (!Number.isInteger(parsed) || parsed <= 0 || String(parsed) !== raw) {
1116
+ throw new WorkflowOptionsError(`Error: ${label} must be a positive integer, got "${raw}".`);
1117
+ }
1118
+ return parsed;
1119
+ }
1120
+ function parseOptionalPositiveInteger(raw, label) {
1121
+ if (raw == null || raw === "") return void 0;
1122
+ return parsePositiveInteger(raw, label);
1123
+ }
1124
+ function parseStatusType(raw) {
1125
+ if (raw == null || raw === "") return void 0;
1126
+ const parsed = Number.parseInt(raw, 10);
1127
+ if ((parsed === 1 || parsed === 2 || parsed === 3) && String(parsed) === raw) return parsed;
1128
+ throw new WorkflowOptionsError("Error: --type must be one of 1 (todo), 2 (in-progress), or 3 (done).");
1129
+ }
1130
+ function parseRequiredStatusType(raw) {
1131
+ const value = parseStatusType(raw);
1132
+ if (value === void 0) {
1133
+ throw new WorkflowOptionsError("Error: --type is required.");
1134
+ }
1135
+ return value;
1136
+ }
1137
+ function parsePriority(raw) {
1138
+ if (raw == null || raw === "") return void 0;
1139
+ if (raw === "urgent") return 0;
1140
+ if (raw === "high") return 1;
1141
+ if (raw === "medium") return 2;
1142
+ if (raw === "low") return 3;
1143
+ throw new WorkflowOptionsError('Error: --priority must be "urgent", "high", "medium", or "low".');
1144
+ }
1145
+ function parseRequiredPriority(raw) {
1146
+ return parsePriority(raw ?? "medium") ?? 2;
1147
+ }
1148
+ function parseWorkflowScope(raw) {
1149
+ if (raw == null || raw === "") return "all";
1150
+ if (raw === "all" || raw === "owner_is_me") return raw;
1151
+ throw new WorkflowOptionsError('Error: --scope must be "all" or "owner_is_me".');
1152
+ }
1153
+ function parseTaskScope(raw) {
1154
+ if (raw == null || raw === "") return "all";
1155
+ if (raw === "all" || raw === "assigned_to_me" || raw === "subscribed_by_me" || raw === "owner_is_me") {
1156
+ return raw;
1157
+ }
1158
+ throw new WorkflowOptionsError(
1159
+ 'Error: --scope must be "all", "assigned_to_me", "subscribed_by_me", or "owner_is_me".'
1160
+ );
1161
+ }
1162
+ function parseFileIds(raw, label = "--file-ids") {
1163
+ if (raw == null || raw === "") return void 0;
1164
+ const ids = raw.split(",").map((item) => item.trim()).filter(Boolean);
1165
+ if (ids.length === 0) {
1166
+ throw new WorkflowOptionsError(`Error: ${label} must include at least one file ID.`);
1167
+ }
1168
+ return ids;
1169
+ }
1170
+ function readDescFile(path2) {
1171
+ if (path2 == null || path2 === "") return void 0;
1172
+ if (!fs3.existsSync(path2)) {
1173
+ throw new WorkflowOptionsError(`Error: desc file not found: ${path2}`);
1174
+ }
1175
+ const stat = fs3.statSync(path2);
1176
+ if (!stat.isFile()) {
1177
+ throw new WorkflowOptionsError(`Error: desc file is not a file: ${path2}`);
1178
+ }
1179
+ const buffer2 = fs3.readFileSync(path2);
1180
+ if (isBinaryContent(buffer2)) {
1181
+ throw new WorkflowOptionsError("Error: desc file must be UTF-8 text, not binary content.");
1182
+ }
1183
+ const content = buffer2.toString("utf8");
1184
+ if (content.length > MAX_DESC_LENGTH) {
1185
+ throw new WorkflowOptionsError(`Error: desc file must be at most ${MAX_DESC_LENGTH} characters.`);
1186
+ }
1187
+ return content;
1188
+ }
1189
+ function parseAssigneeBody(options, allowClear) {
1190
+ const hasClear = options.clear === true;
1191
+ const humanId = parseOptionalPositiveInteger(options.humanId, "--human-id");
1192
+ const assistantId = parseOptionalPositiveInteger(options.assistantId, "--assistant-id");
1193
+ const teammateId = parseOptionalPositiveInteger(options.teammateId, "--teammate-id");
1194
+ const count = (humanId !== void 0 ? 1 : 0) + (assistantId !== void 0 ? 1 : 0) + (teammateId !== void 0 ? 1 : 0);
1195
+ if (hasClear && !allowClear) {
1196
+ throw new WorkflowOptionsError("Error: --clear is not supported here.");
1197
+ }
1198
+ if (hasClear && count > 0) {
1199
+ throw new WorkflowOptionsError("Error: --clear cannot be used with assignee ID options.");
1200
+ }
1201
+ if (count > 1) {
1202
+ throw new WorkflowOptionsError("Error: assignee ID options are mutually exclusive.");
1203
+ }
1204
+ if (hasClear) return {};
1205
+ if (humanId !== void 0) return { assigneeHumanId: humanId };
1206
+ if (assistantId !== void 0) return { assigneeAiAssistantId: assistantId };
1207
+ if (teammateId !== void 0) return { assigneeAiTeammateId: teammateId };
1208
+ return {};
1209
+ }
1210
+ function parsePreferredAssigneeBody(options) {
1211
+ const hasClear = options.clearPreferredAssignee === true;
1212
+ const humanId = parseOptionalPositiveInteger(options.preferredHumanId, "--preferred-human-id");
1213
+ const assistantId = parseOptionalPositiveInteger(options.preferredAssistantId, "--preferred-assistant-id");
1214
+ const teammateId = parseOptionalPositiveInteger(options.preferredTeammateId, "--preferred-teammate-id");
1215
+ const count = (humanId !== void 0 ? 1 : 0) + (assistantId !== void 0 ? 1 : 0) + (teammateId !== void 0 ? 1 : 0);
1216
+ if (hasClear && count > 0) {
1217
+ throw new WorkflowOptionsError("Error: --clear-preferred-assignee cannot be used with preferred assignee ID options.");
1218
+ }
1219
+ if (count > 1) {
1220
+ throw new WorkflowOptionsError("Error: preferred assignee ID options are mutually exclusive.");
1221
+ }
1222
+ if (hasClear) return { preferredAssigneeHumanId: null };
1223
+ if (humanId !== void 0) return { preferredAssigneeHumanId: humanId };
1224
+ if (assistantId !== void 0) return { preferredAssigneeAiAssistantId: assistantId };
1225
+ if (teammateId !== void 0) return { preferredAssigneeAiTeammateId: teammateId };
1226
+ return {};
1227
+ }
1228
+ function assertNonEmptyBody(body) {
1229
+ if (Object.keys(body).length === 0) {
1230
+ throw new WorkflowOptionsError("Error: at least one update option must be provided.");
1231
+ }
1232
+ }
1233
+ function buildQuery(params) {
1234
+ const query = new URLSearchParams();
1235
+ for (const [key, value] of Object.entries(params)) {
1236
+ if (value !== void 0) {
1237
+ query.set(key, String(value));
1238
+ }
1239
+ }
1240
+ const text = query.toString();
1241
+ return text ? `?${text}` : "";
1242
+ }
1243
+ function failResponse(response) {
1244
+ printError(`Error [${response.status}]: ${JSON.stringify(response.data)}`);
1245
+ process.exit(1);
1246
+ }
1247
+ function priorityLabel(value) {
1248
+ switch (value) {
1249
+ case 0:
1250
+ return "urgent";
1251
+ case 1:
1252
+ return "high";
1253
+ case 2:
1254
+ return "medium";
1255
+ case 3:
1256
+ return "low";
1257
+ default: {
1258
+ const unsupportedValue = value;
1259
+ throw new Error(`Unsupported workflow task priority: ${String(unsupportedValue)}`);
1260
+ }
1261
+ }
1262
+ }
1263
+ function statusTypeLabel(value) {
1264
+ switch (value) {
1265
+ case 1:
1266
+ return "todo";
1267
+ case 2:
1268
+ return "in-progress";
1269
+ case 3:
1270
+ return "done";
1271
+ default: {
1272
+ const unsupportedValue = value;
1273
+ throw new Error(`Unsupported workflow status type: ${String(unsupportedValue)}`);
1274
+ }
1275
+ }
1276
+ }
1277
+ function formatAssignee(assignee) {
1278
+ if (!assignee) return "-";
1279
+ if (assignee.type === "human") return assignee.displayName ?? `human:${assignee.humanId}`;
1280
+ if (assignee.type === "assistant") return assignee.displayName ?? `assistant:${assignee.aiAssistantId}`;
1281
+ if (assignee.type === "teammate") return assignee.displayName ?? `teammate:${assignee.aiTeammateId}`;
1282
+ return "unknown";
1283
+ }
1284
+ function printWorkflow(item) {
1285
+ print(`${colors.bold}${item.fileId}${colors.reset} owner=${item.ownerHumanId ?? "-"} desc=${item.desc ?? "-"}`);
1286
+ }
1287
+ function indentation(size) {
1288
+ return " ".repeat(size);
1289
+ }
1290
+ function formatDetailScalar(value) {
1291
+ if (value === null) return "null";
1292
+ if (value === "") return '""';
1293
+ if (typeof value === "string" && /[\r\n]/.test(value)) return JSON.stringify(value);
1294
+ return String(value);
1295
+ }
1296
+ function printDetailField(name, value, indent = 0) {
1297
+ print(`${indentation(indent)}${name}: ${formatDetailScalar(value)}`);
1298
+ }
1299
+ function printDetailTextField(name, value, indent = 0) {
1300
+ if (value === null || value === "") {
1301
+ printDetailField(name, value, indent);
1302
+ return;
1303
+ }
1304
+ print(`${indentation(indent)}${name}:`);
1305
+ for (const line of value.split(/\r?\n/)) {
1306
+ print(`${indentation(indent + 2)}${line}`);
1307
+ }
1308
+ }
1309
+ function printAssigneeDetail(name, assignee, indent = 0) {
1310
+ if (assignee === null) {
1311
+ printDetailField(name, null, indent);
1312
+ return;
1313
+ }
1314
+ print(`${indentation(indent)}${name}:`);
1315
+ printDetailField("type", assignee.type, indent + 2);
1316
+ switch (assignee.type) {
1317
+ case "human":
1318
+ printDetailField("humanId", assignee.humanId, indent + 2);
1319
+ printDetailField("displayName", assignee.displayName, indent + 2);
1320
+ printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
1321
+ return;
1322
+ case "assistant":
1323
+ printDetailField("aiAssistantId", assignee.aiAssistantId, indent + 2);
1324
+ printDetailField("displayName", assignee.displayName, indent + 2);
1325
+ printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
1326
+ return;
1327
+ case "teammate":
1328
+ printDetailField("aiTeammateId", assignee.aiTeammateId, indent + 2);
1329
+ printDetailField("displayName", assignee.displayName, indent + 2);
1330
+ printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
1331
+ return;
1332
+ case "unknown":
1333
+ printDetailField("humanId", assignee.humanId, indent + 2);
1334
+ printDetailField("aiAssistantId", assignee.aiAssistantId, indent + 2);
1335
+ printDetailField("aiTeammateId", assignee.aiTeammateId, indent + 2);
1336
+ }
1337
+ }
1338
+ function printStatusRemainingFields(item, indent) {
1339
+ printDetailField("name", item.name, indent);
1340
+ printDetailTextField("desc", item.desc, indent);
1341
+ printDetailField("type", `${item.type} (${statusTypeLabel(item.type)})`, indent);
1342
+ printAssigneeDetail("preferredAssignee", item.preferredAssignee, indent);
1343
+ printDetailField("order", item.order, indent);
1344
+ }
1345
+ function printStatusDetail(item) {
1346
+ print(`${colors.bold}Status${colors.reset}`);
1347
+ printDetailField("scopedId", item.scopedId);
1348
+ printStatusRemainingFields(item, 0);
1349
+ }
1350
+ function printWorkflowDetail(item) {
1351
+ print(`${colors.bold}Workflow${colors.reset}`);
1352
+ printDetailField("fileId", item.fileId);
1353
+ printDetailField("ownerHumanId", item.ownerHumanId);
1354
+ printDetailTextField("desc", item.desc);
1355
+ if (item.statusList.length === 0) {
1356
+ print("statusList: []");
1357
+ } else {
1358
+ print("statusList:");
1359
+ for (const status of item.statusList) {
1360
+ print(` - scopedId: ${status.scopedId}`);
1361
+ printStatusRemainingFields(status, 4);
1362
+ }
1363
+ }
1364
+ if (item.transitions.length === 0) {
1365
+ print("transitions: []");
1366
+ } else {
1367
+ print("transitions:");
1368
+ for (const transition of item.transitions) {
1369
+ print(` - statusFromScopedId: ${transition.statusFromScopedId}`);
1370
+ printDetailField("statusToScopedId", transition.statusToScopedId, 4);
1371
+ }
1372
+ }
1373
+ }
1374
+ function printTaskSummary(item) {
1375
+ const runIds = item.agentRuns.map((run) => run.pipelineTriggerId).join(",") || "-";
1376
+ print(
1377
+ `${colors.bold}#${item.scopedId}${colors.reset} ${item.title} status=${item.statusScopedId} priority=${priorityLabel(item.priority)} assignee=${formatAssignee(item.assignee)} runs=${runIds}`
1378
+ );
1379
+ }
1380
+ function printTaskDetail(item) {
1381
+ print(`${colors.bold}Task${colors.reset}`);
1382
+ printDetailField("scopedId", item.scopedId);
1383
+ printDetailField("workflowFileId", item.workflowFileId);
1384
+ printDetailField("ownerHumanId", item.ownerHumanId);
1385
+ printDetailField("title", item.title);
1386
+ printDetailTextField("desc", item.desc);
1387
+ printDetailField("statusScopedId", item.statusScopedId);
1388
+ printDetailField("priority", `${item.priority} (${priorityLabel(item.priority)})`);
1389
+ printAssigneeDetail("assignee", item.assignee);
1390
+ if (item.linkedFileIds.length === 0) {
1391
+ print("linkedFileIds: []");
1392
+ } else {
1393
+ print("linkedFileIds:");
1394
+ for (const fileId of item.linkedFileIds) print(` - ${fileId}`);
1395
+ }
1396
+ printDetailField("subscribed", item.subscribed);
1397
+ if (item.agentRuns.length === 0) {
1398
+ print("agentRuns: []");
1399
+ } else {
1400
+ print("agentRuns:");
1401
+ for (const run of item.agentRuns) {
1402
+ print(` - pipelineTriggerId: ${run.pipelineTriggerId}`);
1403
+ printAssigneeDetail("executor", run.executor, 4);
1404
+ }
1405
+ }
1406
+ printDetailField("createdAt", item.createdAt);
1407
+ printDetailField("latestActAt", item.latestActAt);
1408
+ }
1409
+ function registerWorkflowCommand(program2) {
1410
+ const workflow = program2.command("workflow").description("Workflow operations");
1411
+ addWorkspaceOption(
1412
+ workflow.command("list").description("List accessible workflows").option("--scope <scope>", "Workflow scope: all or owner_is_me", "all")
1413
+ ).action(async (options) => {
1414
+ const scope = runOrExit3(() => parseWorkflowScope(options.scope));
1415
+ startSpinner("Fetching workflows...");
1416
+ const response = await httpGet(
1417
+ `/workspaces/${options.workspace}/workflows${buildQuery({ scope })}`
1418
+ );
1419
+ if (!response.ok) {
1420
+ stopSpinner(false, "Failed");
1421
+ failResponse(response);
1422
+ }
1423
+ if (response.data.workflows.length === 0) {
1424
+ stopSpinner(true, "No workflows found.");
1425
+ return;
1426
+ }
1427
+ stopSpinner(true, `Found ${response.data.workflows.length} workflow(s)`);
1428
+ for (const item of response.data.workflows) printWorkflow(item);
1429
+ });
1430
+ addSpaceOptions2(
1431
+ workflow.command("create").description("Create a blank workflow").requiredOption("-p, --path <path>", "Target .workflow path").option("--desc-file <localPath>", "Local UTF-8 text file for workflow description").option("--owner-human-id <humanId>", "Workflow owner human ID")
1432
+ ).action(async (options) => {
1433
+ const body = runOrExit3(() => {
1434
+ const next = {
1435
+ path: options.path,
1436
+ ...resolveSpaceSelector(options)
1437
+ };
1438
+ const desc = readDescFile(options.descFile);
1439
+ if (desc !== void 0) next.desc = desc;
1440
+ const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
1441
+ if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
1442
+ return next;
1443
+ });
1444
+ startSpinner("Creating workflow...");
1445
+ const response = await httpPost(`/workspaces/${options.workspace}/workflows`, body);
1446
+ if (!response.ok) {
1447
+ stopSpinner(false, "Failed");
1448
+ failResponse(response);
1449
+ }
1450
+ stopSpinner(true, "Workflow created");
1451
+ printWorkflow(response.data);
1452
+ });
1453
+ addWorkspaceOption(workflow.command("get").description("Get a workflow").argument("<workflowFileId>")).action(
1454
+ async (workflowFileId, options) => {
1455
+ startSpinner("Fetching workflow...");
1456
+ const response = await httpGet(
1457
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}`
1458
+ );
1459
+ if (!response.ok) {
1460
+ stopSpinner(false, "Failed");
1461
+ failResponse(response);
1462
+ }
1463
+ stopSpinner(true, "Workflow fetched");
1464
+ printWorkflowDetail(response.data);
1465
+ }
1466
+ );
1467
+ addWorkspaceOption(
1468
+ workflow.command("update").description("Update workflow metadata").argument("<workflowFileId>").option("--desc-file <localPath>", "Local UTF-8 text file for workflow description").option("--owner-human-id <humanId>", "Workflow owner human ID")
1469
+ ).action(async (workflowFileId, options) => {
1470
+ const body = runOrExit3(() => {
1471
+ const next = {};
1472
+ const desc = readDescFile(options.descFile);
1473
+ if (desc !== void 0) next.desc = desc;
1474
+ const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
1475
+ if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
1476
+ assertNonEmptyBody(next);
1477
+ return next;
1478
+ });
1479
+ startSpinner("Updating workflow...");
1480
+ const response = await httpPatch(`/workspaces/${options.workspace}/workflows/${workflowFileId}`, body);
1481
+ if (!response.ok) {
1482
+ stopSpinner(false, "Failed");
1483
+ failResponse(response);
1484
+ }
1485
+ stopSpinner(true, "Workflow updated");
1486
+ printWorkflow(response.data);
1487
+ });
1488
+ addWorkspaceOption(
1489
+ workflow.command("delete").description("Delete a workflow").argument("<workflowFileId>").option("--message <message>", "Commit message")
1490
+ ).action(async (workflowFileId, options) => {
1491
+ startSpinner("Deleting workflow...");
1492
+ const response = await httpDelete(
1493
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}`,
1494
+ options.message ? { message: options.message } : {}
1495
+ );
1496
+ if (!response.ok) {
1497
+ stopSpinner(false, "Failed");
1498
+ failResponse(response);
1499
+ }
1500
+ stopSpinner(true, `Deleted: ${response.data.fileId}`);
1501
+ });
1502
+ addWorkspaceOption(workflow.command("duplicate").description("Duplicate a workflow").argument("<workflowFileId>")).action(
1503
+ async (workflowFileId, options) => {
1504
+ startSpinner("Duplicating workflow...");
1505
+ const response = await httpPost(
1506
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/duplicate`,
1507
+ {}
1508
+ );
1509
+ if (!response.ok) {
1510
+ stopSpinner(false, "Failed");
1511
+ failResponse(response);
1512
+ }
1513
+ stopSpinner(true, "Workflow duplicated");
1514
+ printWorkflow(response.data);
1515
+ }
1516
+ );
1517
+ registerStatusCommands(workflow);
1518
+ registerTransitionCommands(workflow);
1519
+ registerTaskCommands(workflow);
1520
+ registerRunCommands(workflow);
1521
+ }
1522
+ function registerStatusCommands(workflow) {
1523
+ const status = workflow.command("status").description("Workflow status operations");
1524
+ addPreferredAssigneeOptions(
1525
+ addWorkspaceOption(
1526
+ status.command("create").description("Create a workflow status").argument("<workflowFileId>").requiredOption("--name <name>", "Status name").requiredOption("--type <type>", "Status type value: 1=todo, 2=in-progress, 3=done").option("--desc-file <localPath>", "Local UTF-8 text file for status description")
1527
+ )
1528
+ ).action(
1529
+ async (workflowFileId, options) => {
1530
+ const body = runOrExit3(() => {
1531
+ const next = {
1532
+ name: options.name,
1533
+ type: parseRequiredStatusType(options.type)
1534
+ };
1535
+ const desc = readDescFile(options.descFile);
1536
+ if (desc !== void 0) next.desc = desc;
1537
+ Object.assign(next, parsePreferredAssigneeBody(options));
1538
+ return next;
1539
+ });
1540
+ startSpinner("Creating status...");
1541
+ const response = await httpPost(
1542
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses`,
1543
+ body
1544
+ );
1545
+ if (!response.ok) {
1546
+ stopSpinner(false, "Failed");
1547
+ failResponse(response);
1548
+ }
1549
+ stopSpinner(true, "Status created");
1550
+ printStatusDetail(response.data);
1551
+ }
1552
+ );
1553
+ addPreferredAssigneeOptions(
1554
+ addWorkspaceOption(
1555
+ status.command("update").description("Update a workflow status").argument("<workflowFileId>").argument("<statusScopedId>").option("--name <name>", "Status name").option("--type <type>", "Status type value: 1=todo, 2=in-progress, 3=done").option("--desc-file <localPath>", "Local UTF-8 text file for status description").option("--clear-desc", "Clear status description").option("--clear-preferred-assignee", "Clear preferred assignee")
1556
+ )
1557
+ ).action(
1558
+ async (workflowFileId, statusScopedId, options) => {
1559
+ const body = runOrExit3(() => {
1560
+ if (options.descFile && options.clearDesc) {
1561
+ throw new WorkflowOptionsError("Error: --desc-file cannot be used with --clear-desc.");
1562
+ }
1563
+ const next = {};
1564
+ if (options.name) next.name = options.name;
1565
+ const type = parseStatusType(options.type);
1566
+ if (type !== void 0) next.type = type;
1567
+ const desc = readDescFile(options.descFile);
1568
+ if (desc !== void 0) next.desc = desc;
1569
+ if (options.clearDesc) next.desc = null;
1570
+ Object.assign(next, parsePreferredAssigneeBody(options));
1571
+ assertNonEmptyBody(next);
1572
+ return next;
1573
+ });
1574
+ const id = runOrExit3(() => parsePositiveInteger(statusScopedId, "statusScopedId"));
1575
+ startSpinner("Updating status...");
1576
+ const response = await httpPatch(
1577
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses/${id}`,
1578
+ body
1579
+ );
1580
+ if (!response.ok) {
1581
+ stopSpinner(false, "Failed");
1582
+ failResponse(response);
1583
+ }
1584
+ stopSpinner(true, "Status updated");
1585
+ printStatusDetail(response.data);
1586
+ }
1587
+ );
1588
+ addWorkspaceOption(
1589
+ status.command("move-after").description("Move a workflow status after another status").argument("<workflowFileId>").argument("<statusScopedId>").option("--after <statusScopedId>", "Anchor status scoped ID").option("--first", "Move to first position")
1590
+ ).action(
1591
+ async (workflowFileId, statusScopedId, options) => {
1592
+ const { id, anchoredStatusScopedId } = runOrExit3(() => {
1593
+ if (options.after && options.first) {
1594
+ throw new WorkflowOptionsError("Error: --after cannot be used with --first.");
1595
+ }
1596
+ if (!options.after && !options.first) {
1597
+ throw new WorkflowOptionsError("Error: either --after or --first is required.");
1598
+ }
1599
+ return {
1600
+ id: parsePositiveInteger(statusScopedId, "statusScopedId"),
1601
+ anchoredStatusScopedId: options.first ? null : parsePositiveInteger(options.after, "--after")
1602
+ };
1603
+ });
1604
+ startSpinner("Moving status...");
1605
+ const response = await httpPost(
1606
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses/${id}/move-after`,
1607
+ { anchoredStatusScopedId }
1608
+ );
1609
+ if (!response.ok) {
1610
+ stopSpinner(false, "Failed");
1611
+ failResponse(response);
1612
+ }
1613
+ stopSpinner(true, "Status moved");
1614
+ }
1615
+ );
1616
+ addWorkspaceOption(
1617
+ status.command("delete").description("Delete a workflow status").argument("<workflowFileId>").argument("<statusScopedId>")
1618
+ ).action(async (workflowFileId, statusScopedId, options) => {
1619
+ const id = runOrExit3(() => parsePositiveInteger(statusScopedId, "statusScopedId"));
1620
+ startSpinner("Deleting status...");
1621
+ const response = await httpDelete(
1622
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/statuses/${id}`
1623
+ );
1624
+ if (!response.ok) {
1625
+ stopSpinner(false, "Failed");
1626
+ failResponse(response);
1627
+ }
1628
+ stopSpinner(true, `Deleted: ${response.data.scopedId}`);
1629
+ });
1630
+ }
1631
+ function registerTransitionCommands(workflow) {
1632
+ const transition = workflow.command("transition").description("Workflow transition operations");
1633
+ for (const action of ["create", "delete"]) {
1634
+ addWorkspaceOption(
1635
+ transition.command(action).description(`${action === "create" ? "Create" : "Delete"} a workflow transition`).argument("<workflowFileId>").requiredOption("--from <statusScopedId>", "Source status scoped ID").requiredOption("--to <statusScopedId>", "Target status scoped ID")
1636
+ ).action(async (workflowFileId, options) => {
1637
+ const body = runOrExit3(() => ({
1638
+ statusFromScopedId: parsePositiveInteger(options.from, "--from"),
1639
+ statusToScopedId: parsePositiveInteger(options.to, "--to")
1640
+ }));
1641
+ startSpinner(`${action === "create" ? "Creating" : "Deleting"} transition...`);
1642
+ const response = action === "create" ? await httpPost(
1643
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/transitions`,
1644
+ body
1645
+ ) : await httpDelete(
1646
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/transitions`,
1647
+ body
1648
+ );
1649
+ if (!response.ok) {
1650
+ stopSpinner(false, "Failed");
1651
+ failResponse(response);
1652
+ }
1653
+ stopSpinner(true, action === "create" ? "Transition created" : "Transition deleted");
1654
+ print(`${response.data.statusFromScopedId} -> ${response.data.statusToScopedId}`);
1655
+ });
1656
+ }
1657
+ }
1658
+ function registerTaskCommands(workflow) {
1659
+ const task = workflow.command("task").description("Workflow task operations");
1660
+ addWorkspaceOption(
1661
+ task.command("list").description("List workflow tasks").option("--workflow-file-id <workflowFileId>", "Filter by workflow file ID").option("--scope <scope>", "Task scope", "all").option("-l, --limit <limit>", "Maximum number of tasks to return", "20").option("--cursor <cursor>", "Pagination cursor")
1662
+ ).action(
1663
+ async (options) => {
1664
+ const query = runOrExit3(() => buildQuery({
1665
+ scope: parseTaskScope(options.scope),
1666
+ limit: parseSearchLimit(options.limit, 20, 100),
1667
+ cursor: options.cursor,
1668
+ workflowFileId: options.workflowFileId
1669
+ }));
1670
+ startSpinner("Fetching workflow tasks...");
1671
+ const response = await httpGet(`/workspaces/${options.workspace}/workflow-tasks${query}`);
1672
+ if (!response.ok) {
1673
+ stopSpinner(false, "Failed");
1674
+ failResponse(response);
1675
+ }
1676
+ if (response.data.tasks.length === 0) {
1677
+ stopSpinner(true, "No workflow tasks found.");
1678
+ return;
1679
+ }
1680
+ stopSpinner(true, `Found ${response.data.tasks.length} task(s)`);
1681
+ for (const item of response.data.tasks) printTaskSummary(item);
1682
+ if (response.data.nextCursor) print(`${colors.dim}nextCursor=${response.data.nextCursor}${colors.reset}`);
1683
+ }
1684
+ );
1685
+ addWorkspaceOption(
1686
+ task.command("get").description("Get a workflow task").argument("<workflowFileId>").argument("<taskScopedId>")
1687
+ ).action(async (workflowFileId, taskScopedId, options) => {
1688
+ const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
1689
+ startSpinner("Fetching task...");
1690
+ const response = await httpGet(
1691
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}`
1692
+ );
1693
+ if (!response.ok) {
1694
+ stopSpinner(false, "Failed");
1695
+ failResponse(response);
1696
+ }
1697
+ stopSpinner(true, "Task fetched");
1698
+ printTaskDetail(response.data);
1699
+ });
1700
+ addAssigneeOptions(
1701
+ addWorkspaceOption(
1702
+ task.command("create").description("Create a workflow task").argument("<workflowFileId>").requiredOption("--title <title>", "Task title").requiredOption("--status <statusScopedId>", "Status scoped ID").option("--priority <priority>", "Priority: urgent, high, medium, or low", "medium").option("--desc-file <localPath>", "Local UTF-8 text file for task description").option("--owner-human-id <humanId>", "Task owner human ID")
1703
+ )
1704
+ ).action(
1705
+ async (workflowFileId, options) => {
1706
+ const body = runOrExit3(() => {
1707
+ const next = {
1708
+ title: options.title,
1709
+ statusScopedId: parsePositiveInteger(options.status, "--status"),
1710
+ priority: parseRequiredPriority(options.priority)
1711
+ };
1712
+ const desc = readDescFile(options.descFile);
1713
+ if (desc !== void 0) next.desc = desc;
1714
+ const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
1715
+ if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
1716
+ Object.assign(next, parseAssigneeBody(options, false));
1717
+ return next;
1718
+ });
1719
+ startSpinner("Creating task...");
1720
+ const response = await httpPost(
1721
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks`,
1722
+ body
1723
+ );
1724
+ if (!response.ok) {
1725
+ stopSpinner(false, "Failed");
1726
+ failResponse(response);
1727
+ }
1728
+ stopSpinner(true, "Task created");
1729
+ printTaskDetail(response.data);
1730
+ }
1731
+ );
1732
+ addWorkspaceOption(
1733
+ task.command("update").description("Update workflow task metadata").argument("<workflowFileId>").argument("<taskScopedId>").option("--title <title>", "Task title").option("--priority <priority>", "Priority: urgent, high, medium, or low").option("--desc-file <localPath>", "Local UTF-8 text file for task description").option("--clear-desc", "Clear task description").option("--owner-human-id <humanId>", "Task owner human ID")
1734
+ ).action(
1735
+ async (workflowFileId, taskScopedId, options) => {
1736
+ const body = runOrExit3(() => {
1737
+ if (options.descFile && options.clearDesc) {
1738
+ throw new WorkflowOptionsError("Error: --desc-file cannot be used with --clear-desc.");
1739
+ }
1740
+ const next = {};
1741
+ if (options.title) next.title = options.title;
1742
+ const priority = parsePriority(options.priority);
1743
+ if (priority !== void 0) next.priority = priority;
1744
+ const desc = readDescFile(options.descFile);
1745
+ if (desc !== void 0) next.desc = desc;
1746
+ if (options.clearDesc) next.desc = null;
1747
+ const ownerHumanId = parseOptionalPositiveInteger(options.ownerHumanId, "--owner-human-id");
1748
+ if (ownerHumanId !== void 0) next.ownerHumanId = ownerHumanId;
1749
+ assertNonEmptyBody(next);
1750
+ return next;
1751
+ });
1752
+ const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
1753
+ startSpinner("Updating task...");
1754
+ const response = await httpPatch(
1755
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/metadata`,
1756
+ body
1757
+ );
1758
+ if (!response.ok) {
1759
+ stopSpinner(false, "Failed");
1760
+ failResponse(response);
1761
+ }
1762
+ stopSpinner(true, "Task updated");
1763
+ printTaskDetail(response.data);
1764
+ }
1765
+ );
1766
+ for (const action of ["add-linked-files", "remove-linked-files"]) {
1767
+ addWorkspaceOption(
1768
+ task.command(action).description(`${action === "add-linked-files" ? "Add" : "Remove"} linked files on a workflow task`).argument("<workflowFileId>").argument("<taskScopedId>").requiredOption("--file-ids <ids>", "Comma-separated file IDs")
1769
+ ).action(
1770
+ async (workflowFileId, taskScopedId, options) => {
1771
+ const { id, fileIds } = runOrExit3(() => ({
1772
+ id: parsePositiveInteger(taskScopedId, "taskScopedId"),
1773
+ fileIds: parseFileIds(options.fileIds, "--file-ids")
1774
+ }));
1775
+ startSpinner(action === "add-linked-files" ? "Adding linked files..." : "Removing linked files...");
1776
+ const path2 = `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/linked-files`;
1777
+ const response = action === "add-linked-files" ? await httpPost(path2, { fileIds }) : await httpDelete(path2, { fileIds });
1778
+ if (!response.ok) {
1779
+ stopSpinner(false, "Failed");
1780
+ failResponse(response);
1781
+ }
1782
+ stopSpinner(true, action === "add-linked-files" ? "Linked files added" : "Linked files removed");
1783
+ printTaskDetail(response.data);
1784
+ }
1785
+ );
1786
+ }
1787
+ addWorkspaceOption(
1788
+ task.command("move").description("Move a workflow task to another status").argument("<workflowFileId>").argument("<taskScopedId>").requiredOption("--status <statusScopedId>", "Target status scoped ID")
1789
+ ).action(
1790
+ async (workflowFileId, taskScopedId, options) => {
1791
+ const { id, statusScopedId } = runOrExit3(() => ({
1792
+ id: parsePositiveInteger(taskScopedId, "taskScopedId"),
1793
+ statusScopedId: parsePositiveInteger(options.status, "--status")
1794
+ }));
1795
+ startSpinner("Moving task...");
1796
+ const response = await httpPatch(
1797
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/status`,
1798
+ { statusScopedId }
1799
+ );
1800
+ if (!response.ok) {
1801
+ stopSpinner(false, "Failed");
1802
+ failResponse(response);
1803
+ }
1804
+ stopSpinner(true, "Task moved");
1805
+ printTaskDetail(response.data);
1806
+ }
1807
+ );
1808
+ addAssigneeOptions(
1809
+ addWorkspaceOption(
1810
+ task.command("assign").description("Update workflow task assignee").argument("<workflowFileId>").argument("<taskScopedId>").option("--clear", "Clear assignee")
1811
+ )
1812
+ ).action(
1813
+ async (workflowFileId, taskScopedId, options) => {
1814
+ const body = runOrExit3(() => {
1815
+ const next = parseAssigneeBody(options, true);
1816
+ if (Object.keys(next).length === 0 && options.clear !== true) assertNonEmptyBody(next);
1817
+ return next;
1818
+ });
1819
+ const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
1820
+ startSpinner("Updating task assignee...");
1821
+ const response = await httpPatch(
1822
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/assignee`,
1823
+ body
1824
+ );
1825
+ if (!response.ok) {
1826
+ stopSpinner(false, "Failed");
1827
+ failResponse(response);
1828
+ }
1829
+ stopSpinner(true, "Task assignee updated");
1830
+ printTaskDetail(response.data);
1831
+ }
1832
+ );
1833
+ addWorkspaceOption(
1834
+ task.command("delete").description("Delete a workflow task").argument("<workflowFileId>").argument("<taskScopedId>")
1835
+ ).action(async (workflowFileId, taskScopedId, options) => {
1836
+ const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
1837
+ startSpinner("Deleting task...");
1838
+ const response = await httpDelete(
1839
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}`
1840
+ );
1841
+ if (!response.ok) {
1842
+ stopSpinner(false, "Failed");
1843
+ failResponse(response);
1844
+ }
1845
+ stopSpinner(true, `Deleted: ${response.data.scopedId}`);
1846
+ });
1847
+ }
1848
+ function registerRunCommands(workflow) {
1849
+ const run = workflow.command("run").description("Workflow agent run operations");
1850
+ addWorkspaceOption(
1851
+ run.command("status").description("Get workflow agent run statuses").argument("<workflowFileId>").argument("<triggerIds...>")
1852
+ ).action(async (workflowFileId, triggerIds, options) => {
1853
+ const workflowPipelineTriggerIds = runOrExit3(
1854
+ () => triggerIds.map((triggerId) => parsePositiveInteger(triggerId, "workflowPipelineTriggerId"))
1855
+ );
1856
+ startSpinner("Fetching run statuses...");
1857
+ const response = await httpPost(
1858
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/pipeline/triggers/statuses`,
1859
+ { workflowPipelineTriggerIds }
1860
+ );
1861
+ if (!response.ok) {
1862
+ stopSpinner(false, "Failed");
1863
+ failResponse(response);
1864
+ }
1865
+ stopSpinner(true, `Found ${response.data.length} run status item(s)`);
1866
+ for (const item of response.data) {
1867
+ print(`${colors.bold}${item.workflowPipelineTriggerId}${colors.reset} ${item.pipelineStatus}`);
1868
+ }
1869
+ });
1870
+ addWorkspaceOption(
1871
+ run.command("output").description("Get workflow agent run output events").argument("<workflowFileId>").argument("<triggerId>")
1872
+ ).action(async (workflowFileId, triggerId, options) => {
1873
+ const id = runOrExit3(() => parsePositiveInteger(triggerId, "workflowPipelineTriggerId"));
1874
+ startSpinner("Fetching run output...");
1875
+ const response = await httpGet(
1876
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/pipeline/trigger/${id}/output`
1877
+ );
1878
+ if (!response.ok) {
1879
+ stopSpinner(false, "Failed");
1880
+ failResponse(response);
1881
+ }
1882
+ stopSpinner(true, `Run ${response.data.workflowPipelineTriggerId}`);
1883
+ for (const event of response.data.pipelineEvents) print(event);
1884
+ });
1885
+ addWorkspaceOption(
1886
+ run.command("abort").description("Abort a workflow agent run").argument("<workflowFileId>").argument("<triggerId>")
1887
+ ).action(async (workflowFileId, triggerId, options) => {
1888
+ const id = runOrExit3(() => parsePositiveInteger(triggerId, "workflowPipelineTriggerId"));
1889
+ startSpinner("Aborting run...");
1890
+ const response = await httpPost(
1891
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/pipeline/trigger/${id}/abort`,
1892
+ {}
1893
+ );
1894
+ if (!response.ok) {
1895
+ stopSpinner(false, "Failed");
1896
+ failResponse(response);
1897
+ }
1898
+ stopSpinner(true, `Abort accepted: ${id}`);
1899
+ });
1900
+ }
1901
+
1902
+ // src/program.ts
1903
+ function createProgram(version) {
1904
+ const program2 = new Command();
1905
+ program2.name("moxt").description("Moxt CLI - AI Workspace").version(version).action(() => {
1906
+ program2.help();
1907
+ });
1908
+ registerAuthCommand(program2);
1909
+ registerWhoamiCommand(program2);
1910
+ registerWorkspaceCommand(program2);
1911
+ registerFileCommand(program2);
1912
+ registerMemoryCommand(program2);
1913
+ registerWorkflowCommand(program2);
1914
+ registerMiniappCommand(program2);
1915
+ registerTelemetryCommand(program2);
1916
+ return program2;
1917
+ }
1918
+
942
1919
  // src/telemetry/client.ts
943
1920
  import { arch as arch2, platform as platform2 } from "os";
944
1921
  var buffer = [];
@@ -946,7 +1923,7 @@ var FLUSH_TIMEOUT_MS = 3e3;
946
1923
  var MAX_BATCH_SIZE = 100;
947
1924
  function commonLabels() {
948
1925
  return {
949
- cli_version: "0.3.2",
1926
+ cli_version: "0.3.3-beta.1",
950
1927
  node_version: process.versions.node,
951
1928
  os: platform2(),
952
1929
  arch: arch2(),
@@ -992,7 +1969,7 @@ async function flush() {
992
1969
  }
993
1970
  const batch = buffer.splice(0, MAX_BATCH_SIZE);
994
1971
  const payload = {
995
- release_id: "0.3.2",
1972
+ release_id: "0.3.3-beta.1",
996
1973
  client_type: "cli",
997
1974
  metric_data_list: batch.map((m) => ({
998
1975
  profile: "cli",
@@ -1133,20 +2110,11 @@ function installExitHandler() {
1133
2110
 
1134
2111
  // src/index.ts
1135
2112
  updateNotifier({
1136
- pkg: { name: "@moxt-ai/cli", version: "0.3.2" }
2113
+ pkg: { name: "@moxt-ai/cli", version: "0.3.3-beta.1" }
1137
2114
  }).notify();
1138
- var program = new Command();
1139
- program.name("moxt").description("Moxt CLI - AI Workspace").version("0.3.2").action(() => {
1140
- program.help();
1141
- });
2115
+ var program = createProgram("0.3.3-beta.1");
1142
2116
  instrumentProgram(program);
1143
2117
  installExitHandler();
1144
- registerWhoamiCommand(program);
1145
- registerWorkspaceCommand(program);
1146
- registerFileCommand(program);
1147
- registerMemoryCommand(program);
1148
- registerMiniappCommand(program);
1149
- registerTelemetryCommand(program);
1150
2118
  program.parseAsync(process.argv).then(async () => {
1151
2119
  await flush();
1152
2120
  }).catch(async (err) => {