@moxt-ai/cli 0.3.3-beta.0 → 0.3.3-beta.2

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 CHANGED
@@ -193,6 +193,11 @@ moxt workflow task assign -w <workspace-id> <workflow-file-id> <task-id> --teamm
193
193
  moxt workflow task assign -w <workspace-id> <workflow-file-id> <task-id> --clear
194
194
  moxt workflow task delete -w <workspace-id> <workflow-file-id> <task-id>
195
195
 
196
+ # Manage workflow task comments
197
+ moxt workflow comment create -w <workspace-id> <workflow-file-id> <task-id> --content-path <local-path>
198
+ moxt workflow comment list -w <workspace-id> <workflow-file-id> <task-id>
199
+ moxt workflow comment delete -w <workspace-id> <workflow-file-id> <task-id> <comment-id>
200
+
196
201
  # Inspect or abort workflow agent runs
197
202
  moxt workflow run status -w <workspace-id> <workflow-file-id> <trigger-id> [trigger-id...]
198
203
  moxt workflow run output -w <workspace-id> <workflow-file-id> <trigger-id>
@@ -201,6 +206,8 @@ moxt workflow run abort -w <workspace-id> <workflow-file-id> <trigger-id>
201
206
 
202
207
  Workflow, status, and task descriptions are read from local UTF-8 text files with `--desc-file`.
203
208
  Inline `--desc` is not supported.
209
+ Workflow task comment content is read only from a local UTF-8 text file with `--content-path` and is limited to
210
+ 5,000 characters. Inline `--content` is not supported.
204
211
  Workflow status type values are `1=todo`, `2=in-progress`, and `3=done`.
205
212
 
206
213
  #### File command options
package/dist/index.js CHANGED
@@ -6,6 +6,157 @@ import updateNotifier from "update-notifier";
6
6
  // src/program.ts
7
7
  import { Command } from "commander";
8
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
+
9
160
  // src/commands/file.ts
10
161
  import * as fs from "fs";
11
162
 
@@ -149,7 +300,7 @@ function getApiKeyOrUndefined() {
149
300
 
150
301
  // src/utils/http.ts
151
302
  function getUserAgent() {
152
- return `moxt-cli/${"0.3.3-beta.0"} (${platform()}/${arch()}) node/${process.versions.node}`;
303
+ return `moxt-cli/${"0.3.3-beta.2"} (${platform()}/${arch()}) node/${process.versions.node}`;
153
304
  }
154
305
  async function fetchApi(method, path2, body) {
155
306
  const apiKey = getApiKey();
@@ -205,23 +356,6 @@ async function httpRaw(method, path2, body) {
205
356
  };
206
357
  }
207
358
 
208
- // src/utils/output.ts
209
- var colors = {
210
- bold: "\x1B[1m",
211
- blue: "\x1B[1;34m",
212
- green: "\x1B[32m",
213
- red: "\x1B[31m",
214
- cyan: "\x1B[36m",
215
- dim: "\x1B[2m",
216
- reset: "\x1B[0m"
217
- };
218
- function print(message) {
219
- console.log(message);
220
- }
221
- function printError(message) {
222
- console.error(`${colors.red}${message}${colors.reset}`);
223
- }
224
-
225
359
  // src/utils/search-options.ts
226
360
  var SearchOptionsError = class extends Error {
227
361
  constructor(message) {
@@ -946,7 +1080,10 @@ function registerWorkspaceCommand(program2) {
946
1080
 
947
1081
  // src/commands/workflow.ts
948
1082
  import * as fs3 from "fs";
1083
+ import { TextDecoder } from "util";
949
1084
  var MAX_DESC_LENGTH = 5e3;
1085
+ var MAX_COMMENT_CONTENT_LENGTH = 5e3;
1086
+ var MAX_COMMENT_CONTENT_BYTES = MAX_COMMENT_CONTENT_LENGTH * 4;
950
1087
  var WorkflowOptionsError = class extends Error {
951
1088
  constructor(message) {
952
1089
  super(message);
@@ -1052,6 +1189,42 @@ function readDescFile(path2) {
1052
1189
  }
1053
1190
  return content;
1054
1191
  }
1192
+ function readCommentContentFile(path2) {
1193
+ if (!fs3.existsSync(path2)) {
1194
+ throw new WorkflowOptionsError(`Error: comment content file not found: ${path2}`);
1195
+ }
1196
+ const stat = fs3.statSync(path2);
1197
+ if (!stat.isFile()) {
1198
+ throw new WorkflowOptionsError(`Error: comment content path is not a file: ${path2}`);
1199
+ }
1200
+ if (stat.size > MAX_COMMENT_CONTENT_BYTES) {
1201
+ throw new WorkflowOptionsError(
1202
+ `Error: comment content file must be at most ${MAX_COMMENT_CONTENT_LENGTH} characters.`
1203
+ );
1204
+ }
1205
+ const buffer2 = fs3.readFileSync(path2);
1206
+ if (buffer2.includes(0)) {
1207
+ throw new WorkflowOptionsError("Error: comment content file must not contain NUL bytes.");
1208
+ }
1209
+ if (isBinaryContent(buffer2)) {
1210
+ throw new WorkflowOptionsError("Error: comment content file must be UTF-8 text, not binary content.");
1211
+ }
1212
+ let content;
1213
+ try {
1214
+ content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(buffer2);
1215
+ } catch {
1216
+ throw new WorkflowOptionsError("Error: comment content file must contain valid UTF-8 text.");
1217
+ }
1218
+ if (content.length === 0) {
1219
+ throw new WorkflowOptionsError("Error: comment content file must not be empty.");
1220
+ }
1221
+ if (content.length > MAX_COMMENT_CONTENT_LENGTH) {
1222
+ throw new WorkflowOptionsError(
1223
+ `Error: comment content file must be at most ${MAX_COMMENT_CONTENT_LENGTH} characters.`
1224
+ );
1225
+ }
1226
+ return content;
1227
+ }
1055
1228
  function parseAssigneeBody(options, allowClear) {
1056
1229
  const hasClear = options.clear === true;
1057
1230
  const humanId = parseOptionalPositiveInteger(options.humanId, "--human-id");
@@ -1111,10 +1284,34 @@ function failResponse(response) {
1111
1284
  process.exit(1);
1112
1285
  }
1113
1286
  function priorityLabel(value) {
1114
- if (value === 0) return "urgent";
1115
- if (value === 1) return "high";
1116
- if (value === 2) return "medium";
1117
- return "low";
1287
+ switch (value) {
1288
+ case 0:
1289
+ return "urgent";
1290
+ case 1:
1291
+ return "high";
1292
+ case 2:
1293
+ return "medium";
1294
+ case 3:
1295
+ return "low";
1296
+ default: {
1297
+ const unsupportedValue = value;
1298
+ throw new Error(`Unsupported workflow task priority: ${String(unsupportedValue)}`);
1299
+ }
1300
+ }
1301
+ }
1302
+ function statusTypeLabel(value) {
1303
+ switch (value) {
1304
+ case 1:
1305
+ return "todo";
1306
+ case 2:
1307
+ return "in-progress";
1308
+ case 3:
1309
+ return "done";
1310
+ default: {
1311
+ const unsupportedValue = value;
1312
+ throw new Error(`Unsupported workflow status type: ${String(unsupportedValue)}`);
1313
+ }
1314
+ }
1118
1315
  }
1119
1316
  function formatAssignee(assignee) {
1120
1317
  if (!assignee) return "-";
@@ -1126,17 +1323,149 @@ function formatAssignee(assignee) {
1126
1323
  function printWorkflow(item) {
1127
1324
  print(`${colors.bold}${item.fileId}${colors.reset} owner=${item.ownerHumanId ?? "-"} desc=${item.desc ?? "-"}`);
1128
1325
  }
1129
- function printStatus(item) {
1130
- print(
1131
- `${colors.bold}${item.scopedId}${colors.reset} ${item.name} type=${item.type} preferred=${formatAssignee(item.preferredAssignee)}`
1326
+ function indentation(size) {
1327
+ return " ".repeat(size);
1328
+ }
1329
+ function escapeTerminalControlCharacters(value) {
1330
+ return value.replace(/\r\n/g, "\n").replace(
1331
+ /[\u0000-\u0008\u000B-\u001F\u007F-\u009F]/g,
1332
+ (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`
1132
1333
  );
1133
1334
  }
1134
- function printTask(item) {
1335
+ function formatDetailScalar(value) {
1336
+ if (value === null) return "null";
1337
+ if (typeof value === "string") {
1338
+ const escaped = escapeTerminalControlCharacters(value);
1339
+ if (escaped === "") return '""';
1340
+ if (/\n/.test(escaped)) return JSON.stringify(escaped);
1341
+ return escaped;
1342
+ }
1343
+ return String(value);
1344
+ }
1345
+ function printDetailField(name, value, indent = 0) {
1346
+ print(`${indentation(indent)}${name}: ${formatDetailScalar(value)}`);
1347
+ }
1348
+ function printDetailTextField(name, value, indent = 0) {
1349
+ if (value === null || value === "") {
1350
+ printDetailField(name, value, indent);
1351
+ return;
1352
+ }
1353
+ const escaped = escapeTerminalControlCharacters(value);
1354
+ print(`${indentation(indent)}${name}:`);
1355
+ for (const line of escaped.split("\n")) {
1356
+ print(`${indentation(indent + 2)}${line}`);
1357
+ }
1358
+ }
1359
+ function printAssigneeDetail(name, assignee, indent = 0) {
1360
+ if (assignee === null) {
1361
+ printDetailField(name, null, indent);
1362
+ return;
1363
+ }
1364
+ print(`${indentation(indent)}${name}:`);
1365
+ printDetailField("type", assignee.type, indent + 2);
1366
+ switch (assignee.type) {
1367
+ case "human":
1368
+ printDetailField("humanId", assignee.humanId, indent + 2);
1369
+ printDetailField("displayName", assignee.displayName, indent + 2);
1370
+ printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
1371
+ return;
1372
+ case "assistant":
1373
+ printDetailField("aiAssistantId", assignee.aiAssistantId, indent + 2);
1374
+ printDetailField("displayName", assignee.displayName, indent + 2);
1375
+ printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
1376
+ return;
1377
+ case "teammate":
1378
+ printDetailField("aiTeammateId", assignee.aiTeammateId, indent + 2);
1379
+ printDetailField("displayName", assignee.displayName, indent + 2);
1380
+ printDetailField("avatarUrl", assignee.avatarUrl, indent + 2);
1381
+ return;
1382
+ case "unknown":
1383
+ printDetailField("humanId", assignee.humanId, indent + 2);
1384
+ printDetailField("aiAssistantId", assignee.aiAssistantId, indent + 2);
1385
+ printDetailField("aiTeammateId", assignee.aiTeammateId, indent + 2);
1386
+ }
1387
+ }
1388
+ function printStatusRemainingFields(item, indent) {
1389
+ printDetailField("name", item.name, indent);
1390
+ printDetailTextField("desc", item.desc, indent);
1391
+ printDetailField("type", `${item.type} (${statusTypeLabel(item.type)})`, indent);
1392
+ printAssigneeDetail("preferredAssignee", item.preferredAssignee, indent);
1393
+ printDetailField("order", item.order, indent);
1394
+ }
1395
+ function printStatusDetail(item) {
1396
+ print(`${colors.bold}Status${colors.reset}`);
1397
+ printDetailField("scopedId", item.scopedId);
1398
+ printStatusRemainingFields(item, 0);
1399
+ }
1400
+ function printWorkflowDetail(item) {
1401
+ print(`${colors.bold}Workflow${colors.reset}`);
1402
+ printDetailField("fileId", item.fileId);
1403
+ printDetailField("ownerHumanId", item.ownerHumanId);
1404
+ printDetailTextField("desc", item.desc);
1405
+ if (item.statusList.length === 0) {
1406
+ print("statusList: []");
1407
+ } else {
1408
+ print("statusList:");
1409
+ for (const status of item.statusList) {
1410
+ print(` - scopedId: ${status.scopedId}`);
1411
+ printStatusRemainingFields(status, 4);
1412
+ }
1413
+ }
1414
+ if (item.transitions.length === 0) {
1415
+ print("transitions: []");
1416
+ } else {
1417
+ print("transitions:");
1418
+ for (const transition of item.transitions) {
1419
+ print(` - statusFromScopedId: ${transition.statusFromScopedId}`);
1420
+ printDetailField("statusToScopedId", transition.statusToScopedId, 4);
1421
+ }
1422
+ }
1423
+ }
1424
+ function printTaskSummary(item) {
1135
1425
  const runIds = item.agentRuns.map((run) => run.pipelineTriggerId).join(",") || "-";
1136
1426
  print(
1137
1427
  `${colors.bold}#${item.scopedId}${colors.reset} ${item.title} status=${item.statusScopedId} priority=${priorityLabel(item.priority)} assignee=${formatAssignee(item.assignee)} runs=${runIds}`
1138
1428
  );
1139
1429
  }
1430
+ function printTaskDetail(item) {
1431
+ print(`${colors.bold}Task${colors.reset}`);
1432
+ printDetailField("scopedId", item.scopedId);
1433
+ printDetailField("workflowFileId", item.workflowFileId);
1434
+ printDetailField("ownerHumanId", item.ownerHumanId);
1435
+ printDetailField("title", item.title);
1436
+ printDetailTextField("desc", item.desc);
1437
+ printDetailField("statusScopedId", item.statusScopedId);
1438
+ printDetailField("priority", `${item.priority} (${priorityLabel(item.priority)})`);
1439
+ printAssigneeDetail("assignee", item.assignee);
1440
+ if (item.linkedFileIds.length === 0) {
1441
+ print("linkedFileIds: []");
1442
+ } else {
1443
+ print("linkedFileIds:");
1444
+ for (const fileId of item.linkedFileIds) print(` - ${fileId}`);
1445
+ }
1446
+ printDetailField("subscribed", item.subscribed);
1447
+ if (item.agentRuns.length === 0) {
1448
+ print("agentRuns: []");
1449
+ } else {
1450
+ print("agentRuns:");
1451
+ for (const run of item.agentRuns) {
1452
+ print(` - pipelineTriggerId: ${run.pipelineTriggerId}`);
1453
+ printAssigneeDetail("executor", run.executor, 4);
1454
+ }
1455
+ }
1456
+ printDetailField("createdAt", item.createdAt);
1457
+ printDetailField("latestActAt", item.latestActAt);
1458
+ }
1459
+ function printCommentDetail(item) {
1460
+ print(`${colors.bold}Comment${colors.reset}`);
1461
+ printDetailField("id", item.id);
1462
+ printDetailField("workflowFileId", item.workflowFileId);
1463
+ printDetailField("workflowTaskScopedId", item.workflowTaskScopedId);
1464
+ printDetailTextField("content", item.content);
1465
+ printAssigneeDetail("createdBy", item.createdBy);
1466
+ printDetailField("createdAt", item.createdAt);
1467
+ printDetailField("updatedAt", item.updatedAt);
1468
+ }
1140
1469
  function registerWorkflowCommand(program2) {
1141
1470
  const workflow = program2.command("workflow").description("Workflow operations");
1142
1471
  addWorkspaceOption(
@@ -1192,13 +1521,7 @@ function registerWorkflowCommand(program2) {
1192
1521
  failResponse(response);
1193
1522
  }
1194
1523
  stopSpinner(true, "Workflow fetched");
1195
- printWorkflow(response.data);
1196
- print(`${colors.bold}Statuses${colors.reset}`);
1197
- for (const status of response.data.statusList) printStatus(status);
1198
- print(`${colors.bold}Transitions${colors.reset}`);
1199
- for (const transition of response.data.transitions) {
1200
- print(`${transition.statusFromScopedId} -> ${transition.statusToScopedId}`);
1201
- }
1524
+ printWorkflowDetail(response.data);
1202
1525
  }
1203
1526
  );
1204
1527
  addWorkspaceOption(
@@ -1254,6 +1577,7 @@ function registerWorkflowCommand(program2) {
1254
1577
  registerStatusCommands(workflow);
1255
1578
  registerTransitionCommands(workflow);
1256
1579
  registerTaskCommands(workflow);
1580
+ registerCommentCommands(workflow);
1257
1581
  registerRunCommands(workflow);
1258
1582
  }
1259
1583
  function registerStatusCommands(workflow) {
@@ -1284,7 +1608,7 @@ function registerStatusCommands(workflow) {
1284
1608
  failResponse(response);
1285
1609
  }
1286
1610
  stopSpinner(true, "Status created");
1287
- printStatus(response.data);
1611
+ printStatusDetail(response.data);
1288
1612
  }
1289
1613
  );
1290
1614
  addPreferredAssigneeOptions(
@@ -1319,7 +1643,7 @@ function registerStatusCommands(workflow) {
1319
1643
  failResponse(response);
1320
1644
  }
1321
1645
  stopSpinner(true, "Status updated");
1322
- printStatus(response.data);
1646
+ printStatusDetail(response.data);
1323
1647
  }
1324
1648
  );
1325
1649
  addWorkspaceOption(
@@ -1415,7 +1739,7 @@ function registerTaskCommands(workflow) {
1415
1739
  return;
1416
1740
  }
1417
1741
  stopSpinner(true, `Found ${response.data.tasks.length} task(s)`);
1418
- for (const item of response.data.tasks) printTask(item);
1742
+ for (const item of response.data.tasks) printTaskSummary(item);
1419
1743
  if (response.data.nextCursor) print(`${colors.dim}nextCursor=${response.data.nextCursor}${colors.reset}`);
1420
1744
  }
1421
1745
  );
@@ -1432,7 +1756,7 @@ function registerTaskCommands(workflow) {
1432
1756
  failResponse(response);
1433
1757
  }
1434
1758
  stopSpinner(true, "Task fetched");
1435
- printTask(response.data);
1759
+ printTaskDetail(response.data);
1436
1760
  });
1437
1761
  addAssigneeOptions(
1438
1762
  addWorkspaceOption(
@@ -1463,7 +1787,7 @@ function registerTaskCommands(workflow) {
1463
1787
  failResponse(response);
1464
1788
  }
1465
1789
  stopSpinner(true, "Task created");
1466
- printTask(response.data);
1790
+ printTaskDetail(response.data);
1467
1791
  }
1468
1792
  );
1469
1793
  addWorkspaceOption(
@@ -1497,7 +1821,7 @@ function registerTaskCommands(workflow) {
1497
1821
  failResponse(response);
1498
1822
  }
1499
1823
  stopSpinner(true, "Task updated");
1500
- printTask(response.data);
1824
+ printTaskDetail(response.data);
1501
1825
  }
1502
1826
  );
1503
1827
  for (const action of ["add-linked-files", "remove-linked-files"]) {
@@ -1517,7 +1841,7 @@ function registerTaskCommands(workflow) {
1517
1841
  failResponse(response);
1518
1842
  }
1519
1843
  stopSpinner(true, action === "add-linked-files" ? "Linked files added" : "Linked files removed");
1520
- printTask(response.data);
1844
+ printTaskDetail(response.data);
1521
1845
  }
1522
1846
  );
1523
1847
  }
@@ -1539,7 +1863,7 @@ function registerTaskCommands(workflow) {
1539
1863
  failResponse(response);
1540
1864
  }
1541
1865
  stopSpinner(true, "Task moved");
1542
- printTask(response.data);
1866
+ printTaskDetail(response.data);
1543
1867
  }
1544
1868
  );
1545
1869
  addAssigneeOptions(
@@ -1564,7 +1888,7 @@ function registerTaskCommands(workflow) {
1564
1888
  failResponse(response);
1565
1889
  }
1566
1890
  stopSpinner(true, "Task assignee updated");
1567
- printTask(response.data);
1891
+ printTaskDetail(response.data);
1568
1892
  }
1569
1893
  );
1570
1894
  addWorkspaceOption(
@@ -1582,6 +1906,71 @@ function registerTaskCommands(workflow) {
1582
1906
  stopSpinner(true, `Deleted: ${response.data.scopedId}`);
1583
1907
  });
1584
1908
  }
1909
+ function registerCommentCommands(workflow) {
1910
+ const comment = workflow.command("comment").description("Workflow task comment operations");
1911
+ addWorkspaceOption(
1912
+ comment.command("create").description("Create a workflow task comment").argument("<workflowFileId>").argument("<taskScopedId>").requiredOption("--content-path <localPath>", "Local UTF-8 text file for comment content")
1913
+ ).action(
1914
+ async (workflowFileId, taskScopedId, options) => {
1915
+ const { id, content } = runOrExit3(() => ({
1916
+ id: parsePositiveInteger(taskScopedId, "taskScopedId"),
1917
+ content: readCommentContentFile(options.contentPath)
1918
+ }));
1919
+ startSpinner("Creating comment...");
1920
+ const response = await httpPost(
1921
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/comments`,
1922
+ { content }
1923
+ );
1924
+ if (!response.ok) {
1925
+ stopSpinner(false, "Failed");
1926
+ failResponse(response);
1927
+ }
1928
+ stopSpinner(true, "Comment created");
1929
+ printCommentDetail(response.data);
1930
+ }
1931
+ );
1932
+ addWorkspaceOption(
1933
+ comment.command("list").description("List workflow task comments").argument("<workflowFileId>").argument("<taskScopedId>")
1934
+ ).action(async (workflowFileId, taskScopedId, options) => {
1935
+ const id = runOrExit3(() => parsePositiveInteger(taskScopedId, "taskScopedId"));
1936
+ startSpinner("Fetching comments...");
1937
+ const response = await httpGet(
1938
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${id}/comments`
1939
+ );
1940
+ if (!response.ok) {
1941
+ stopSpinner(false, "Failed");
1942
+ failResponse(response);
1943
+ }
1944
+ if (response.data.items.length === 0) {
1945
+ stopSpinner(true, "No comments found.");
1946
+ return;
1947
+ }
1948
+ stopSpinner(true, `Found ${response.data.items.length} comment(s)`);
1949
+ response.data.items.forEach((item, index) => {
1950
+ if (index > 0) print("");
1951
+ printCommentDetail(item);
1952
+ });
1953
+ });
1954
+ addWorkspaceOption(
1955
+ comment.command("delete").description("Delete a workflow task comment").argument("<workflowFileId>").argument("<taskScopedId>").argument("<commentId>")
1956
+ ).action(
1957
+ async (workflowFileId, taskScopedId, commentId, options) => {
1958
+ const ids = runOrExit3(() => ({
1959
+ task: parsePositiveInteger(taskScopedId, "taskScopedId"),
1960
+ comment: parsePositiveInteger(commentId, "commentId")
1961
+ }));
1962
+ startSpinner("Deleting comment...");
1963
+ const response = await httpDelete(
1964
+ `/workspaces/${options.workspace}/workflows/${workflowFileId}/tasks/${ids.task}/comments/${ids.comment}`
1965
+ );
1966
+ if (!response.ok) {
1967
+ stopSpinner(false, "Failed");
1968
+ failResponse(response);
1969
+ }
1970
+ stopSpinner(true, `Deleted: ${response.data.id}`);
1971
+ }
1972
+ );
1973
+ }
1585
1974
  function registerRunCommands(workflow) {
1586
1975
  const run = workflow.command("run").description("Workflow agent run operations");
1587
1976
  addWorkspaceOption(
@@ -1642,6 +2031,7 @@ function createProgram(version) {
1642
2031
  program2.name("moxt").description("Moxt CLI - AI Workspace").version(version).action(() => {
1643
2032
  program2.help();
1644
2033
  });
2034
+ registerAuthCommand(program2);
1645
2035
  registerWhoamiCommand(program2);
1646
2036
  registerWorkspaceCommand(program2);
1647
2037
  registerFileCommand(program2);
@@ -1659,7 +2049,7 @@ var FLUSH_TIMEOUT_MS = 3e3;
1659
2049
  var MAX_BATCH_SIZE = 100;
1660
2050
  function commonLabels() {
1661
2051
  return {
1662
- cli_version: "0.3.3-beta.0",
2052
+ cli_version: "0.3.3-beta.2",
1663
2053
  node_version: process.versions.node,
1664
2054
  os: platform2(),
1665
2055
  arch: arch2(),
@@ -1705,7 +2095,7 @@ async function flush() {
1705
2095
  }
1706
2096
  const batch = buffer.splice(0, MAX_BATCH_SIZE);
1707
2097
  const payload = {
1708
- release_id: "0.3.3-beta.0",
2098
+ release_id: "0.3.3-beta.2",
1709
2099
  client_type: "cli",
1710
2100
  metric_data_list: batch.map((m) => ({
1711
2101
  profile: "cli",
@@ -1846,9 +2236,9 @@ function installExitHandler() {
1846
2236
 
1847
2237
  // src/index.ts
1848
2238
  updateNotifier({
1849
- pkg: { name: "@moxt-ai/cli", version: "0.3.3-beta.0" }
2239
+ pkg: { name: "@moxt-ai/cli", version: "0.3.3-beta.2" }
1850
2240
  }).notify();
1851
- var program = createProgram("0.3.3-beta.0");
2241
+ var program = createProgram("0.3.3-beta.2");
1852
2242
  instrumentProgram(program);
1853
2243
  installExitHandler();
1854
2244
  program.parseAsync(process.argv).then(async () => {