tines 0.0.186 → 0.0.187

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 (2) hide show
  1. package/dist/index.js +174 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4139,7 +4139,10 @@ function createApiClient(options) {
4139
4139
  appendRunLog: (runId, body) => request("POST", `/api/v1/runs/${runId}/logs`, body),
4140
4140
  finishRun: (runId, body) => request("POST", `/api/v1/runs/${runId}/finish`, body),
4141
4141
  // Agent runs
4142
- listRuns: (filters = {}) => get(`/api/v1/runs${query(filters)}`),
4142
+ listRuns: (filters = {}) => get(
4143
+ `/api/v1/runs${query(filters)}`
4144
+ ),
4145
+ getUsage: (filters = {}) => get(`/api/v1/usage${query(filters)}`),
4143
4146
  getRun: (id2) => get(`/api/v1/runs/${id2}`),
4144
4147
  /**
4145
4148
  * The run's complete log (not the 256 KB tail `getRun` returns) as a
@@ -4540,6 +4543,20 @@ var schedule = shape({
4540
4543
  // ../shared/src/library/choices.ts
4541
4544
  var PACKAGE_CHOICES_MAX_BYTES = 512 * 1024;
4542
4545
 
4546
+ // ../shared/src/usage.ts
4547
+ function usageCostLabel(cost, finalized = 1) {
4548
+ if (finalized === 0) return "No runs";
4549
+ if (cost === null) return "Unknown";
4550
+ if (cost === 0) return "$0";
4551
+ if (cost < 0.01) return "<$0.01";
4552
+ return cost.toLocaleString("en-US", {
4553
+ style: "currency",
4554
+ currency: "USD",
4555
+ minimumFractionDigits: 2,
4556
+ maximumFractionDigits: 2
4557
+ });
4558
+ }
4559
+
4543
4560
  // src/format.ts
4544
4561
  function timestamp(ms) {
4545
4562
  return new Date(ms).toISOString().replace("T", " ").slice(0, 19);
@@ -9641,22 +9658,71 @@ function register8(program3) {
9641
9658
  });
9642
9659
  const runsCmd = program3.command("runs").description("Agent runs: attempts at issues by runners");
9643
9660
  withList(
9644
- runsCmd.command("list").description("List runs, newest first").option("-i, --issue <ref>", "filter to one issue (<project>/<number>)").option("-r, --runner <name>", "filter by runner name").option("--active", "only runs holding a claim (assigned/launching/running)")
9661
+ runsCmd.command("list").description("List runs, newest first").option("-i, --issue <ref>", "filter to one issue (<project>/<number>)").option("-r, --runner <name>", "filter by runner name").option("--active", "only runs holding a claim (assigned/launching/running)").addOption(
9662
+ new Option("--population <mode>", "period evidence population").choices([
9663
+ "finalized",
9664
+ "pending"
9665
+ ])
9666
+ ).option("--from <timestamp>", "inclusive period start").option("--to <timestamp>", "exclusive period cutoff").option("--project <name-or-id>", "project, including archived").option("--workflow <name-or-id>", "workflow").option("--state <id>", "starting state id").option("--tier <tier>", "model tier").addOption(
9667
+ new Option("--outcome <outcome>", "recorded outcome").choices([
9668
+ "advanced",
9669
+ "stalled",
9670
+ "interrupted",
9671
+ "unknown"
9672
+ ])
9673
+ ).addOption(
9674
+ new Option("--accounting-status <status>", "usage accounting status").choices([
9675
+ "priced",
9676
+ "unpriced",
9677
+ "unreported"
9678
+ ])
9679
+ )
9645
9680
  ).action(async (opts) => {
9646
9681
  const api = client(opts);
9682
+ const evidence = opts;
9683
+ if (evidence.from === void 0 !== (evidence.to === void 0))
9684
+ die("--from and --to are required together");
9685
+ if ((evidence.from || evidence.to) && !evidence.population)
9686
+ die("period filters require --population");
9647
9687
  const issueId = opts.issue ? (await resolveIssue(api, opts.issue)).id : void 0;
9648
9688
  const runnerId = opts.runner ? (await resolveRunner(api, opts.runner)).id : void 0;
9689
+ const projectId = evidence.project ? (await resolveProject(api, evidence.project)).id : void 0;
9690
+ const workflowId = evidence.workflow ? (await resolveWorkflow(api, evidence.workflow)).id : void 0;
9649
9691
  const res = await fetchList(
9650
9692
  opts,
9651
9693
  (page) => api.listRuns({
9652
9694
  issue: issueId,
9653
9695
  runner: runnerId,
9654
9696
  active: opts.active ? true : void 0,
9697
+ population: evidence.population,
9698
+ from: evidence.from,
9699
+ to: evidence.to,
9700
+ project: projectId,
9701
+ workflow: workflowId,
9702
+ state: evidence.state,
9703
+ tier: evidence.tier,
9704
+ outcome: evidence.outcome,
9705
+ accounting_status: evidence.accountingStatus,
9655
9706
  ...page
9656
9707
  })
9657
9708
  );
9658
9709
  printList(res, opts, (items) => {
9659
9710
  if (items.length === 0) return console.log(opts.active ? "no active runs" : "no runs");
9711
+ if (evidence.population === "pending") {
9712
+ table([
9713
+ ["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "COST", "CREATED"],
9714
+ ...items.map((run) => [
9715
+ run.id,
9716
+ run.issue_ref ? `${run.issue_ref.project_name}/${run.issue_ref.number}` : run.issue_id,
9717
+ run.runner_name,
9718
+ run.tier,
9719
+ "Pending at cutoff",
9720
+ "\u2014",
9721
+ timestamp(run.created_at)
9722
+ ])
9723
+ ]);
9724
+ return;
9725
+ }
9660
9726
  table([
9661
9727
  ["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "DURATION", "COST", "CREATED"],
9662
9728
  ...items.map(runRow)
@@ -10442,6 +10508,111 @@ see \`tines workflows create --help\` for the expected shape`
10442
10508
  });
10443
10509
  }
10444
10510
 
10511
+ // src/commands/usage.ts
10512
+ var money = (value) => usageCostLabel(value);
10513
+ function printReport(report) {
10514
+ console.log(`Usage \xB7 ${report.filters.project ?? "All projects"} \xB7 ${report.by}`);
10515
+ console.log(
10516
+ `${new Date(report.from).toISOString()} \u2014 ${new Date(report.to).toISOString()} \xB7 ${report.timezone} (${report.timezone_source.replace("_", " ")})`
10517
+ );
10518
+ console.log(
10519
+ `generated ${new Date(report.generated_at).toISOString()} \xB7 ${report.accounting_basis}`
10520
+ );
10521
+ const scope = report.scope_total;
10522
+ console.log(
10523
+ `Scope total: ${money(scope.cost_usd)} \xB7 ${scope.coverage} \xB7 ${scope.finalized_run_count} finalized \xB7 ${scope.priced_run_count} priced \xB7 ${scope.unpriced_run_count + scope.unreported_run_count} without price`
10524
+ );
10525
+ if (JSON.stringify(report.filters) !== JSON.stringify({ ...report.filters.project ? { project: report.filters.project } : {} })) {
10526
+ const matching = report.matching_total;
10527
+ console.log(
10528
+ `Matching subtotal: ${money(matching.cost_usd)} \xB7 ${matching.coverage} \xB7 ${matching.finalized_run_count} finalized`
10529
+ );
10530
+ }
10531
+ const total = report.matching_total;
10532
+ console.log(
10533
+ `Pending at cutoff: ${report.pending.matching_count}${report.pending.unapplied_filters.length ? ` (before ${report.pending.unapplied_filters.join("/")} filters)` : ""}`
10534
+ );
10535
+ if (report.groups.length === 0) return console.log("no finalized runs");
10536
+ table([
10537
+ ["GROUP", "SPEND", "COVERAGE", "FINAL", "PRICED", "MISSING", "MEDIAN", "P95", "MAX", "MEAN"],
10538
+ ...report.groups.map(({ dimension, aggregate: a }) => [
10539
+ dimension.name,
10540
+ money(a.cost_usd),
10541
+ a.coverage,
10542
+ String(a.finalized_run_count),
10543
+ String(a.priced_run_count),
10544
+ String(a.unpriced_run_count + a.unreported_run_count),
10545
+ money(a.distribution.median_cost_usd),
10546
+ money(a.distribution.p95_cost_usd),
10547
+ money(a.distribution.max_cost_usd),
10548
+ money(a.distribution.mean_cost_usd)
10549
+ ])
10550
+ ]);
10551
+ console.log("Per-run cost \xB7 priced subset; p95 uses nearest rank.");
10552
+ if (report.groups.some((g) => g.aggregate.distribution.low_sample))
10553
+ console.log("Small samples (under 20): p95 equals maximum.");
10554
+ console.log(
10555
+ `Matching tokens: ${Object.entries(total.tokens).map(
10556
+ ([name2, t]) => `${name2} ${t.value ?? "unknown"} (${t.reported_runs}/${total.finalized_run_count} runs)`
10557
+ ).join(" \xB7 ")}`
10558
+ );
10559
+ console.log(
10560
+ `Matching sources: provider ${total.portions.provider.cost_usd_exact} \xB7 calculated ${total.portions.calculated.cost_usd_exact} \xB7 unknown ${total.portions.unknown_source.cost_usd_exact}`
10561
+ );
10562
+ for (const portion of total.rate_portions) {
10563
+ const basis = portion.basis;
10564
+ console.log(
10565
+ basis ? `Rate: ${basis.rate_id} v${basis.rate_version} \xB7 ${basis.model} \xB7 ${basis.source_url}` : `Rate: historical calculated amount \xB7 basis unavailable`
10566
+ );
10567
+ }
10568
+ }
10569
+ function register12(program3) {
10570
+ withCommon(
10571
+ program3.command("usage").description("Compare finalized run spend for a period").addOption(
10572
+ new Option("--window <window>", "today, 7d, or 30d").choices(["today", "7d", "30d"])
10573
+ ).option("--from <bound>", "custom inclusive start (date or offset timestamp)").option("--to <bound>", "custom exclusive end (date or offset timestamp)").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(
10574
+ new Option("--outcome <outcome>", "recorded outcome").choices([
10575
+ "advanced",
10576
+ "stalled",
10577
+ "interrupted",
10578
+ "unknown"
10579
+ ])
10580
+ ).addOption(
10581
+ new Option("--accounting-status <status>", "price coverage").choices([
10582
+ "priced",
10583
+ "unpriced",
10584
+ "unreported"
10585
+ ])
10586
+ ).addOption(
10587
+ new Option("--by <dimension>", "group dimension").choices(["project", "workflow", "state", "outcome", "runner", "tier"]).default("workflow")
10588
+ )
10589
+ ).action(async (opts) => {
10590
+ if (opts.from === void 0 !== (opts.to === void 0))
10591
+ throw new Error("--from and --to are required together");
10592
+ if (opts.window && opts.from) throw new Error("--window cannot be combined with --from/--to");
10593
+ if (opts.state && !opts.workflow) throw new Error("--state requires --workflow");
10594
+ const api = client(opts);
10595
+ const project = opts.project && opts.project !== "unknown" ? (await resolveProject(api, opts.project)).id : opts.project;
10596
+ const workflow = opts.workflow && opts.workflow !== "unknown" ? (await resolveWorkflow(api, opts.workflow)).id : opts.workflow;
10597
+ const runner = opts.runner && opts.runner !== "unknown" ? (await resolveRunner(api, opts.runner)).id : opts.runner;
10598
+ const report = await api.getUsage({
10599
+ window: opts.window,
10600
+ from: opts.from,
10601
+ to: opts.to,
10602
+ project,
10603
+ workflow,
10604
+ state: opts.state,
10605
+ runner,
10606
+ tier: opts.tier,
10607
+ outcome: opts.outcome,
10608
+ accounting_status: opts.accountingStatus,
10609
+ by: opts.by
10610
+ });
10611
+ if (opts.json) return printJson(report);
10612
+ printReport(report);
10613
+ });
10614
+ }
10615
+
10445
10616
  // src/program.ts
10446
10617
  var program2 = new Command();
10447
10618
  program2.name("tines").description("CLI for Tines").version(cliVersion()).enablePositionalOptions();
@@ -10457,6 +10628,7 @@ register9(program2);
10457
10628
  register8(program2);
10458
10629
  register7(program2);
10459
10630
  register10(program2);
10631
+ register12(program2);
10460
10632
  registerEvents(program2);
10461
10633
 
10462
10634
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.186",
3
+ "version": "0.0.187",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",