tines 0.0.79 → 0.0.81
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 +236 -142
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -126,6 +126,14 @@ function runDurationLabel(run, now = Date.now()) {
|
|
|
126
126
|
const seconds = Math.max(0, Math.round(((run.ended_at ?? now) - run.started_at) / 1e3));
|
|
127
127
|
return seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m`;
|
|
128
128
|
}
|
|
129
|
+
function runCostLabel(run) {
|
|
130
|
+
const usage = run.usage;
|
|
131
|
+
if (!usage) return null;
|
|
132
|
+
if (usage.cost_usd !== void 0) return `$${usage.cost_usd.toFixed(2)}`;
|
|
133
|
+
if (usage.cost_source === "none") return "unreported";
|
|
134
|
+
const tokens = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
|
|
135
|
+
return tokens > 0 ? `${tokens.toLocaleString()} tok` : null;
|
|
136
|
+
}
|
|
129
137
|
function utilizationLabel(quota2, activeRuns, stateName = (id) => id) {
|
|
130
138
|
if (quota2.type === "global_cap") {
|
|
131
139
|
return `${activeRuns.length}/${quota2.limit} global slot${quota2.limit === 1 ? "" : "s"} in use`;
|
|
@@ -143,7 +151,164 @@ function utilizationLabel(quota2, activeRuns, stateName = (id) => id) {
|
|
|
143
151
|
if (!counts.has(stateId)) counts.set(stateId, { name: stateName(stateId), n: 0 });
|
|
144
152
|
}
|
|
145
153
|
if (counts.size === 0) return `no active runs (roster default ${quota2.default_limit} per state)`;
|
|
146
|
-
return [...counts.entries()].map(([stateId, { name, n }]) => `${
|
|
154
|
+
return [...counts.entries()].map(([stateId, { name: name2, n }]) => `${name2} ${n}/${quota2.overrides[stateId] ?? quota2.default_limit}`).join(" \xB7 ");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ../shared/src/events.ts
|
|
158
|
+
var text = (t) => ({ kind: "text", text: t });
|
|
159
|
+
var name = (t) => ({ kind: "name", text: str(t) });
|
|
160
|
+
var selfRef = () => ({ kind: "self-ref" });
|
|
161
|
+
function str(v) {
|
|
162
|
+
return v === null || v === void 0 ? "" : String(v);
|
|
163
|
+
}
|
|
164
|
+
function joinChanged(v, sep) {
|
|
165
|
+
return Array.isArray(v) ? v.join(sep) : "";
|
|
166
|
+
}
|
|
167
|
+
function action(type) {
|
|
168
|
+
return type.split(".")[1] ?? type;
|
|
169
|
+
}
|
|
170
|
+
function otherRef(p) {
|
|
171
|
+
return {
|
|
172
|
+
kind: "other-ref",
|
|
173
|
+
project_name: str(p.other_project_name),
|
|
174
|
+
number: Number(p.other_number)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function linkSegments(ev, p) {
|
|
178
|
+
const lead = ev.type === "issue.link_added" ? "marked" : "unmarked";
|
|
179
|
+
const duplicate = p.kind === "duplicate_of";
|
|
180
|
+
if (duplicate && p.role === "target") {
|
|
181
|
+
return [text(lead), otherRef(p), text("as a duplicate of"), selfRef()];
|
|
182
|
+
}
|
|
183
|
+
const middle = duplicate ? "as a duplicate of" : p.role === "target" ? "as blocked by" : "as blocking";
|
|
184
|
+
const segs = [text(lead), selfRef(), text(middle), otherRef(p)];
|
|
185
|
+
if (p.other_title) segs.push(text(`\u2014 ${str(p.other_title)}`));
|
|
186
|
+
return segs;
|
|
187
|
+
}
|
|
188
|
+
var DESCRIBERS = {
|
|
189
|
+
"issue.created": (_ev, p) => {
|
|
190
|
+
const segs = [text("created"), selfRef()];
|
|
191
|
+
if (p.title) segs.push(text(`\u2014 ${str(p.title)}`));
|
|
192
|
+
return segs;
|
|
193
|
+
},
|
|
194
|
+
"issue.updated": (_ev, p) => {
|
|
195
|
+
const changed = joinChanged(p.changed, " and ");
|
|
196
|
+
const segs = [text(changed ? `updated ${changed} of` : "updated"), selfRef()];
|
|
197
|
+
if (p.workflow_to_name) {
|
|
198
|
+
segs.push(text("from"), name(p.workflow_from_name), text("to"), name(p.workflow_to_name));
|
|
199
|
+
}
|
|
200
|
+
return segs;
|
|
201
|
+
},
|
|
202
|
+
"issue.transitioned": (_ev, p) => {
|
|
203
|
+
const segs = [text("moved"), selfRef()];
|
|
204
|
+
if (p.action) segs.push(text("via"), name(p.action));
|
|
205
|
+
else if (p.forced) segs.push(text("directly"));
|
|
206
|
+
segs.push({
|
|
207
|
+
kind: "state-transition",
|
|
208
|
+
from: str(p.from_state_name) || "?",
|
|
209
|
+
to: str(p.to_state_name) || "?"
|
|
210
|
+
});
|
|
211
|
+
return segs;
|
|
212
|
+
},
|
|
213
|
+
"issue.commented": () => [text("commented on"), selfRef()],
|
|
214
|
+
"issue.link_added": linkSegments,
|
|
215
|
+
"issue.link_removed": linkSegments,
|
|
216
|
+
"issue.parked": (_ev, p) => [
|
|
217
|
+
text("parked"),
|
|
218
|
+
selfRef(),
|
|
219
|
+
text(`after ${str(p.attempt_count)} strikes \u2014 needs attention`)
|
|
220
|
+
],
|
|
221
|
+
"issue.resumed": () => [text("resumed"), selfRef(), text("(attempt count reset)")],
|
|
222
|
+
"project.created": (ev, p) => projectSegments(ev, p),
|
|
223
|
+
"project.updated": (ev, p) => projectSegments(ev, p),
|
|
224
|
+
"project.deleted": (ev, p) => projectSegments(ev, p),
|
|
225
|
+
"workflow.created": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
226
|
+
"workflow.updated": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
227
|
+
"workflow.deleted": (ev, p) => [text(`${action(ev.type)} workflow`), name(p.name)],
|
|
228
|
+
"api_key.created": (_ev, p) => [text("created API key"), name(p.name)],
|
|
229
|
+
"api_key.revoked": (_ev, p) => [text("revoked API key"), name(p.name)],
|
|
230
|
+
"scheduled_task.created": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
231
|
+
"scheduled_task.updated": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
232
|
+
"scheduled_task.deleted": (ev, p) => [text(`${action(ev.type)} schedule`), name(p.name)],
|
|
233
|
+
"scheduled_task.skipped": (_ev, p) => {
|
|
234
|
+
const blocking = Array.isArray(p.blocking) ? p.blocking.length : 0;
|
|
235
|
+
return [
|
|
236
|
+
text("skipped an occurrence of schedule"),
|
|
237
|
+
name(p.name),
|
|
238
|
+
text(`(${blocking} open instance${blocking === 1 ? "" : "s"})`)
|
|
239
|
+
];
|
|
240
|
+
},
|
|
241
|
+
"context.created": (ev, p) => contextSegments(ev, p),
|
|
242
|
+
"context.updated": (ev, p) => contextSegments(ev, p),
|
|
243
|
+
"context.deleted": (ev, p) => contextSegments(ev, p),
|
|
244
|
+
"runner.registered": (ev, p) => [text(`${action(ev.type)} runner`), name(p.name)],
|
|
245
|
+
"runner.updated": (ev, p) => [text(`${action(ev.type)} runner`), name(p.name)],
|
|
246
|
+
"runner.removed": (ev, p) => [text(`${action(ev.type)} runner`), name(p.name)],
|
|
247
|
+
"runner.errored": (_ev, p) => [
|
|
248
|
+
text("saw runner"),
|
|
249
|
+
name(p.runner_name),
|
|
250
|
+
text(`fail to launch (${str(p.consecutive_failures)} consecutive): ${str(p.error)}`)
|
|
251
|
+
],
|
|
252
|
+
"routing_rule.created": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
|
|
253
|
+
"routing_rule.updated": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
|
|
254
|
+
"routing_rule.deleted": (ev, p) => [text(`${action(ev.type)} the ${str(p.scope_label)} routing rule`)],
|
|
255
|
+
"settings.updated": (_ev, p) => [
|
|
256
|
+
text(`updated supervisor settings (${joinChanged(p.changed, ", ") || "no changes"})`)
|
|
257
|
+
],
|
|
258
|
+
"agent_run.started": (_ev, p) => {
|
|
259
|
+
const segs = [text(`started a ${str(p.tier)} run`)];
|
|
260
|
+
if (p.model) segs.push(text(`(${str(p.model)})`));
|
|
261
|
+
segs.push(text("via"), name(p.runner_name), text("on"), selfRef());
|
|
262
|
+
return segs;
|
|
263
|
+
},
|
|
264
|
+
"agent_run.ended": (_ev, p) => {
|
|
265
|
+
const segs = [text(`run ${str(p.status).replaceAll("_", " ")}`)];
|
|
266
|
+
if (p.outcome) segs.push(text(`\u2014 ${str(p.outcome)}`));
|
|
267
|
+
segs.push(text("via"), name(p.runner_name), text("on"), selfRef());
|
|
268
|
+
return segs;
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
function projectSegments(ev, p) {
|
|
272
|
+
return [text(`${action(ev.type)} project`), name(p.name ?? ev.project_name)];
|
|
273
|
+
}
|
|
274
|
+
function contextSegments(ev, p) {
|
|
275
|
+
const segs = [text(`${action(ev.type)} ${str(p.kind)}`), name(p.name)];
|
|
276
|
+
if (ev.issue_ref) {
|
|
277
|
+
segs.push(text("on"), selfRef());
|
|
278
|
+
} else {
|
|
279
|
+
const label = p.scope?.label;
|
|
280
|
+
if (label) segs.push(text(`[${label}]`));
|
|
281
|
+
}
|
|
282
|
+
return segs;
|
|
283
|
+
}
|
|
284
|
+
function describeEvent(ev) {
|
|
285
|
+
const describe = DESCRIBERS[ev.type];
|
|
286
|
+
const segs = describe ? describe(ev, ev.payload ?? {}) : ev.issue_ref ? [text(ev.type), selfRef()] : [text(ev.type)];
|
|
287
|
+
return segs.filter((s) => s.kind === "text" || s.kind === "name" ? s.text !== "" : true);
|
|
288
|
+
}
|
|
289
|
+
function segmentText(ev, seg) {
|
|
290
|
+
switch (seg.kind) {
|
|
291
|
+
case "text":
|
|
292
|
+
return seg.text;
|
|
293
|
+
case "name":
|
|
294
|
+
return `"${seg.text}"`;
|
|
295
|
+
case "self-ref":
|
|
296
|
+
return ev.issue_ref ? `${ev.issue_ref.project_name}/#${ev.issue_ref.number}` : "this issue";
|
|
297
|
+
case "other-ref":
|
|
298
|
+
return `${seg.project_name}/#${seg.number}`;
|
|
299
|
+
case "state-transition":
|
|
300
|
+
return `${seg.from} \u2192 ${seg.to}`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function eventSummary(ev) {
|
|
304
|
+
return describeEvent(ev).map((seg) => segmentText(ev, seg)).filter((s) => s !== "").join(" ");
|
|
305
|
+
}
|
|
306
|
+
function displayActor(ev) {
|
|
307
|
+
const p = ev.payload ?? {};
|
|
308
|
+
if (ev.type === "issue.created" && p.scheduled_task_name && !p.manual) {
|
|
309
|
+
return `${ev.actor.user_name} via schedule \u201C${str(p.scheduled_task_name)}\u201D`;
|
|
310
|
+
}
|
|
311
|
+
return actorLabel(ev.actor);
|
|
147
312
|
}
|
|
148
313
|
|
|
149
314
|
// ../shared/src/client.ts
|
|
@@ -206,7 +371,7 @@ function createApiClient(options) {
|
|
|
206
371
|
}
|
|
207
372
|
return res;
|
|
208
373
|
}
|
|
209
|
-
const artifactPath = (issueId,
|
|
374
|
+
const artifactPath = (issueId, name2, suffix = "") => `/api/v1/issues/${issueId}/artifacts/${encodeURIComponent(name2)}${suffix}`;
|
|
210
375
|
return {
|
|
211
376
|
getTime: () => get("/api/time"),
|
|
212
377
|
// Projects
|
|
@@ -266,14 +431,14 @@ function createApiClient(options) {
|
|
|
266
431
|
getIssuePrompt: (issueId) => get(`/api/v1/issues/${issueId}/prompt`),
|
|
267
432
|
// Issue artifacts (name-addressed under the issue)
|
|
268
433
|
listArtifacts: (issueId) => get(`/api/v1/issues/${issueId}/artifacts`),
|
|
269
|
-
getArtifact: (issueId,
|
|
434
|
+
getArtifact: (issueId, name2) => get(artifactPath(issueId, name2)),
|
|
270
435
|
/** JSON upsert for text/link/pr: creates the artifact or appends a version. */
|
|
271
|
-
putArtifact: (issueId,
|
|
436
|
+
putArtifact: (issueId, name2, body) => request("PUT", artifactPath(issueId, name2), body),
|
|
272
437
|
/** Raw-body upload for `file`: creates the artifact or appends a version. */
|
|
273
|
-
uploadArtifactFile: async (issueId,
|
|
438
|
+
uploadArtifactFile: async (issueId, name2, bytes, opts) => {
|
|
274
439
|
const res = await raw(
|
|
275
440
|
"PUT",
|
|
276
|
-
artifactPath(issueId,
|
|
441
|
+
artifactPath(issueId, name2, `/file?filename=${encodeURIComponent(opts.filename)}`),
|
|
277
442
|
{
|
|
278
443
|
body: bytes,
|
|
279
444
|
headers: { "content-type": opts.contentType }
|
|
@@ -285,23 +450,23 @@ function createApiClient(options) {
|
|
|
285
450
|
* Multipart snapshot upload for `folder`: every file of the new version
|
|
286
451
|
* in one request (path as the part filename, MIME as the part type).
|
|
287
452
|
*/
|
|
288
|
-
uploadArtifactFolder: async (issueId,
|
|
453
|
+
uploadArtifactFolder: async (issueId, name2, files) => {
|
|
289
454
|
const form = new FormData();
|
|
290
455
|
for (const file of files) {
|
|
291
456
|
form.append("file", new Blob([file.bytes], { type: file.contentType }), file.path);
|
|
292
457
|
}
|
|
293
|
-
const res = await raw("PUT", artifactPath(issueId,
|
|
458
|
+
const res = await raw("PUT", artifactPath(issueId, name2, "/folder"), { body: form });
|
|
294
459
|
return await res.json();
|
|
295
460
|
},
|
|
296
461
|
/** Bless the current content as fresh: appends a reaffirming version. */
|
|
297
|
-
reaffirmArtifact: (issueId,
|
|
462
|
+
reaffirmArtifact: (issueId, name2) => request("POST", artifactPath(issueId, name2, "/reaffirm")),
|
|
298
463
|
/** Bytes of a version (default: current; `path` selects a folder entry). */
|
|
299
|
-
getArtifactContent: async (issueId,
|
|
464
|
+
getArtifactContent: async (issueId, name2, opts = {}) => {
|
|
300
465
|
const params = new URLSearchParams();
|
|
301
466
|
if (opts.version !== void 0) params.set("version", String(opts.version));
|
|
302
467
|
if (opts.path !== void 0) params.set("path", opts.path);
|
|
303
468
|
const query2 = params.toString() ? `?${params.toString()}` : "";
|
|
304
|
-
const res = await raw("GET", artifactPath(issueId,
|
|
469
|
+
const res = await raw("GET", artifactPath(issueId, name2, `/content${query2}`));
|
|
305
470
|
const disposition = res.headers.get("content-disposition") ?? "";
|
|
306
471
|
const filenameMatch = disposition.match(/filename="((?:[^"\\]|\\.)*)"/);
|
|
307
472
|
return {
|
|
@@ -310,7 +475,7 @@ function createApiClient(options) {
|
|
|
310
475
|
filename: filenameMatch ? filenameMatch[1].replaceAll('\\"', '"') : null
|
|
311
476
|
};
|
|
312
477
|
},
|
|
313
|
-
deleteArtifact: (issueId,
|
|
478
|
+
deleteArtifact: (issueId, name2) => request("DELETE", artifactPath(issueId, name2)),
|
|
314
479
|
// Events
|
|
315
480
|
listEvents: (filters = {}) => get(`/api/v1/events${query(filters)}`),
|
|
316
481
|
// Runners
|
|
@@ -405,30 +570,30 @@ function writeJsonFile(path, value, { secret = false } = {}) {
|
|
|
405
570
|
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
406
571
|
`, secret ? { mode: 384 } : {});
|
|
407
572
|
}
|
|
408
|
-
function credentialsKey(url,
|
|
409
|
-
return `${url.replace(/\/+$/, "")}#${
|
|
573
|
+
function credentialsKey(url, name2) {
|
|
574
|
+
return `${url.replace(/\/+$/, "")}#${name2}`;
|
|
410
575
|
}
|
|
411
576
|
function credentialsPath(dir) {
|
|
412
577
|
return join(dir, "runners.json");
|
|
413
578
|
}
|
|
414
|
-
function loadRunnerCredentials(dir, url,
|
|
579
|
+
function loadRunnerCredentials(dir, url, name2) {
|
|
415
580
|
const all = readJsonFile(credentialsPath(dir));
|
|
416
|
-
const entry = all?.[credentialsKey(url,
|
|
581
|
+
const entry = all?.[credentialsKey(url, name2)];
|
|
417
582
|
return entry && typeof entry.runner_id === "string" && typeof entry.token === "string" ? entry : null;
|
|
418
583
|
}
|
|
419
|
-
function saveRunnerCredentials(dir, url,
|
|
584
|
+
function saveRunnerCredentials(dir, url, name2, creds) {
|
|
420
585
|
const path = credentialsPath(dir);
|
|
421
586
|
const all = readJsonFile(path) ?? {};
|
|
422
|
-
all[credentialsKey(url,
|
|
587
|
+
all[credentialsKey(url, name2)] = creds;
|
|
423
588
|
writeJsonFile(path, all, { secret: true });
|
|
424
589
|
}
|
|
425
|
-
function hasRunnerCredentials(dir, url,
|
|
426
|
-
return loadRunnerCredentials(dir, url,
|
|
590
|
+
function hasRunnerCredentials(dir, url, name2) {
|
|
591
|
+
return loadRunnerCredentials(dir, url, name2) !== null;
|
|
427
592
|
}
|
|
428
|
-
function clearRunnerCredentials(dir, url,
|
|
593
|
+
function clearRunnerCredentials(dir, url, name2) {
|
|
429
594
|
const path = credentialsPath(dir);
|
|
430
595
|
const all = readJsonFile(path) ?? {};
|
|
431
|
-
delete all[credentialsKey(url,
|
|
596
|
+
delete all[credentialsKey(url, name2)];
|
|
432
597
|
writeJsonFile(path, all, { secret: true });
|
|
433
598
|
}
|
|
434
599
|
function daemonStatePath(dir, runnerId) {
|
|
@@ -563,9 +728,9 @@ var LogBatcher = class {
|
|
|
563
728
|
buffer = "";
|
|
564
729
|
timer = null;
|
|
565
730
|
sending = Promise.resolve();
|
|
566
|
-
append(
|
|
567
|
-
if (!
|
|
568
|
-
this.buffer +=
|
|
731
|
+
append(text2) {
|
|
732
|
+
if (!text2) return;
|
|
733
|
+
this.buffer += text2;
|
|
569
734
|
if (Buffer.byteLength(this.buffer, "utf8") >= (this.opts.maxBytes ?? 8 * 1024)) {
|
|
570
735
|
void this.flush();
|
|
571
736
|
} else if (!this.timer) {
|
|
@@ -1057,13 +1222,13 @@ function parseScheduleRef(ref) {
|
|
|
1057
1222
|
return { project: ref.slice(0, sep), name: ref.slice(sep + 1) };
|
|
1058
1223
|
}
|
|
1059
1224
|
async function resolveSchedule(api, ref) {
|
|
1060
|
-
const { project, name } = parseScheduleRef(ref);
|
|
1225
|
+
const { project, name: name2 } = parseScheduleRef(ref);
|
|
1061
1226
|
const proj = await resolveProject(api, project);
|
|
1062
1227
|
const { items } = await api.listProjectSchedules(proj.id, { limit: 100 });
|
|
1063
|
-
const found = items.find((s) => s.name ===
|
|
1228
|
+
const found = items.find((s) => s.name === name2) ?? items.find((s) => s.id === name2);
|
|
1064
1229
|
if (!found) {
|
|
1065
1230
|
die(
|
|
1066
|
-
`no schedule "${
|
|
1231
|
+
`no schedule "${name2}" in project "${proj.name}" (have: ${items.map((s) => s.name).join(", ") || "none"})`
|
|
1067
1232
|
);
|
|
1068
1233
|
}
|
|
1069
1234
|
return found;
|
|
@@ -1307,69 +1472,6 @@ function printWorkflowDetail(wf) {
|
|
|
1307
1472
|
for (const w of wf.warnings ?? []) console.log(`
|
|
1308
1473
|
warning: ${w}`);
|
|
1309
1474
|
}
|
|
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
1475
|
function cliVersion() {
|
|
1374
1476
|
try {
|
|
1375
1477
|
const manifest = new URL("../package.json", import.meta.url);
|
|
@@ -1401,7 +1503,7 @@ withList(projects.command("list").description("List projects")).action(async (op
|
|
|
1401
1503
|
withCommon(
|
|
1402
1504
|
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
1505
|
).action(
|
|
1404
|
-
async (
|
|
1506
|
+
async (name2, opts) => {
|
|
1405
1507
|
if (opts.prompt === void 0 || opts.prompt === true) {
|
|
1406
1508
|
die(
|
|
1407
1509
|
'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 +1512,7 @@ withCommon(
|
|
|
1410
1512
|
const api = client(opts);
|
|
1411
1513
|
const workflowId = opts.defaultWorkflow ? (await resolveWorkflow(api, opts.defaultWorkflow)).id : void 0;
|
|
1412
1514
|
const project = await api.createProject({
|
|
1413
|
-
name,
|
|
1515
|
+
name: name2,
|
|
1414
1516
|
description: opts.description,
|
|
1415
1517
|
default_workflow_id: workflowId,
|
|
1416
1518
|
initial_prompt: typeof opts.prompt === "string" ? readBodyValue(opts.prompt) : void 0
|
|
@@ -1647,13 +1749,13 @@ withCommon(
|
|
|
1647
1749
|
);
|
|
1648
1750
|
withCommon(
|
|
1649
1751
|
issues.command("move <ref> <action>").description('Take a transition on an issue by its action name (e.g. "approve")')
|
|
1650
|
-
).action(async (ref,
|
|
1752
|
+
).action(async (ref, action2, opts) => {
|
|
1651
1753
|
const api = client(opts);
|
|
1652
1754
|
const issue = await resolveIssue(api, ref);
|
|
1653
|
-
const moved = await api.transitionIssue(issue.id, { action });
|
|
1755
|
+
const moved = await api.transitionIssue(issue.id, { action: action2 });
|
|
1654
1756
|
if (opts.json) return printJson(moved);
|
|
1655
1757
|
console.log(
|
|
1656
|
-
`${moved.project_name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${
|
|
1758
|
+
`${moved.project_name}/#${moved.number}: ${issue.state.name} \u2192 ${moved.state.name} ("${action2}")`
|
|
1657
1759
|
);
|
|
1658
1760
|
});
|
|
1659
1761
|
withCommon(
|
|
@@ -1863,10 +1965,10 @@ withCommon(artifactsCmd.command("list <ref>").description("List the artifacts at
|
|
|
1863
1965
|
);
|
|
1864
1966
|
withCommon(
|
|
1865
1967
|
artifactsCmd.command("show <ref> <name>").description("Show an artifact with its full version history")
|
|
1866
|
-
).action(async (ref,
|
|
1968
|
+
).action(async (ref, name2, opts) => {
|
|
1867
1969
|
const api = client(opts);
|
|
1868
1970
|
const issue = await resolveIssue(api, ref);
|
|
1869
|
-
const artifact = await api.getArtifact(issue.id,
|
|
1971
|
+
const artifact = await api.getArtifact(issue.id, name2);
|
|
1870
1972
|
if (opts.json) return printJson(artifact);
|
|
1871
1973
|
console.log(`${artifact.artifact_type} artifact "${artifact.name}" on ${issue.project_name}/${issue.number}`);
|
|
1872
1974
|
if (artifact.description) console.log(artifact.description);
|
|
@@ -1898,7 +2000,7 @@ withCommon(
|
|
|
1898
2000
|
// --url is the link payload here; the API base comes from TINES_API_URL.
|
|
1899
2001
|
{ baseUrlFlag: false }
|
|
1900
2002
|
).action(
|
|
1901
|
-
async (ref,
|
|
2003
|
+
async (ref, name2, opts) => {
|
|
1902
2004
|
const api = client({ apiKey: opts.apiKey, json: opts.json });
|
|
1903
2005
|
const sources = [opts.file, opts.folder, opts.text, opts.url, opts.pr].filter((v) => v !== void 0);
|
|
1904
2006
|
if (sources.length !== 1) {
|
|
@@ -1914,9 +2016,9 @@ withCommon(
|
|
|
1914
2016
|
}
|
|
1915
2017
|
const files = walkFolder(opts.folder);
|
|
1916
2018
|
if (files.length === 0) die(`${opts.folder} contains no files to snapshot`);
|
|
1917
|
-
artifact = await api.uploadArtifactFolder(issue.id,
|
|
2019
|
+
artifact = await api.uploadArtifactFolder(issue.id, name2, files);
|
|
1918
2020
|
if (opts.description !== void 0) {
|
|
1919
|
-
artifact = await api.putArtifact(issue.id,
|
|
2021
|
+
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
1920
2022
|
}
|
|
1921
2023
|
} else if (opts.file !== void 0) {
|
|
1922
2024
|
let bytes;
|
|
@@ -1925,15 +2027,15 @@ withCommon(
|
|
|
1925
2027
|
} catch (err) {
|
|
1926
2028
|
die(`cannot read ${opts.file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1927
2029
|
}
|
|
1928
|
-
artifact = await api.uploadArtifactFile(issue.id,
|
|
2030
|
+
artifact = await api.uploadArtifactFile(issue.id, name2, bytes, {
|
|
1929
2031
|
filename: opts.filename ?? basename(opts.file),
|
|
1930
2032
|
contentType: opts.contentType ?? sniffContentType(opts.file)
|
|
1931
2033
|
});
|
|
1932
2034
|
if (opts.description !== void 0) {
|
|
1933
|
-
artifact = await api.putArtifact(issue.id,
|
|
2035
|
+
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
1934
2036
|
}
|
|
1935
2037
|
} else if (opts.text !== void 0) {
|
|
1936
|
-
artifact = await api.putArtifact(issue.id,
|
|
2038
|
+
artifact = await api.putArtifact(issue.id, name2, {
|
|
1937
2039
|
type: "text",
|
|
1938
2040
|
content: readBodyValue(opts.text),
|
|
1939
2041
|
...opts.filename !== void 0 ? { filename: opts.filename } : {},
|
|
@@ -1941,7 +2043,7 @@ withCommon(
|
|
|
1941
2043
|
...opts.description !== void 0 ? { description: opts.description } : {}
|
|
1942
2044
|
});
|
|
1943
2045
|
} else if (opts.url !== void 0) {
|
|
1944
|
-
artifact = await api.putArtifact(issue.id,
|
|
2046
|
+
artifact = await api.putArtifact(issue.id, name2, {
|
|
1945
2047
|
type: "link",
|
|
1946
2048
|
url: opts.url,
|
|
1947
2049
|
...opts.title !== void 0 ? { title: opts.title } : {},
|
|
@@ -1952,7 +2054,7 @@ withCommon(
|
|
|
1952
2054
|
if (!parsed) {
|
|
1953
2055
|
die(`--pr takes owner/repo#N or a GitHub PR URL, got "${opts.pr}"`);
|
|
1954
2056
|
}
|
|
1955
|
-
artifact = await api.putArtifact(issue.id,
|
|
2057
|
+
artifact = await api.putArtifact(issue.id, name2, {
|
|
1956
2058
|
type: "pr",
|
|
1957
2059
|
pr_repo_url: parsed.repo_url,
|
|
1958
2060
|
pr_number: parsed.number,
|
|
@@ -1967,10 +2069,10 @@ withCommon(
|
|
|
1967
2069
|
);
|
|
1968
2070
|
withCommon(
|
|
1969
2071
|
artifactsCmd.command("reaffirm <ref> <name>").description("Bless the current content as fresh (appends a version reusing the same payload)")
|
|
1970
|
-
).action(async (ref,
|
|
2072
|
+
).action(async (ref, name2, opts) => {
|
|
1971
2073
|
const api = client(opts);
|
|
1972
2074
|
const issue = await resolveIssue(api, ref);
|
|
1973
|
-
const artifact = await api.reaffirmArtifact(issue.id,
|
|
2075
|
+
const artifact = await api.reaffirmArtifact(issue.id, name2);
|
|
1974
2076
|
if (opts.json) return printJson(artifact);
|
|
1975
2077
|
console.log(
|
|
1976
2078
|
`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 +2081,14 @@ withCommon(
|
|
|
1979
2081
|
withCommon(
|
|
1980
2082
|
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
2083
|
).action(
|
|
1982
|
-
async (ref,
|
|
2084
|
+
async (ref, name2, opts) => {
|
|
1983
2085
|
const api = client(opts);
|
|
1984
2086
|
const issue = await resolveIssue(api, ref);
|
|
1985
|
-
const artifact = await api.getArtifact(issue.id,
|
|
2087
|
+
const artifact = await api.getArtifact(issue.id, name2);
|
|
1986
2088
|
const version = opts.version === void 0 ? artifact.current_version : artifact.versions.find((v) => v.version === opts.version);
|
|
1987
2089
|
if (!version) {
|
|
1988
2090
|
die(
|
|
1989
|
-
`artifact "${
|
|
2091
|
+
`artifact "${name2}" has no version ${opts.version} (history: v1\u2013v${artifact.current_version.version})`
|
|
1990
2092
|
);
|
|
1991
2093
|
}
|
|
1992
2094
|
if (artifact.artifact_type === "link" || artifact.artifact_type === "pr") {
|
|
@@ -1996,7 +2098,7 @@ withCommon(
|
|
|
1996
2098
|
}
|
|
1997
2099
|
if (artifact.artifact_type === "folder") {
|
|
1998
2100
|
if (opts.out === void 0) {
|
|
1999
|
-
die(`artifact "${
|
|
2101
|
+
die(`artifact "${name2}" is a folder \u2014 pass --out <dir> to write its tree`);
|
|
2000
2102
|
}
|
|
2001
2103
|
if (existsSync2(opts.out) && !statSync(opts.out).isDirectory()) {
|
|
2002
2104
|
die(`--out for a folder must be a directory, and "${opts.out}" is a file`);
|
|
@@ -2004,7 +2106,7 @@ withCommon(
|
|
|
2004
2106
|
const files = version.files ?? [];
|
|
2005
2107
|
let total = 0;
|
|
2006
2108
|
for (const file of files) {
|
|
2007
|
-
const content2 = await api.getArtifactContent(issue.id,
|
|
2109
|
+
const content2 = await api.getArtifactContent(issue.id, name2, {
|
|
2008
2110
|
version: opts.version,
|
|
2009
2111
|
path: file.path
|
|
2010
2112
|
});
|
|
@@ -2014,15 +2116,15 @@ withCommon(
|
|
|
2014
2116
|
total += content2.bytes.byteLength;
|
|
2015
2117
|
}
|
|
2016
2118
|
return console.log(
|
|
2017
|
-
`wrote ${files.length} file${files.length === 1 ? "" : "s"} (${total} bytes) from "${
|
|
2119
|
+
`wrote ${files.length} file${files.length === 1 ? "" : "s"} (${total} bytes) from "${name2}" v${version.version} into ${opts.out}/`
|
|
2018
2120
|
);
|
|
2019
2121
|
}
|
|
2020
|
-
const content = await api.getArtifactContent(issue.id,
|
|
2122
|
+
const content = await api.getArtifactContent(issue.id, name2, { version: opts.version });
|
|
2021
2123
|
const bytes = Buffer.from(content.bytes);
|
|
2022
2124
|
if (opts.out !== void 0) {
|
|
2023
2125
|
let target2 = opts.out;
|
|
2024
2126
|
if (existsSync2(target2) && statSync(target2).isDirectory()) {
|
|
2025
|
-
target2 = join3(target2, version.filename ??
|
|
2127
|
+
target2 = join3(target2, version.filename ?? name2);
|
|
2026
2128
|
}
|
|
2027
2129
|
writeFileSync3(target2, bytes);
|
|
2028
2130
|
return console.log(`wrote ${target2} (${bytes.byteLength} bytes, ${content.content_type})`);
|
|
@@ -2031,20 +2133,20 @@ withCommon(
|
|
|
2031
2133
|
process.stdout.write(bytes);
|
|
2032
2134
|
return;
|
|
2033
2135
|
}
|
|
2034
|
-
const target = version.filename ??
|
|
2136
|
+
const target = version.filename ?? name2;
|
|
2035
2137
|
writeFileSync3(target, bytes);
|
|
2036
2138
|
console.log(`wrote ${target} (${bytes.byteLength} bytes, ${content.content_type})`);
|
|
2037
2139
|
}
|
|
2038
2140
|
);
|
|
2039
2141
|
withCommon(
|
|
2040
2142
|
artifactsCmd.command("delete <ref> <name>").description("Delete an artifact \u2014 every version and its stored files (history is not recoverable)")
|
|
2041
|
-
).action(async (ref,
|
|
2143
|
+
).action(async (ref, name2, opts) => {
|
|
2042
2144
|
const api = client(opts);
|
|
2043
2145
|
const issue = await resolveIssue(api, ref);
|
|
2044
|
-
const artifact = await api.getArtifact(issue.id,
|
|
2045
|
-
await api.deleteArtifact(issue.id,
|
|
2146
|
+
const artifact = await api.getArtifact(issue.id, name2);
|
|
2147
|
+
await api.deleteArtifact(issue.id, name2);
|
|
2046
2148
|
console.log(
|
|
2047
|
-
`deleted ${artifact.artifact_type} artifact "${
|
|
2149
|
+
`deleted ${artifact.artifact_type} artifact "${name2}" from ${issue.project_name}/${issue.number} (${artifact.version_count} version${artifact.version_count === 1 ? "" : "s"})`
|
|
2048
2150
|
);
|
|
2049
2151
|
});
|
|
2050
2152
|
withCommon(
|
|
@@ -2059,8 +2161,8 @@ withCommon(
|
|
|
2059
2161
|
return console.log(`unpinned ${updated2.project_name}/#${updated2.number} \u2014 routing rules apply again`);
|
|
2060
2162
|
}
|
|
2061
2163
|
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,
|
|
2164
|
+
const { name: name2, tier } = parseTargetSpec(runnerSpec);
|
|
2165
|
+
const runner = await resolveRunner(api, name2);
|
|
2064
2166
|
const updated = await api.updateIssue(issue.id, {
|
|
2065
2167
|
pinned_runner_id: runner.id,
|
|
2066
2168
|
pinned_tier: tier ?? null
|
|
@@ -2357,12 +2459,12 @@ withCommon(
|
|
|
2357
2459
|
).action(
|
|
2358
2460
|
async (ref, markdown, opts, command) => {
|
|
2359
2461
|
if (helpGuard(command, markdown)) return;
|
|
2360
|
-
const
|
|
2462
|
+
const text2 = readBodyValue(markdown);
|
|
2361
2463
|
const api = client(opts);
|
|
2362
2464
|
const { scope, note, item } = await resolveJournal(api, ref, opts.state);
|
|
2363
2465
|
printNote(note);
|
|
2364
2466
|
if (item) {
|
|
2365
|
-
const updated = await api.appendContextItem(item.id, { text });
|
|
2467
|
+
const updated = await api.appendContextItem(item.id, { text: text2 });
|
|
2366
2468
|
if (opts.json) return printJson(updated);
|
|
2367
2469
|
return console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
|
|
2368
2470
|
}
|
|
@@ -2372,7 +2474,7 @@ withCommon(
|
|
|
2372
2474
|
name: JOURNAL_NAME,
|
|
2373
2475
|
project_id: scope.project_id ?? void 0,
|
|
2374
2476
|
workflow_state_id: scope.workflow_state_id ?? void 0,
|
|
2375
|
-
body:
|
|
2477
|
+
body: text2.trim()
|
|
2376
2478
|
});
|
|
2377
2479
|
if (opts.json) return printJson(created);
|
|
2378
2480
|
console.log(`started the ${scope.label} journal (${created.id})`);
|
|
@@ -2380,7 +2482,7 @@ withCommon(
|
|
|
2380
2482
|
if (!(err instanceof ApiError) || err.code !== "duplicate_context_name") throw err;
|
|
2381
2483
|
const { item: fresh } = await resolveJournal(api, ref, opts.state);
|
|
2382
2484
|
if (!fresh) throw err;
|
|
2383
|
-
const updated = await api.appendContextItem(fresh.id, { text });
|
|
2485
|
+
const updated = await api.appendContextItem(fresh.id, { text: text2 });
|
|
2384
2486
|
if (opts.json) return printJson(updated);
|
|
2385
2487
|
console.log(`appended to the ${scope.label} journal (now v${updated.version})`);
|
|
2386
2488
|
}
|
|
@@ -2561,13 +2663,13 @@ async function resolveRunner(api, ref) {
|
|
|
2561
2663
|
function parseTargetSpec(spec) {
|
|
2562
2664
|
const sep = spec.lastIndexOf(":");
|
|
2563
2665
|
if (sep === -1) return { name: spec };
|
|
2564
|
-
const
|
|
2666
|
+
const name2 = spec.slice(0, sep);
|
|
2565
2667
|
const tier = spec.slice(sep + 1);
|
|
2566
|
-
if (!
|
|
2668
|
+
if (!name2) die(`target must look like <runner>[:tier], got "${spec}"`);
|
|
2567
2669
|
if (!MODEL_TIERS.includes(tier)) {
|
|
2568
2670
|
die(`unknown tier "${tier}" in "${spec}" (tiers: ${MODEL_TIERS.join(", ")})`);
|
|
2569
2671
|
}
|
|
2570
|
-
return { name, tier };
|
|
2672
|
+
return { name: name2, tier };
|
|
2571
2673
|
}
|
|
2572
2674
|
function runnerStatusLabel(runner) {
|
|
2573
2675
|
if (runner.status === "paused") return "paused";
|
|
@@ -2805,14 +2907,6 @@ withCommon(
|
|
|
2805
2907
|
}
|
|
2806
2908
|
);
|
|
2807
2909
|
var runsCmd = program.command("runs").description("Agent runs: attempts at issues by runners");
|
|
2808
|
-
function runCostLabel(run) {
|
|
2809
|
-
const usage = run.usage;
|
|
2810
|
-
if (!usage) return "\u2014";
|
|
2811
|
-
if (usage.cost_usd !== void 0) return `$${usage.cost_usd.toFixed(2)}`;
|
|
2812
|
-
if (usage.cost_source === "none") return "unreported";
|
|
2813
|
-
const tokens = (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
|
|
2814
|
-
return tokens > 0 ? `${tokens.toLocaleString()} tok` : "\u2014";
|
|
2815
|
-
}
|
|
2816
2910
|
function runRow(run) {
|
|
2817
2911
|
return [
|
|
2818
2912
|
run.id,
|
|
@@ -2821,7 +2915,7 @@ function runRow(run) {
|
|
|
2821
2915
|
`${run.tier}${run.model ? ` (${run.model})` : ""}`,
|
|
2822
2916
|
run.status,
|
|
2823
2917
|
runDurationLabel(run),
|
|
2824
|
-
runCostLabel(run),
|
|
2918
|
+
runCostLabel(run) ?? "\u2014",
|
|
2825
2919
|
timestamp(run.created_at)
|
|
2826
2920
|
];
|
|
2827
2921
|
}
|
|
@@ -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
|
});
|