@runcofactor/cli 0.10.0
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 +125 -0
- package/dist/api-catalog-GFGCLVS4.js +401 -0
- package/dist/api-catalog-GFGCLVS4.js.map +1 -0
- package/dist/attachment-SJC7D7FY.js +276 -0
- package/dist/attachment-SJC7D7FY.js.map +1 -0
- package/dist/auth-EN6NQCJY.js +31 -0
- package/dist/auth-EN6NQCJY.js.map +1 -0
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-3CZU7XCS.js +272 -0
- package/dist/chunk-3CZU7XCS.js.map +1 -0
- package/dist/chunk-4NRDONSC.js +1048 -0
- package/dist/chunk-4NRDONSC.js.map +1 -0
- package/dist/chunk-4PNOV7OV.js +51980 -0
- package/dist/chunk-4PNOV7OV.js.map +1 -0
- package/dist/chunk-52IO7ACJ.js +2941 -0
- package/dist/chunk-52IO7ACJ.js.map +1 -0
- package/dist/chunk-6LEZCKKV.js +663 -0
- package/dist/chunk-6LEZCKKV.js.map +1 -0
- package/dist/chunk-A4OFTUVV.js +170 -0
- package/dist/chunk-A4OFTUVV.js.map +1 -0
- package/dist/chunk-B2VISN2V.js +2459 -0
- package/dist/chunk-B2VISN2V.js.map +1 -0
- package/dist/chunk-CLAWT7BJ.js +271 -0
- package/dist/chunk-CLAWT7BJ.js.map +1 -0
- package/dist/chunk-GKY2KXO5.js +281 -0
- package/dist/chunk-GKY2KXO5.js.map +1 -0
- package/dist/chunk-I4QLI3JO.js +413 -0
- package/dist/chunk-I4QLI3JO.js.map +1 -0
- package/dist/chunk-IMPZAX6L.js +180 -0
- package/dist/chunk-IMPZAX6L.js.map +1 -0
- package/dist/chunk-J6SH7GQT.js +241 -0
- package/dist/chunk-J6SH7GQT.js.map +1 -0
- package/dist/chunk-JDBCZ3Z2.js +298 -0
- package/dist/chunk-JDBCZ3Z2.js.map +1 -0
- package/dist/chunk-JICSYFRJ.js +46 -0
- package/dist/chunk-JICSYFRJ.js.map +1 -0
- package/dist/chunk-JWHYXEA2.js +32 -0
- package/dist/chunk-JWHYXEA2.js.map +1 -0
- package/dist/chunk-N7HFALX3.js +88 -0
- package/dist/chunk-N7HFALX3.js.map +1 -0
- package/dist/chunk-Q4BO4Z5V.js +178 -0
- package/dist/chunk-Q4BO4Z5V.js.map +1 -0
- package/dist/chunk-QFDOWABX.js +195 -0
- package/dist/chunk-QFDOWABX.js.map +1 -0
- package/dist/chunk-SJJW77WN.js +605 -0
- package/dist/chunk-SJJW77WN.js.map +1 -0
- package/dist/chunk-VKVL7WBN.js +50 -0
- package/dist/chunk-VKVL7WBN.js.map +1 -0
- package/dist/chunk-WOHL4FTB.js +353 -0
- package/dist/chunk-WOHL4FTB.js.map +1 -0
- package/dist/chunk-XQXVG7RA.js +426 -0
- package/dist/chunk-XQXVG7RA.js.map +1 -0
- package/dist/chunk-ZD3KHESI.js +94 -0
- package/dist/chunk-ZD3KHESI.js.map +1 -0
- package/dist/credential-COLTBV6D.js +450 -0
- package/dist/credential-COLTBV6D.js.map +1 -0
- package/dist/credential-template-EEPYFG7R.js +291 -0
- package/dist/credential-template-EEPYFG7R.js.map +1 -0
- package/dist/debug-workflow-KOVH5GOP.js +259 -0
- package/dist/debug-workflow-KOVH5GOP.js.map +1 -0
- package/dist/dist-44PZR7BQ.js +423 -0
- package/dist/dist-44PZR7BQ.js.map +1 -0
- package/dist/docs-3SSORHB5.js +366 -0
- package/dist/docs-3SSORHB5.js.map +1 -0
- package/dist/doctor-OTSIWV6G.js +132 -0
- package/dist/doctor-OTSIWV6G.js.map +1 -0
- package/dist/feature-flag-T2LRZME3.js +283 -0
- package/dist/feature-flag-T2LRZME3.js.map +1 -0
- package/dist/file-type-S3UUBVCT.js +4205 -0
- package/dist/file-type-S3UUBVCT.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +982 -0
- package/dist/index.js.map +1 -0
- package/dist/init-TGWRECT2.js +208 -0
- package/dist/init-TGWRECT2.js.map +1 -0
- package/dist/install-DXSNWZKO.js +295 -0
- package/dist/install-DXSNWZKO.js.map +1 -0
- package/dist/issues-XIZOPX6T.js +564 -0
- package/dist/issues-XIZOPX6T.js.map +1 -0
- package/dist/knobs-NBINZIUB.js +295 -0
- package/dist/knobs-NBINZIUB.js.map +1 -0
- package/dist/list-3TDHK637.js +59 -0
- package/dist/list-3TDHK637.js.map +1 -0
- package/dist/local-HICVD6PU.js +100 -0
- package/dist/local-HICVD6PU.js.map +1 -0
- package/dist/login-UOIUXVUK.js +315 -0
- package/dist/login-UOIUXVUK.js.map +1 -0
- package/dist/mcp-54IJEOUA.js +1956 -0
- package/dist/mcp-54IJEOUA.js.map +1 -0
- package/dist/pdf-extract-ABWV3L5X.js +13 -0
- package/dist/pdf-extract-ABWV3L5X.js.map +1 -0
- package/dist/pdfjs-DQ2YA5S3.js +41561 -0
- package/dist/pdfjs-DQ2YA5S3.js.map +1 -0
- package/dist/property-UXLOLPTD.js +701 -0
- package/dist/property-UXLOLPTD.js.map +1 -0
- package/dist/property-skill-guide.md +189 -0
- package/dist/psql-BYUUNLGG.js +115 -0
- package/dist/psql-BYUUNLGG.js.map +1 -0
- package/dist/record-4TR2UIIS.js +233 -0
- package/dist/record-4TR2UIIS.js.map +1 -0
- package/dist/redis-35VBUYW7.js +116 -0
- package/dist/redis-35VBUYW7.js.map +1 -0
- package/dist/release-OOLTUBUT.js +354 -0
- package/dist/release-OOLTUBUT.js.map +1 -0
- package/dist/sandbox-XGSUU6G7.js +2046 -0
- package/dist/sandbox-XGSUU6G7.js.map +1 -0
- package/dist/scenarios-GWLJEGHA.js +280 -0
- package/dist/scenarios-GWLJEGHA.js.map +1 -0
- package/dist/session-MYFNZPRP.js +2414 -0
- package/dist/session-MYFNZPRP.js.map +1 -0
- package/dist/skill-KM34LDMO.js +1839 -0
- package/dist/skill-KM34LDMO.js.map +1 -0
- package/dist/skills/debug-run/SKILL.md +262 -0
- package/dist/skills/debug-run/history/1.0.0.md +244 -0
- package/dist/skills/debug-run/history/1.0.1.md +245 -0
- package/dist/skills/debug-run/history/1.0.2.md +254 -0
- package/dist/skills/debug-run/history/1.1.0.md +262 -0
- package/dist/skills/debug-run/version.json +3 -0
- package/dist/spec-RA3CFEKG.js +951 -0
- package/dist/spec-RA3CFEKG.js.map +1 -0
- package/dist/src-LB4HBTJQ.js +1516 -0
- package/dist/src-LB4HBTJQ.js.map +1 -0
- package/dist/src-N3TPKGTY.js +244 -0
- package/dist/src-N3TPKGTY.js.map +1 -0
- package/dist/src-UVDX5UP6.js +17 -0
- package/dist/src-UVDX5UP6.js.map +1 -0
- package/dist/start-PSEQSQA3.js +151 -0
- package/dist/start-PSEQSQA3.js.map +1 -0
- package/dist/tag-YY6YAABQ.js +22 -0
- package/dist/tag-YY6YAABQ.js.map +1 -0
- package/dist/task-IQC3YR5K.js +922 -0
- package/dist/task-IQC3YR5K.js.map +1 -0
- package/dist/test-suite-DZNJFXDW.js +394 -0
- package/dist/test-suite-DZNJFXDW.js.map +1 -0
- package/dist/trace-HXIW7U2D.js +14 -0
- package/dist/trace-HXIW7U2D.js.map +1 -0
- package/dist/triage-ORG45R5W.js +16 -0
- package/dist/triage-ORG45R5W.js.map +1 -0
- package/dist/upgrade-L2VYMP2V.js +61 -0
- package/dist/upgrade-L2VYMP2V.js.map +1 -0
- package/dist/workflow-XYFH5LTP.js +3708 -0
- package/dist/workflow-XYFH5LTP.js.map +1 -0
- package/dist/workflow-cache-NQJQFWMV.js +253 -0
- package/dist/workflow-cache-NQJQFWMV.js.map +1 -0
- package/dist/workflow-debug-BGD4UIXU.js +927 -0
- package/dist/workflow-debug-BGD4UIXU.js.map +1 -0
- package/dist/workflow-proposals-MTUDX46O.js +382 -0
- package/dist/workflow-proposals-MTUDX46O.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,922 @@
|
|
|
1
|
+
import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
apiRequest
|
|
4
|
+
} from "./chunk-WOHL4FTB.js";
|
|
5
|
+
import "./chunk-SJJW77WN.js";
|
|
6
|
+
import "./chunk-3CZU7XCS.js";
|
|
7
|
+
import {
|
|
8
|
+
getArgValue,
|
|
9
|
+
hasFlag,
|
|
10
|
+
resolveConfig
|
|
11
|
+
} from "./chunk-A4OFTUVV.js";
|
|
12
|
+
import "./chunk-JWHYXEA2.js";
|
|
13
|
+
import "./chunk-VKVL7WBN.js";
|
|
14
|
+
|
|
15
|
+
// src/task.ts
|
|
16
|
+
import process from "process";
|
|
17
|
+
import { execSync } from "child_process";
|
|
18
|
+
import { readFileSync } from "fs";
|
|
19
|
+
function getHeadCommit() {
|
|
20
|
+
try {
|
|
21
|
+
return execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function printHelp() {
|
|
27
|
+
console.log(
|
|
28
|
+
[
|
|
29
|
+
"canary task: Manage trigger-based agent tasks",
|
|
30
|
+
"",
|
|
31
|
+
"Sub-commands:",
|
|
32
|
+
" create Create a new task",
|
|
33
|
+
" list [options] List tasks",
|
|
34
|
+
" get <id> Get task details",
|
|
35
|
+
" update <id> [options] Update task fields",
|
|
36
|
+
" complete <id> [--pr <number>] [--commit <sha>] Mark task complete (--pr preferred under squash-merge)",
|
|
37
|
+
' set-commit <id> [--pr <number>] [--commit <sha>] Update resolution PR/commit (use "" to clear)',
|
|
38
|
+
" cancel <id> Cancel a task",
|
|
39
|
+
" reopen <id> [--status <s>] Reopen a canceled task (default: actionable)",
|
|
40
|
+
" release <id> [--status <s>] Release an in-progress task (default: actionable)",
|
|
41
|
+
" trigger <id> Manually trigger a task",
|
|
42
|
+
" claim <id> Claim a task (mark in progress)",
|
|
43
|
+
" execute <id> --instance <id> Execute task on a sandbox instance",
|
|
44
|
+
" comment <id> <message> Add a comment to a task",
|
|
45
|
+
" comments <id> List comments for a task",
|
|
46
|
+
" subtasks <id> List subtasks of a parent task",
|
|
47
|
+
" link-customer <id> [opts] Link a customer to a task",
|
|
48
|
+
" unlink-customer <id> [opts] Unlink a customer from a task",
|
|
49
|
+
"",
|
|
50
|
+
"Create options:",
|
|
51
|
+
" --title <text> Short title (required)",
|
|
52
|
+
" --description <text> Task description (required, or -file/-stdin)",
|
|
53
|
+
" --description-file <path> Read description from file",
|
|
54
|
+
" --description-stdin Read description from stdin (pipe or heredoc)",
|
|
55
|
+
" --trigger <type> Trigger type (required)",
|
|
56
|
+
" --commit <sha> Source commit (default: git HEAD)",
|
|
57
|
+
" --flag <name> Flag name (for flag-* triggers)",
|
|
58
|
+
" --knob <key> Knob key (for knob-* triggers)",
|
|
59
|
+
" --pr <number> PR number (for pr-merged)",
|
|
60
|
+
" --scheduled-date <date> ISO date (for scheduled)",
|
|
61
|
+
" --assign <email> Assignee",
|
|
62
|
+
" --priority <0|1|2> Priority (0=normal, 1=high, 2=critical)",
|
|
63
|
+
" --tag <tag> Tag (repeatable)",
|
|
64
|
+
" --repo <owner/repo> Target repo (auto-selected if org has one)",
|
|
65
|
+
" --role <frontend|backend|...> Resolve repo by role (alternative to --repo)",
|
|
66
|
+
" --expires <date> Expiry date (ISO 8601)",
|
|
67
|
+
" --parent <id> Parent task ID (creates a subtask)",
|
|
68
|
+
" --customer <value> Customer (org UUID or freeform name, repeatable)",
|
|
69
|
+
"",
|
|
70
|
+
"Update options:",
|
|
71
|
+
" --title <text> New title",
|
|
72
|
+
" --description <text> New description",
|
|
73
|
+
" --description-file <path> Read new description from file",
|
|
74
|
+
" --description-stdin Read new description from stdin",
|
|
75
|
+
' --assign <email> New assignee (use "" to unassign)',
|
|
76
|
+
" --priority <0|1|2> New priority",
|
|
77
|
+
" --tag <tag> Replace tags (repeatable)",
|
|
78
|
+
' --commit <sha> Resolution commit (use "" to clear)',
|
|
79
|
+
"",
|
|
80
|
+
"List options:",
|
|
81
|
+
" --assigned-to <email> Filter by assignee",
|
|
82
|
+
" --page <n> Page number (1-based, default 1)",
|
|
83
|
+
" --page-size <n> Page size (max 100, default 25)",
|
|
84
|
+
" --parent <id> Filter by parent task (show subtasks)",
|
|
85
|
+
" --group-key <key> Filter by groupKey",
|
|
86
|
+
" --priority <val> Filter by priority (repeatable). Accepts critical|high|normal|0|1|2",
|
|
87
|
+
" --sort <field> [<dir>] Sort by field (repeatable, applied in order).",
|
|
88
|
+
" field \u2208 priority | createdAt (alias: date)",
|
|
89
|
+
" dir \u2208 asc | desc (default: desc)",
|
|
90
|
+
" Default: --sort priority desc --sort createdAt asc",
|
|
91
|
+
" --status <status> Filter by status",
|
|
92
|
+
" --trigger <type> Filter by trigger type",
|
|
93
|
+
"",
|
|
94
|
+
"Link-customer options:",
|
|
95
|
+
" --org <orgId> Organization UUID to link",
|
|
96
|
+
" --name <text> Freeform customer name (e.g. prospect)",
|
|
97
|
+
"",
|
|
98
|
+
"Unlink-customer options:",
|
|
99
|
+
" --customer-id <id> Customer link ID (from `canary task get`)",
|
|
100
|
+
"",
|
|
101
|
+
"Trigger types:",
|
|
102
|
+
" production-deploy, flag-deprecated, flag-cleanup, flag-deleted,",
|
|
103
|
+
" knob-deprecated, knob-cleanup, knob-deleted, scheduled,",
|
|
104
|
+
" pr-merged, immediate, manual",
|
|
105
|
+
"",
|
|
106
|
+
"Global options:",
|
|
107
|
+
" --env <env> Target environment",
|
|
108
|
+
" --json JSON output",
|
|
109
|
+
" --api-url <url> API URL override",
|
|
110
|
+
" --token <key> Token override"
|
|
111
|
+
].join("\n")
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
var STATUS_ICONS = {
|
|
115
|
+
waiting: "\u23F3",
|
|
116
|
+
actionable: "\u{1F7E2}",
|
|
117
|
+
in_progress: "\u{1F535}",
|
|
118
|
+
completed: "\u2705",
|
|
119
|
+
canceled: "\u274C",
|
|
120
|
+
expired: "\u23F0"
|
|
121
|
+
};
|
|
122
|
+
var PRIORITY_LABELS = ["normal", "high", "critical"];
|
|
123
|
+
var PRIORITY_VALUE_BY_NAME = {
|
|
124
|
+
normal: 0,
|
|
125
|
+
high: 1,
|
|
126
|
+
critical: 2
|
|
127
|
+
};
|
|
128
|
+
function parsePriorityArgs(argv) {
|
|
129
|
+
const values = [];
|
|
130
|
+
for (let i = 0; i < argv.length; i++) {
|
|
131
|
+
if (argv[i] !== "--priority" || !argv[i + 1]) continue;
|
|
132
|
+
const raw = argv[i + 1].toLowerCase();
|
|
133
|
+
for (const part of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
134
|
+
if (part in PRIORITY_VALUE_BY_NAME) {
|
|
135
|
+
values.push(PRIORITY_VALUE_BY_NAME[part]);
|
|
136
|
+
} else {
|
|
137
|
+
const n = Number(part);
|
|
138
|
+
if (!Number.isInteger(n)) {
|
|
139
|
+
console.error(`Error: invalid --priority value "${part}". Use critical|high|normal or 0|1|2.`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
values.push(n);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return values;
|
|
147
|
+
}
|
|
148
|
+
function parseSortArgs(argv) {
|
|
149
|
+
const tokens = [];
|
|
150
|
+
for (let i = 0; i < argv.length; i++) {
|
|
151
|
+
if (argv[i] !== "--sort" || !argv[i + 1]) continue;
|
|
152
|
+
const field = argv[i + 1];
|
|
153
|
+
const maybeDir = argv[i + 2];
|
|
154
|
+
let dir;
|
|
155
|
+
if (maybeDir && !maybeDir.startsWith("--")) {
|
|
156
|
+
if (maybeDir === "asc" || maybeDir === "desc") {
|
|
157
|
+
dir = maybeDir;
|
|
158
|
+
} else {
|
|
159
|
+
console.error(
|
|
160
|
+
`Error: invalid --sort direction "${maybeDir}" for field "${field}". Use: asc, desc.`
|
|
161
|
+
);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
tokens.push(dir ? `${field}:${dir}` : field);
|
|
166
|
+
}
|
|
167
|
+
return tokens;
|
|
168
|
+
}
|
|
169
|
+
async function handleList(argv, apiUrl, token) {
|
|
170
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
171
|
+
const status = getArgValue(argv, "--status");
|
|
172
|
+
const triggerType = getArgValue(argv, "--trigger");
|
|
173
|
+
const assignedTo = getArgValue(argv, "--assigned-to");
|
|
174
|
+
const page = getArgValue(argv, "--page");
|
|
175
|
+
const pageSize = getArgValue(argv, "--page-size");
|
|
176
|
+
const parentId = getArgValue(argv, "--parent");
|
|
177
|
+
const groupKey = getArgValue(argv, "--group-key");
|
|
178
|
+
const priorities = parsePriorityArgs(argv);
|
|
179
|
+
const sortTokens = parseSortArgs(argv);
|
|
180
|
+
const params = new URLSearchParams();
|
|
181
|
+
if (status) params.set("status", status);
|
|
182
|
+
if (triggerType) params.set("triggerType", triggerType);
|
|
183
|
+
if (assignedTo) params.set("assignedTo", assignedTo);
|
|
184
|
+
if (parentId) params.set("parentTaskId", parentId);
|
|
185
|
+
if (groupKey) params.set("groupKey", groupKey);
|
|
186
|
+
if (priorities.length > 0) params.set("priority", priorities.join(","));
|
|
187
|
+
if (sortTokens.length > 0) params.set("sort", sortTokens.join(","));
|
|
188
|
+
if (page) params.set("page", page);
|
|
189
|
+
if (pageSize) params.set("pageSize", pageSize);
|
|
190
|
+
const qs = params.toString();
|
|
191
|
+
const path = `/tasks${qs ? `?${qs}` : ""}`;
|
|
192
|
+
const result = await apiRequest(apiUrl, token, "GET", path);
|
|
193
|
+
if (!result.ok) {
|
|
194
|
+
console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
if (jsonOutput) {
|
|
198
|
+
console.log(JSON.stringify(result, null, 2));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (result.data.length === 0) {
|
|
202
|
+
console.log("No tasks found.");
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
console.log(`Tasks (page ${result.pagination.page}/${result.pagination.totalPages}, ${result.pagination.totalItems} total):
|
|
206
|
+
`);
|
|
207
|
+
for (const task of result.data) {
|
|
208
|
+
const icon = STATUS_ICONS[task.status] ?? "?";
|
|
209
|
+
const priority = task.priority > 0 ? ` [${PRIORITY_LABELS[task.priority]}]` : "";
|
|
210
|
+
const commit = task.sourceCommit ? ` (${task.sourceCommit.slice(0, 7)})` : "";
|
|
211
|
+
const created = task.createdAt ? new Date(task.createdAt).toLocaleString() : "";
|
|
212
|
+
const updated = task.updatedAt ? new Date(task.updatedAt).toLocaleString() : "";
|
|
213
|
+
console.log(` ${icon} ${task.id.slice(0, 8)} | ${task.title}${priority}${commit}`);
|
|
214
|
+
console.log(` trigger=${task.triggerType} status=${task.status} created=${created} updated=${updated}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function handleGet(argv, apiUrl, token) {
|
|
218
|
+
const id = argv[0];
|
|
219
|
+
if (!id || id.startsWith("--")) {
|
|
220
|
+
console.error("Error: Missing task ID.");
|
|
221
|
+
console.error("Usage: canary task get <id>");
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
225
|
+
const result = await apiRequest(apiUrl, token, "GET", `/tasks/${id}`);
|
|
226
|
+
if (!result.ok || !result.data) {
|
|
227
|
+
console.error(`Error: ${result.error ?? "Task not found"}`);
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
if (jsonOutput) {
|
|
231
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const task = result.data;
|
|
235
|
+
const icon = STATUS_ICONS[task.status] ?? "?";
|
|
236
|
+
console.log(`${icon} ${task.title}`);
|
|
237
|
+
console.log(`ID: ${task.id}`);
|
|
238
|
+
console.log(`Status: ${task.status}`);
|
|
239
|
+
console.log(`Trigger: ${task.triggerType}`);
|
|
240
|
+
console.log(`Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}`);
|
|
241
|
+
if (task.sourceCommit) console.log(`Source: ${task.sourceCommit}`);
|
|
242
|
+
if (task.resolutionCommit) console.log(`Resolution: ${task.resolutionCommit}`);
|
|
243
|
+
if (task.assignedTo) console.log(`Assigned to: ${task.assignedTo}`);
|
|
244
|
+
if (task.tags.length > 0) console.log(`Tags: ${task.tags.join(", ")}`);
|
|
245
|
+
if (task.groupKey) console.log(`Group key: ${task.groupKey}`);
|
|
246
|
+
if (task.parentTaskId) console.log(`Parent: ${task.parentTaskId}`);
|
|
247
|
+
if (task.subtaskSummary && task.subtaskSummary.total > 0) {
|
|
248
|
+
const s = task.subtaskSummary;
|
|
249
|
+
console.log(`Subtasks: ${s.completed}/${s.total} completed, ${s.inProgress} in progress, ${s.canceled} canceled`);
|
|
250
|
+
}
|
|
251
|
+
if (task.relevantCustomers && task.relevantCustomers.length > 0) {
|
|
252
|
+
console.log("Customers:");
|
|
253
|
+
for (const c of task.relevantCustomers) {
|
|
254
|
+
const orgTag = c.orgId ? ` (org: ${c.orgId.slice(0, 8)})` : " (freeform)";
|
|
255
|
+
console.log(` ${c.name}${orgTag} [${c.id}]`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
console.log(`Created: ${task.createdAt}`);
|
|
259
|
+
console.log(`Updated: ${task.updatedAt}`);
|
|
260
|
+
if (task.triggeredAt) console.log(`Triggered: ${task.triggeredAt}`);
|
|
261
|
+
if (task.completedAt) console.log(`Completed: ${task.completedAt}`);
|
|
262
|
+
if (Object.keys(task.triggerConfig).length > 0) {
|
|
263
|
+
console.log(`Trigger cfg: ${JSON.stringify(task.triggerConfig)}`);
|
|
264
|
+
}
|
|
265
|
+
console.log(`
|
|
266
|
+
Description:
|
|
267
|
+
${task.description}`);
|
|
268
|
+
if (task.attachments && task.attachments.length > 0) {
|
|
269
|
+
console.log("\nAttachments:");
|
|
270
|
+
for (const att of task.attachments) {
|
|
271
|
+
const sizeKb = Math.round(att.sizeBytes / 1024);
|
|
272
|
+
console.log(` ${att.filename} (${sizeKb} KB) \u2014 ${att.downloadUrl}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (task.events && task.events.length > 0) {
|
|
276
|
+
console.log("\nEvent history:");
|
|
277
|
+
for (const evt of task.events) {
|
|
278
|
+
console.log(` ${evt.createdAt} ${evt.eventType} by ${evt.actor ?? "system"}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function handleSubtasks(argv, apiUrl, token) {
|
|
283
|
+
const id = argv[0];
|
|
284
|
+
if (!id || id.startsWith("--")) {
|
|
285
|
+
console.error("Error: Missing parent task ID.");
|
|
286
|
+
console.error("Usage: canary task subtasks <parent-id>");
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
290
|
+
const result = await apiRequest(
|
|
291
|
+
apiUrl,
|
|
292
|
+
token,
|
|
293
|
+
"GET",
|
|
294
|
+
`/tasks/${id}/subtasks`
|
|
295
|
+
);
|
|
296
|
+
if (!result.ok) {
|
|
297
|
+
console.error(`Error: ${result.error ?? "Failed to list subtasks"}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
if (jsonOutput) {
|
|
301
|
+
console.log(JSON.stringify(result, null, 2));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const subtasks = result.data ?? [];
|
|
305
|
+
if (subtasks.length === 0) {
|
|
306
|
+
console.log("No subtasks found.");
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
console.log(`Subtasks of ${id} (${subtasks.length} total):
|
|
310
|
+
`);
|
|
311
|
+
for (const task of subtasks) {
|
|
312
|
+
const icon = STATUS_ICONS[task.status] ?? "?";
|
|
313
|
+
const priority = task.priority > 0 ? ` [${PRIORITY_LABELS[task.priority]}]` : "";
|
|
314
|
+
const created = task.createdAt ? new Date(task.createdAt).toLocaleString() : "";
|
|
315
|
+
console.log(` ${icon} ${task.id.slice(0, 8)} | ${task.title}${priority}`);
|
|
316
|
+
console.log(` status=${task.status} created=${created}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function handleCreate(argv, apiUrl, token) {
|
|
320
|
+
const title = getArgValue(argv, "--title");
|
|
321
|
+
const triggerType = getArgValue(argv, "--trigger");
|
|
322
|
+
let description = getArgValue(argv, "--description");
|
|
323
|
+
const descriptionFile = getArgValue(argv, "--description-file");
|
|
324
|
+
const descriptionStdin = hasFlag(argv, "--description-stdin");
|
|
325
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
326
|
+
if (!title) {
|
|
327
|
+
console.error("Error: --title is required.");
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
if (!triggerType) {
|
|
331
|
+
console.error("Error: --trigger is required.");
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
if (descriptionFile) {
|
|
335
|
+
try {
|
|
336
|
+
description = readFileSync(descriptionFile, "utf-8");
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error(`Error reading description file: ${err}`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
} else if (descriptionStdin) {
|
|
342
|
+
try {
|
|
343
|
+
description = readFileSync("/dev/stdin", "utf-8");
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.error(`Error reading description from stdin: ${err}`);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (!description) {
|
|
350
|
+
console.error("Error: --description, --description-file, or --description-stdin is required.");
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
const triggerConfig = {};
|
|
354
|
+
const flagName = getArgValue(argv, "--flag");
|
|
355
|
+
const knobKey = getArgValue(argv, "--knob");
|
|
356
|
+
const prNumber = getArgValue(argv, "--pr");
|
|
357
|
+
const scheduledDate = getArgValue(argv, "--scheduled-date");
|
|
358
|
+
if (flagName) triggerConfig.flagName = flagName;
|
|
359
|
+
if (knobKey) triggerConfig.knobKey = knobKey;
|
|
360
|
+
if (prNumber) triggerConfig.prNumber = parseInt(prNumber, 10);
|
|
361
|
+
if (scheduledDate) triggerConfig.scheduledDate = scheduledDate;
|
|
362
|
+
const commit = getArgValue(argv, "--commit") ?? getHeadCommit();
|
|
363
|
+
let propertyGithubRepoId = getArgValue(argv, "--repo-id");
|
|
364
|
+
const repoFullName = getArgValue(argv, "--repo");
|
|
365
|
+
const role = getArgValue(argv, "--role");
|
|
366
|
+
if (!propertyGithubRepoId) {
|
|
367
|
+
const reposResult = await apiRequest(apiUrl, token, "GET", "/tasks/repos");
|
|
368
|
+
if (!reposResult.ok || !reposResult.data?.length) {
|
|
369
|
+
console.error("Error: No GitHub repositories linked to your organization.");
|
|
370
|
+
console.error("Connect a repository first before creating tasks.");
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
if (repoFullName) {
|
|
374
|
+
const match = reposResult.data.find((r) => r.repoFullName === repoFullName);
|
|
375
|
+
if (!match) {
|
|
376
|
+
console.error(`Error: Repository "${repoFullName}" not found in your organization.`);
|
|
377
|
+
console.error(`Available: ${reposResult.data.map((r) => r.repoFullName).join(", ")}`);
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
propertyGithubRepoId = match.id;
|
|
381
|
+
} else if (role) {
|
|
382
|
+
const matches = reposResult.data.filter((r) => r.role === role);
|
|
383
|
+
if (matches.length === 0) {
|
|
384
|
+
console.error(`Error: No repos found with role "${role}".`);
|
|
385
|
+
const seenRoles = Array.from(
|
|
386
|
+
new Set(reposResult.data.map((r) => r.role).filter((x) => !!x))
|
|
387
|
+
);
|
|
388
|
+
if (seenRoles.length) console.error(`Available roles: ${seenRoles.join(", ")}`);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
if (matches.length > 1) {
|
|
392
|
+
console.error(
|
|
393
|
+
`Error: Multiple repos with role "${role}". Use --repo <owner/repo> to disambiguate.`
|
|
394
|
+
);
|
|
395
|
+
console.error(`Available: ${matches.map((r) => r.repoFullName).join(", ")}`);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
propertyGithubRepoId = matches[0].id;
|
|
399
|
+
} else if (reposResult.data.length === 1) {
|
|
400
|
+
propertyGithubRepoId = reposResult.data[0].id;
|
|
401
|
+
} else {
|
|
402
|
+
console.error(
|
|
403
|
+
"Error: Multiple repos found. Use --repo <owner/repo> or --role <role> to specify."
|
|
404
|
+
);
|
|
405
|
+
console.error(`Available: ${reposResult.data.map((r) => r.repoFullName).join(", ")}`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
if (triggerType === "pr-merged") {
|
|
410
|
+
triggerConfig.propertyGithubRepoId = propertyGithubRepoId;
|
|
411
|
+
}
|
|
412
|
+
const tags = [];
|
|
413
|
+
for (let i = 0; i < argv.length; i++) {
|
|
414
|
+
if (argv[i] === "--tag" && argv[i + 1]) {
|
|
415
|
+
tags.push(argv[i + 1]);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
419
|
+
const customers = [];
|
|
420
|
+
for (let i = 0; i < argv.length; i++) {
|
|
421
|
+
if (argv[i] === "--customer" && argv[i + 1]) {
|
|
422
|
+
const val = argv[i + 1];
|
|
423
|
+
if (UUID_RE.test(val)) {
|
|
424
|
+
customers.push({ orgId: val });
|
|
425
|
+
} else {
|
|
426
|
+
customers.push({ name: val });
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const assignedTo = getArgValue(argv, "--assign");
|
|
431
|
+
const priorityRaw = getArgValue(argv, "--priority");
|
|
432
|
+
const expiresAt = getArgValue(argv, "--expires");
|
|
433
|
+
const parentTaskId = getArgValue(argv, "--parent");
|
|
434
|
+
const body = {
|
|
435
|
+
propertyGithubRepoId,
|
|
436
|
+
title,
|
|
437
|
+
description,
|
|
438
|
+
triggerType,
|
|
439
|
+
triggerConfig,
|
|
440
|
+
sourceCommit: commit,
|
|
441
|
+
assignedTo: assignedTo ?? void 0,
|
|
442
|
+
priority: priorityRaw ? parseInt(priorityRaw, 10) : void 0,
|
|
443
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
444
|
+
parentTaskId: parentTaskId ?? void 0,
|
|
445
|
+
expiresAt: expiresAt ?? void 0,
|
|
446
|
+
customers: customers.length > 0 ? customers : void 0
|
|
447
|
+
};
|
|
448
|
+
const result = await apiRequest(apiUrl, token, "POST", "/tasks", body);
|
|
449
|
+
if (!result.ok) {
|
|
450
|
+
console.error(`Error: ${result.error ?? "Failed to create task"}`);
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
if (jsonOutput) {
|
|
454
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const task = result.data;
|
|
458
|
+
console.log(`Task created: ${task.id}`);
|
|
459
|
+
console.log(` Title: ${task.title}`);
|
|
460
|
+
console.log(` Status: ${task.status}`);
|
|
461
|
+
console.log(` Trigger: ${task.triggerType}`);
|
|
462
|
+
if (task.sourceCommit) console.log(` Commit: ${task.sourceCommit.slice(0, 7)}`);
|
|
463
|
+
if (triggerType === "pr-merged") {
|
|
464
|
+
console.log("\nThis task will become actionable when the matching PR is merged.");
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function handleUpdate(argv, apiUrl, token) {
|
|
468
|
+
const id = argv[0];
|
|
469
|
+
if (!id || id.startsWith("--")) {
|
|
470
|
+
console.error("Error: Missing task ID.");
|
|
471
|
+
console.error("Usage: canary task update <id> [--title ...] [--description ...] [--assign ...] [--priority ...] [--tag ...]");
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
475
|
+
const title = getArgValue(argv, "--title");
|
|
476
|
+
let description = getArgValue(argv, "--description");
|
|
477
|
+
const descriptionFile = getArgValue(argv, "--description-file");
|
|
478
|
+
const descriptionStdin = hasFlag(argv, "--description-stdin");
|
|
479
|
+
const assignRaw = getArgValue(argv, "--assign");
|
|
480
|
+
const priorityRaw = getArgValue(argv, "--priority");
|
|
481
|
+
const commitRaw = getArgValue(argv, "--commit");
|
|
482
|
+
if (descriptionFile) {
|
|
483
|
+
try {
|
|
484
|
+
description = readFileSync(descriptionFile, "utf-8");
|
|
485
|
+
} catch (err) {
|
|
486
|
+
console.error(`Error reading description file: ${err}`);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
} else if (descriptionStdin) {
|
|
490
|
+
try {
|
|
491
|
+
description = readFileSync("/dev/stdin", "utf-8");
|
|
492
|
+
} catch (err) {
|
|
493
|
+
console.error(`Error reading description from stdin: ${err}`);
|
|
494
|
+
process.exit(1);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const tags = [];
|
|
498
|
+
let hasTags = false;
|
|
499
|
+
for (let i = 0; i < argv.length; i++) {
|
|
500
|
+
if (argv[i] === "--tag") {
|
|
501
|
+
hasTags = true;
|
|
502
|
+
if (argv[i + 1]) tags.push(argv[i + 1]);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
const body = {};
|
|
506
|
+
if (title) body.title = title;
|
|
507
|
+
if (description) body.description = description;
|
|
508
|
+
if (assignRaw !== void 0) body.assignedTo = assignRaw === "" ? null : assignRaw;
|
|
509
|
+
if (priorityRaw) body.priority = parseInt(priorityRaw, 10);
|
|
510
|
+
if (hasTags) body.tags = tags;
|
|
511
|
+
if (commitRaw !== void 0) body.resolutionCommit = commitRaw === "" ? null : commitRaw;
|
|
512
|
+
if (Object.keys(body).length === 0) {
|
|
513
|
+
console.error("Error: No fields to update. Provide at least one of --title, --description, --assign, --priority, --tag, --commit.");
|
|
514
|
+
process.exit(1);
|
|
515
|
+
}
|
|
516
|
+
const result = await apiRequest(apiUrl, token, "PATCH", `/tasks/${id}`, body);
|
|
517
|
+
if (!result.ok) {
|
|
518
|
+
console.error(`Error: ${result.error ?? "Failed to update task"}`);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
if (jsonOutput) {
|
|
522
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const task = result.data;
|
|
526
|
+
console.log(`Task ${id} updated.`);
|
|
527
|
+
console.log(` Title: ${task.title}`);
|
|
528
|
+
console.log(` Priority: ${PRIORITY_LABELS[task.priority] ?? task.priority}`);
|
|
529
|
+
if (task.assignedTo) console.log(` Assigned: ${task.assignedTo}`);
|
|
530
|
+
}
|
|
531
|
+
async function handleComplete(argv, apiUrl, token) {
|
|
532
|
+
const id = argv[0];
|
|
533
|
+
if (!id || id.startsWith("--")) {
|
|
534
|
+
console.error("Error: Missing task ID.");
|
|
535
|
+
console.error("Usage: canary task complete <id> [--pr <number>] [--commit <sha>]");
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
const prRaw = getArgValue(argv, "--pr");
|
|
539
|
+
const prNumber = parsePrFlag(prRaw);
|
|
540
|
+
const explicitCommit = getArgValue(argv, "--commit");
|
|
541
|
+
const commit = explicitCommit ?? (prNumber !== void 0 ? void 0 : getHeadCommit() ?? void 0);
|
|
542
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
543
|
+
const body = {};
|
|
544
|
+
if (commit) body.resolutionCommit = commit;
|
|
545
|
+
if (prNumber !== void 0) body.resolutionPrNumber = prNumber;
|
|
546
|
+
const result = await apiRequest(apiUrl, token, "POST", `/tasks/${id}/complete`, body);
|
|
547
|
+
if (!result.ok) {
|
|
548
|
+
console.error(`Error: ${result.error ?? "Failed to complete task"}`);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
if (jsonOutput) {
|
|
552
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
console.log(`Task ${id} marked as completed.`);
|
|
556
|
+
if (prNumber !== void 0) console.log(`Resolution PR: #${prNumber}`);
|
|
557
|
+
if (commit) console.log(`Resolution commit: ${commit.slice(0, 7)}`);
|
|
558
|
+
}
|
|
559
|
+
async function handleSetCommit(argv, apiUrl, token) {
|
|
560
|
+
const id = argv[0];
|
|
561
|
+
if (!id || id.startsWith("--")) {
|
|
562
|
+
console.error("Error: Missing task ID.");
|
|
563
|
+
console.error("Usage: canary task set-commit <id> [--pr <number>] [--commit <sha>]");
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
const prRaw = getArgValue(argv, "--pr");
|
|
567
|
+
const prFlagPresent = prRaw !== void 0;
|
|
568
|
+
const prNumber = prFlagPresent ? prRaw === "" ? null : parsePrFlag(prRaw) ?? void 0 : void 0;
|
|
569
|
+
const explicit = getArgValue(argv, "--commit");
|
|
570
|
+
let commit;
|
|
571
|
+
if (explicit !== void 0) {
|
|
572
|
+
commit = explicit === "" ? null : explicit;
|
|
573
|
+
} else if (prFlagPresent) {
|
|
574
|
+
commit = void 0;
|
|
575
|
+
} else {
|
|
576
|
+
const head = getHeadCommit();
|
|
577
|
+
if (head === null) {
|
|
578
|
+
console.error("Error: Could not determine commit \u2014 not in a git repo. Pass --commit <sha> or --pr <n> explicitly.");
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
commit = head;
|
|
582
|
+
}
|
|
583
|
+
const body = {};
|
|
584
|
+
if (commit !== void 0) body.resolutionCommit = commit;
|
|
585
|
+
if (prNumber !== void 0) body.resolutionPrNumber = prNumber;
|
|
586
|
+
if (Object.keys(body).length === 0) {
|
|
587
|
+
console.error("Error: Nothing to update. Pass --commit <sha> or --pr <n>.");
|
|
588
|
+
process.exit(1);
|
|
589
|
+
}
|
|
590
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
591
|
+
const result = await apiRequest(apiUrl, token, "PATCH", `/tasks/${id}`, body);
|
|
592
|
+
if (!result.ok) {
|
|
593
|
+
console.error(`Error: ${result.error ?? "Failed to update resolution"}`);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
596
|
+
if (jsonOutput) {
|
|
597
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
if (commit === null) console.log(`Task ${id} resolution commit cleared.`);
|
|
601
|
+
else if (commit !== void 0) console.log(`Task ${id} resolution commit set to ${commit.slice(0, 7)}.`);
|
|
602
|
+
if (prNumber === null) console.log(`Task ${id} resolution PR cleared.`);
|
|
603
|
+
else if (prNumber !== void 0) console.log(`Task ${id} resolution PR set to #${prNumber}.`);
|
|
604
|
+
}
|
|
605
|
+
function parsePrFlag(raw) {
|
|
606
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
607
|
+
if (raw.includes("/") || raw.includes("http")) {
|
|
608
|
+
console.error(`Error: --pr expects a number, got "${raw}". Pass the PR number only.`);
|
|
609
|
+
process.exit(1);
|
|
610
|
+
}
|
|
611
|
+
const n = Number(raw);
|
|
612
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
613
|
+
console.error(`Error: --pr must be a positive integer, got "${raw}".`);
|
|
614
|
+
process.exit(1);
|
|
615
|
+
}
|
|
616
|
+
return n;
|
|
617
|
+
}
|
|
618
|
+
async function handleCancel(argv, apiUrl, token) {
|
|
619
|
+
const id = argv[0];
|
|
620
|
+
if (!id || id.startsWith("--")) {
|
|
621
|
+
console.error("Error: Missing task ID.");
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
const result = await apiRequest(apiUrl, token, "POST", `/tasks/${id}/cancel`);
|
|
625
|
+
if (!result.ok) {
|
|
626
|
+
console.error(`Error: ${result.error ?? "Failed to cancel task"}`);
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
629
|
+
console.log(`Task ${id} canceled.`);
|
|
630
|
+
}
|
|
631
|
+
async function handleTrigger(argv, apiUrl, token) {
|
|
632
|
+
const id = argv[0];
|
|
633
|
+
if (!id || id.startsWith("--")) {
|
|
634
|
+
console.error("Error: Missing task ID.");
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
const result = await apiRequest(apiUrl, token, "POST", `/tasks/${id}/trigger`);
|
|
638
|
+
if (!result.ok) {
|
|
639
|
+
console.error(`Error: ${result.error ?? "Failed to trigger task"}`);
|
|
640
|
+
process.exit(1);
|
|
641
|
+
}
|
|
642
|
+
console.log(`Task ${id} triggered (now actionable).`);
|
|
643
|
+
}
|
|
644
|
+
async function handleReopen(argv, apiUrl, token) {
|
|
645
|
+
const id = argv[0];
|
|
646
|
+
if (!id || id.startsWith("--")) {
|
|
647
|
+
console.error("Error: Missing task ID.");
|
|
648
|
+
console.error("Usage: canary task reopen <id> [--status <actionable|in_progress>]");
|
|
649
|
+
process.exit(1);
|
|
650
|
+
}
|
|
651
|
+
const status = getArgValue(argv, "--status") ?? "actionable";
|
|
652
|
+
if (status !== "actionable" && status !== "in_progress") {
|
|
653
|
+
console.error(`Error: Invalid status "${status}". Must be "actionable" or "in_progress".`);
|
|
654
|
+
process.exit(1);
|
|
655
|
+
}
|
|
656
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
657
|
+
const result = await apiRequest(apiUrl, token, "POST", `/tasks/${id}/reopen`, { status });
|
|
658
|
+
if (!result.ok) {
|
|
659
|
+
console.error(`Error: ${result.error ?? "Failed to reopen task"}`);
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
if (jsonOutput) {
|
|
663
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
console.log(`Task ${id} reopened (now ${status}).`);
|
|
667
|
+
}
|
|
668
|
+
async function handleRelease(argv, apiUrl, token) {
|
|
669
|
+
const id = argv[0];
|
|
670
|
+
if (!id || id.startsWith("--")) {
|
|
671
|
+
console.error("Error: Missing task ID.");
|
|
672
|
+
console.error("Usage: canary task release <id> [--status <actionable|waiting>]");
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
const status = getArgValue(argv, "--status") ?? "actionable";
|
|
676
|
+
if (status !== "actionable" && status !== "waiting") {
|
|
677
|
+
console.error(`Error: Invalid status "${status}". Must be "actionable" or "waiting".`);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
681
|
+
const result = await apiRequest(apiUrl, token, "POST", `/tasks/${id}/release`, { status });
|
|
682
|
+
if (!result.ok) {
|
|
683
|
+
console.error(`Error: ${result.error ?? "Failed to release task"}`);
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
if (jsonOutput) {
|
|
687
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
console.log(`Task ${id} released (now ${status}).`);
|
|
691
|
+
}
|
|
692
|
+
async function handleClaim(argv, apiUrl, token) {
|
|
693
|
+
const id = argv[0];
|
|
694
|
+
if (!id || id.startsWith("--")) {
|
|
695
|
+
console.error("Error: Missing task ID.");
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
const result = await apiRequest(apiUrl, token, "POST", `/tasks/${id}/claim`);
|
|
699
|
+
if (!result.ok) {
|
|
700
|
+
console.error(`Error: ${result.error ?? "Failed to claim task"}`);
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
console.log(`Task ${id} claimed (now in progress).`);
|
|
704
|
+
}
|
|
705
|
+
async function handleExecute(argv, apiUrl, token) {
|
|
706
|
+
const id = argv[0];
|
|
707
|
+
if (!id || id.startsWith("--")) {
|
|
708
|
+
console.error("Error: Missing task ID.");
|
|
709
|
+
console.error("Usage: canary task execute <id> --instance <instanceId>");
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
const instanceId = getArgValue(argv, "--instance");
|
|
713
|
+
if (!instanceId) {
|
|
714
|
+
console.error("Error: --instance <instanceId> is required.");
|
|
715
|
+
process.exit(1);
|
|
716
|
+
}
|
|
717
|
+
const result = await apiRequest(
|
|
718
|
+
apiUrl,
|
|
719
|
+
token,
|
|
720
|
+
"POST",
|
|
721
|
+
`/tasks/${id}/execute`,
|
|
722
|
+
{ instanceId }
|
|
723
|
+
);
|
|
724
|
+
if (!result.ok) {
|
|
725
|
+
console.error(`Error: ${result.error ?? "Failed to execute task"}`);
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
console.log(`Task ${id} dispatched to sandbox instance ${instanceId}.`);
|
|
729
|
+
if (result.data?.jobId) {
|
|
730
|
+
console.log(`Job ID: ${result.data.jobId}`);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async function handleLinkCustomer(argv, apiUrl, token) {
|
|
734
|
+
const id = argv[0];
|
|
735
|
+
if (!id || id.startsWith("--")) {
|
|
736
|
+
console.error("Error: Missing task ID.");
|
|
737
|
+
console.error('Usage: canary task link-customer <id> --org <orgId> | --name "Customer Name"');
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
const orgId = getArgValue(argv, "--org");
|
|
741
|
+
const name = getArgValue(argv, "--name");
|
|
742
|
+
if (!orgId && !name) {
|
|
743
|
+
console.error('Error: --org <orgId> or --name "Customer Name" is required.');
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
const body = {};
|
|
747
|
+
if (orgId) body.orgId = orgId;
|
|
748
|
+
if (name) body.name = name;
|
|
749
|
+
const result = await apiRequest(
|
|
750
|
+
apiUrl,
|
|
751
|
+
token,
|
|
752
|
+
"POST",
|
|
753
|
+
`/tasks/${id}/customers`,
|
|
754
|
+
body
|
|
755
|
+
);
|
|
756
|
+
if (!result.ok) {
|
|
757
|
+
console.error(`Error: ${result.error ?? "Failed to link customer"}`);
|
|
758
|
+
process.exit(1);
|
|
759
|
+
}
|
|
760
|
+
console.log(`Customer linked to task ${id}: ${result.data?.name ?? name ?? orgId}`);
|
|
761
|
+
}
|
|
762
|
+
async function handleUnlinkCustomer(argv, apiUrl, token) {
|
|
763
|
+
const id = argv[0];
|
|
764
|
+
if (!id || id.startsWith("--")) {
|
|
765
|
+
console.error("Error: Missing task ID.");
|
|
766
|
+
console.error("Usage: canary task unlink-customer <taskId> --customer-id <customerId>");
|
|
767
|
+
process.exit(1);
|
|
768
|
+
}
|
|
769
|
+
const customerId = getArgValue(argv, "--customer-id");
|
|
770
|
+
if (!customerId) {
|
|
771
|
+
console.error("Error: --customer-id is required. Use `canary task get <id>` to see customer IDs.");
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
const result = await apiRequest(
|
|
775
|
+
apiUrl,
|
|
776
|
+
token,
|
|
777
|
+
"DELETE",
|
|
778
|
+
`/tasks/${id}/customers/${customerId}`
|
|
779
|
+
);
|
|
780
|
+
if (!result.ok) {
|
|
781
|
+
console.error(`Error: ${result.error ?? "Failed to unlink customer"}`);
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
console.log(`Customer unlinked from task ${id}.`);
|
|
785
|
+
}
|
|
786
|
+
async function handleComment(argv, apiUrl, token) {
|
|
787
|
+
const id = argv[0];
|
|
788
|
+
if (!id || id.startsWith("--")) {
|
|
789
|
+
console.error("Error: Missing task ID.");
|
|
790
|
+
console.error("Usage: canary task comment <id> <message>");
|
|
791
|
+
console.error(" canary task comment <id> --body <message>");
|
|
792
|
+
process.exit(1);
|
|
793
|
+
}
|
|
794
|
+
let body = getArgValue(argv, "--body");
|
|
795
|
+
if (!body) {
|
|
796
|
+
const rest = argv.slice(1).filter((a) => !a.startsWith("--"));
|
|
797
|
+
body = rest.join(" ").trim();
|
|
798
|
+
}
|
|
799
|
+
if (!body) {
|
|
800
|
+
console.error("Error: Comment body is required.");
|
|
801
|
+
console.error('Usage: canary task comment <id> "Your comment here"');
|
|
802
|
+
process.exit(1);
|
|
803
|
+
}
|
|
804
|
+
const result = await apiRequest(
|
|
805
|
+
apiUrl,
|
|
806
|
+
token,
|
|
807
|
+
"POST",
|
|
808
|
+
`/tasks/${id}/comments`,
|
|
809
|
+
{ body }
|
|
810
|
+
);
|
|
811
|
+
if (!result.ok) {
|
|
812
|
+
console.error(`Error: ${result.error ?? "Failed to add comment"}`);
|
|
813
|
+
process.exit(1);
|
|
814
|
+
}
|
|
815
|
+
console.log(`Comment added to task ${id.slice(0, 8)}.`);
|
|
816
|
+
}
|
|
817
|
+
async function handleComments(argv, apiUrl, token) {
|
|
818
|
+
const id = argv[0];
|
|
819
|
+
if (!id || id.startsWith("--")) {
|
|
820
|
+
console.error("Error: Missing task ID.");
|
|
821
|
+
console.error("Usage: canary task comments <id>");
|
|
822
|
+
process.exit(1);
|
|
823
|
+
}
|
|
824
|
+
const jsonOutput = hasFlag(argv, "--json");
|
|
825
|
+
const page = getArgValue(argv, "--page") ?? "1";
|
|
826
|
+
const result = await apiRequest(
|
|
827
|
+
apiUrl,
|
|
828
|
+
token,
|
|
829
|
+
"GET",
|
|
830
|
+
`/tasks/${id}/comments?page=${page}&pageSize=25`
|
|
831
|
+
);
|
|
832
|
+
if (!result.ok) {
|
|
833
|
+
console.error(`Error: ${result.error ?? "Failed to list comments"}`);
|
|
834
|
+
process.exit(1);
|
|
835
|
+
}
|
|
836
|
+
if (jsonOutput) {
|
|
837
|
+
console.log(JSON.stringify(result, null, 2));
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (result.data.length === 0) {
|
|
841
|
+
console.log("No comments.");
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
console.log(`Comments (page ${result.pagination.page}/${result.pagination.totalPages}, ${result.pagination.totalItems} total):
|
|
845
|
+
`);
|
|
846
|
+
for (const c of result.data) {
|
|
847
|
+
const author = c.authorDisplayName ?? c.authorId.slice(0, 8);
|
|
848
|
+
const date = new Date(c.createdAt).toLocaleString();
|
|
849
|
+
console.log(` ${author} \xB7 ${date}`);
|
|
850
|
+
console.log(` ${c.body}
|
|
851
|
+
`);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
async function runTask(argv) {
|
|
855
|
+
const [subCommand, ...rest] = argv;
|
|
856
|
+
if (!subCommand || hasFlag(argv, "--help", "-h")) {
|
|
857
|
+
printHelp();
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const { apiUrl, token } = await resolveConfig(argv);
|
|
861
|
+
switch (subCommand) {
|
|
862
|
+
case "create":
|
|
863
|
+
await handleCreate(rest, apiUrl, token);
|
|
864
|
+
break;
|
|
865
|
+
case "list":
|
|
866
|
+
await handleList(rest, apiUrl, token);
|
|
867
|
+
break;
|
|
868
|
+
case "get":
|
|
869
|
+
await handleGet(rest, apiUrl, token);
|
|
870
|
+
break;
|
|
871
|
+
case "update":
|
|
872
|
+
await handleUpdate(rest, apiUrl, token);
|
|
873
|
+
break;
|
|
874
|
+
case "complete":
|
|
875
|
+
await handleComplete(rest, apiUrl, token);
|
|
876
|
+
break;
|
|
877
|
+
case "set-commit":
|
|
878
|
+
await handleSetCommit(rest, apiUrl, token);
|
|
879
|
+
break;
|
|
880
|
+
case "cancel":
|
|
881
|
+
await handleCancel(rest, apiUrl, token);
|
|
882
|
+
break;
|
|
883
|
+
case "reopen":
|
|
884
|
+
await handleReopen(rest, apiUrl, token);
|
|
885
|
+
break;
|
|
886
|
+
case "release":
|
|
887
|
+
await handleRelease(rest, apiUrl, token);
|
|
888
|
+
break;
|
|
889
|
+
case "trigger":
|
|
890
|
+
await handleTrigger(rest, apiUrl, token);
|
|
891
|
+
break;
|
|
892
|
+
case "claim":
|
|
893
|
+
await handleClaim(rest, apiUrl, token);
|
|
894
|
+
break;
|
|
895
|
+
case "execute":
|
|
896
|
+
await handleExecute(rest, apiUrl, token);
|
|
897
|
+
break;
|
|
898
|
+
case "comment":
|
|
899
|
+
await handleComment(rest, apiUrl, token);
|
|
900
|
+
break;
|
|
901
|
+
case "comments":
|
|
902
|
+
await handleComments(rest, apiUrl, token);
|
|
903
|
+
break;
|
|
904
|
+
case "subtasks":
|
|
905
|
+
await handleSubtasks(rest, apiUrl, token);
|
|
906
|
+
break;
|
|
907
|
+
case "link-customer":
|
|
908
|
+
await handleLinkCustomer(rest, apiUrl, token);
|
|
909
|
+
break;
|
|
910
|
+
case "unlink-customer":
|
|
911
|
+
await handleUnlinkCustomer(rest, apiUrl, token);
|
|
912
|
+
break;
|
|
913
|
+
default:
|
|
914
|
+
console.error(`Unknown sub-command: ${subCommand}`);
|
|
915
|
+
printHelp();
|
|
916
|
+
process.exit(1);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
export {
|
|
920
|
+
runTask
|
|
921
|
+
};
|
|
922
|
+
//# sourceMappingURL=task-IQC3YR5K.js.map
|