@khangal.j/fireside-cli 0.0.4 → 0.0.5
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 +2 -1
- package/dist/index.js +366 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,8 @@ fireside tasks handoff create <task-id> --to @alice --summary "Ready for review"
|
|
|
38
38
|
- `fireside tasks delete <task-id> [--project <project>] [--json]`
|
|
39
39
|
- `fireside tasks handoff create <task-id> --to <member> --summary <summary> [--project <project>] [--next <member>] [--context <markdown> | --context-file <path>] [--json]`
|
|
40
40
|
|
|
41
|
-
Member selectors accept
|
|
41
|
+
Member selectors accept `@username`, email, user id, or `me`.
|
|
42
|
+
CLI text output prefers usernames so the assignable handle stays visible.
|
|
42
43
|
For handoffs, `--to` must be another project member.
|
|
43
44
|
|
|
44
45
|
By default, the CLI talks to:
|
package/dist/index.js
CHANGED
|
@@ -154,6 +154,19 @@ async function createTask(baseUrl, accessToken, projectId, boardId, input) {
|
|
|
154
154
|
}
|
|
155
155
|
);
|
|
156
156
|
}
|
|
157
|
+
async function createTasks(baseUrl, accessToken, projectId, boardId, inputs) {
|
|
158
|
+
return requestJson(
|
|
159
|
+
`${baseUrl}/api/projects/${encodeURIComponent(projectId)}/boards/${encodeURIComponent(boardId)}/tasks/bulk`,
|
|
160
|
+
{
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: {
|
|
163
|
+
...getAuthHeaders(accessToken),
|
|
164
|
+
"content-type": "application/json"
|
|
165
|
+
},
|
|
166
|
+
body: JSON.stringify({ tasks: inputs })
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
}
|
|
157
170
|
async function updateTask(baseUrl, accessToken, projectId, boardId, taskId, input) {
|
|
158
171
|
return requestJson(
|
|
159
172
|
`${baseUrl}/api/projects/${encodeURIComponent(projectId)}/boards/${encodeURIComponent(boardId)}/tasks/${encodeURIComponent(taskId)}`,
|
|
@@ -167,6 +180,19 @@ async function updateTask(baseUrl, accessToken, projectId, boardId, taskId, inpu
|
|
|
167
180
|
}
|
|
168
181
|
);
|
|
169
182
|
}
|
|
183
|
+
async function moveTask(baseUrl, accessToken, projectId, boardId, taskId, input) {
|
|
184
|
+
return requestJson(
|
|
185
|
+
`${baseUrl}/api/projects/${encodeURIComponent(projectId)}/boards/${encodeURIComponent(boardId)}/tasks/${encodeURIComponent(taskId)}`,
|
|
186
|
+
{
|
|
187
|
+
method: "PATCH",
|
|
188
|
+
headers: {
|
|
189
|
+
...getAuthHeaders(accessToken),
|
|
190
|
+
"content-type": "application/json"
|
|
191
|
+
},
|
|
192
|
+
body: JSON.stringify(input)
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
170
196
|
async function deleteTask(baseUrl, accessToken, projectId, boardId, taskId) {
|
|
171
197
|
return requestJson(
|
|
172
198
|
`${baseUrl}/api/projects/${encodeURIComponent(projectId)}/boards/${encodeURIComponent(boardId)}/tasks/${encodeURIComponent(taskId)}`,
|
|
@@ -483,6 +509,9 @@ function formatCode(code) {
|
|
|
483
509
|
function formatUsername(username) {
|
|
484
510
|
return username ? import_picocolors.default.cyan(`@${username}`) : import_picocolors.default.dim("Not set");
|
|
485
511
|
}
|
|
512
|
+
function formatUserHandle(user) {
|
|
513
|
+
return user.username ? import_picocolors.default.cyan(`@${user.username}`) : import_picocolors.default.cyan(user.email);
|
|
514
|
+
}
|
|
486
515
|
function formatHeading(title, id) {
|
|
487
516
|
if (!id) {
|
|
488
517
|
return import_picocolors.default.bold(title);
|
|
@@ -519,12 +548,6 @@ function formatProjectMarker(color) {
|
|
|
519
548
|
const formatter = colorFormatters[color] || ((value) => value);
|
|
520
549
|
return formatter("o");
|
|
521
550
|
}
|
|
522
|
-
function formatNames(names) {
|
|
523
|
-
if (!names.length) {
|
|
524
|
-
return import_picocolors.default.dim("None");
|
|
525
|
-
}
|
|
526
|
-
return names.map((name) => import_picocolors.default.cyan(name)).join(import_picocolors.default.dim(", "));
|
|
527
|
-
}
|
|
528
551
|
function formatUserCodeForDisplay(userCode) {
|
|
529
552
|
return userCode.match(/.{1,4}/g)?.join("-") || userCode;
|
|
530
553
|
}
|
|
@@ -616,7 +639,7 @@ function uniqueStrings(values) {
|
|
|
616
639
|
return [...new Set(values)];
|
|
617
640
|
}
|
|
618
641
|
function formatMember(member) {
|
|
619
|
-
return
|
|
642
|
+
return formatUserHandle(member);
|
|
620
643
|
}
|
|
621
644
|
function formatMemberList(members) {
|
|
622
645
|
if (!members.length) {
|
|
@@ -772,6 +795,58 @@ async function resolveTextInput(value, filePath, label) {
|
|
|
772
795
|
}
|
|
773
796
|
return value;
|
|
774
797
|
}
|
|
798
|
+
function coerceAssignees(value, label) {
|
|
799
|
+
if (value === void 0 || value === null) {
|
|
800
|
+
return void 0;
|
|
801
|
+
}
|
|
802
|
+
if (typeof value === "string") {
|
|
803
|
+
return value.trim() ? [value.trim()] : [];
|
|
804
|
+
}
|
|
805
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
806
|
+
return value;
|
|
807
|
+
}
|
|
808
|
+
throw new Error(
|
|
809
|
+
`${label}: \`assignees\` must be a string or array of strings.`
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
function normalizeBulkTaskRow(value, label) {
|
|
813
|
+
if (typeof value === "string") {
|
|
814
|
+
return { title: value.trim() };
|
|
815
|
+
}
|
|
816
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
817
|
+
return { title: "" };
|
|
818
|
+
}
|
|
819
|
+
const row = value;
|
|
820
|
+
const optionalString = (key) => typeof row[key] === "string" ? row[key] : void 0;
|
|
821
|
+
return {
|
|
822
|
+
title: typeof row.title === "string" ? row.title.trim() : "",
|
|
823
|
+
description: optionalString("description"),
|
|
824
|
+
column: optionalString("column"),
|
|
825
|
+
dueDate: optionalString("dueDate") ?? optionalString("due-date"),
|
|
826
|
+
assignees: coerceAssignees(row.assignees, label)
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
function parseBulkTaskRows(content) {
|
|
830
|
+
const trimmed = content.trim();
|
|
831
|
+
if (!trimmed) {
|
|
832
|
+
return [];
|
|
833
|
+
}
|
|
834
|
+
let parsedJson;
|
|
835
|
+
try {
|
|
836
|
+
parsedJson = JSON.parse(trimmed);
|
|
837
|
+
} catch {
|
|
838
|
+
return trimmed.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#")).map((line) => ({ title: line }));
|
|
839
|
+
}
|
|
840
|
+
const rows = Array.isArray(parsedJson) ? parsedJson : parsedJson && typeof parsedJson === "object" && "tasks" in parsedJson ? parsedJson.tasks : void 0;
|
|
841
|
+
if (!Array.isArray(rows)) {
|
|
842
|
+
throw new Error(
|
|
843
|
+
'Tasks file must be a JSON array, a `{ "tasks": [...] }` object, or one title per line.'
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
return rows.map(
|
|
847
|
+
(row, index) => normalizeBulkTaskRow(row, `Task ${index + 1}`)
|
|
848
|
+
);
|
|
849
|
+
}
|
|
775
850
|
async function loadCliContext(baseUrl) {
|
|
776
851
|
const state = await requireAuthState();
|
|
777
852
|
const configState = await loadConfigState();
|
|
@@ -806,6 +881,37 @@ async function resolveTaskEntry(baseUrl, accessToken, taskId, projectSelector) {
|
|
|
806
881
|
}
|
|
807
882
|
return taskEntries[0];
|
|
808
883
|
}
|
|
884
|
+
function parseTargetPosition(value) {
|
|
885
|
+
if (value === void 0) {
|
|
886
|
+
return 0;
|
|
887
|
+
}
|
|
888
|
+
const position = Number(value);
|
|
889
|
+
if (!Number.isInteger(position) || position < 0) {
|
|
890
|
+
throw new Error("`--position` must be a non-negative integer.");
|
|
891
|
+
}
|
|
892
|
+
return position;
|
|
893
|
+
}
|
|
894
|
+
async function runTaskMove(baseUrl, accessToken, taskEntry, targetColumn, targetPosition, json) {
|
|
895
|
+
const movedTask = await moveTask(
|
|
896
|
+
baseUrl,
|
|
897
|
+
accessToken,
|
|
898
|
+
taskEntry.project.id,
|
|
899
|
+
taskEntry.board.id,
|
|
900
|
+
taskEntry.task.id,
|
|
901
|
+
{ targetColumnId: targetColumn.id, targetPosition }
|
|
902
|
+
);
|
|
903
|
+
const movedTaskEntry = {
|
|
904
|
+
...taskEntry,
|
|
905
|
+
column: targetColumn,
|
|
906
|
+
task: movedTask
|
|
907
|
+
};
|
|
908
|
+
if (json) {
|
|
909
|
+
console.log(JSON.stringify(serializeTaskEntry(movedTaskEntry), null, 2));
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
printSuccess(`Moved task to ${targetColumn.title}.`);
|
|
913
|
+
printTaskSummary(movedTaskEntry);
|
|
914
|
+
}
|
|
809
915
|
function resolveProjectMember(members, selector, currentUserId) {
|
|
810
916
|
const trimmedSelector = selector.trim();
|
|
811
917
|
if (!trimmedSelector.length) {
|
|
@@ -901,10 +1007,7 @@ function printAssignedTasks(tasks, projectFilter) {
|
|
|
901
1007
|
if (task.dueDate) {
|
|
902
1008
|
printKeyValue(" Due", import_picocolors.default.yellow(formatDueDate(task.dueDate)));
|
|
903
1009
|
}
|
|
904
|
-
printKeyValue(
|
|
905
|
-
" Assignees",
|
|
906
|
-
formatNames(task.assignees.map((assignee) => assignee.name))
|
|
907
|
-
);
|
|
1010
|
+
printKeyValue(" Assignees", formatMemberList(task.assignees));
|
|
908
1011
|
if (task.description) {
|
|
909
1012
|
console.log(` ${task.description}`);
|
|
910
1013
|
}
|
|
@@ -945,10 +1048,9 @@ addBaseUrlOption(
|
|
|
945
1048
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
946
1049
|
});
|
|
947
1050
|
const user = await getCurrentUser(baseUrl, accessToken);
|
|
948
|
-
printSuccess(`Signed in as ${import_picocolors.default.bold(user
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
}
|
|
1051
|
+
printSuccess(`Signed in as ${import_picocolors.default.bold(formatUserHandle(user))}.`);
|
|
1052
|
+
printKeyValue("Email", import_picocolors.default.cyan(user.email));
|
|
1053
|
+
printKeyValue("Username", formatUsername(user.username));
|
|
952
1054
|
})
|
|
953
1055
|
);
|
|
954
1056
|
program.command("logout").description("Remove the local CLI session").action(async () => {
|
|
@@ -967,9 +1069,8 @@ addBaseUrlOption(
|
|
|
967
1069
|
const baseUrl = await resolveBaseUrl(options.baseUrl, configState);
|
|
968
1070
|
const user = await getCurrentUser(baseUrl, state.accessToken);
|
|
969
1071
|
printKeyValue("Base URL", formatUrl(baseUrl));
|
|
970
|
-
printSuccess(
|
|
971
|
-
|
|
972
|
-
);
|
|
1072
|
+
printSuccess(`Signed in as ${import_picocolors.default.bold(formatUserHandle(user))}.`);
|
|
1073
|
+
printKeyValue("Email", import_picocolors.default.cyan(user.email));
|
|
973
1074
|
printKeyValue("Username", formatUsername(user.username));
|
|
974
1075
|
})
|
|
975
1076
|
);
|
|
@@ -1092,7 +1193,7 @@ addBaseUrlOption(
|
|
|
1092
1193
|
addBaseUrlOption(
|
|
1093
1194
|
tasksCommand.command("create").description("Create a task").requiredOption("-p, --project <project>", "Project id or title").requiredOption("-t, --title <title>", "Task title").requiredOption("-c, --column <column>", "Column id or title").option("--board <board>", "Board id or title").option("--description <description>", "Task description").option("--description-file <path>", "Read task description from a file").option("--due-date <date>", "Due date in YYYY-MM-DD format").option(
|
|
1094
1195
|
"-a, --assignee <member>",
|
|
1095
|
-
"Assign a member by
|
|
1196
|
+
"Assign a member by @username, email, id, or me",
|
|
1096
1197
|
collectOptionValue,
|
|
1097
1198
|
[]
|
|
1098
1199
|
).option("--json", "Print the created task as JSON").action(
|
|
@@ -1147,10 +1248,202 @@ addBaseUrlOption(
|
|
|
1147
1248
|
}
|
|
1148
1249
|
)
|
|
1149
1250
|
);
|
|
1251
|
+
addBaseUrlOption(
|
|
1252
|
+
tasksCommand.command("bulk-create").description("Create many tasks at once from a file").requiredOption("-p, --project <project>", "Project id or title").requiredOption(
|
|
1253
|
+
"--file <path>",
|
|
1254
|
+
"Tasks file: JSON array, `{ tasks: [...] }`, or one title per line"
|
|
1255
|
+
).option("--board <board>", "Board id or title").option(
|
|
1256
|
+
"-c, --column <column>",
|
|
1257
|
+
"Default column id or title for rows without their own"
|
|
1258
|
+
).option(
|
|
1259
|
+
"--due-date <date>",
|
|
1260
|
+
"Default due date (YYYY-MM-DD) for rows without their own"
|
|
1261
|
+
).option(
|
|
1262
|
+
"-a, --assignee <member>",
|
|
1263
|
+
"Default assignee (@username, email, id, or me) for rows without their own",
|
|
1264
|
+
collectOptionValue,
|
|
1265
|
+
[]
|
|
1266
|
+
).option(
|
|
1267
|
+
"--continue-on-error",
|
|
1268
|
+
"Best-effort: create row by row and keep going on failures (non-atomic)"
|
|
1269
|
+
).option("--dry-run", "Resolve and preview tasks without creating them").option("--json", "Print results as JSON").action(
|
|
1270
|
+
async (options) => {
|
|
1271
|
+
const { accessToken, baseUrl, user } = await loadCliContextWithCurrentUser(options.baseUrl);
|
|
1272
|
+
const rows = parseBulkTaskRows(
|
|
1273
|
+
await readTextFile(options.file, "tasks")
|
|
1274
|
+
);
|
|
1275
|
+
if (!rows.length) {
|
|
1276
|
+
throw new Error("No tasks found in the file.");
|
|
1277
|
+
}
|
|
1278
|
+
const [projectBoardsResult] = await loadProjectBoardsResults(
|
|
1279
|
+
baseUrl,
|
|
1280
|
+
accessToken,
|
|
1281
|
+
options.project
|
|
1282
|
+
);
|
|
1283
|
+
const board = resolveBoard(
|
|
1284
|
+
projectBoardsResult.boardsData.boards,
|
|
1285
|
+
options.board
|
|
1286
|
+
);
|
|
1287
|
+
const members = projectBoardsResult.boardsData.members;
|
|
1288
|
+
const resolved = [];
|
|
1289
|
+
const failed = [];
|
|
1290
|
+
rows.forEach((row, index) => {
|
|
1291
|
+
const label = row.title || `Task ${index + 1}`;
|
|
1292
|
+
try {
|
|
1293
|
+
if (!row.title) {
|
|
1294
|
+
throw new Error("Task title is required.");
|
|
1295
|
+
}
|
|
1296
|
+
const column = resolveColumn(
|
|
1297
|
+
board.columns,
|
|
1298
|
+
row.column ?? options.column
|
|
1299
|
+
);
|
|
1300
|
+
const assigneeIds = resolveProjectMemberIds(
|
|
1301
|
+
members,
|
|
1302
|
+
row.assignees ?? options.assignee,
|
|
1303
|
+
user.id
|
|
1304
|
+
);
|
|
1305
|
+
resolved.push({
|
|
1306
|
+
column,
|
|
1307
|
+
input: {
|
|
1308
|
+
assigneeIds,
|
|
1309
|
+
boardColumnId: column.id,
|
|
1310
|
+
description: row.description ?? "",
|
|
1311
|
+
dueDate: row.dueDate ?? options.dueDate ?? "",
|
|
1312
|
+
title: row.title
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
failed.push({
|
|
1317
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1318
|
+
title: label
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
});
|
|
1322
|
+
const toTaskEntry = (createdTask) => ({
|
|
1323
|
+
board,
|
|
1324
|
+
column: board.columns.find(
|
|
1325
|
+
(column) => column.id === createdTask.boardColumnId
|
|
1326
|
+
) ?? board.columns[0],
|
|
1327
|
+
members,
|
|
1328
|
+
project: projectBoardsResult.project,
|
|
1329
|
+
task: createdTask
|
|
1330
|
+
});
|
|
1331
|
+
if (options.dryRun) {
|
|
1332
|
+
if (options.json) {
|
|
1333
|
+
console.log(
|
|
1334
|
+
JSON.stringify(
|
|
1335
|
+
{
|
|
1336
|
+
failed,
|
|
1337
|
+
plan: resolved.map(({ column, input }) => ({
|
|
1338
|
+
boardColumnId: input.boardColumnId,
|
|
1339
|
+
column: column.title,
|
|
1340
|
+
dueDate: input.dueDate || null,
|
|
1341
|
+
title: input.title
|
|
1342
|
+
}))
|
|
1343
|
+
},
|
|
1344
|
+
null,
|
|
1345
|
+
2
|
|
1346
|
+
)
|
|
1347
|
+
);
|
|
1348
|
+
} else {
|
|
1349
|
+
printInfo(
|
|
1350
|
+
`Dry run: ${resolved.length} task(s) ready for ${projectBoardsResult.project.title} / ${board.title}.`
|
|
1351
|
+
);
|
|
1352
|
+
for (const { column, input } of resolved) {
|
|
1353
|
+
console.log(
|
|
1354
|
+
` ${import_picocolors.default.green("+")} ${input.title} ${import_picocolors.default.dim(`(${column.title})`)}`
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
for (const failure of failed) {
|
|
1358
|
+
console.log(
|
|
1359
|
+
` ${import_picocolors.default.red("x")} ${failure.title} ${import_picocolors.default.dim(`\u2014 ${failure.error}`)}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (failed.length) {
|
|
1364
|
+
process.exitCode = 1;
|
|
1365
|
+
}
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
const created = [];
|
|
1369
|
+
if (options.continueOnError) {
|
|
1370
|
+
for (const { input } of resolved) {
|
|
1371
|
+
try {
|
|
1372
|
+
created.push(
|
|
1373
|
+
await createTask(
|
|
1374
|
+
baseUrl,
|
|
1375
|
+
accessToken,
|
|
1376
|
+
projectBoardsResult.project.id,
|
|
1377
|
+
board.id,
|
|
1378
|
+
input
|
|
1379
|
+
)
|
|
1380
|
+
);
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
failed.push({
|
|
1383
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1384
|
+
title: input.title
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
} else {
|
|
1389
|
+
if (failed.length) {
|
|
1390
|
+
throw new Error(
|
|
1391
|
+
`${failed.length} task(s) could not be resolved. Fix them or pass --continue-on-error.
|
|
1392
|
+
` + failed.map((failure) => ` - ${failure.title}: ${failure.error}`).join("\n")
|
|
1393
|
+
);
|
|
1394
|
+
}
|
|
1395
|
+
created.push(
|
|
1396
|
+
...await createTasks(
|
|
1397
|
+
baseUrl,
|
|
1398
|
+
accessToken,
|
|
1399
|
+
projectBoardsResult.project.id,
|
|
1400
|
+
board.id,
|
|
1401
|
+
resolved.map(({ input }) => input)
|
|
1402
|
+
)
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
const createdEntries = created.map(toTaskEntry);
|
|
1406
|
+
if (options.json) {
|
|
1407
|
+
console.log(
|
|
1408
|
+
JSON.stringify(
|
|
1409
|
+
{
|
|
1410
|
+
created: createdEntries.map(
|
|
1411
|
+
(entry) => serializeTaskEntry(entry)
|
|
1412
|
+
),
|
|
1413
|
+
failed
|
|
1414
|
+
},
|
|
1415
|
+
null,
|
|
1416
|
+
2
|
|
1417
|
+
)
|
|
1418
|
+
);
|
|
1419
|
+
} else {
|
|
1420
|
+
printSuccess(
|
|
1421
|
+
`Created ${created.length} task(s) in ${projectBoardsResult.project.title} / ${board.title}.`
|
|
1422
|
+
);
|
|
1423
|
+
for (const entry of createdEntries) {
|
|
1424
|
+
console.log(
|
|
1425
|
+
` ${import_picocolors.default.green("+")} ${entry.task.title} ${import_picocolors.default.dim(`(${entry.column.title})`)}`
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
if (failed.length) {
|
|
1429
|
+
printWarning(`${failed.length} task(s) failed:`);
|
|
1430
|
+
for (const failure of failed) {
|
|
1431
|
+
console.log(
|
|
1432
|
+
` ${import_picocolors.default.red("x")} ${failure.title} ${import_picocolors.default.dim(`\u2014 ${failure.error}`)}`
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
if (failed.length) {
|
|
1438
|
+
process.exitCode = 1;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
)
|
|
1442
|
+
);
|
|
1150
1443
|
addBaseUrlOption(
|
|
1151
1444
|
tasksCommand.command("update <taskId>").description("Update a task").option("-p, --project <project>", "Limit lookup to a project id or title").option("-t, --title <title>", "New task title").option("--description <description>", "New task description").option("--description-file <path>", "Read task description from a file").option("--clear-description", "Clear the task description").option("--due-date <date>", "Set the due date in YYYY-MM-DD format").option("--clear-due-date", "Clear the due date").option(
|
|
1152
1445
|
"-a, --assignee <member>",
|
|
1153
|
-
"Replace assignees using
|
|
1446
|
+
"Replace assignees using @username, email, id, or me",
|
|
1154
1447
|
collectOptionValue,
|
|
1155
1448
|
[]
|
|
1156
1449
|
).option("--clear-assignees", "Remove all assignees").option("--json", "Print the updated task as JSON").action(
|
|
@@ -1218,6 +1511,58 @@ addBaseUrlOption(
|
|
|
1218
1511
|
}
|
|
1219
1512
|
)
|
|
1220
1513
|
);
|
|
1514
|
+
addBaseUrlOption(
|
|
1515
|
+
tasksCommand.command("move <taskId>").description("Move a task to a different column").requiredOption("-c, --column <column>", "Target column id or title").option("-p, --project <project>", "Limit lookup to a project id or title").option("--position <position>", "Target position in the column (0 = top)").option("--json", "Print the moved task as JSON").action(
|
|
1516
|
+
async (taskId, options) => {
|
|
1517
|
+
const { accessToken, baseUrl } = await loadCliContext(options.baseUrl);
|
|
1518
|
+
const taskEntry = await resolveTaskEntry(
|
|
1519
|
+
baseUrl,
|
|
1520
|
+
accessToken,
|
|
1521
|
+
taskId,
|
|
1522
|
+
options.project
|
|
1523
|
+
);
|
|
1524
|
+
const targetColumn = resolveColumn(
|
|
1525
|
+
taskEntry.board.columns,
|
|
1526
|
+
options.column
|
|
1527
|
+
);
|
|
1528
|
+
await runTaskMove(
|
|
1529
|
+
baseUrl,
|
|
1530
|
+
accessToken,
|
|
1531
|
+
taskEntry,
|
|
1532
|
+
targetColumn,
|
|
1533
|
+
parseTargetPosition(options.position),
|
|
1534
|
+
options.json
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
)
|
|
1538
|
+
);
|
|
1539
|
+
addBaseUrlOption(
|
|
1540
|
+
tasksCommand.command("done <taskId>").description("Mark a task done (move it to the board's done column)").option("-p, --project <project>", "Limit lookup to a project id or title").option("--json", "Print the moved task as JSON").action(
|
|
1541
|
+
async (taskId, options) => {
|
|
1542
|
+
const { accessToken, baseUrl } = await loadCliContext(options.baseUrl);
|
|
1543
|
+
const taskEntry = await resolveTaskEntry(
|
|
1544
|
+
baseUrl,
|
|
1545
|
+
accessToken,
|
|
1546
|
+
taskId,
|
|
1547
|
+
options.project
|
|
1548
|
+
);
|
|
1549
|
+
const doneColumn = taskEntry.board.columns.find(
|
|
1550
|
+
(column) => column.role === "done"
|
|
1551
|
+
);
|
|
1552
|
+
if (!doneColumn) {
|
|
1553
|
+
throw new Error("This board has no done column.");
|
|
1554
|
+
}
|
|
1555
|
+
await runTaskMove(
|
|
1556
|
+
baseUrl,
|
|
1557
|
+
accessToken,
|
|
1558
|
+
taskEntry,
|
|
1559
|
+
doneColumn,
|
|
1560
|
+
0,
|
|
1561
|
+
options.json
|
|
1562
|
+
);
|
|
1563
|
+
}
|
|
1564
|
+
)
|
|
1565
|
+
);
|
|
1221
1566
|
addBaseUrlOption(
|
|
1222
1567
|
tasksCommand.command("delete <taskId>").description("Delete a task").option("-p, --project <project>", "Limit lookup to a project id or title").option("--json", "Print the deleted task metadata as JSON").action(
|
|
1223
1568
|
async (taskId, options) => {
|
|
@@ -1267,7 +1612,7 @@ addBaseUrlOption(
|
|
|
1267
1612
|
);
|
|
1268
1613
|
var taskHandoffCommand = tasksCommand.command("handoff").description("Create and manage task handoffs");
|
|
1269
1614
|
addBaseUrlOption(
|
|
1270
|
-
taskHandoffCommand.command("create <taskId>").description("Create a handoff for a task").requiredOption("--to <member>", "Target
|
|
1615
|
+
taskHandoffCommand.command("create <taskId>").description("Create a handoff for a task").requiredOption("--to <member>", "Target @username, email, or user id").requiredOption("--summary <summary>", "Short handoff summary").option("-p, --project <project>", "Limit lookup to a project id or title").option("--next <member>", "Next assignee by @username, email, id, or me").option("--context <markdown>", "Handoff AI context markdown").option("--context-file <path>", "Read handoff AI context from a file").option("--json", "Print the created handoff as JSON").action(
|
|
1271
1616
|
async (taskId, options) => {
|
|
1272
1617
|
const { accessToken, baseUrl, user } = await loadCliContextWithCurrentUser(options.baseUrl);
|
|
1273
1618
|
const taskEntry = await resolveTaskEntry(
|