codiedev 0.10.0-beta.16 → 0.10.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +63 -18
- package/dist/codie.js +19 -2
- package/dist/codieLauncher.js +20 -1
- package/dist/commands/agents.js +18 -1
- package/dist/commands/agentsSdk.generated.js +3 -3
- package/dist/commands/inbox.js +9 -0
- package/dist/commands/pack.js +258 -12
- package/dist/commands/ping.js +35 -10
- package/dist/commands/pingFiles.js +145 -0
- package/dist/commands/push.js +33 -4
- package/dist/commands/reply.js +49 -0
- package/dist/commands/scrub.js +1 -2
- package/dist/commands/send.js +11 -51
- package/dist/commands/shared.js +51 -0
- package/dist/commands/work.js +761 -0
- package/dist/connect.js +3 -0
- package/dist/hook.js +50 -5
- package/dist/inboxFormat.js +17 -1
- package/dist/launchContext.js +56 -0
- package/dist/launcher.js +24 -1
- package/dist/managedPi.js +1 -0
- package/dist/mcp.js +15 -77
- package/dist/packs/files.js +291 -0
- package/dist/packs/harnesses.js +2 -3
- package/dist/packs/install.js +31 -3
- package/dist/packs/localSkills.js +120 -0
- package/dist/packs/manifest.js +51 -2
- package/dist/packs/skillFiles.js +106 -0
- package/dist/packs/store.js +21 -3
- package/dist/promptGuard.js +223 -0
- package/dist/scrub/rules.js +260 -21
- package/dist/teamSkillContext.js +1 -1
- package/dist/unreadNotice.js +72 -0
- package/dist/utils.js +120 -25
- package/package.json +2 -2
- package/dist/commands/ask.js +0 -55
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.WEBHOOKS_HELP = exports.ISSUE_HELP = exports.PROJECT_HELP = exports.PROJECTS_OFF = void 0;
|
|
37
|
+
exports.parseWorkArgs = parseWorkArgs;
|
|
38
|
+
exports.listFlag = listFlag;
|
|
39
|
+
exports.projectTree = projectTree;
|
|
40
|
+
exports.formatDuration = formatDuration;
|
|
41
|
+
exports.formatIssueContext = formatIssueContext;
|
|
42
|
+
exports.runProject = runProject;
|
|
43
|
+
exports.runIssue = runIssue;
|
|
44
|
+
exports.runWebhooks = runWebhooks;
|
|
45
|
+
const child_process_1 = require("child_process");
|
|
46
|
+
const fs = __importStar(require("fs"));
|
|
47
|
+
const shared_1 = require("./shared");
|
|
48
|
+
const utils_1 = require("../utils");
|
|
49
|
+
exports.PROJECTS_OFF = /Projects isn't on for your company/;
|
|
50
|
+
const BOOLEAN_FLAGS = new Set(["json", "all", "remove", "help", "top"]);
|
|
51
|
+
function parseWorkArgs(args) {
|
|
52
|
+
const pos = [];
|
|
53
|
+
const flags = {};
|
|
54
|
+
for (let i = 0; i < args.length; i++) {
|
|
55
|
+
const a = args[i];
|
|
56
|
+
if (a.startsWith("--")) {
|
|
57
|
+
const eq = a.indexOf("=");
|
|
58
|
+
if (eq > 0) {
|
|
59
|
+
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const next = args[i + 1];
|
|
63
|
+
if (next !== undefined && !next.startsWith("--") && !BOOLEAN_FLAGS.has(a.slice(2))) {
|
|
64
|
+
flags[a.slice(2)] = next;
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
flags[a.slice(2)] = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
pos.push((0, shared_1.stripQuotes)(a));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { pos, flags };
|
|
77
|
+
}
|
|
78
|
+
const flagStr = (flags, k) => typeof flags[k] === "string" ? flags[k] : undefined;
|
|
79
|
+
function listFlag(flags, k) {
|
|
80
|
+
const raw = flagStr(flags, k);
|
|
81
|
+
if (!raw)
|
|
82
|
+
return undefined;
|
|
83
|
+
const keys = raw.split(/[\s,]+/).map((x) => x.trim()).filter(Boolean);
|
|
84
|
+
return keys.length ? keys : undefined;
|
|
85
|
+
}
|
|
86
|
+
function readBody(flags, name = "body") {
|
|
87
|
+
const file = flagStr(flags, `${name}-file`);
|
|
88
|
+
if (file)
|
|
89
|
+
return fs.readFileSync(file === "-" ? 0 : file, "utf8");
|
|
90
|
+
return flagStr(flags, name);
|
|
91
|
+
}
|
|
92
|
+
const PRIORITY_LABEL = { 0: "", 1: "urgent", 2: "high", 3: "medium", 4: "low" };
|
|
93
|
+
const STATE_LABEL = {
|
|
94
|
+
backlog: "Backlog",
|
|
95
|
+
todo: "Todo",
|
|
96
|
+
in_progress: "In Progress",
|
|
97
|
+
in_review: "In Review",
|
|
98
|
+
done: "Done",
|
|
99
|
+
canceled: "Canceled",
|
|
100
|
+
};
|
|
101
|
+
const STATE_ORDER = ["in_progress", "in_review", "todo", "backlog", "done", "canceled"];
|
|
102
|
+
function projectTree(projects) {
|
|
103
|
+
const byKey = new Map(projects.map((p) => [p.key, p]));
|
|
104
|
+
const kids = new Map();
|
|
105
|
+
const roots = [];
|
|
106
|
+
for (const p of projects) {
|
|
107
|
+
const parent = p.parentKey && p.parentKey !== p.key ? byKey.get(p.parentKey) : undefined;
|
|
108
|
+
if (parent)
|
|
109
|
+
kids.set(parent.key, [...(kids.get(parent.key) ?? []), p]);
|
|
110
|
+
else
|
|
111
|
+
roots.push(p);
|
|
112
|
+
}
|
|
113
|
+
const out = [];
|
|
114
|
+
const walk = (p, depth, seen) => {
|
|
115
|
+
const row = { p, depth, done: p.done, total: p.total, inProgress: p.counts.in_progress ?? 0 };
|
|
116
|
+
out.push(row);
|
|
117
|
+
for (const c of kids.get(p.key) ?? []) {
|
|
118
|
+
if (seen.has(c.key))
|
|
119
|
+
continue;
|
|
120
|
+
const sub = walk(c, depth + 1, new Set([...seen, c.key]));
|
|
121
|
+
row.done += sub.done;
|
|
122
|
+
row.total += sub.total;
|
|
123
|
+
row.inProgress += sub.inProgress;
|
|
124
|
+
}
|
|
125
|
+
return row;
|
|
126
|
+
};
|
|
127
|
+
for (const r of roots)
|
|
128
|
+
walk(r, 0, new Set([r.key]));
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function formatDuration(ms) {
|
|
132
|
+
const min = Math.max(1, Math.round(ms / 60_000));
|
|
133
|
+
if (min < 60)
|
|
134
|
+
return `${min}m`;
|
|
135
|
+
const h = Math.floor(min / 60);
|
|
136
|
+
const m = min % 60;
|
|
137
|
+
return m ? `${h}h ${m}m` : `${h}h`;
|
|
138
|
+
}
|
|
139
|
+
function firstName(p) {
|
|
140
|
+
return p?.name.split(/\s+/)[0] ?? "someone";
|
|
141
|
+
}
|
|
142
|
+
function prLine(pr) {
|
|
143
|
+
const diff = pr.additions !== null || pr.deletions !== null
|
|
144
|
+
? ` +${pr.additions ?? 0}/-${pr.deletions ?? 0}${pr.changedFiles ? ` in ${pr.changedFiles} file${pr.changedFiles === 1 ? "" : "s"}` : ""}`
|
|
145
|
+
: "";
|
|
146
|
+
return `PR ${pr.state}${diff}: ${pr.url}`;
|
|
147
|
+
}
|
|
148
|
+
function formatIssueContext(i, opts = {}) {
|
|
149
|
+
const out = [];
|
|
150
|
+
if (i.requester)
|
|
151
|
+
out.push(`Requested by ${i.requester.name} (they get a reply when it's done)`);
|
|
152
|
+
for (const pr of i.pullRequests ?? [])
|
|
153
|
+
out.push(prLine(pr));
|
|
154
|
+
if (i.body)
|
|
155
|
+
out.push("", i.body);
|
|
156
|
+
if (i.artifacts?.length) {
|
|
157
|
+
out.push("", "Attached docs (pull before you start):");
|
|
158
|
+
for (const a of i.artifacts)
|
|
159
|
+
out.push(` codiedev pull ${a.key} ${a.title}`);
|
|
160
|
+
}
|
|
161
|
+
const sessions = i.sessions ?? [];
|
|
162
|
+
if (sessions.length) {
|
|
163
|
+
const yw = i.yourWork;
|
|
164
|
+
const yours = yw && yw.sessions
|
|
165
|
+
? ` · yours: ${yw.sessions}${yw.activeMs ? `, ${formatDuration(yw.activeMs)} active` : ""}${yw.costUsd ? `, $${yw.costUsd.toFixed(2)}` : ""}`
|
|
166
|
+
: "";
|
|
167
|
+
out.push("", `Sessions (${sessions.length}${yours})`);
|
|
168
|
+
for (const s of sessions.slice(0, opts.forStart ? 3 : 5)) {
|
|
169
|
+
const who = s.mine ? "you: " : s.person ? `${firstName(s.person)}: ` : "";
|
|
170
|
+
const head = s.headline ?? (s.captured ? "(no summary)" : "(not captured yet)");
|
|
171
|
+
out.push(` ${who}${head}`);
|
|
172
|
+
}
|
|
173
|
+
const last = sessions.find((s) => s.mine && s.summary);
|
|
174
|
+
if (opts.forStart && last?.summary) {
|
|
175
|
+
out.push("", "Where your last session left off:", ` ${last.summary.trim().replace(/\n+/g, "\n ")}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const comments = i.comments.filter((c) => c.kind === "comment");
|
|
179
|
+
if (comments.length) {
|
|
180
|
+
out.push("", opts.forStart ? "Latest comments" : "Comments");
|
|
181
|
+
for (const c of opts.forStart ? comments.slice(-5) : comments)
|
|
182
|
+
out.push(` ${c.author?.name ?? "someone"}: ${c.body}`);
|
|
183
|
+
}
|
|
184
|
+
return out;
|
|
185
|
+
}
|
|
186
|
+
function issueLine(i) {
|
|
187
|
+
const bits = [
|
|
188
|
+
i.identifier.padEnd(8),
|
|
189
|
+
i.title,
|
|
190
|
+
PRIORITY_LABEL[i.priority] ? `(${PRIORITY_LABEL[i.priority]})` : "",
|
|
191
|
+
i.pullRequestUrl
|
|
192
|
+
? `${(i.pullRequestCount ?? 1) > 1 ? `${i.pullRequestCount} PRs` : "PR"} ${i.pullRequestState ?? ""}`.trim()
|
|
193
|
+
: "",
|
|
194
|
+
i.assignee ? `@${i.assignee.name.split(/\s+/)[0]}` : "",
|
|
195
|
+
];
|
|
196
|
+
return " " + bits.filter(Boolean).join(" ");
|
|
197
|
+
}
|
|
198
|
+
async function call(config, op, body) {
|
|
199
|
+
const start = Date.now();
|
|
200
|
+
try {
|
|
201
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/work", { config, body: { op, ...body } });
|
|
202
|
+
(0, shared_1.recordAgentEvent)(config, { tool: `codiedev_${op.replace(".", "_")}`, ok: true, latencyMs: Date.now() - start });
|
|
203
|
+
(0, utils_1.setProjectsFeature)(true);
|
|
204
|
+
return res;
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
if (exports.PROJECTS_OFF.test(err.message))
|
|
208
|
+
(0, utils_1.setProjectsFeature)(false);
|
|
209
|
+
(0, shared_1.recordAgentEvent)(config, {
|
|
210
|
+
tool: `codiedev_${op.replace(".", "_")}`,
|
|
211
|
+
ok: false,
|
|
212
|
+
latencyMs: Date.now() - start,
|
|
213
|
+
error: err.message,
|
|
214
|
+
});
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function fail(msg) {
|
|
219
|
+
console.error(msg);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
function git(args) {
|
|
223
|
+
try {
|
|
224
|
+
return (0, child_process_1.execFileSync)("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || undefined;
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function currentSessionId(flags) {
|
|
231
|
+
return (flagStr(flags, "session") ??
|
|
232
|
+
process.env.CLAUDE_CODE_SESSION_ID ??
|
|
233
|
+
process.env.CLAUDE_SESSION_ID ??
|
|
234
|
+
process.env.CODEX_SESSION_ID ??
|
|
235
|
+
undefined);
|
|
236
|
+
}
|
|
237
|
+
exports.PROJECT_HELP = `
|
|
238
|
+
codiedev project — Break Room projects (folders of tasks)
|
|
239
|
+
|
|
240
|
+
codiedev project create "<name>" [--key WEB] [--description "…"] [--in <key>]
|
|
241
|
+
codiedev project move <key> --in <key> | --top
|
|
242
|
+
nest a project inside another
|
|
243
|
+
(up to 6 levels); --top un-nests
|
|
244
|
+
codiedev project list [--archived] projects you own or were invited to
|
|
245
|
+
codiedev project edit <key> [--name "…"] [--description "…"]
|
|
246
|
+
codiedev project archive <key> hide it (restore: project restore <key>)
|
|
247
|
+
codiedev project invite <key> <teammate> add them; they get a ping + link
|
|
248
|
+
codiedev project show <key|name> [--json]
|
|
249
|
+
codiedev project next <key|name> open tasks, most urgent first
|
|
250
|
+
|
|
251
|
+
Tasks: see 'codiedev task help'.
|
|
252
|
+
`.trim();
|
|
253
|
+
exports.ISSUE_HELP = `
|
|
254
|
+
codiedev task — work inside a project (alias: codiedev issue)
|
|
255
|
+
|
|
256
|
+
codiedev task add <project> "<title>" [--body "…" | --body-file f.md]
|
|
257
|
+
[--priority urgent|high|medium|low]
|
|
258
|
+
[--state backlog|todo] [--with spec-a,spec-b]
|
|
259
|
+
[--assignee <teammate|me>] hand it off in one step
|
|
260
|
+
codiedev task add <project> --from-ping <ping-id> ["<title>"]
|
|
261
|
+
a teammate's ping becomes the
|
|
262
|
+
task; they get one reply in
|
|
263
|
+
that thread when it's done
|
|
264
|
+
codiedev task mine [--json] your open tasks, all projects
|
|
265
|
+
codiedev task search "<words>" [--json] tasks across your projects
|
|
266
|
+
codiedev task show <ID> [--json] e.g. WEB-3: body, docs, PR,
|
|
267
|
+
sessions, comments
|
|
268
|
+
codiedev task start <ID> In Progress + link this
|
|
269
|
+
git branch: its PRs attach
|
|
270
|
+
and close the task on merge
|
|
271
|
+
(a branch or PR title with
|
|
272
|
+
the ID, e.g. web-3-fix, links
|
|
273
|
+
even without start)
|
|
274
|
+
codiedev task state <ID> <state> todo, in_progress, in_review,
|
|
275
|
+
done, canceled, backlog
|
|
276
|
+
codiedev task done <ID>
|
|
277
|
+
codiedev task edit <ID> [--title "…"] [--body "…" | --append "…"]
|
|
278
|
+
[--priority high] [--assignee <teammate|me|none>]
|
|
279
|
+
codiedev task assign <ID> <teammate|me|none> they get a ping + project access
|
|
280
|
+
codiedev task attach <ID> <key> [<key>…] [--remove]
|
|
281
|
+
library docs/skills as context
|
|
282
|
+
codiedev task comment <ID> "<text>"
|
|
283
|
+
codiedev task ask <ID> <teammate> "<question>" comment + ping them
|
|
284
|
+
`.trim();
|
|
285
|
+
async function runProject(args) {
|
|
286
|
+
const [sub, ...rest] = args;
|
|
287
|
+
const { pos, flags } = parseWorkArgs(rest);
|
|
288
|
+
if (!sub || sub === "help" || sub === "--help") {
|
|
289
|
+
console.log(exports.PROJECT_HELP);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const config = (0, shared_1.requireConfig)();
|
|
293
|
+
try {
|
|
294
|
+
switch (sub) {
|
|
295
|
+
case "create":
|
|
296
|
+
case "new": {
|
|
297
|
+
const name = pos.join(" ").trim();
|
|
298
|
+
if (!name)
|
|
299
|
+
fail('Usage: codiedev project create "<name>" [--key WEB] [--in <parent key>]');
|
|
300
|
+
const res = await call(config, "project.create", {
|
|
301
|
+
name,
|
|
302
|
+
key: flagStr(flags, "key"),
|
|
303
|
+
description: readBody(flags, "description"),
|
|
304
|
+
parent: flagStr(flags, "in") ?? flagStr(flags, "parent"),
|
|
305
|
+
});
|
|
306
|
+
console.log(`✓ Created project ${res.name} (${res.key})${res.parentKey ? ` inside ${res.parentKey}` : ""}`);
|
|
307
|
+
console.log(` Add tasks: codiedev task add ${res.key} "<title>"`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
case "list":
|
|
311
|
+
case "ls": {
|
|
312
|
+
const archived = !!flags.archived;
|
|
313
|
+
const res = await call(config, archived ? "project.archived" : "project.list", {});
|
|
314
|
+
if (flags.json)
|
|
315
|
+
return void console.log(JSON.stringify(res.projects, null, 2));
|
|
316
|
+
if (res.projects.length === 0) {
|
|
317
|
+
if (archived)
|
|
318
|
+
return void console.log("No archived projects.");
|
|
319
|
+
console.log('No projects yet. Create one: codiedev project create "<name>"');
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
for (const r of projectTree(res.projects)) {
|
|
323
|
+
const indent = " ".repeat(r.depth);
|
|
324
|
+
console.log(`${indent}${r.p.key.padEnd(6)} ${r.p.name} ${r.done}/${r.total} done, ${r.inProgress} in progress`);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
case "show":
|
|
329
|
+
case "view": {
|
|
330
|
+
const key = pos.join(" ").trim();
|
|
331
|
+
if (!key)
|
|
332
|
+
fail("Usage: codiedev project show <key|name>");
|
|
333
|
+
const res = await call(config, "project.show", { project: key });
|
|
334
|
+
const p = res.project;
|
|
335
|
+
if (flags.json)
|
|
336
|
+
return void console.log(JSON.stringify(p, null, 2));
|
|
337
|
+
if (p.ancestors?.length)
|
|
338
|
+
console.log(p.ancestors.map((a) => `${a.name} (${a.key})`).join(" › ") + " ›");
|
|
339
|
+
console.log(`${p.name} (${p.key}) ${p.done}/${p.total} done`);
|
|
340
|
+
if (p.description)
|
|
341
|
+
console.log(p.description);
|
|
342
|
+
if (p.members.length) {
|
|
343
|
+
console.log(`Members: ${[p.owner?.name, ...p.members.map((m) => m.name)].filter(Boolean).join(", ")}`);
|
|
344
|
+
}
|
|
345
|
+
if (p.children?.length) {
|
|
346
|
+
console.log(`\nSub-projects (${p.children.length})`);
|
|
347
|
+
for (const c of p.children)
|
|
348
|
+
console.log(` ${c.key.padEnd(6)} ${c.name} ${c.done}/${c.total} done`);
|
|
349
|
+
}
|
|
350
|
+
for (const state of STATE_ORDER) {
|
|
351
|
+
const rows = p.issues.filter((i) => i.state === state);
|
|
352
|
+
if (rows.length === 0)
|
|
353
|
+
continue;
|
|
354
|
+
if ((state === "done" || state === "canceled") && !flags.all) {
|
|
355
|
+
console.log(`\n${STATE_LABEL[state]} (${rows.length}) — --all to list`);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
console.log(`\n${STATE_LABEL[state]} (${rows.length})`);
|
|
359
|
+
for (const i of rows)
|
|
360
|
+
console.log(issueLine(i));
|
|
361
|
+
}
|
|
362
|
+
if (p.issues.length === 0)
|
|
363
|
+
console.log(`\nNo tasks yet: codiedev task add ${p.key} "<title>"`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
case "edit":
|
|
367
|
+
case "rename": {
|
|
368
|
+
const key = pos[0];
|
|
369
|
+
const name = flagStr(flags, "name") ?? (sub === "rename" ? pos.slice(1).join(" ").trim() || undefined : undefined);
|
|
370
|
+
const description = readBody(flags, "description");
|
|
371
|
+
if (!key || (name === undefined && description === undefined)) {
|
|
372
|
+
fail('Usage: codiedev project edit <key> [--name "…"] [--description "…"]');
|
|
373
|
+
}
|
|
374
|
+
const res = await call(config, "project.edit", { project: key, name, description });
|
|
375
|
+
console.log(`✓ Updated ${res.key} (${res.name})`);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
case "archive":
|
|
379
|
+
case "restore":
|
|
380
|
+
case "unarchive": {
|
|
381
|
+
const key = pos.join(" ").trim();
|
|
382
|
+
if (!key)
|
|
383
|
+
fail(`Usage: codiedev project ${sub} <key>`);
|
|
384
|
+
const archived = sub === "archive";
|
|
385
|
+
const res = await call(config, "project.edit", { project: key, archived });
|
|
386
|
+
console.log(archived
|
|
387
|
+
? `✓ Archived ${res.key}. It's hidden from your lists; bring it back with: codiedev project restore ${res.key}`
|
|
388
|
+
: `✓ Restored ${res.key} (${res.name})`);
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
case "invite":
|
|
392
|
+
case "share": {
|
|
393
|
+
const [key, ...who] = pos;
|
|
394
|
+
const handle = who.join(" ").trim();
|
|
395
|
+
if (!key || !handle)
|
|
396
|
+
fail("Usage: codiedev project invite <key> <teammate name or email>");
|
|
397
|
+
const res = await call(config, "project.invite", { project: key, handle });
|
|
398
|
+
console.log(res.alreadyMember
|
|
399
|
+
? `${res.person.name} is already on ${res.project.name}.`
|
|
400
|
+
: `✓ Added ${res.person.name} <${res.person.email}> to ${res.project.name}. They got a ping with the link.`);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
case "move":
|
|
404
|
+
case "nest": {
|
|
405
|
+
const key = pos[0];
|
|
406
|
+
const into = flagStr(flags, "in") ?? flagStr(flags, "parent") ?? pos[1];
|
|
407
|
+
if (!key || (!into && !flags.top))
|
|
408
|
+
fail("Usage: codiedev project move <key> --in <parent key> | --top");
|
|
409
|
+
const res = await call(config, "project.edit", {
|
|
410
|
+
project: key,
|
|
411
|
+
parent: flags.top ? null : into,
|
|
412
|
+
});
|
|
413
|
+
console.log(res.parentKey ? `✓ ${res.key} is now inside ${res.parentKey}` : `✓ ${res.key} is now top level`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
case "next": {
|
|
417
|
+
const key = pos.join(" ").trim();
|
|
418
|
+
if (!key)
|
|
419
|
+
fail("Usage: codiedev project next <key|name>");
|
|
420
|
+
await printNext(config, key, flags);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
default:
|
|
424
|
+
fail(`Unknown project command "${sub}".\n\n${exports.PROJECT_HELP}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch (err) {
|
|
428
|
+
fail(`project ${sub} failed: ${err.message}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function printNext(config, project, flags) {
|
|
432
|
+
const res = await call(config, "issue.next", {
|
|
433
|
+
project,
|
|
434
|
+
limit: flagStr(flags, "limit") ? Number(flagStr(flags, "limit")) : undefined,
|
|
435
|
+
});
|
|
436
|
+
if (flags.json)
|
|
437
|
+
return void console.log(JSON.stringify(res, null, 2));
|
|
438
|
+
if (res.issues.length === 0) {
|
|
439
|
+
console.log(`Nothing open in ${res.project.name}.`);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
console.log(`Open in ${res.project.name}, most urgent first:`);
|
|
443
|
+
for (const i of res.issues)
|
|
444
|
+
console.log(issueLine(i));
|
|
445
|
+
console.log(`\nStart one: codiedev task start ${res.issues[0].identifier}`);
|
|
446
|
+
}
|
|
447
|
+
async function runIssue(args) {
|
|
448
|
+
const [sub, ...rest] = args;
|
|
449
|
+
const { pos, flags } = parseWorkArgs(rest);
|
|
450
|
+
if (!sub || sub === "help" || sub === "--help") {
|
|
451
|
+
console.log(exports.ISSUE_HELP);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const config = (0, shared_1.requireConfig)();
|
|
455
|
+
try {
|
|
456
|
+
switch (sub) {
|
|
457
|
+
case "add":
|
|
458
|
+
case "create":
|
|
459
|
+
case "new": {
|
|
460
|
+
const [project, ...titleParts] = pos;
|
|
461
|
+
const title = titleParts.join(" ").trim();
|
|
462
|
+
const fromPingId = flagStr(flags, "from-ping");
|
|
463
|
+
if (!project || (!title && !fromPingId)) {
|
|
464
|
+
fail('Usage: codiedev task add <project> "<title>" [--body …] [--priority high] [--assignee <teammate>] [--with <key>]\n codiedev task add <project> --from-ping <ping-id>');
|
|
465
|
+
}
|
|
466
|
+
const res = await call(config, "issue.add", {
|
|
467
|
+
project,
|
|
468
|
+
title: title || undefined,
|
|
469
|
+
body: readBody(flags),
|
|
470
|
+
priority: flagStr(flags, "priority"),
|
|
471
|
+
state: flagStr(flags, "state"),
|
|
472
|
+
artifactKeys: listFlag(flags, "with"),
|
|
473
|
+
fromPingId,
|
|
474
|
+
assignee: flagStr(flags, "assignee"),
|
|
475
|
+
});
|
|
476
|
+
if (res.existing) {
|
|
477
|
+
console.log(`Already tracked: ${res.identifier} ${res.title}`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
console.log(`✓ ${res.identifier} ${res.title}`);
|
|
481
|
+
if (res.requester)
|
|
482
|
+
console.log(` From ${res.requester.name}'s ping: they get a reply in that thread when it's done.`);
|
|
483
|
+
if (res.artifacts?.length)
|
|
484
|
+
console.log(` Attached: ${res.artifacts.map((a) => a.key).join(", ")}`);
|
|
485
|
+
if (res.assignee)
|
|
486
|
+
console.log(` Assigned to ${res.assignee}; they got a ping with the link.`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
case "show":
|
|
490
|
+
case "view": {
|
|
491
|
+
const id = pos[0];
|
|
492
|
+
if (!id)
|
|
493
|
+
fail("Usage: codiedev task show <ID>");
|
|
494
|
+
const res = await call(config, "issue.show", { identifier: id });
|
|
495
|
+
const i = res.issue;
|
|
496
|
+
if (flags.json)
|
|
497
|
+
return void console.log(JSON.stringify(i, null, 2));
|
|
498
|
+
console.log(`${i.identifier} ${i.title}`);
|
|
499
|
+
console.log([
|
|
500
|
+
STATE_LABEL[i.state],
|
|
501
|
+
PRIORITY_LABEL[i.priority] ? `priority ${PRIORITY_LABEL[i.priority]}` : "",
|
|
502
|
+
i.assignee ? `@${i.assignee.name}` : "",
|
|
503
|
+
i.project ? `in ${i.project.name}` : "",
|
|
504
|
+
]
|
|
505
|
+
.filter(Boolean)
|
|
506
|
+
.join(" · "));
|
|
507
|
+
if (i.branch)
|
|
508
|
+
console.log(`branch ${i.branch}`);
|
|
509
|
+
if (i.pullRequestUrl && !i.pullRequests?.length)
|
|
510
|
+
console.log(`PR ${i.pullRequestState}: ${i.pullRequestUrl}`);
|
|
511
|
+
for (const line of formatIssueContext(i))
|
|
512
|
+
console.log(line);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
case "start": {
|
|
516
|
+
const id = pos[0];
|
|
517
|
+
if (!id)
|
|
518
|
+
fail("Usage: codiedev task start <ID>");
|
|
519
|
+
const repoUrl = git(["remote", "get-url", "origin"]);
|
|
520
|
+
const branch = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
521
|
+
const res = await call(config, "issue.start", { identifier: id, repoUrl, branch, sessionId: currentSessionId(flags) });
|
|
522
|
+
if (flags.json)
|
|
523
|
+
return void console.log(JSON.stringify(res, null, 2));
|
|
524
|
+
console.log(`✓ ${res.identifier} → ${STATE_LABEL[res.state]}`);
|
|
525
|
+
if (res.branch && res.repoLinked) {
|
|
526
|
+
console.log(` Linked to branch ${res.branch}. Its PR attaches automatically and merging closes ${res.identifier}.`);
|
|
527
|
+
}
|
|
528
|
+
else if (res.branch) {
|
|
529
|
+
console.log(` Branch ${res.branch} saved, but this repo isn't connected to Codiedev, so close it with: codiedev task done ${res.identifier}`);
|
|
530
|
+
}
|
|
531
|
+
else if (!repoUrl) {
|
|
532
|
+
console.log(` Not in a git repo: run this from the repo, on a feature branch, to auto-link its PR.`);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
const slug = res.identifier.toLowerCase();
|
|
536
|
+
console.log(` On the default branch: create a feature branch with ${res.identifier} in its name (e.g. git checkout -b ${slug}-short-desc). Its PR links on its own and merging closes ${res.identifier}.`);
|
|
537
|
+
}
|
|
538
|
+
if (res.pullRequestUrl && !res.context?.pullRequests?.length)
|
|
539
|
+
console.log(` PR: ${res.pullRequestUrl}`);
|
|
540
|
+
if (res.context) {
|
|
541
|
+
console.log(`\n${res.context.identifier} ${res.context.title}`);
|
|
542
|
+
for (const line of formatIssueContext(res.context, { forStart: true }))
|
|
543
|
+
console.log(line);
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
case "attach":
|
|
548
|
+
case "detach": {
|
|
549
|
+
const [id, ...keys] = pos;
|
|
550
|
+
if (!id || keys.length === 0)
|
|
551
|
+
fail("Usage: codiedev task attach <ID> <library-key> [<key>…] [--remove]");
|
|
552
|
+
const remove = sub === "detach" || !!flags.remove;
|
|
553
|
+
const res = await call(config, "issue.attach", { identifier: id, keys, remove });
|
|
554
|
+
console.log(`✓ ${res.identifier} ${remove ? "detached" : "attached"} ${keys.join(", ")}`);
|
|
555
|
+
if (res.artifacts.length)
|
|
556
|
+
console.log(` Now: ${res.artifacts.map((a) => a.key).join(", ")}`);
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
case "mine":
|
|
560
|
+
case "my": {
|
|
561
|
+
const res = await call(config, "issue.mine", { limit: flagStr(flags, "limit") ? Number(flagStr(flags, "limit")) : undefined });
|
|
562
|
+
if (flags.json)
|
|
563
|
+
return void console.log(JSON.stringify(res.issues, null, 2));
|
|
564
|
+
if (res.issues.length === 0) {
|
|
565
|
+
console.log("Nothing assigned to you is open. Find work: codiedev project next <key>");
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
for (const state of ["in_progress", "in_review", "todo"]) {
|
|
569
|
+
const rows = res.issues.filter((r) => r.state === state);
|
|
570
|
+
if (rows.length === 0)
|
|
571
|
+
continue;
|
|
572
|
+
console.log(`\n${STATE_LABEL[state]} (${rows.length})`);
|
|
573
|
+
for (const r of rows)
|
|
574
|
+
console.log(`${issueLine(r)} [${r.project.key}]`);
|
|
575
|
+
}
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
case "state":
|
|
579
|
+
case "move":
|
|
580
|
+
case "status": {
|
|
581
|
+
const [id, ...stateParts] = pos;
|
|
582
|
+
const state = stateParts.join(" ").trim();
|
|
583
|
+
if (!id || !state)
|
|
584
|
+
fail("Usage: codiedev task state <ID> <todo|in_progress|in_review|done|canceled|backlog>");
|
|
585
|
+
const res = await call(config, "issue.update", { identifier: id, state });
|
|
586
|
+
console.log(`✓ ${res.identifier} → ${STATE_LABEL[res.state]}`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
case "done":
|
|
590
|
+
case "close": {
|
|
591
|
+
const id = pos[0];
|
|
592
|
+
if (!id)
|
|
593
|
+
fail("Usage: codiedev task done <ID>");
|
|
594
|
+
const res = await call(config, "issue.update", { identifier: id, state: "done" });
|
|
595
|
+
console.log(`✓ ${res.identifier} → Done`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
case "edit":
|
|
599
|
+
case "update": {
|
|
600
|
+
const id = pos[0];
|
|
601
|
+
if (!id)
|
|
602
|
+
fail('Usage: codiedev task edit <ID> [--title …] [--body … | --append …] [--priority high]');
|
|
603
|
+
const res = await call(config, "issue.update", {
|
|
604
|
+
identifier: id,
|
|
605
|
+
title: flagStr(flags, "title"),
|
|
606
|
+
body: readBody(flags),
|
|
607
|
+
appendBody: readBody(flags, "append"),
|
|
608
|
+
priority: flagStr(flags, "priority"),
|
|
609
|
+
state: flagStr(flags, "state"),
|
|
610
|
+
assignee: flagStr(flags, "assignee"),
|
|
611
|
+
});
|
|
612
|
+
console.log(`✓ Updated ${res.identifier}`);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
case "assign": {
|
|
616
|
+
const [id, ...who] = pos;
|
|
617
|
+
const assignee = who.join(" ").trim();
|
|
618
|
+
if (!id || !assignee)
|
|
619
|
+
fail("Usage: codiedev task assign <ID> <teammate|me|none>");
|
|
620
|
+
const res = await call(config, "issue.update", {
|
|
621
|
+
identifier: id,
|
|
622
|
+
assignee,
|
|
623
|
+
});
|
|
624
|
+
console.log(res.assignee ? `✓ ${res.identifier} → ${res.assignee}` : `✓ ${res.identifier} unassigned`);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
case "search":
|
|
628
|
+
case "find": {
|
|
629
|
+
const q = pos.join(" ").trim();
|
|
630
|
+
if (!q)
|
|
631
|
+
fail('Usage: codiedev task search "<words>"');
|
|
632
|
+
const res = await call(config, "issue.search", { q });
|
|
633
|
+
if (flags.json)
|
|
634
|
+
return void console.log(JSON.stringify(res.issues, null, 2));
|
|
635
|
+
if (res.issues.length === 0)
|
|
636
|
+
return void console.log(`No tasks match "${q}".`);
|
|
637
|
+
for (const r of res.issues)
|
|
638
|
+
console.log(`${issueLine(r)} ${STATE_LABEL[r.state]}`);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
case "comment": {
|
|
642
|
+
const [id, ...text] = pos;
|
|
643
|
+
const body = text.join(" ").trim() || readBody(flags);
|
|
644
|
+
if (!id || !body)
|
|
645
|
+
fail('Usage: codiedev task comment <ID> "<text>"');
|
|
646
|
+
await call(config, "issue.comment", { identifier: id, body });
|
|
647
|
+
console.log(`✓ Commented on ${id.toUpperCase()}`);
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
case "ask":
|
|
651
|
+
case "feedback": {
|
|
652
|
+
const [id, to, ...text] = pos;
|
|
653
|
+
const question = text.join(" ").trim();
|
|
654
|
+
if (!id || !to || !question)
|
|
655
|
+
fail('Usage: codiedev task ask <ID> <teammate> "<question>"');
|
|
656
|
+
const res = await call(config, "issue.ask", { identifier: id, handle: to, question });
|
|
657
|
+
console.log(`✓ Asked ${res.person.name} about ${res.identifier} (comment added, ping sent).`);
|
|
658
|
+
if (res.sharedProject)
|
|
659
|
+
console.log(` They weren't on the project, so it's now shared with them.`);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
case "next": {
|
|
663
|
+
const project = pos.join(" ").trim();
|
|
664
|
+
if (!project)
|
|
665
|
+
fail("Usage: codiedev task next <project>");
|
|
666
|
+
await printNext(config, project, flags);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
default:
|
|
670
|
+
fail(`Unknown task command "${sub}".\n\n${exports.ISSUE_HELP}`);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
catch (err) {
|
|
674
|
+
fail(`task ${sub} failed: ${err.message}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
exports.WEBHOOKS_HELP = `
|
|
678
|
+
codiedev webhooks — get told when something happens, at your own URL
|
|
679
|
+
|
|
680
|
+
codiedev webhooks add <https-url> [--events task.done,ping.received]
|
|
681
|
+
[--project WEB] prints the signing secret once
|
|
682
|
+
codiedev webhooks list
|
|
683
|
+
codiedev webhooks test <id> sends a webhook.test event
|
|
684
|
+
codiedev webhooks rm <id>
|
|
685
|
+
|
|
686
|
+
Events: task.created, task.updated, task.done, task.commented,
|
|
687
|
+
task.pr_linked, ping.received (default: all). You only get events
|
|
688
|
+
for projects you can see and pings sent to you.
|
|
689
|
+
Each POST is JSON {id, event, at, data}, signed:
|
|
690
|
+
X-Codiedev-Signature: sha256=HMAC_SHA256(secret, "<X-Codiedev-Timestamp>.<raw body>")
|
|
691
|
+
`.trim();
|
|
692
|
+
async function runWebhooks(args) {
|
|
693
|
+
const [sub, ...rest] = args;
|
|
694
|
+
const { pos, flags } = parseWorkArgs(rest);
|
|
695
|
+
if (!sub || sub === "help" || sub === "--help") {
|
|
696
|
+
console.log(exports.WEBHOOKS_HELP);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
const config = (0, shared_1.requireConfig)();
|
|
700
|
+
try {
|
|
701
|
+
switch (sub) {
|
|
702
|
+
case "add":
|
|
703
|
+
case "create": {
|
|
704
|
+
const url = pos[0];
|
|
705
|
+
if (!url)
|
|
706
|
+
fail("Usage: codiedev webhooks add <https-url> [--events task.done,ping.received] [--project WEB]");
|
|
707
|
+
const res = await call(config, "webhook.add", {
|
|
708
|
+
url,
|
|
709
|
+
events: listFlag(flags, "events"),
|
|
710
|
+
project: flagStr(flags, "project"),
|
|
711
|
+
});
|
|
712
|
+
const w = res.webhook;
|
|
713
|
+
if (flags.json)
|
|
714
|
+
return void console.log(JSON.stringify(w, null, 2));
|
|
715
|
+
console.log(`✓ Webhook ${w.id}`);
|
|
716
|
+
console.log(` ${w.url}`);
|
|
717
|
+
console.log(` Events: ${w.events.join(", ") === "*" ? "all" : w.events.join(", ")}${w.project ? ` (project ${w.project})` : ""}`);
|
|
718
|
+
console.log(` Signing secret (shown once): ${w.secret}`);
|
|
719
|
+
console.log(` Try it: codiedev webhooks test ${w.id}`);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
case "list":
|
|
723
|
+
case "ls": {
|
|
724
|
+
const res = await call(config, "webhook.list", {});
|
|
725
|
+
if (flags.json)
|
|
726
|
+
return void console.log(JSON.stringify(res.webhooks, null, 2));
|
|
727
|
+
if (res.webhooks.length === 0)
|
|
728
|
+
return void console.log("No webhooks. Add one: codiedev webhooks add <https-url>");
|
|
729
|
+
for (const w of res.webhooks) {
|
|
730
|
+
const events = w.events.includes("*") ? "all events" : w.events.join(", ");
|
|
731
|
+
console.log(`${w.id} ${w.url}`);
|
|
732
|
+
console.log(` ${events}${w.project ? ` · ${w.project}` : ""}${w.lastStatus ? ` · last: ${w.lastStatus}` : ""}`);
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
case "test": {
|
|
737
|
+
const id = pos[0];
|
|
738
|
+
if (!id)
|
|
739
|
+
fail("Usage: codiedev webhooks test <id>");
|
|
740
|
+
const res = await call(config, "webhook.test", { id });
|
|
741
|
+
console.log(`✓ Sent webhook.test to ${res.url}. Check it with: codiedev webhooks list`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
case "rm":
|
|
745
|
+
case "remove":
|
|
746
|
+
case "delete": {
|
|
747
|
+
const id = pos[0];
|
|
748
|
+
if (!id)
|
|
749
|
+
fail("Usage: codiedev webhooks rm <id>");
|
|
750
|
+
await call(config, "webhook.remove", { id });
|
|
751
|
+
console.log(`✓ Removed webhook ${id}`);
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
default:
|
|
755
|
+
fail(`Unknown webhooks command "${sub}".\n\n${exports.WEBHOOKS_HELP}`);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch (err) {
|
|
759
|
+
fail(`webhooks ${sub} failed: ${err.message}`);
|
|
760
|
+
}
|
|
761
|
+
}
|