tines 0.0.208 → 0.0.210
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/index.js +312 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,8 @@ tines issues comment <project>/<number> - <<'EOF' # body from stdin; @file al
|
|
|
45
45
|
…
|
|
46
46
|
EOF
|
|
47
47
|
tines events list --issue <project>/<number>
|
|
48
|
+
tines events list --since 2026-09-01T00:00:00Z --until 2026-09-08T00:00:00Z --state Engineering/Review
|
|
49
|
+
tines supervisor stats --window 7d --project Tines
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
Issues are addressed as `<project>/<number>`; schedules as `<project>/<name>`; workflow
|
package/dist/index.js
CHANGED
|
@@ -3482,6 +3482,7 @@ function parsePrSpec(spec) {
|
|
|
3482
3482
|
return repoUrl2 ? { repo_url: repoUrl2, number: Number.parseInt(url[2], 10) } : null;
|
|
3483
3483
|
}
|
|
3484
3484
|
var MODEL_TIERS = ["smartest", "balanced", "cheapest"];
|
|
3485
|
+
var RUN_END_OUTCOMES = ["advanced", "stalled", "interrupted"];
|
|
3485
3486
|
var RUNNER_ONLINE_WINDOW_MS = 2 * 60 * 1e3;
|
|
3486
3487
|
var DEFAULT_RESUME_WINDOW_HOURS = 48;
|
|
3487
3488
|
var DEFAULT_RESUME_MAX_TURNS = 25;
|
|
@@ -3575,6 +3576,11 @@ function utilizationLabel(quota, activeRuns, stateName = (id2) => id2) {
|
|
|
3575
3576
|
([stateId, { name: name2, n }]) => `${name2} ${n}/${quota.overrides[stateId] ?? quota.default_limit}`
|
|
3576
3577
|
).join(" \xB7 ");
|
|
3577
3578
|
}
|
|
3579
|
+
var RUN_OUTCOME_BUCKETS = [
|
|
3580
|
+
...RUN_END_OUTCOMES,
|
|
3581
|
+
"failed",
|
|
3582
|
+
"unrecorded"
|
|
3583
|
+
];
|
|
3578
3584
|
var LIBRARY_MAX_BYTES = 5 * 1024 * 1024;
|
|
3579
3585
|
|
|
3580
3586
|
// ../shared/src/events.ts
|
|
@@ -4153,6 +4159,9 @@ function createApiClient(options) {
|
|
|
4153
4159
|
`/api/v1/runs${query(filters)}`
|
|
4154
4160
|
),
|
|
4155
4161
|
getUsage: (filters = {}) => get(`/api/v1/usage${query(filters)}`),
|
|
4162
|
+
getIssueUsage: (issue) => get(`/api/v1/usage${query({ mode: "issue", issue })}`),
|
|
4163
|
+
getUsageScope: (scope) => get(`/api/v1/usage${query({ scope })}`),
|
|
4164
|
+
getUsageEvidence: (filters) => get(`/api/v1/usage/evidence${query(filters)}`),
|
|
4156
4165
|
getRun: (id2) => get(`/api/v1/runs/${id2}`),
|
|
4157
4166
|
/**
|
|
4158
4167
|
* The run's complete log (not the 256 KB tail `getRun` returns) as a
|
|
@@ -4180,7 +4189,9 @@ function createApiClient(options) {
|
|
|
4180
4189
|
getPreferences: () => get("/api/v1/preferences"),
|
|
4181
4190
|
updatePreferences: (body) => request("PATCH", "/api/v1/preferences", body),
|
|
4182
4191
|
getSupervisorSettings: () => get("/api/v1/supervisor/settings"),
|
|
4183
|
-
getSupervisorQueue: () => get(
|
|
4192
|
+
getSupervisorQueue: (q = {}) => get(`/api/v1/supervisor/queue${query(q)}`),
|
|
4193
|
+
getSupervisorStats: (q = {}) => get(`/api/v1/supervisor/stats${query(q)}`),
|
|
4194
|
+
getSupervisorSentBack: (q) => get(`/api/v1/supervisor/stats/sent-back${query(q)}`),
|
|
4184
4195
|
updateSupervisorSettings: (body) => request("PUT", "/api/v1/supervisor/settings", body),
|
|
4185
4196
|
// API keys (create/revoke require a browser session, not a key)
|
|
4186
4197
|
listApiKeys: (filters = {}) => get(`/api/v1/api-keys${query(filters)}`),
|
|
@@ -4423,6 +4434,38 @@ async function listAll(fetchPage, opts = {}) {
|
|
|
4423
4434
|
// ../shared/src/routing.ts
|
|
4424
4435
|
var INHERIT_RUNNER_ID = "*";
|
|
4425
4436
|
|
|
4437
|
+
// ../shared/src/stats-format.ts
|
|
4438
|
+
var MIN = 6e4;
|
|
4439
|
+
var HOUR = 60 * MIN;
|
|
4440
|
+
var DAY = 24 * HOUR;
|
|
4441
|
+
function durationLabel(ms) {
|
|
4442
|
+
if (ms === null || ms === void 0) return "\u2014";
|
|
4443
|
+
if (ms < MIN) return `${Math.round(ms / 1e3)} s`;
|
|
4444
|
+
if (ms < HOUR) return `${Math.round(ms / MIN)} min`;
|
|
4445
|
+
if (ms < DAY) return `${(ms / HOUR).toFixed(1)} h`;
|
|
4446
|
+
return `${(ms / DAY).toFixed(1)} d`;
|
|
4447
|
+
}
|
|
4448
|
+
function shareLabel(share) {
|
|
4449
|
+
if (share === null || share === void 0) return "\u2014";
|
|
4450
|
+
return `${Math.round(share * 100)}%`;
|
|
4451
|
+
}
|
|
4452
|
+
function deltaLabel(delta, kind) {
|
|
4453
|
+
if (delta === null || delta === void 0) return "";
|
|
4454
|
+
if (delta === 0) return "=";
|
|
4455
|
+
const arrow = delta > 0 ? "\u25B2" : "\u25BC";
|
|
4456
|
+
const size = Math.abs(delta);
|
|
4457
|
+
switch (kind) {
|
|
4458
|
+
case "ms":
|
|
4459
|
+
return `${arrow} ${durationLabel(size)}`;
|
|
4460
|
+
case "share":
|
|
4461
|
+
return `${arrow} ${Math.round(size * 100)} pp`;
|
|
4462
|
+
case "ratio":
|
|
4463
|
+
return `${arrow} ${size.toFixed(1)}`;
|
|
4464
|
+
default:
|
|
4465
|
+
return `${arrow} ${size}`;
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
|
|
4426
4469
|
// ../shared/src/issue-transfer.ts
|
|
4427
4470
|
var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
4428
4471
|
function conflictsByKey(context2) {
|
|
@@ -8168,27 +8211,33 @@ function registerTime(program3) {
|
|
|
8168
8211
|
function registerEvents(program3) {
|
|
8169
8212
|
const events = program3.command("events").description("Read the activity log");
|
|
8170
8213
|
withList(
|
|
8171
|
-
events.command("list").description("List activity events, newest first").option("-i, --issue <ref>", "filter to one issue (<project>/<number>)").option("-p, --project <name>", "filter by project name or id").option("-t, --type <type>", "filter by event type (e.g. issue.transitioned)")
|
|
8172
|
-
).action(
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
opts,
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8214
|
+
events.command("list").description("List activity events, newest first").option("-i, --issue <ref>", "filter to one issue (<project>/<number>)").option("-p, --project <name>", "filter by project name or id").option("-t, --type <type>", "filter by event type (e.g. issue.transitioned)").option("--since <time>", "inclusive lower bound (ISO 8601 or epoch ms)").option("--until <time>", "exclusive upper bound (ISO 8601 or epoch ms)").option("--state <workflow/state>", "events referencing a workflow state")
|
|
8215
|
+
).action(
|
|
8216
|
+
async (opts) => {
|
|
8217
|
+
const api = client(opts);
|
|
8218
|
+
const issueId = opts.issue ? (await resolveIssue(api, opts.issue)).id : void 0;
|
|
8219
|
+
const stateId = opts.state ? (await resolveStateFlag(api, opts.state)).state.id : void 0;
|
|
8220
|
+
const res = await fetchList(
|
|
8221
|
+
opts,
|
|
8222
|
+
(page) => api.listEvents({
|
|
8223
|
+
issue: issueId,
|
|
8224
|
+
project: opts.project,
|
|
8225
|
+
type: opts.type,
|
|
8226
|
+
since: opts.since,
|
|
8227
|
+
until: opts.until,
|
|
8228
|
+
state: stateId,
|
|
8229
|
+
...page
|
|
8230
|
+
})
|
|
8231
|
+
);
|
|
8232
|
+
printList(res, opts, (items) => {
|
|
8233
|
+
if (items.length === 0) return console.log("no events");
|
|
8234
|
+
table([
|
|
8235
|
+
["WHEN", "ACTOR", "EVENT"],
|
|
8236
|
+
...items.map((ev) => [timestamp(ev.created_at), displayActor(ev), eventSummary(ev)])
|
|
8237
|
+
]);
|
|
8238
|
+
});
|
|
8239
|
+
}
|
|
8240
|
+
);
|
|
8192
8241
|
}
|
|
8193
8242
|
|
|
8194
8243
|
// src/commands/projects.ts
|
|
@@ -11009,9 +11058,11 @@ function usageAggregateLines(label, aggregate) {
|
|
|
11009
11058
|
function usageEvidenceLines(id2, dimensions, accounting) {
|
|
11010
11059
|
const dims = ["project", "workflow", "state", "outcome", "runner", "tier"].map((name2) => `${name2}=${dimensions[name2].name} [${dimensions[name2].id ?? "unknown"}]`).join(" \xB7 ");
|
|
11011
11060
|
const diagnostics = Object.entries(accounting.diagnostics).filter(([, count]) => count > 0).map(([name2, count]) => `${name2}=${count}`).join(" \xB7 ");
|
|
11061
|
+
const tokens = ["input_tokens", "output_tokens", "cache_read_tokens", "cache_write_tokens"].map((name2) => `${name2}=${present(accounting.tokens[name2])}`).join(" \xB7 ");
|
|
11012
11062
|
return [
|
|
11013
11063
|
`Evidence ${id2}: ${dims}`,
|
|
11014
11064
|
`Accounting ${id2}: ${accounting.status} \xB7 source ${accounting.source ?? "unavailable"} \xB7 exact cost ${accounting.cost_exact ?? "unavailable"}${accounting.pricing_reason ? ` \xB7 reason ${accounting.pricing_reason}` : ""}${diagnostics ? ` \xB7 diagnostics ${diagnostics}` : ""}`,
|
|
11065
|
+
`Tokens ${id2}: ${tokens} \xB7 invalid_tokens=${accounting.invalid_tokens.length ? accounting.invalid_tokens.join(",") : "none"}`,
|
|
11015
11066
|
...accounting.source === "calculated" ? [`Rate ${id2}: ${basisLine(accounting.basis)}`] : []
|
|
11016
11067
|
];
|
|
11017
11068
|
}
|
|
@@ -11829,10 +11880,26 @@ function queueFix(block) {
|
|
|
11829
11880
|
return null;
|
|
11830
11881
|
}
|
|
11831
11882
|
}
|
|
11883
|
+
function outcomeLabel(stats) {
|
|
11884
|
+
const o = stats.current.runs.outcomes;
|
|
11885
|
+
const parts = [`adv ${o.advanced}`, `stalled ${o.stalled}`, `failed ${o.failed}`];
|
|
11886
|
+
if (o.interrupted > 0) parts.push(`intr ${o.interrupted}`);
|
|
11887
|
+
if (o.unrecorded > 0) parts.push(`unrecorded ${o.unrecorded}`);
|
|
11888
|
+
if (stats.current.runs.active > 0) parts.push(`active ${stats.current.runs.active}`);
|
|
11889
|
+
return parts.join(" \xB7 ");
|
|
11890
|
+
}
|
|
11891
|
+
function statsLevers() {
|
|
11892
|
+
return [
|
|
11893
|
+
"levers:",
|
|
11894
|
+
" queue wait tines supervisor quota roster --state <wf>/<state>=<n>",
|
|
11895
|
+
" runs tines runs list --state <wf>/<state>",
|
|
11896
|
+
" sent back tines context show <wf>/<state> instructions"
|
|
11897
|
+
];
|
|
11898
|
+
}
|
|
11832
11899
|
function register10(program3) {
|
|
11833
11900
|
const supervisor = program3.command("supervisor").description("The automation kill switch, quota policy, and attempt limit");
|
|
11834
11901
|
withCommon(
|
|
11835
|
-
supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")
|
|
11902
|
+
supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners").option("--project <name-or-id>", "filter waiting work to one project")
|
|
11836
11903
|
).action(async (opts) => {
|
|
11837
11904
|
const api = client(opts);
|
|
11838
11905
|
const [settings, runnersRes, workflows, activeRunItems, queue] = await Promise.all([
|
|
@@ -11840,7 +11907,7 @@ function register10(program3) {
|
|
|
11840
11907
|
api.listRunners(),
|
|
11841
11908
|
api.listWorkflows(),
|
|
11842
11909
|
listAll((page) => api.listRuns({ active: true, ...page })),
|
|
11843
|
-
api.getSupervisorQueue()
|
|
11910
|
+
api.getSupervisorQueue({ project: opts.project })
|
|
11844
11911
|
]);
|
|
11845
11912
|
if (opts.json) {
|
|
11846
11913
|
return printJson({
|
|
@@ -11897,6 +11964,71 @@ function register10(program3) {
|
|
|
11897
11964
|
);
|
|
11898
11965
|
}
|
|
11899
11966
|
});
|
|
11967
|
+
withCommon(
|
|
11968
|
+
supervisor.command("stats").description("Per-stage flow this week: queue wait, work time, runs per visit, sent back").option("--window <window>", "Rolling window, e.g. 24h or 7d", "7d").option("--project <ref>", "Narrow to one project (id or name)").option("--sent-back <workflow/state>", "Show the issues behind one sent-back figure")
|
|
11969
|
+
).action(async (opts) => {
|
|
11970
|
+
const api = client(opts);
|
|
11971
|
+
const project = opts.project ? await resolveProject(api, opts.project) : null;
|
|
11972
|
+
if (opts.sentBack) {
|
|
11973
|
+
const { state } = await resolveStateFlag(api, opts.sentBack);
|
|
11974
|
+
const detail = await api.getSupervisorSentBack({
|
|
11975
|
+
state: state.id,
|
|
11976
|
+
window: opts.window,
|
|
11977
|
+
project: project?.id ?? void 0
|
|
11978
|
+
});
|
|
11979
|
+
if (opts.json) return printJson(detail);
|
|
11980
|
+
console.log(`sent back from ${detail.state.workflow_name}/${detail.state.name}`);
|
|
11981
|
+
if (detail.prompt)
|
|
11982
|
+
console.log(`prompt: ${detail.prompt.name} (current v${detail.prompt.current_version})`);
|
|
11983
|
+
if (detail.items.length === 0) return console.log("no issues sent back");
|
|
11984
|
+
for (const item of detail.items) {
|
|
11985
|
+
console.log(
|
|
11986
|
+
`${item.issue.project_name}/${item.issue.number} \u2192 ${item.to_state_name} \xB7 prompt ${item.prompt_version ? `v${item.prompt_version}` : "unknown"} \xB7 ${item.actor.user_name}`
|
|
11987
|
+
);
|
|
11988
|
+
console.log(` ${item.comment?.excerpt.split("\n")[0] ?? "no comment"}`);
|
|
11989
|
+
}
|
|
11990
|
+
return;
|
|
11991
|
+
}
|
|
11992
|
+
const report = await api.getSupervisorStats({
|
|
11993
|
+
window: opts.window,
|
|
11994
|
+
project: project?.id ?? void 0
|
|
11995
|
+
});
|
|
11996
|
+
if (opts.json) return printJson(report);
|
|
11997
|
+
if (report.states.length === 0) {
|
|
11998
|
+
console.log("No agent stage saw work in the last window.");
|
|
11999
|
+
return;
|
|
12000
|
+
}
|
|
12001
|
+
const windowLabel = opts.window ?? "7d";
|
|
12002
|
+
console.log(
|
|
12003
|
+
`this week (${windowLabel}${report.project ? `, project ${report.project.name}` : ""})${report.previous ? ` \u2014 vs the ${windowLabel} before` : ""}`
|
|
12004
|
+
);
|
|
12005
|
+
table([
|
|
12006
|
+
[" stage", "visits", "queue p50", "p90", "work p50", "runs/visit", "ended", "sent back"],
|
|
12007
|
+
...report.states.map((s) => [
|
|
12008
|
+
` ${s.workflow_name}/${s.state_name}`,
|
|
12009
|
+
`${s.current.visits}\xB7${s.current.exits} ${deltaLabel(s.delta.visits, "count")}`.trim(),
|
|
12010
|
+
`${durationLabel(s.current.queue_wait?.p50 ?? null)} ${deltaLabel(s.delta.queue_wait_p50, "ms")}`.trim(),
|
|
12011
|
+
durationLabel(s.current.queue_wait?.p90 ?? null),
|
|
12012
|
+
`${durationLabel(s.current.work?.p50 ?? null)} ${deltaLabel(s.delta.work_p50, "ms")}`.trim(),
|
|
12013
|
+
s.current.runs.per_visit === null ? "\u2014" : `${s.current.runs.per_visit.toFixed(1)} ${deltaLabel(s.delta.runs_per_visit, "ratio")}`.trim(),
|
|
12014
|
+
outcomeLabel(s),
|
|
12015
|
+
`${s.current.sent_back.count} of ${s.current.exits} (${shareLabel(s.current.sent_back.share)}) \xB7 agents ${s.current.sent_back.agent} ${deltaLabel(s.delta.sent_back_share, "share")}`.trim()
|
|
12016
|
+
])
|
|
12017
|
+
]);
|
|
12018
|
+
if (report.markers.length > 0) {
|
|
12019
|
+
console.log("changes:");
|
|
12020
|
+
for (const marker of report.markers) {
|
|
12021
|
+
console.log(` ${marker.label}`);
|
|
12022
|
+
for (const effect of marker.effects) {
|
|
12023
|
+
const state = report.states.find((row) => row.state_id === effect.state_id);
|
|
12024
|
+
console.log(
|
|
12025
|
+
` ${state?.state_name ?? effect.state_id}: since ${effect.after?.exits ?? 0} exits, ${shareLabel(effect.after?.sent_back_share)} sent back, queue ${durationLabel(effect.after?.queue_wait_p50)} \xB7 before ${effect.before?.exits ?? 0} exits, ${shareLabel(effect.before?.sent_back_share)} sent back, queue ${durationLabel(effect.before?.queue_wait_p50)}`
|
|
12026
|
+
);
|
|
12027
|
+
}
|
|
12028
|
+
}
|
|
12029
|
+
}
|
|
12030
|
+
for (const line of statsLevers()) console.log(line);
|
|
12031
|
+
});
|
|
11900
12032
|
withCommon(
|
|
11901
12033
|
supervisor.command("enable").description("Resume automation (the kill switch on)")
|
|
11902
12034
|
).action(async (opts) => {
|
|
@@ -12740,6 +12872,62 @@ function normalizeUrl(value) {
|
|
|
12740
12872
|
}
|
|
12741
12873
|
|
|
12742
12874
|
// src/commands/usage.ts
|
|
12875
|
+
function printEvidence(page) {
|
|
12876
|
+
console.log(
|
|
12877
|
+
`Usage evidence \xB7 ${page.kind} \xB7 ${page.population} \xB7 ${page.total_count} total \xB7 ${page.sort} ${page.direction}`
|
|
12878
|
+
);
|
|
12879
|
+
if (page.kind === "issues")
|
|
12880
|
+
table([
|
|
12881
|
+
["ISSUE", "SPEND", "COVERAGE", "RUNS"],
|
|
12882
|
+
...page.items.map((item) => [
|
|
12883
|
+
item.issue_ref ? `${item.issue_ref.project_name}/${item.issue_ref.number} ${item.issue_ref.title}` : item.issue_id ? `Unavailable issue (${item.issue_id})` : "Unknown issue",
|
|
12884
|
+
money(item.aggregate.cost_usd),
|
|
12885
|
+
item.aggregate.coverage,
|
|
12886
|
+
String(item.attempt_count)
|
|
12887
|
+
])
|
|
12888
|
+
]);
|
|
12889
|
+
else
|
|
12890
|
+
table([
|
|
12891
|
+
["RUN", "ISSUE", "SPEND", "AT"],
|
|
12892
|
+
...page.items.map((item) => {
|
|
12893
|
+
const run2 = item;
|
|
12894
|
+
return [
|
|
12895
|
+
run2.id,
|
|
12896
|
+
run2.issue_id,
|
|
12897
|
+
page.population === "pending" ? "Pending" : money(run2.usage_accounting?.cost ?? null),
|
|
12898
|
+
new Date(run2.ended_at ?? run2.created_at).toISOString()
|
|
12899
|
+
];
|
|
12900
|
+
})
|
|
12901
|
+
]);
|
|
12902
|
+
if (page.kind === "runs" && page.population === "finalized")
|
|
12903
|
+
for (const item of page.items) {
|
|
12904
|
+
if ("id" in item && "usage_accounting" in item && item.usage_dimensions)
|
|
12905
|
+
for (const line of usageEvidenceLines(
|
|
12906
|
+
item.id,
|
|
12907
|
+
item.usage_dimensions,
|
|
12908
|
+
item.usage_accounting
|
|
12909
|
+
))
|
|
12910
|
+
console.log(line);
|
|
12911
|
+
}
|
|
12912
|
+
console.log(
|
|
12913
|
+
`Matching total: ${money(page.matching_total.cost_usd)} \xB7 ${page.attempt_count} attempts \xB7 ${page.pending_count} pending`
|
|
12914
|
+
);
|
|
12915
|
+
if (page.next_cursor) console.log(`Next cursor: ${page.next_cursor}`);
|
|
12916
|
+
}
|
|
12917
|
+
function printIssueReport(report) {
|
|
12918
|
+
const { issue } = report;
|
|
12919
|
+
console.log(
|
|
12920
|
+
`Usage \xB7 ${issue.issue_ref ? `${issue.issue_ref.project_name}/${issue.issue_ref.number}` : issue.issue_id} \xB7 Lifetime through now`
|
|
12921
|
+
);
|
|
12922
|
+
console.log(`as of ${new Date(report.cutoff).toISOString()} \xB7 direct retained attempts`);
|
|
12923
|
+
if (issue.attempt_count === 0) console.log("No agent runs");
|
|
12924
|
+
else {
|
|
12925
|
+
console.log(
|
|
12926
|
+
`${money(issue.aggregate.cost_usd)} \xB7 ${issue.aggregate.coverage} \xB7 ${issue.aggregate.finalized_run_count} finalized \xB7 ${issue.pending_count} pending at cutoff`
|
|
12927
|
+
);
|
|
12928
|
+
for (const line of usageAggregateLines("Lifetime", issue.aggregate)) console.log(line);
|
|
12929
|
+
}
|
|
12930
|
+
}
|
|
12743
12931
|
var money = (value) => usageCostLabel(value);
|
|
12744
12932
|
function printReport(report) {
|
|
12745
12933
|
console.log(`Usage \xB7 ${report.filters.project ?? "All projects"} \xB7 ${report.by}`);
|
|
@@ -12798,7 +12986,12 @@ function register12(program3) {
|
|
|
12798
12986
|
withCommon(
|
|
12799
12987
|
program3.command("usage").description("Compare finalized run spend for a period").addOption(
|
|
12800
12988
|
new Option("--window <window>", "today, 7d, or 30d").choices(["today", "7d", "30d"])
|
|
12801
|
-
).option("--from <bound>", "custom inclusive start (date or offset timestamp)").option("--to <bound>", "custom exclusive end (date or offset timestamp)").option("--
|
|
12989
|
+
).option("--from <bound>", "custom inclusive start (date or offset timestamp)").option("--to <bound>", "custom exclusive end (date or offset timestamp)").option("--issue <ref>", "direct issue lifetime through now").option("--scope <token>", "replay a frozen usage scope").addOption(new Option("--evidence <kind>", "list evidence").choices(["issues", "runs"])).option("--member <issue-id>", "narrow run evidence to one contributing issue").addOption(
|
|
12990
|
+
new Option("--population <population>", "finalized or pending").choices([
|
|
12991
|
+
"finalized",
|
|
12992
|
+
"pending"
|
|
12993
|
+
])
|
|
12994
|
+
).addOption(new Option("--sort <sort>", "evidence sort").choices(["cost", "time"])).addOption(new Option("--direction <direction>", "sort direction").choices(["asc", "desc"])).option("--cursor <token>", "evidence page cursor").option("--limit <count>", "evidence page size (1-100)").option("--all-pages", "fetch every evidence page").option("--project <name-or-id>", "project scope (including archived), or unknown").option("--workflow <name-or-id>", "workflow filter, or unknown").option("--state <id>", "starting state id (requires --workflow)").option("--runner <name-or-id>", "runner filter, or unknown").option("--tier <tier>", "tier filter").addOption(
|
|
12802
12995
|
new Option("--outcome <outcome>", "recorded outcome").choices([
|
|
12803
12996
|
"advanced",
|
|
12804
12997
|
"stalled",
|
|
@@ -12814,7 +13007,100 @@ function register12(program3) {
|
|
|
12814
13007
|
).addOption(
|
|
12815
13008
|
new Option("--by <dimension>", "group dimension").choices(["project", "workflow", "state", "outcome", "runner", "tier"]).default("workflow")
|
|
12816
13009
|
)
|
|
12817
|
-
).action(async (opts) => {
|
|
13010
|
+
).action(async (opts, command) => {
|
|
13011
|
+
const evidenceOnly = [
|
|
13012
|
+
opts.member,
|
|
13013
|
+
opts.population,
|
|
13014
|
+
opts.sort,
|
|
13015
|
+
opts.direction,
|
|
13016
|
+
opts.cursor,
|
|
13017
|
+
opts.limit,
|
|
13018
|
+
opts.allPages
|
|
13019
|
+
];
|
|
13020
|
+
if (evidenceOnly.some((value) => value !== void 0) && !opts.evidence)
|
|
13021
|
+
throw new Error(
|
|
13022
|
+
"--member/--population/--sort/--direction/--cursor/--limit/--all-pages require --evidence"
|
|
13023
|
+
);
|
|
13024
|
+
if (opts.evidence && !opts.scope) throw new Error("--evidence requires --scope");
|
|
13025
|
+
if (opts.scope) {
|
|
13026
|
+
const reportFlags = [
|
|
13027
|
+
opts.issue,
|
|
13028
|
+
opts.window,
|
|
13029
|
+
opts.from,
|
|
13030
|
+
opts.to,
|
|
13031
|
+
opts.project,
|
|
13032
|
+
opts.workflow,
|
|
13033
|
+
opts.state,
|
|
13034
|
+
opts.runner,
|
|
13035
|
+
opts.tier,
|
|
13036
|
+
opts.outcome,
|
|
13037
|
+
opts.accountingStatus
|
|
13038
|
+
];
|
|
13039
|
+
if (command.getOptionValueSource("by") === "cli") reportFlags.push(opts.by);
|
|
13040
|
+
if (reportFlags.some((value) => value !== void 0))
|
|
13041
|
+
throw new Error("--scope cannot be combined with report options");
|
|
13042
|
+
const api2 = client(opts);
|
|
13043
|
+
if (!opts.evidence) {
|
|
13044
|
+
const report2 = await api2.getUsageScope(opts.scope);
|
|
13045
|
+
if (opts.json) return printJson(report2);
|
|
13046
|
+
if (report2.mode === "issue") printIssueReport(report2);
|
|
13047
|
+
else printReport(report2);
|
|
13048
|
+
return;
|
|
13049
|
+
}
|
|
13050
|
+
let cursor = opts.cursor;
|
|
13051
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
13052
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
13053
|
+
let combined = null;
|
|
13054
|
+
do {
|
|
13055
|
+
if (cursor && seenCursors.has(cursor))
|
|
13056
|
+
throw new Error("Evidence pagination repeated a cursor");
|
|
13057
|
+
if (cursor) seenCursors.add(cursor);
|
|
13058
|
+
const page = await api2.getUsageEvidence({
|
|
13059
|
+
scope: opts.scope,
|
|
13060
|
+
kind: opts.evidence,
|
|
13061
|
+
population: opts.population,
|
|
13062
|
+
member: opts.member,
|
|
13063
|
+
sort: opts.sort,
|
|
13064
|
+
direction: opts.direction,
|
|
13065
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
13066
|
+
cursor
|
|
13067
|
+
});
|
|
13068
|
+
for (const item of page.items) {
|
|
13069
|
+
const id2 = "id" in item ? item.id : item.issue_id ?? "unknown";
|
|
13070
|
+
if (seenIds.has(id2)) throw new Error(`Evidence pagination repeated ${id2}`);
|
|
13071
|
+
seenIds.add(id2);
|
|
13072
|
+
}
|
|
13073
|
+
combined = combined ? { ...page, items: [...combined.items, ...page.items], previous_cursor: null } : page;
|
|
13074
|
+
cursor = page.next_cursor ?? void 0;
|
|
13075
|
+
} while (opts.allPages && cursor);
|
|
13076
|
+
if (!combined) throw new Error("Evidence response was empty");
|
|
13077
|
+
if (opts.json) return printJson(combined);
|
|
13078
|
+
printEvidence(combined);
|
|
13079
|
+
return;
|
|
13080
|
+
}
|
|
13081
|
+
if (opts.issue) {
|
|
13082
|
+
const contradictions = [
|
|
13083
|
+
opts.window,
|
|
13084
|
+
opts.from,
|
|
13085
|
+
opts.to,
|
|
13086
|
+
opts.project,
|
|
13087
|
+
opts.workflow,
|
|
13088
|
+
opts.state,
|
|
13089
|
+
opts.runner,
|
|
13090
|
+
opts.tier,
|
|
13091
|
+
opts.outcome,
|
|
13092
|
+
opts.accountingStatus
|
|
13093
|
+
];
|
|
13094
|
+
if (command.getOptionValueSource("by") === "cli") contradictions.push(opts.by);
|
|
13095
|
+
if (contradictions.some((value) => value !== void 0))
|
|
13096
|
+
throw new Error("--issue cannot be combined with period or filter options");
|
|
13097
|
+
const api2 = client(opts);
|
|
13098
|
+
const issueId = isUsageIdentity(opts.issue, "iss") ? opts.issue : (await resolveIssue(api2, opts.issue)).id;
|
|
13099
|
+
const report2 = await api2.getIssueUsage(issueId);
|
|
13100
|
+
if (opts.json) return printJson(report2);
|
|
13101
|
+
printIssueReport(report2);
|
|
13102
|
+
return;
|
|
13103
|
+
}
|
|
12818
13104
|
if (opts.from === void 0 !== (opts.to === void 0))
|
|
12819
13105
|
throw new Error("--from and --to are required together");
|
|
12820
13106
|
if (opts.window && opts.from) throw new Error("--window cannot be combined with --from/--to");
|