tines 0.0.211 → 0.0.213

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.
Files changed (3) hide show
  1. package/README.md +7 -1
  2. package/dist/index.js +134 -18
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -46,12 +46,18 @@ tines issues comment <project>/<number> - <<'EOF' # body from stdin; @file al
46
46
  EOF
47
47
  tines events list --issue <project>/<number>
48
48
  tines events list --since 2026-09-01T00:00:00Z --until 2026-09-08T00:00:00Z --state Engineering/Review
49
+ tines events list --project Tines --all-pages --max-items 20000 --json
49
50
  tines supervisor stats --window 7d --project Tines
50
51
  ```
51
52
 
52
53
  Issues are addressed as `<project>/<number>`; schedules as `<project>/<name>`; workflow
53
54
  states as `<workflow>/<state>`. Every `list` command returns one page — add `--all-pages`
54
- for the whole list — and every command takes `--json` for machine-readable output.
55
+ for the whole list — and every command takes `--json` for machine-readable output. Complete
56
+ list walks have a default 10,000-item safety ceiling. Use `--max-items <n>` with
57
+ `--all-pages` to choose a different positive finite bound; exceeding it fails without
58
+ printing a partial result. `--limit` remains the per-request page size. A larger bound keeps
59
+ more output in memory and makes more requests, so increase it deliberately or narrow the
60
+ list's filters.
55
61
  `tines <noun> --help` lists the rest: `workflows`, `context`, `journal`, `schedules`,
56
62
  `runners`, `runs`, `routing`, `supervisor`.
57
63
 
package/dist/index.js CHANGED
@@ -4160,6 +4160,7 @@ function createApiClient(options) {
4160
4160
  ),
4161
4161
  getUsage: (filters = {}) => get(`/api/v1/usage${query(filters)}`),
4162
4162
  getIssueUsage: (issue) => get(`/api/v1/usage${query({ mode: "issue", issue })}`),
4163
+ getCohortUsage: (filters) => get(`/api/v1/usage${query({ mode: "cohort", ...filters })}`),
4163
4164
  getUsageScope: (scope) => get(`/api/v1/usage${query({ scope })}`),
4164
4165
  getUsageEvidence: (filters) => get(`/api/v1/usage/evidence${query(filters)}`),
4165
4166
  getRun: (id2) => get(`/api/v1/runs/${id2}`),
@@ -5904,23 +5905,39 @@ function withCommon(cmd) {
5904
5905
  ).option("--api-key <key>", "API key (or set TINES_API_KEY, or run `tines login`)").option("--json", "output the raw JSON response");
5905
5906
  }
5906
5907
  function withList(cmd) {
5907
- return withCommon(
5908
- cmd.option(
5909
- "--limit <n>",
5910
- "maximum items to return (page size under --all-pages)",
5911
- parsePositiveInteger
5912
- ).option("--cursor <cursor>", "resume from the next_cursor of a previous page").addOption(
5913
- new Option(
5914
- "--all-pages",
5915
- "fetch every page, not just the first (slower on large lists)"
5916
- ).conflicts("cursor")
5917
- )
5918
- );
5908
+ const listCommand = cmd.option(
5909
+ "--limit <n>",
5910
+ "maximum items to return (page size under --all-pages)",
5911
+ parsePositiveInteger
5912
+ ).option("--cursor <cursor>", "resume from the next_cursor of a previous page").addOption(
5913
+ new Option(
5914
+ "--all-pages",
5915
+ "fetch every page, not just the first (default safety ceiling: 10000 items)"
5916
+ ).conflicts("cursor")
5917
+ ).option(
5918
+ "--max-items <n>",
5919
+ "safety ceiling for --all-pages (default 10000; exceeding it errors)",
5920
+ parseMaxItems
5921
+ ).hook("preAction", (_thisCommand, actionCommand) => {
5922
+ const opts = actionCommand.opts();
5923
+ if (opts.maxItems !== void 0 && !opts.allPages) {
5924
+ actionCommand.error("--max-items requires --all-pages");
5925
+ }
5926
+ });
5927
+ return withCommon(listCommand);
5919
5928
  }
5920
5929
  function parsePositiveInteger(value) {
5921
- if (!/^[1-9]\d*$/.test(value)) throw new Error("limit must be a positive integer");
5930
+ return parsePositiveIntegerOption(value, "limit");
5931
+ }
5932
+ function parseMaxItems(value) {
5933
+ return parsePositiveIntegerOption(value, "max-items");
5934
+ }
5935
+ function parsePositiveIntegerOption(value, optionName) {
5936
+ if (!/^[1-9]\d*$/.test(value)) throw new Error(`${optionName} must be a positive integer`);
5922
5937
  const result = Number(value);
5923
- if (!Number.isSafeInteger(result)) throw new Error("limit must be a positive safe integer");
5938
+ if (!Number.isSafeInteger(result)) {
5939
+ throw new Error(`${optionName} must be a positive safe integer`);
5940
+ }
5924
5941
  return result;
5925
5942
  }
5926
5943
  function isUsageIdentity(value, prefix) {
@@ -5999,7 +6016,10 @@ more results: rerun with --all-pages, or resume with --cursor ${res.next_cursor}
5999
6016
  }
6000
6017
  async function fetchList(opts, fetchPage) {
6001
6018
  if (opts.allPages) {
6002
- return { items: await listAll(fetchPage, { pageSize: opts.limit }), next_cursor: null };
6019
+ return {
6020
+ items: await listAll(fetchPage, { pageSize: opts.limit, maxItems: opts.maxItems }),
6021
+ next_cursor: null
6022
+ };
6003
6023
  }
6004
6024
  const res = await fetchPage({ limit: opts.limit, cursor: opts.cursor });
6005
6025
  if (res.next_cursor && opts.json && !opts.cursor) {
@@ -12872,6 +12892,7 @@ function normalizeUrl(value) {
12872
12892
  }
12873
12893
 
12874
12894
  // src/commands/usage.ts
12895
+ var collect3 = (value, previous = []) => [...previous, value];
12875
12896
  function printEvidence(page) {
12876
12897
  console.log(
12877
12898
  `Usage evidence \xB7 ${page.kind} \xB7 ${page.population} \xB7 ${page.total_count} total \xB7 ${page.sort} ${page.direction}`
@@ -12886,6 +12907,17 @@ function printEvidence(page) {
12886
12907
  String(item.attempt_count)
12887
12908
  ])
12888
12909
  ]);
12910
+ else if (page.kind === "entries")
12911
+ table([
12912
+ ["EVENT", "ISSUE", "STATE", "AT", "EVIDENCE"],
12913
+ ...page.items.map((item) => [
12914
+ item.event_id,
12915
+ item.issue_id ?? "Unavailable issue",
12916
+ item.state_name ?? item.state_id ?? "Unclassifiable",
12917
+ new Date(item.created_at).toISOString(),
12918
+ item.chosen ? "Chosen completion" : item.reopening_relevant ? "Reopening witness" : item.qualifies ? "Qualifying entry" : item.unavailable_reason ?? "Excluded"
12919
+ ])
12920
+ ]);
12889
12921
  else
12890
12922
  table([
12891
12923
  ["RUN", "ISSUE", "SPEND", "AT"],
@@ -12912,6 +12944,18 @@ function printEvidence(page) {
12912
12944
  console.log(
12913
12945
  `Matching total: ${money(page.matching_total.cost_usd)} \xB7 ${page.attempt_count} attempts \xB7 ${page.pending_count} pending`
12914
12946
  );
12947
+ if (page.counters && page.history && page.from !== void 0 && page.to !== void 0 && page.observed_through !== void 0) {
12948
+ console.log(
12949
+ `Cohort selection: ${page.counters.distinct_issue_count} completed \xB7 ${page.counters.attempt_count} attempts \xB7 ${page.counters.pending_count} pending`
12950
+ );
12951
+ console.log(
12952
+ `Entries [${new Date(page.from).toISOString()}, ${new Date(page.to).toISOString()}) \xB7 history ${page.history.status} \xB7 observed through ${new Date(page.observed_through).toISOString()}`
12953
+ );
12954
+ if (page.parent_counters)
12955
+ console.log(
12956
+ `Parent cohort: ${page.parent_counters.distinct_issue_count} completed \xB7 ${money(page.parent_matching_total?.cost_usd ?? null)}`
12957
+ );
12958
+ }
12915
12959
  if (page.next_cursor) console.log(`Next cursor: ${page.next_cursor}`);
12916
12960
  }
12917
12961
  function printIssueReport(report) {
@@ -12928,6 +12972,35 @@ function printIssueReport(report) {
12928
12972
  for (const line of usageAggregateLines("Lifetime", issue.aggregate)) console.log(line);
12929
12973
  }
12930
12974
  }
12975
+ function printCohortReport(report) {
12976
+ console.log(`Completed issues \xB7 ${report.workflow.name}`);
12977
+ console.log(
12978
+ `${new Date(report.from).toISOString()} \u2014 ${new Date(report.to).toISOString()} \xB7 costs through exclusive cutoff`
12979
+ );
12980
+ console.log(
12981
+ `Terminal states: ${report.selected_states.map((state) => state.name).join(", ") || "None available"}`
12982
+ );
12983
+ console.log("Completed-issue costs are not additive to period spend.");
12984
+ console.log(
12985
+ `${report.counters.distinct_issue_count} issues \xB7 ${report.counters.attempt_count}/${report.counters.distinct_issue_count} attempts/all issues \xB7 ${money(report.counters.known_cost_per_issue.value_usd)} known USD/all issues`
12986
+ );
12987
+ console.log(
12988
+ `${report.counters.priced_run_coverage.numerator}/${report.counters.priced_run_coverage.denominator} priced finalized runs \xB7 ${report.counters.fully_priced_issue_count}/${report.counters.distinct_issue_count} fully priced issues \xB7 ${report.counters.pending_count} pending \xB7 ${report.counters.zero_run_issue_count} no-run`
12989
+ );
12990
+ console.log(
12991
+ `${report.counters.reopened_issue_count} reopened \xB7 ${report.counters.reopening_history_unavailable_issue_count} reopening unknown \xB7 observed through ${new Date(report.observed_through).toISOString()}`
12992
+ );
12993
+ console.log(
12994
+ `History: ${report.history.status} \xB7 ${report.history.qualifying_fact_count} qualifying entries`
12995
+ );
12996
+ for (const state of report.terminal_states)
12997
+ console.log(
12998
+ `${state.state.name}: ${state.counters.distinct_issue_count} completed \xB7 ${money(state.aggregate.cost_usd)} known`
12999
+ );
13000
+ if (report.counters.distinct_issue_count === 0)
13001
+ console.log("No completed issues in available history");
13002
+ if (report.scope) console.log(`Scope: ${report.scope}`);
13003
+ }
12931
13004
  var money = (value) => usageCostLabel(value);
12932
13005
  function printReport(report) {
12933
13006
  console.log(`Usage \xB7 ${report.filters.project ?? "All projects"} \xB7 ${report.by}`);
@@ -12986,8 +13059,15 @@ function register12(program3) {
12986
13059
  withCommon(
12987
13060
  program3.command("usage").description("Compare finalized run spend for a period").addOption(
12988
13061
  new Option("--window <window>", "today, 7d, or 30d").choices(["today", "7d", "30d"])
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([
13062
+ ).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("--cohort", "report issues completed in the selected workflow and window").option(
13063
+ "--done-state <id>",
13064
+ "terminal state id (repeatable; defaults to all done states)",
13065
+ collect3
13066
+ ).option("--scope <token>", "replay a frozen usage scope").addOption(
13067
+ new Option("--evidence <kind>", "list evidence").choices(["issues", "runs", "entries"])
13068
+ ).option("--member <issue-id>", "narrow run evidence to one contributing issue").addOption(
13069
+ new Option("--population <population>", "all, finalized, or pending").choices([
13070
+ "all",
12991
13071
  "finalized",
12992
13072
  "pending"
12993
13073
  ])
@@ -13025,6 +13105,8 @@ function register12(program3) {
13025
13105
  if (opts.scope) {
13026
13106
  const reportFlags = [
13027
13107
  opts.issue,
13108
+ opts.cohort,
13109
+ opts.doneState,
13028
13110
  opts.window,
13029
13111
  opts.from,
13030
13112
  opts.to,
@@ -13044,6 +13126,7 @@ function register12(program3) {
13044
13126
  const report2 = await api2.getUsageScope(opts.scope);
13045
13127
  if (opts.json) return printJson(report2);
13046
13128
  if (report2.mode === "issue") printIssueReport(report2);
13129
+ else if (report2.mode === "cohort") printCohortReport(report2);
13047
13130
  else printReport(report2);
13048
13131
  return;
13049
13132
  }
@@ -13066,7 +13149,7 @@ function register12(program3) {
13066
13149
  cursor
13067
13150
  });
13068
13151
  for (const item of page.items) {
13069
- const id2 = "id" in item ? item.id : item.issue_id ?? "unknown";
13152
+ const id2 = "event_id" in item ? item.event_id : "id" in item ? item.id : item.issue_id ?? "unknown";
13070
13153
  if (seenIds.has(id2)) throw new Error(`Evidence pagination repeated ${id2}`);
13071
13154
  seenIds.add(id2);
13072
13155
  }
@@ -13080,6 +13163,8 @@ function register12(program3) {
13080
13163
  }
13081
13164
  if (opts.issue) {
13082
13165
  const contradictions = [
13166
+ opts.cohort,
13167
+ opts.doneState,
13083
13168
  opts.window,
13084
13169
  opts.from,
13085
13170
  opts.to,
@@ -13101,6 +13186,37 @@ function register12(program3) {
13101
13186
  printIssueReport(report2);
13102
13187
  return;
13103
13188
  }
13189
+ if (opts.doneState?.length && !opts.cohort) throw new Error("--done-state requires --cohort");
13190
+ if (opts.cohort) {
13191
+ if (!opts.workflow) throw new Error("--cohort requires --workflow");
13192
+ if ([
13193
+ opts.issue,
13194
+ opts.scope,
13195
+ opts.state,
13196
+ opts.runner,
13197
+ opts.tier,
13198
+ opts.outcome,
13199
+ opts.accountingStatus
13200
+ ].some((value) => value !== void 0) || command.getOptionValueSource("by") === "cli")
13201
+ throw new Error("--cohort cannot be combined with issue, scope, grouping, or run filters");
13202
+ if (opts.from === void 0 !== (opts.to === void 0))
13203
+ throw new Error("--from and --to are required together");
13204
+ if (opts.window && opts.from) throw new Error("--window cannot be combined with --from/--to");
13205
+ const api2 = client(opts);
13206
+ const project2 = opts.project && !isUsageIdentity(opts.project, "prj") ? (await resolveProject(api2, opts.project)).id : opts.project;
13207
+ const workflow3 = !isUsageIdentity(opts.workflow, "wf") ? (await resolveWorkflow(api2, opts.workflow)).id : opts.workflow;
13208
+ const report2 = await api2.getCohortUsage({
13209
+ workflow: workflow3,
13210
+ project: project2,
13211
+ window: opts.window,
13212
+ from: opts.from,
13213
+ to: opts.to,
13214
+ done_state: opts.doneState
13215
+ });
13216
+ if (opts.json) return printJson(report2);
13217
+ printCohortReport(report2);
13218
+ return;
13219
+ }
13104
13220
  if (opts.from === void 0 !== (opts.to === void 0))
13105
13221
  throw new Error("--from and --to are required together");
13106
13222
  if (opts.window && opts.from) throw new Error("--window cannot be combined with --from/--to");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.211",
3
+ "version": "0.0.213",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",