tines 0.0.78 → 0.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +227 -133
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -143,7 +143,164 @@ function utilizationLabel(quota2, activeRuns, stateName = (id) => id) {
|
|
|
143
143
|
if (!counts.has(stateId)) counts.set(stateId, { name: stateName(stateId), n: 0 });
|
|
144
144
|
}
|
|
145
145
|
if (counts.size === 0) return `no active runs (roster default ${quota2.default_limit} per state)`;
|
|
146
|
-
return [...counts.entries()].map(([stateId, { name, n }]) => `${
|
|
146
|
+
return [...counts.entries()].map(([stateId, { name: name2, n }]) => `${name2} ${n}/${quota2.overrides[stateId] ?? quota2.default_limit}`).join(" \xB7 ");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ../shared/src/events.ts
|
|
150
|
+
var text = (t) => ({ kind: "text", text: t });
|
|
151
|
+
var name = (t) => ({ kind: "name", text: str(t) });
|
|
152
|
+
var selfRef = () => ({ kind: "self-ref" });
|
|
153
|
+
function str(v) {
|
|
154
|
+
return v === null || v === void 0 ? "" : String(v);
|
|
155
|
+
}
|
|
156
|
+
function joinChanged(v, sep) {
|
|
157
|
+
return Array.isArray(v) ? v.join(sep) : "";
|
|
158
|
+
}
|
|
159
|
+
function action(type) {
|
|
160
|
+
return type.split(".")[1] ?? type;
|
|
161
|
+
}
|
|
162
|
+
function otherRef(p) {
|
|
163
|
+
return {
|
|
164
|
+
kind: "other-ref",
|
|
165
|
+
project_name: str(p.other_project_name),
|
|
166
|
+
number: Number(p.other_number)
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function linkSegments(ev, p) {
|
|
170
|
+
const lead = ev.type === "issue.link_added" ? "marked" : "unmarked";
|
|
171
|
+
const duplicate = p.kind === "duplicate_of";
|
|
172
|
+
if (duplicate && p.role === "target") {
|
|
173
|
+
return [text(lead), otherRef(p), text("as a duplicate of"), selfRef()];
|
|
174
|
+
}
|
|
175
|
+
const middle = duplicate ? "as a duplicate of" : p.role === "target" ? "as blocked by" : "as blocking";
|
|
176
|
+
const segs = [text(lead), selfRef(), text(middle), otherRef(p)];
|
|
177
|
+
if (p.other_title) segs.push(text(`\u2014 ${str(p.other_title)}`));
|
|
178
|
+
return segs;
|
|
179
|
+
}
|
|
180
|
+
var DESCRIBERS = {
|
|
181
|
+
"issue.created": (_ev, p) => {
|
|
182
|
+
const segs = [text("created"), selfRef()];
|
|
183
|
+
if (p.title) segs.push(text(`\u2014 ${str(p.title)}`));
|
|
184
|
+
return segs;
|
|
185
|
+
},
|
|
186
|
+
"issue.updated": (_ev, p) => {
|
|
187
|
+
const changed = joinChanged(p.changed, " and ");
|
|
188
|
+
const segs = [text(changed ? `updated ${changed} of` : "updated"), selfRef()];
|
|
189
|
+
if (p.workflow_to_name) {
|
|
190
|
+
segs.push(text("from"), name(p.workflow_from_name), text("to"), name(p.workflow_to_name));
|
|
191
|
+
}
|
|
192
|
+
return segs;
|
|
193
|
+
},
|
|
194
|
+
"issue.transitioned": (_ev, p) => {
|
|
195
|
+
const segs = [text("moved"), selfRef()];
|
|
196
|
+
if (p.action) segs.push(text("via"), name(p.action));
|
|
197
|
+
else if (p.forced) segs.push(text("directly"));
|
|
198
|
+
segs.push({
|
|
199
|
+
kind: "state-transition",
|
|
200
|
+
from: str(p.from_state_name) || "?",
|
|
201
|
+
to: str(p.to_state_name) || "?"
|
|
202
|
+
});
|
|
203
|
+
return segs;
|
|
204
|
+
},
|
|
205
|
+
"issue.commented": () => [text("commented on"), selfRef()],
|
|
206
|
+
"issue.link_added": linkSegments,
|
|
207
|
+
"issue.link_removed": linkSegments,
|
|
208
|
+
"issue.parked": (_ev, p) => [
|
|
209
|
+
text("parked"),
|
|
210
|
+
selfRef(),
|
|
211
|
+
text(`after ${str(p.attempt_count)} strikes \u2014 needs attention`)
|
|
212
|
+
],
|
|
213
|
+
"issue.resumed": () => [text("resumed"), selfRef(), text("(attempt count reset)")],
|
|
214
|
+
"project.created": (ev, p) => projectSegments(ev, p),
|
|
215
|
+
"project.updated": (ev, p) => projectSegments(ev, p),
|
|
216
|
+
"project.deleted": (ev, p) => projectSegments(ev, p),
|
|
217
|
+
"workflow.created": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
218
|
+
"workflow.updated": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
219
|
+
"workflow.deleted": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
220
|
+
"api_key.created": (_ev, p) => [text("created API key"), name(p.name)],
|
|
221
|
+
"api_key.revoked": (_ev, p) => [text("revoked API key"), name(p.name)],
|
|
222
|
+
"scheduled_task.created": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
223
|
+
"scheduled_task.updated": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
224
|
+
"scheduled_task.deleted": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
225
|
+
"scheduled_task.skipped": (_ev, p) => {
|
|
226
|
+
const blocking = Array.isArray(p.blocking) ? p.blocking.length : 0;
|
|
227
|
+
return [
|
|
228
|
+
text("skipped an occurrence of schedule"),
|
|
229
|
+
name(p.name),
|
|
230
|
+
text(`(${blocking} open instance${blocking === 1 ? "" : "s"})`)
|
|
231
|
+
];
|
|
232
|
+
},
|
|
233
|
+
"context.created": (ev, p) => contextSegments(ev, p),
|
|
234
|
+
"context.updated": (ev, p) => contextSegments(ev, p),
|
|
235
|
+
"context.deleted": (ev, p) => contextSegments(ev, p),
|
|
236
|
+
"runner.registered": (ev, p) => [text(`${action(ev.type)} runner`), name(p.name)],
|
|
237
|
+
"runner.updated": (ev, p) => [text(`${action(ev.type)} runner`), name(p.name)],
|
|
238
|
+
"runner.removed": (ev, p) => [text(`${action(ev.type)} runner`), name(p.name)],
|
|
239
|
+
"runner.errored": (_ev, p) => [
|
|
240
|
+
text("saw runner"),
|
|
241
|
+
name(p.runner_name),
|
|
242
|
+
text(`fail to launch (${str(p.consecutive_failures)} consecutive): ${str(p.error)}`)
|
|
243
|
+
],
|
|
244
|
+
"routing_rule.created": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
|
|
245
|
+
"routing_rule.updated": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
|
|
246
|
+
"routing_rule.deleted": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
|
|
247
|
+
"settings.updated": (_ev, p) => [
|
|
248
|
+
text(`updated supervisor settings (${joinChanged(p.changed, ", ") || "no changes"})`)
|
|
249
|
+
],
|
|
250
|
+
"agent_run.started": (_ev, p) => {
|
|
251
|
+
const segs = [text(`started a ${str(p.tier)} run`)];
|
|
252
|
+
if (p.model) segs.push(text(`(${str(p.model)})`));
|
|
253
|
+
segs.push(text("via"), name(p.runner_name), text("on"), selfRef());
|
|
254
|
+
return segs;
|
|
255
|
+
},
|
|
256
|
+
"agent_run.ended": (_ev, p) => {
|
|
257
|
+
const segs = [text(`run ${str(p.status).replaceAll("_", " ")}`)];
|
|
258
|
+
if (p.outcome) segs.push(text(`\u2014 ${str(p.outcome)}`));
|
|
259
|
+
segs.push(text("via"), name(p.runner_name), text("on"), selfRef());
|
|
260
|
+
return segs;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
function projectSegments(ev, p) {
|
|
264
|
+
return [text(`${action(ev.type)} project`), name(p.name ?? ev.project_name)];
|
|
265
|
+
}
|
|
266
|
+
function contextSegments(ev, p) {
|
|
267
|
+
const segs = [text(`${action(ev.type)} ${str(p.kind)}`), name(p.name)];
|
|
268
|
+
if (ev.issue_ref) {
|
|
269
|
+
segs.push(text("on"), selfRef());
|
|
270
|
+
} else {
|
|
271
|
+
const label = p.scope?.label;
|
|
272
|
+
if (label) segs.push(text(`[${label}]`));
|
|
273
|
+
}
|
|
274
|
+
return segs;
|
|
275
|
+
}
|
|
276
|
+
function describeEvent(ev) {
|
|
277
|
+
const describe = DESCRIBERS[ev.type];
|
|
278
|
+
const segs = describe ? describe(ev, ev.payload ?? {}) : ev.issue_ref ? [text(ev.type), selfRef()] : [text(ev.type)];
|
|
279
|
+
return segs.filter((s) => s.kind === "text" || s.kind === "name" ? s.text !== "" : true);
|
|
280
|
+
}
|
|
281
|
+
function segmentText(ev, seg) {
|
|
282
|
+
switch (seg.kind) {
|
|
283
|
+
case "text":
|
|
284
|
+
return seg.text;
|
|
285
|
+
case "name":
|
|
286
|
+
return `"${seg.text}"`;
|
|
287
|
+
case "self-ref":
|
|
288
|
+
return ev.issue_ref ? `${ev.issue_ref.project_name}/#${ev.issue_ref.number}` : "this issue";
|
|
289
|
+
case "other-ref":
|
|
290
|
+
return `${seg.project_name}/#${seg.number}`;
|
|
291
|
+
case "state-transition":
|
|
292
|
+
return `${seg.from} \u2192 ${seg.to}`;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function eventSummary(ev) {
|
|
296
|
+
return describeEvent(ev).map((seg) => segmentText(ev, seg)).filter((s) => s !== "").join(" ");
|
|
297
|
+
}
|
|
298
|
+
function displayActor(ev) {
|
|
299
|
+
const p = ev.payload ?? {};
|
|
300
|
+
if (ev.type === "issue.created" && p.scheduled_task_name && !p.manual) {
|
|
301
|
+
return `${ev.actor.user_name} via schedule \u201C${str(p.scheduled_task_name)}\u201D`;
|
|
302
|
+
}
|
|
303
|
+
return actorLabel(ev.actor);
|
|
147
304
|
}
|
|
148
305
|
|
|
149
306
|
// ../shared/src/client.ts
|
|
@@ -206,7 +363,7 @@ function createApiClient(options) {
|
|
|
206
363
|
}
|
|
207
364
|
return res;
|
|
208
365
|
}
|
|
209
|
-
const artifactPath = (issueId,
|
|
366
|
+
const artifactPath = (issueId, name2, suffix = "") => `/api/v1/issues/${issueId}/artifacts/${encodeURIComponent(name2)}${suffix}`;
|
|
210
367
|
return {
|
|
211
368
|
getTime: () => get("/api/time"),
|
|
212
369
|
// Projects
|
|
@@ -266,14 +423,14 @@ function createApiClient(options) {
|
|
|
266
423
|
getIssuePrompt: (issueId) => get(`/api/v1/issues/${issueId}/prompt`),
|
|
267
424
|
// Issue artifacts (name-addressed under the issue)
|
|
268
425
|
listArtifacts: (issueId) => get(`/api/v1/issues/${issueId}/artifacts`),
|
|
269
|
-
getArtifact: (issueId,
|
|
426
|
+
getArtifact: (issueId, name2) => get(artifactPath(issueId, name2)),
|
|
270
427
|
/** JSON upsert for text/link/pr: creates the artifact or appends a version. */
|
|
271
|
-
putArtifact: (issueId,
|
|
428
|
+
putArtifact: (issueId, name2, body) => request("PUT", artifactPath(issueId, name2), body),
|
|
272
429
|
/** Raw-body upload for `file`: creates the artifact or appends a version. */
|
|
273
|
-
uploadArtifactFile: async (issueId,
|
|
430
|
+
uploadArtifactFile: async (issueId, name2, bytes, opts) => {
|
|
274
431
|
const res = await raw(
|
|
275
432
|
"PUT",
|
|
276
|
-
artifactPath(issueId,
|
|
433
|
+
artifactPath(issueId, name2, `/file?filename=${encodeURIComponent(opts.filename)}`),
|
|
277
434
|
{
|
|
278
435
|
body: bytes,
|
|
279
436
|
headers: { "content-type": opts.contentType }
|
|
@@ -285,23 +442,23 @@ function createApiClient(options) {
|
|
|
285
442
|
* Multipart snapshot upload for `folder`: every file of the new version
|
|
286
443
|
* in one request (path as the part filename, MIME as the part type).
|
|
287
444
|
*/
|
|
288
|
-
uploadArtifactFolder: async (issueId,
|
|
445
|
+
uploadArtifactFolder: async (issueId, name2, files) => {
|
|
289
446
|
const form = new FormData();
|
|
290
447
|
for (const file of files) {
|
|
291
448
|
form.append("file", new Blob([file.bytes], { type: file.contentType }), file.path);
|
|
292
449
|
}
|
|
293
|
-
const res = await raw("PUT", artifactPath(issueId,
|
|
450
|
+
const res = await raw("PUT", artifactPath(issueId, name2, "/folder"), { body: form });
|
|
294
451
|
return await res.json();
|
|
295
452
|
},
|
|
296
453
|
/** Bless the current content as fresh: appends a reaffirming version. */
|
|
297
|
-
reaffirmArtifact: (issueId,
|
|
454
|
+
reaffirmArtifact: (issueId, name2) => request("POST", artifactPath(issueId, name2, "/reaffirm")),
|
|
298
455
|
/** Bytes of a version (default: current; `path` selects a folder entry). */
|
|
299
|
-
getArtifactContent: async (issueId,
|
|
456
|
+
getArtifactContent: async (issueId, name2, opts = {}) => {
|
|
300
457
|
const params = new URLSearchParams();
|
|
301
458
|
if (opts.version !== void 0) params.set("version", String(opts.version));
|
|
302
459
|
if (opts.path !== void 0) params.set("path", opts.path);
|
|
303
460
|
const query2 = params.toString() ? `?${params.toString()}` : "";
|
|
304
|
-
const res = await raw("GET", artifactPath(issueId,
|
|
461
|
+
const res = await raw("GET", artifactPath(issueId, name2, `/content${query2}`));
|
|
305
462
|
const disposition = res.headers.get("content-disposition") ?? "";
|
|
306
463
|
const filenameMatch = disposition.match(/filename="((?:[^"\\]|\\.)*)"/);
|
|
307
464
|
return {
|
|
@@ -310,7 +467,7 @@ function createApiClient(options) {
|
|
|
310
467
|
filename: filenameMatch ? filenameMatch[1].replaceAll('\\"', '"') : null
|
|
311
468
|
};
|
|
312
469
|
},
|
|
313
|
-
deleteArtifact: (issueId,
|
|
470
|
+
deleteArtifact: (issueId, name2) => request("DELETE", artifactPath(issueId, name2)),
|
|
314
471
|
// Events
|
|
315
472
|
listEvents: (filters = {}) => get(`/api/v1/events${query(filters)}`),
|
|
316
473
|
// Runners
|
|
@@ -405,30 +562,30 @@ function writeJsonFile(path, value, { secret = false } = {}) {
|
|
|
405
562
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
406
563
|
`, secret ? { mode: 384 } : {});
|
|
407
564
|
}
|
|
408
|
-
function credentialsKey(url,
|
|
409
|
-
return `${url.replace(/\/+$/, "")}#${
|
|
565
|
+
function credentialsKey(url, name2) {
|
|
566
|
+
return `${url.replace(/\/+$/, "")}#${name2}`;
|
|
410
567
|
}
|
|
411
568
|
function credentialsPath(dir) {
|
|
412
569
|
return join(dir, "runners.json");
|
|
413
570
|
}
|
|
414
|
-
function loadRunnerCredentials(dir, url,
|
|
571
|
+
function loadRunnerCredentials(dir, url, name2) {
|
|
415
572
|
const all = readJsonFile(credentialsPath(dir));
|
|
416
|
-
const entry = all?.[credentialsKey(url,
|
|
573
|
+
const entry = all?.[credentialsKey(url, name2)];
|
|
417
574
|
return entry && typeof entry.runner_id === "string" && typeof entry.token === "string" ? entry : null;
|
|
418
575
|
}
|
|
419
|
-
function saveRunnerCredentials(dir, url,
|
|
576
|
+
function saveRunnerCredentials(dir, url, name2, creds) {
|
|
420
577
|
const path = credentialsPath(dir);
|
|
421
578
|
const all = readJsonFile(path) ?? {};
|
|
422
|
-
all[credentialsKey(url,
|
|
579
|
+
all[credentialsKey(url, name2)] = creds;
|
|
423
580
|
writeJsonFile(path, all, { secret: true });
|
|
424
581
|
}
|
|
425
|
-
function hasRunnerCredentials(dir, url,
|
|
426
|
-
return loadRunnerCredentials(dir, url,
|
|
582
|
+
function hasRunnerCredentials(dir, url, name2) {
|
|
583
|
+
return loadRunnerCredentials(dir, url, name2) !== null;
|
|
427
584
|
}
|
|
428
|
-
function clearRunnerCredentials(dir, url,
|
|
585
|
+
function clearRunnerCredentials(dir, url, name2) {
|
|
429
586
|
const path = credentialsPath(dir);
|
|
430
587
|
const all = readJsonFile(path) ?? {};
|
|
431
|
-
delete all[credentialsKey(url,
|
|
588
|
+
delete all[credentialsKey(url, name2)];
|
|
432
589
|
writeJsonFile(path, all, { secret: true });
|
|
433
590
|
}
|
|
434
591
|
function daemonStatePath(dir, runnerId) {
|
|
@@ -563,9 +720,9 @@ var LogBatcher = class {
|
|
|
563
720
|
buffer = "";
|
|
564
721
|
timer = null;
|
|
565
722
|
sending = Promise.resolve();
|
|
566
|
-
append(
|
|
567
|
-
if (!
|
|
568
|
-
this.buffer +=
|
|
723
|
+
append(text2) {
|
|
724
|
+
if (!text2) return;
|
|
725
|
+
this.buffer += text2;
|
|
569
726
|
if (Buffer.byteLength(this.buffer, "utf8") >= (this.opts.maxBytes ?? 8 * 1024)) {
|
|
570
727
|
void this.flush();
|
|
571
728
|
} else if (!this.timer) {
|
|
@@ -1057,13 +1214,13 @@ function parseScheduleRef(ref) {
|
|
|
1057
1214
|
return { project: ref.slice(0, sep), name: ref.slice(sep + 1) };
|
|
1058
1215
|
}
|
|
1059
1216
|
async function resolveSchedule(api, ref) {
|
|
1060
|
-
const { project, name } = parseScheduleRef(ref);
|
|
1217
|
+
const { project, name: name2 } = parseScheduleRef(ref);
|
|
1061
1218
|
const proj = await resolveProject(api, project);
|
|
1062
1219
|
const { items } = await api.listProjectSchedules(proj.id, { limit: 100 });
|
|
1063
|
-
const found = items.find((s) => s.name ===
|
|
1220
|
+
const found = items.find((s) => s.name === name2) ?? items.find((s) => s.id === name2);
|
|
1064
1221
|
if (!found) {
|
|
1065
1222
|
die(
|
|
1066
|
-
`no schedule "${
|
|
1223
|
+
`no schedule "${name2}" in project "${proj.name}" (have: ${items.map((s) => s.name).join(", ") || "none"})`
|
|
1067
1224
|
);
|
|
1068
1225
|
}
|
|
1069
1226
|
return found;
|
|
@@ -1307,69 +1464,6 @@ function printWorkflowDetail(wf) {
|
|
|
1307
1464
|
for (const w of wf.warnings ?? []) console.log(`
|
|
1308
1465
|
warning: ${w}`);
|
|
1309
1466
|
}
|
|
1310
|
-
function eventSummary(ev) {
|
|
1311
|
-
const p = ev.payload;
|
|
1312
|
-
const issue = ev.issue_ref ? `${ev.issue_ref.project_name}/#${ev.issue_ref.number}` : null;
|
|
1313
|
-
switch (ev.type) {
|
|
1314
|
-
case "issue.created":
|
|
1315
|
-
return `created ${issue}: ${p.title}${p.scheduled_task_name ? ` (via schedule "${p.scheduled_task_name}")` : ""}`;
|
|
1316
|
-
case "issue.updated":
|
|
1317
|
-
return `updated ${issue} (${p.changed?.join(", ")})`;
|
|
1318
|
-
case "issue.transitioned":
|
|
1319
|
-
return `${p.action ? `"${p.action}" on` : "moved"} ${issue}: ${p.from_state_name} \u2192 ${p.to_state_name}`;
|
|
1320
|
-
case "issue.commented":
|
|
1321
|
-
return `commented on ${issue}`;
|
|
1322
|
-
case "project.created":
|
|
1323
|
-
case "project.updated":
|
|
1324
|
-
case "project.deleted":
|
|
1325
|
-
return `${ev.type.split(".")[1]} project "${p.name ?? ev.project_name}"`;
|
|
1326
|
-
case "workflow.created":
|
|
1327
|
-
case "workflow.updated":
|
|
1328
|
-
case "workflow.deleted":
|
|
1329
|
-
return `${ev.type.split(".")[1]} workflow "${p.name}"`;
|
|
1330
|
-
case "api_key.created":
|
|
1331
|
-
return `created API key "${p.name}"`;
|
|
1332
|
-
case "api_key.revoked":
|
|
1333
|
-
return `revoked API key "${p.name}"`;
|
|
1334
|
-
case "scheduled_task.created":
|
|
1335
|
-
case "scheduled_task.updated":
|
|
1336
|
-
case "scheduled_task.deleted":
|
|
1337
|
-
return `${ev.type.split(".")[1]} schedule "${p.name}"`;
|
|
1338
|
-
case "context.created":
|
|
1339
|
-
case "context.updated":
|
|
1340
|
-
case "context.deleted": {
|
|
1341
|
-
const scope = p.scope;
|
|
1342
|
-
const verb = ev.type.split(".")[1];
|
|
1343
|
-
return `${verb} ${p.kind} "${p.name}"${scope?.label ? ` [${scope.label}]` : ""}`;
|
|
1344
|
-
}
|
|
1345
|
-
case "scheduled_task.skipped": {
|
|
1346
|
-
const blocking = Array.isArray(p.blocking) ? p.blocking.length : 0;
|
|
1347
|
-
return `skipped schedule "${p.name}" (${blocking} open instance${blocking === 1 ? "" : "s"})`;
|
|
1348
|
-
}
|
|
1349
|
-
case "runner.registered":
|
|
1350
|
-
case "runner.updated":
|
|
1351
|
-
case "runner.removed":
|
|
1352
|
-
return `${ev.type.split(".")[1]} runner "${p.name}"`;
|
|
1353
|
-
case "runner.errored":
|
|
1354
|
-
return `runner "${p.runner_name}" failed to launch (${p.consecutive_failures} consecutive): ${p.error}`;
|
|
1355
|
-
case "agent_run.started":
|
|
1356
|
-
return `run started on ${issue} via ${p.runner_name} (${p.tier}${p.model ? ` \u2192 ${p.model}` : ""})`;
|
|
1357
|
-
case "agent_run.ended":
|
|
1358
|
-
return `run ${p.status} on ${issue} via ${p.runner_name}${p.outcome ? ` \u2014 ${p.outcome}` : ""}`;
|
|
1359
|
-
case "issue.parked":
|
|
1360
|
-
return `parked ${issue} after ${p.attempt_count} strikes \u2014 needs attention`;
|
|
1361
|
-
case "issue.resumed":
|
|
1362
|
-
return `resumed ${issue} (attempt count reset)`;
|
|
1363
|
-
case "routing_rule.created":
|
|
1364
|
-
case "routing_rule.updated":
|
|
1365
|
-
case "routing_rule.deleted":
|
|
1366
|
-
return `${ev.type.split(".")[1]} the ${p.scope_label} routing rule`;
|
|
1367
|
-
case "settings.updated":
|
|
1368
|
-
return `updated supervisor settings (${p.changed?.join(", ") || "no changes"})`;
|
|
1369
|
-
default:
|
|
1370
|
-
return ev.type;
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
1467
|
function cliVersion() {
|
|
1374
1468
|
try {
|
|
1375
1469
|
const manifest = new URL("../package.json", import.meta.url);
|
|
@@ -1401,7 +1495,7 @@ withList(projects.command("list").description("List projects")).action(async (op
|
|
|
1401
1495
|
withCommon(
|
|
1402
1496
|
projects.command("create <name>").description("Create a project (with its initial context prompt)").option("-d, --description <text>", "project description").option("-w, --default-workflow <id-or-name>", "default workflow for new issues").option("--prompt <md>", "initial conventions prompt, stitched into every issue's agent prompt: inline Markdown or @file").option("--no-prompt", "create without an initial prompt")
|
|
1403
1497
|
).action(
|
|
1404
|
-
async (
|
|
1498
|
+
async (name2, opts) => {
|
|
1405
1499
|
if (opts.prompt === void 0 || opts.prompt === true) {
|
|
1406
1500
|
die(
|
|
1407
1501
|
'every issue in a project inherits its context \u2014 give the project an initial prompt:\n --prompt "<markdown>" house conventions, inline or @file\n --no-prompt create without one (add later: tines context create -k prompt -n conventions -p <name> --body \u2026)'
|
|
@@ -1410,7 +1504,7 @@ withCommon(
|
|
|
1410
1504
|
const api = client(opts);
|
|
1411
1505
|
const workflowId = opts.defaultWorkflow ? (await resolveWorkflow(api, opts.defaultWorkflow)).id : void 0;
|
|
1412
1506
|
const project = await api.createProject({
|
|
1413
|
-
name,
|
|
1507
|
+
name: name2,
|
|
1414
1508
|
description: opts.description,
|
|
1415
1509
|
default_workflow_id: workflowId,
|
|
1416
1510
|
initial_prompt: typeof opts.prompt === "string" ? readBodyValue(opts.prompt) : void 0
|
|
@@ -1647,13 +1741,13 @@ withCommon(
|
|
|
1647
1741
|
);
|
|
1648
1742
|
withCommon(
|
|
1649
1743
|
issues.command("move <ref> <action>").description('Take a transition on an issue by its action name (e.g. "approve")')
|
|
1650
|
-
).action(async (ref,
|
|
1744
|
+
).action(async (ref, action2, opts) => {
|
|
1651
1745
|
const api = client(opts);
|
|
1652
1746
|
const issue = await resolveIssue(api, ref);
|
|
1653
|
-
const moved = await api.transitionIssue(issue.id, { action });
|
|
1747
|
+
const moved = await api.transitionIssue(issue.id, { action: action2 });
|
|
1654
1748
|
if (opts.json) return printJson(moved);
|
|
1655
1749
|
console.log(
|
|
1656
|
-
`${moved.project_name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${
|
|
1750
|
+
`${moved.project_name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${action2}")`
|
|
1657
1751
|
);
|
|
1658
1752
|
});
|
|
1659
1753
|
withCommon(
|
|
@@ -1863,10 +1957,10 @@ withCommon(artifactsCmd.command("list <ref>").description("List the artifacts at
|
|
|
1863
1957
|
);
|
|
1864
1958
|
withCommon(
|
|
1865
1959
|
artifactsCmd.command("show <ref> <name>").description("Show an artifact with its full version history")
|
|
1866
|
-
).action(async (ref,
|
|
1960
|
+
).action(async (ref, name2, opts) => {
|
|
1867
1961
|
const api = client(opts);
|
|
1868
1962
|
const issue = await resolveIssue(api, ref);
|
|
1869
|
-
const artifact = await api.getArtifact(issue.id,
|
|
1963
|
+
const artifact = await api.getArtifact(issue.id, name2);
|
|
1870
1964
|
if (opts.json) return printJson(artifact);
|
|
1871
1965
|
console.log(`${artifact.artifact_type} artifact "${artifact.name}" on ${issue.project_name}/${issue.number}`);
|
|
1872
1966
|
if (artifact.description) console.log(artifact.description);
|
|
@@ -1898,7 +1992,7 @@ withCommon(
|
|
|
1898
1992
|
// --url is the link payload here; the API base comes from TINES_API_URL.
|
|
1899
1993
|
{ baseUrlFlag: false }
|
|
1900
1994
|
).action(
|
|
1901
|
-
async (ref,
|
|
1995
|
+
async (ref, name2, opts) => {
|
|
1902
1996
|
const api = client({ apiKey: opts.apiKey, json: opts.json });
|
|
1903
1997
|
const sources = [opts.file, opts.folder, opts.text, opts.url, opts.pr].filter((v) => v !== void 0);
|
|
1904
1998
|
if (sources.length !== 1) {
|
|
@@ -1914,9 +2008,9 @@ withCommon(
|
|
|
1914
2008
|
}
|
|
1915
2009
|
const files = walkFolder(opts.folder);
|
|
1916
2010
|
if (files.length === 0) die(`${opts.folder} contains no files to snapshot`);
|
|
1917
|
-
artifact = await api.uploadArtifactFolder(issue.id,
|
|
2011
|
+
artifact = await api.uploadArtifactFolder(issue.id, name2, files);
|
|
1918
2012
|
if (opts.description !== void 0) {
|
|
1919
|
-
artifact = await api.putArtifact(issue.id,
|
|
2013
|
+
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
1920
2014
|
}
|
|
1921
2015
|
} else if (opts.file !== void 0) {
|
|
1922
2016
|
let bytes;
|
|
@@ -1925,15 +2019,15 @@ withCommon(
|
|
|
1925
2019
|
} catch (err) {
|
|
1926
2020
|
die(`cannot read ${opts.file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1927
2021
|
}
|
|
1928
|
-
artifact = await api.uploadArtifactFile(issue.id,
|
|
2022
|
+
artifact = await api.uploadArtifactFile(issue.id, name2, bytes, {
|
|
1929
2023
|
filename: opts.filename ?? basename(opts.file),
|
|
1930
2024
|
contentType: opts.contentType ?? sniffContentType(opts.file)
|
|
1931
2025
|
});
|
|
1932
2026
|
if (opts.description !== void 0) {
|
|
1933
|
-
artifact = await api.putArtifact(issue.id,
|
|
2027
|
+
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
1934
2028
|
}
|
|
1935
2029
|
} else if (opts.text !== void 0) {
|
|
1936
|
-
artifact = await api.putArtifact(issue.id,
|
|
2030
|
+
artifact = await api.putArtifact(issue.id, name2, {
|
|
1937
2031
|
type: "text",
|
|
1938
2032
|
content: readBodyValue(opts.text),
|
|
1939
2033
|
...opts.filename !== void 0 ? { filename: opts.filename } : {},
|
|
@@ -1941,7 +2035,7 @@ withCommon(
|
|
|
1941
2035
|
...opts.description !== void 0 ? { description: opts.description } : {}
|
|
1942
2036
|
});
|
|
1943
2037
|
} else if (opts.url !== void 0) {
|
|
1944
|
-
artifact = await api.putArtifact(issue.id,
|
|
2038
|
+
artifact = await api.putArtifact(issue.id, name2, {
|
|
1945
2039
|
type: "link",
|
|
1946
2040
|
url: opts.url,
|
|
1947
2041
|
...opts.title !== void 0 ? { title: opts.title } : {},
|
|
@@ -1952,7 +2046,7 @@ withCommon(
|
|
|
1952
2046
|
if (!parsed) {
|
|
1953
2047
|
die(`--pr takes owner/repo#N or a GitHub PR URL, got "${opts.pr}"`);
|
|
1954
2048
|
}
|
|
1955
|
-
artifact = await api.putArtifact(issue.id,
|
|
2049
|
+
artifact = await api.putArtifact(issue.id, name2, {
|
|
1956
2050
|
type: "pr",
|
|
1957
2051
|
pr_repo_url: parsed.repo_url,
|
|
1958
2052
|
pr_number: parsed.number,
|
|
@@ -1967,10 +2061,10 @@ withCommon(
|
|
|
1967
2061
|
);
|
|
1968
2062
|
withCommon(
|
|
1969
2063
|
artifactsCmd.command("reaffirm <ref> <name>").description("Bless the current content as fresh (appends a version reusing the same payload)")
|
|
1970
|
-
).action(async (ref,
|
|
2064
|
+
).action(async (ref, name2, opts) => {
|
|
1971
2065
|
const api = client(opts);
|
|
1972
2066
|
const issue = await resolveIssue(api, ref);
|
|
1973
|
-
const artifact = await api.reaffirmArtifact(issue.id,
|
|
2067
|
+
const artifact = await api.reaffirmArtifact(issue.id, name2);
|
|
1974
2068
|
if (opts.json) return printJson(artifact);
|
|
1975
2069
|
console.log(
|
|
1976
2070
|
`reaffirmed "${artifact.name}" on ${issue.project_name}/${issue.number}: v${artifact.current_version.version} reaffirms v${artifact.current_version.reaffirmed_from} \u2014 fresh as of now`
|
|
@@ -1979,14 +2073,14 @@ withCommon(
|
|
|
1979
2073
|
withCommon(
|
|
1980
2074
|
artifactsCmd.command("get <ref> <name>").description("Fetch content (current version by default); a link/pr prints its URL").option("--version <n>", "fetch a specific version from the history", (v) => Number.parseInt(v, 10)).option("--out <path>", "write to this file, or into this directory (keeps the stored filename)")
|
|
1981
2075
|
).action(
|
|
1982
|
-
async (ref,
|
|
2076
|
+
async (ref, name2, opts) => {
|
|
1983
2077
|
const api = client(opts);
|
|
1984
2078
|
const issue = await resolveIssue(api, ref);
|
|
1985
|
-
const artifact = await api.getArtifact(issue.id,
|
|
2079
|
+
const artifact = await api.getArtifact(issue.id, name2);
|
|
1986
2080
|
const version = opts.version === void 0 ? artifact.current_version : artifact.versions.find((v) => v.version === opts.version);
|
|
1987
2081
|
if (!version) {
|
|
1988
2082
|
die(
|
|
1989
|
-
`artifact "${
|
|
2083
|
+
`artifact "${name2}" has no version ${opts.version} (history: v1\u2013v${artifact.current_version.version})`
|
|
1990
2084
|
);
|
|
1991
2085
|
}
|
|
1992
2086
|
if (artifact.artifact_type === "link" || artifact.artifact_type === "pr") {
|
|
@@ -1996,7 +2090,7 @@ withCommon(
|
|
|
1996
2090
|
}
|
|
1997
2091
|
if (artifact.artifact_type === "folder") {
|
|
1998
2092
|
if (opts.out === void 0) {
|
|
1999
|
-
die(`artifact "${
|
|
2093
|
+
die(`artifact "${name2}" is a folder \u2014 pass --out <dir> to write its tree`);
|
|
2000
2094
|
}
|
|
2001
2095
|
if (existsSync2(opts.out) && !statSync(opts.out).isDirectory()) {
|
|
2002
2096
|
die(`--out for a folder must be a directory, and "${opts.out}" is a file`);
|
|
@@ -2004,7 +2098,7 @@ withCommon(
|
|
|
2004
2098
|
const files = version.files ?? [];
|
|
2005
2099
|
let total = 0;
|
|
2006
2100
|
for (const file of files) {
|
|
2007
|
-
const content2 = await api.getArtifactContent(issue.id,
|
|
2101
|
+
const content2 = await api.getArtifactContent(issue.id, name2, {
|
|
2008
2102
|
version: opts.version,
|
|
2009
2103
|
path: file.path
|
|
2010
2104
|
});
|
|
@@ -2014,15 +2108,15 @@ withCommon(
|
|
|
2014
2108
|
total += content2.bytes.byteLength;
|
|
2015
2109
|
}
|
|
2016
2110
|
return console.log(
|
|
2017
|
-
`wrote ${files.length} file${files.length === 1 ? "" : "s"} (${total} bytes) from "${
|
|
2111
|
+
`wrote ${files.length} file${files.length === 1 ? "" : "s"} (${total} bytes) from "${name2}" v${version.version} into ${opts.out}/`
|
|
2018
2112
|
);
|
|
2019
2113
|
}
|
|
2020
|
-
const content = await api.getArtifactContent(issue.id,
|
|
2114
|
+
const content = await api.getArtifactContent(issue.id, name2, { version: opts.version });
|
|
2021
2115
|
const bytes = Buffer.from(content.bytes);
|
|
2022
2116
|
if (opts.out !== void 0) {
|
|
2023
2117
|
let target2 = opts.out;
|
|
2024
2118
|
if (existsSync2(target2) && statSync(target2).isDirectory()) {
|
|
2025
|
-
target2 = join3(target2, version.filename ??
|
|
2119
|
+
target2 = join3(target2, version.filename ?? name2);
|
|
2026
2120
|
}
|
|
2027
2121
|
writeFileSync3(target2, bytes);
|
|
2028
2122
|
return console.log(`wrote ${target2} (${bytes.byteLength} bytes, ${content.content_type})`);
|
|
@@ -2031,20 +2125,20 @@ withCommon(
|
|
|
2031
2125
|
process.stdout.write(bytes);
|
|
2032
2126
|
return;
|
|
2033
2127
|
}
|
|
2034
|
-
const target = version.filename ??
|
|
2128
|
+
const target = version.filename ?? name2;
|
|
2035
2129
|
writeFileSync3(target, bytes);
|
|
2036
2130
|
console.log(`wrote ${target} (${bytes.byteLength} bytes, ${content.content_type})`);
|
|
2037
2131
|
}
|
|
2038
2132
|
);
|
|
2039
2133
|
withCommon(
|
|
2040
2134
|
artifactsCmd.command("delete <ref> <name>").description("Delete an artifact \u2014 every version and its stored files (history is not recoverable)")
|
|
2041
|
-
).action(async (ref,
|
|
2135
|
+
).action(async (ref, name2, opts) => {
|
|
2042
2136
|
const api = client(opts);
|
|
2043
2137
|
const issue = await resolveIssue(api, ref);
|
|
2044
|
-
const artifact = await api.getArtifact(issue.id,
|
|
2045
|
-
await api.deleteArtifact(issue.id,
|
|
2138
|
+
const artifact = await api.getArtifact(issue.id, name2);
|
|
2139
|
+
await api.deleteArtifact(issue.id, name2);
|
|
2046
2140
|
console.log(
|
|
2047
|
-
`deleted ${artifact.artifact_type} artifact "${
|
|
2141
|
+
`deleted ${artifact.artifact_type} artifact "${name2}" from ${issue.project_name}/${issue.number} (${artifact.version_count} version${artifact.version_count === 1 ? "" : "s"})`
|
|
2048
2142
|
);
|
|
2049
2143
|
});
|
|
2050
2144
|
withCommon(
|
|
@@ -2059,8 +2153,8 @@ withCommon(
|
|
|
2059
2153
|
return console.log(`unpinned ${updated2.project_name}/#${updated2.number} \u2014 routing rules apply again`);
|
|
2060
2154
|
}
|
|
2061
2155
|
if (runnerSpec === void 0) die("pass <runner>[:tier] to pin, or --clear to unpin");
|
|
2062
|
-
const { name, tier } = parseTargetSpec(runnerSpec);
|
|
2063
|
-
const runner = await resolveRunner(api,
|
|
2156
|
+
const { name: name2, tier } = parseTargetSpec(runnerSpec);
|
|
2157
|
+
const runner = await resolveRunner(api, name2);
|
|
2064
2158
|
const updated = await api.updateIssue(issue.id, {
|
|
2065
2159
|
pinned_runner_id: runner.id,
|
|
2066
2160
|
pinned_tier: tier ?? null
|
|
@@ -2357,12 +2451,12 @@ withCommon(
|
|
|
2357
2451
|
).action(
|
|
2358
2452
|
async (ref, markdown, opts, command) => {
|
|
2359
2453
|
if (helpGuard(command, markdown)) return;
|
|
2360
|
-
const
|
|
2454
|
+
const text2 = readBodyValue(markdown);
|
|
2361
2455
|
const api = client(opts);
|
|
2362
2456
|
const { scope, note, item } = await resolveJournal(api, ref, opts.state);
|
|
2363
2457
|
printNote(note);
|
|
2364
2458
|
if (item) {
|
|
2365
|
-
const updated = await api.appendContextItem(item.id, { text });
|
|
2459
|
+
const updated = await api.appendContextItem(item.id, { text: text2 });
|
|
2366
2460
|
if (opts.json) return printJson(updated);
|
|
2367
2461
|
return console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
|
|
2368
2462
|
}
|
|
@@ -2372,7 +2466,7 @@ withCommon(
|
|
|
2372
2466
|
name: JOURNAL_NAME,
|
|
2373
2467
|
project_id: scope.project_id ?? void 0,
|
|
2374
2468
|
workflow_state_id: scope.workflow_state_id ?? void 0,
|
|
2375
|
-
body:
|
|
2469
|
+
body: text2.trim()
|
|
2376
2470
|
});
|
|
2377
2471
|
if (opts.json) return printJson(created);
|
|
2378
2472
|
console.log(`started the ${scope.label} journal (${created.id})`);
|
|
@@ -2380,7 +2474,7 @@ withCommon(
|
|
|
2380
2474
|
if (!(err instanceof ApiError) || err.code !== "duplicate_context_name") throw err;
|
|
2381
2475
|
const { item: fresh } = await resolveJournal(api, ref, opts.state);
|
|
2382
2476
|
if (!fresh) throw err;
|
|
2383
|
-
const updated = await api.appendContextItem(fresh.id, { text });
|
|
2477
|
+
const updated = await api.appendContextItem(fresh.id, { text: text2 });
|
|
2384
2478
|
if (opts.json) return printJson(updated);
|
|
2385
2479
|
console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
|
|
2386
2480
|
}
|
|
@@ -2561,13 +2655,13 @@ async function resolveRunner(api, ref) {
|
|
|
2561
2655
|
function parseTargetSpec(spec) {
|
|
2562
2656
|
const sep = spec.lastIndexOf(":");
|
|
2563
2657
|
if (sep === -1) return { name: spec };
|
|
2564
|
-
const
|
|
2658
|
+
const name2 = spec.slice(0, sep);
|
|
2565
2659
|
const tier = spec.slice(sep + 1);
|
|
2566
|
-
if (!
|
|
2660
|
+
if (!name2) die(`target must look like <runner>[:tier], got "${spec}"`);
|
|
2567
2661
|
if (!MODEL_TIERS.includes(tier)) {
|
|
2568
2662
|
die(`unknown tier "${tier}" in "${spec}" (tiers: ${MODEL_TIERS.join(", ")})`);
|
|
2569
2663
|
}
|
|
2570
|
-
return { name, tier };
|
|
2664
|
+
return { name: name2, tier };
|
|
2571
2665
|
}
|
|
2572
2666
|
function runnerStatusLabel(runner) {
|
|
2573
2667
|
if (runner.status === "paused") return "paused";
|
|
@@ -2920,8 +3014,8 @@ withCommon(
|
|
|
2920
3014
|
const scope = await resolveRoutingScope(api, opts);
|
|
2921
3015
|
const targets = [];
|
|
2922
3016
|
for (const spec of targetSpecs) {
|
|
2923
|
-
const { name, tier } = parseTargetSpec(spec);
|
|
2924
|
-
const runner = await resolveRunner(api,
|
|
3017
|
+
const { name: name2, tier } = parseTargetSpec(spec);
|
|
3018
|
+
const runner = await resolveRunner(api, name2);
|
|
2925
3019
|
targets.push(tier ? { runner_id: runner.id, tier } : { runner_id: runner.id });
|
|
2926
3020
|
}
|
|
2927
3021
|
const { items } = await api.listRoutingRules();
|
|
@@ -3063,7 +3157,7 @@ withList(
|
|
|
3063
3157
|
if (items.length === 0) return console.log("no events");
|
|
3064
3158
|
table([
|
|
3065
3159
|
["WHEN", "ACTOR", "EVENT"],
|
|
3066
|
-
...items.map((ev) => [timestamp(ev.created_at),
|
|
3160
|
+
...items.map((ev) => [timestamp(ev.created_at), displayActor(ev), eventSummary(ev)])
|
|
3067
3161
|
]);
|
|
3068
3162
|
});
|
|
3069
3163
|
});
|