tines 0.0.186 → 0.0.188

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 +229 -14
  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
@@ -4410,6 +4413,51 @@ async function listAll(fetchPage, opts = {}) {
4410
4413
  // ../shared/src/routing.ts
4411
4414
  var INHERIT_RUNNER_ID = "*";
4412
4415
 
4416
+ // ../shared/src/issue-transfer.ts
4417
+ var compare = (a, b) => a < b ? -1 : a > b ? 1 : 0;
4418
+ function conflictsByKey(context) {
4419
+ const repositories = new Map(context.repos.map((repo) => [repo.item_id, repo]));
4420
+ return new Map(
4421
+ context.conflicts.map((conflict) => {
4422
+ const ids = [...new Set(conflict.item_ids)].sort(compare);
4423
+ const key = JSON.stringify([conflict.dir, ids]);
4424
+ return [
4425
+ key,
4426
+ {
4427
+ dir: conflict.dir,
4428
+ participants: ids.map((item_id) => {
4429
+ const repo = repositories.get(item_id);
4430
+ return {
4431
+ item_id,
4432
+ name: repo?.name ?? null,
4433
+ scope_label: repo?.scope.label ?? null
4434
+ };
4435
+ })
4436
+ }
4437
+ ];
4438
+ })
4439
+ );
4440
+ }
4441
+ function deriveTransferConflictDeltas(before, after) {
4442
+ const beforeByKey = conflictsByKey(before);
4443
+ const afterByKey = conflictsByKey(after);
4444
+ const keys = /* @__PURE__ */ new Set([...beforeByKey.keys(), ...afterByKey.keys()]);
4445
+ const rank = { retained: 0, resolved: 1, introduced: 2 };
4446
+ return [...keys].map((key) => {
4447
+ const oldConflict = beforeByKey.get(key);
4448
+ const newConflict = afterByKey.get(key);
4449
+ return {
4450
+ key,
4451
+ change: oldConflict && newConflict ? "retained" : oldConflict ? "resolved" : "introduced",
4452
+ dir: (oldConflict ?? newConflict).dir,
4453
+ before: oldConflict?.participants ?? [],
4454
+ after: newConflict?.participants ?? []
4455
+ };
4456
+ }).sort(
4457
+ (a, b) => rank[a.change] - rank[b.change] || compare(a.dir, b.dir) || compare(a.key, b.key)
4458
+ );
4459
+ }
4460
+
4413
4461
  // ../shared/src/library/types.ts
4414
4462
  var LibraryValidationError = class extends Error {
4415
4463
  constructor(diagnostics) {
@@ -4540,6 +4588,20 @@ var schedule = shape({
4540
4588
  // ../shared/src/library/choices.ts
4541
4589
  var PACKAGE_CHOICES_MAX_BYTES = 512 * 1024;
4542
4590
 
4591
+ // ../shared/src/usage.ts
4592
+ function usageCostLabel(cost, finalized = 1) {
4593
+ if (finalized === 0) return "No runs";
4594
+ if (cost === null) return "Unknown";
4595
+ if (cost === 0) return "$0";
4596
+ if (cost < 0.01) return "<$0.01";
4597
+ return cost.toLocaleString("en-US", {
4598
+ style: "currency",
4599
+ currency: "USD",
4600
+ minimumFractionDigits: 2,
4601
+ maximumFractionDigits: 2
4602
+ });
4603
+ }
4604
+
4543
4605
  // src/format.ts
4544
4606
  function timestamp(ms) {
4545
4607
  return new Date(ms).toISOString().replace("T", " ").slice(0, 19);
@@ -5380,6 +5442,7 @@ function routingLines(preview) {
5380
5442
  lines.push(
5381
5443
  ` ${check.ok ? "pass" : "FAIL"} ${check.name}: ${check.detail}${check.action?.label ? ` (${check.action.label})` : ""}`
5382
5444
  );
5445
+ if (check.action?.cli) lines.push(` fix: ${check.action.cli}`);
5383
5446
  }
5384
5447
  if (explainer.matched_rule)
5385
5448
  lines.push(` matched rule ${explainer.matched_rule.scope_label}`);
@@ -5437,18 +5500,15 @@ function effectiveRepositoryLines(preview) {
5437
5500
  }
5438
5501
  function conflictLines(preview) {
5439
5502
  const lines = ["", "Repository checkout conflicts:"];
5440
- for (const [label, context] of [
5441
- [" before", preview.context.before],
5442
- [" after ", preview.context.after]
5443
- ]) {
5444
- if (!context.conflicts.length) lines.push(`${label}: none`);
5445
- for (const conflict of context.conflicts) {
5446
- const participants = conflict.item_ids.map((id2) => {
5447
- const repo = context.repos.find((item) => item.item_id === id2);
5448
- return repo ? `${repo.name} (${repo.scope.label})` : id2;
5449
- }).join(", ");
5450
- lines.push(`${label}: ${conflict.dir} \u2014 ${participants}`);
5451
- }
5503
+ const deltas = deriveTransferConflictDeltas(preview.context.before, preview.context.after);
5504
+ if (!deltas.length) return [...lines, " none"];
5505
+ const participants = (items) => items.map(
5506
+ (item) => item.name && item.scope_label ? `${item.name} (${item.scope_label})` : item.item_id
5507
+ ).join(", ");
5508
+ for (const delta of deltas) {
5509
+ lines.push(` ${delta.change[0].toUpperCase()}${delta.change.slice(1)} \u2014 ${delta.dir}`);
5510
+ if (delta.before.length) lines.push(` Before: ${participants(delta.before)}`);
5511
+ if (delta.after.length) lines.push(` After: ${participants(delta.after)}`);
5452
5512
  }
5453
5513
  return lines;
5454
5514
  }
@@ -9641,22 +9701,71 @@ function register8(program3) {
9641
9701
  });
9642
9702
  const runsCmd = program3.command("runs").description("Agent runs: attempts at issues by runners");
9643
9703
  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)")
9704
+ 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(
9705
+ new Option("--population <mode>", "period evidence population").choices([
9706
+ "finalized",
9707
+ "pending"
9708
+ ])
9709
+ ).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(
9710
+ new Option("--outcome <outcome>", "recorded outcome").choices([
9711
+ "advanced",
9712
+ "stalled",
9713
+ "interrupted",
9714
+ "unknown"
9715
+ ])
9716
+ ).addOption(
9717
+ new Option("--accounting-status <status>", "usage accounting status").choices([
9718
+ "priced",
9719
+ "unpriced",
9720
+ "unreported"
9721
+ ])
9722
+ )
9645
9723
  ).action(async (opts) => {
9646
9724
  const api = client(opts);
9725
+ const evidence = opts;
9726
+ if (evidence.from === void 0 !== (evidence.to === void 0))
9727
+ die("--from and --to are required together");
9728
+ if ((evidence.from || evidence.to) && !evidence.population)
9729
+ die("period filters require --population");
9647
9730
  const issueId = opts.issue ? (await resolveIssue(api, opts.issue)).id : void 0;
9648
9731
  const runnerId = opts.runner ? (await resolveRunner(api, opts.runner)).id : void 0;
9732
+ const projectId = evidence.project ? (await resolveProject(api, evidence.project)).id : void 0;
9733
+ const workflowId = evidence.workflow ? (await resolveWorkflow(api, evidence.workflow)).id : void 0;
9649
9734
  const res = await fetchList(
9650
9735
  opts,
9651
9736
  (page) => api.listRuns({
9652
9737
  issue: issueId,
9653
9738
  runner: runnerId,
9654
9739
  active: opts.active ? true : void 0,
9740
+ population: evidence.population,
9741
+ from: evidence.from,
9742
+ to: evidence.to,
9743
+ project: projectId,
9744
+ workflow: workflowId,
9745
+ state: evidence.state,
9746
+ tier: evidence.tier,
9747
+ outcome: evidence.outcome,
9748
+ accounting_status: evidence.accountingStatus,
9655
9749
  ...page
9656
9750
  })
9657
9751
  );
9658
9752
  printList(res, opts, (items) => {
9659
9753
  if (items.length === 0) return console.log(opts.active ? "no active runs" : "no runs");
9754
+ if (evidence.population === "pending") {
9755
+ table([
9756
+ ["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "COST", "CREATED"],
9757
+ ...items.map((run) => [
9758
+ run.id,
9759
+ run.issue_ref ? `${run.issue_ref.project_name}/${run.issue_ref.number}` : run.issue_id,
9760
+ run.runner_name,
9761
+ run.tier,
9762
+ "Pending at cutoff",
9763
+ "\u2014",
9764
+ timestamp(run.created_at)
9765
+ ])
9766
+ ]);
9767
+ return;
9768
+ }
9660
9769
  table([
9661
9770
  ["ID", "ISSUE", "RUNNER", "TIER", "STATUS", "DURATION", "COST", "CREATED"],
9662
9771
  ...items.map(runRow)
@@ -10442,6 +10551,111 @@ see \`tines workflows create --help\` for the expected shape`
10442
10551
  });
10443
10552
  }
10444
10553
 
10554
+ // src/commands/usage.ts
10555
+ var money = (value) => usageCostLabel(value);
10556
+ function printReport(report) {
10557
+ console.log(`Usage \xB7 ${report.filters.project ?? "All projects"} \xB7 ${report.by}`);
10558
+ console.log(
10559
+ `${new Date(report.from).toISOString()} \u2014 ${new Date(report.to).toISOString()} \xB7 ${report.timezone} (${report.timezone_source.replace("_", " ")})`
10560
+ );
10561
+ console.log(
10562
+ `generated ${new Date(report.generated_at).toISOString()} \xB7 ${report.accounting_basis}`
10563
+ );
10564
+ const scope = report.scope_total;
10565
+ console.log(
10566
+ `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`
10567
+ );
10568
+ if (JSON.stringify(report.filters) !== JSON.stringify({ ...report.filters.project ? { project: report.filters.project } : {} })) {
10569
+ const matching = report.matching_total;
10570
+ console.log(
10571
+ `Matching subtotal: ${money(matching.cost_usd)} \xB7 ${matching.coverage} \xB7 ${matching.finalized_run_count} finalized`
10572
+ );
10573
+ }
10574
+ const total = report.matching_total;
10575
+ console.log(
10576
+ `Pending at cutoff: ${report.pending.matching_count}${report.pending.unapplied_filters.length ? ` (before ${report.pending.unapplied_filters.join("/")} filters)` : ""}`
10577
+ );
10578
+ if (report.groups.length === 0) return console.log("no finalized runs");
10579
+ table([
10580
+ ["GROUP", "SPEND", "COVERAGE", "FINAL", "PRICED", "MISSING", "MEDIAN", "P95", "MAX", "MEAN"],
10581
+ ...report.groups.map(({ dimension, aggregate: a }) => [
10582
+ dimension.name,
10583
+ money(a.cost_usd),
10584
+ a.coverage,
10585
+ String(a.finalized_run_count),
10586
+ String(a.priced_run_count),
10587
+ String(a.unpriced_run_count + a.unreported_run_count),
10588
+ money(a.distribution.median_cost_usd),
10589
+ money(a.distribution.p95_cost_usd),
10590
+ money(a.distribution.max_cost_usd),
10591
+ money(a.distribution.mean_cost_usd)
10592
+ ])
10593
+ ]);
10594
+ console.log("Per-run cost \xB7 priced subset; p95 uses nearest rank.");
10595
+ if (report.groups.some((g) => g.aggregate.distribution.low_sample))
10596
+ console.log("Small samples (under 20): p95 equals maximum.");
10597
+ console.log(
10598
+ `Matching tokens: ${Object.entries(total.tokens).map(
10599
+ ([name2, t]) => `${name2} ${t.value ?? "unknown"} (${t.reported_runs}/${total.finalized_run_count} runs)`
10600
+ ).join(" \xB7 ")}`
10601
+ );
10602
+ console.log(
10603
+ `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}`
10604
+ );
10605
+ for (const portion of total.rate_portions) {
10606
+ const basis = portion.basis;
10607
+ console.log(
10608
+ basis ? `Rate: ${basis.rate_id} v${basis.rate_version} \xB7 ${basis.model} \xB7 ${basis.source_url}` : `Rate: historical calculated amount \xB7 basis unavailable`
10609
+ );
10610
+ }
10611
+ }
10612
+ function register12(program3) {
10613
+ withCommon(
10614
+ program3.command("usage").description("Compare finalized run spend for a period").addOption(
10615
+ new Option("--window <window>", "today, 7d, or 30d").choices(["today", "7d", "30d"])
10616
+ ).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(
10617
+ new Option("--outcome <outcome>", "recorded outcome").choices([
10618
+ "advanced",
10619
+ "stalled",
10620
+ "interrupted",
10621
+ "unknown"
10622
+ ])
10623
+ ).addOption(
10624
+ new Option("--accounting-status <status>", "price coverage").choices([
10625
+ "priced",
10626
+ "unpriced",
10627
+ "unreported"
10628
+ ])
10629
+ ).addOption(
10630
+ new Option("--by <dimension>", "group dimension").choices(["project", "workflow", "state", "outcome", "runner", "tier"]).default("workflow")
10631
+ )
10632
+ ).action(async (opts) => {
10633
+ if (opts.from === void 0 !== (opts.to === void 0))
10634
+ throw new Error("--from and --to are required together");
10635
+ if (opts.window && opts.from) throw new Error("--window cannot be combined with --from/--to");
10636
+ if (opts.state && !opts.workflow) throw new Error("--state requires --workflow");
10637
+ const api = client(opts);
10638
+ const project = opts.project && opts.project !== "unknown" ? (await resolveProject(api, opts.project)).id : opts.project;
10639
+ const workflow = opts.workflow && opts.workflow !== "unknown" ? (await resolveWorkflow(api, opts.workflow)).id : opts.workflow;
10640
+ const runner = opts.runner && opts.runner !== "unknown" ? (await resolveRunner(api, opts.runner)).id : opts.runner;
10641
+ const report = await api.getUsage({
10642
+ window: opts.window,
10643
+ from: opts.from,
10644
+ to: opts.to,
10645
+ project,
10646
+ workflow,
10647
+ state: opts.state,
10648
+ runner,
10649
+ tier: opts.tier,
10650
+ outcome: opts.outcome,
10651
+ accounting_status: opts.accountingStatus,
10652
+ by: opts.by
10653
+ });
10654
+ if (opts.json) return printJson(report);
10655
+ printReport(report);
10656
+ });
10657
+ }
10658
+
10445
10659
  // src/program.ts
10446
10660
  var program2 = new Command();
10447
10661
  program2.name("tines").description("CLI for Tines").version(cliVersion()).enablePositionalOptions();
@@ -10457,6 +10671,7 @@ register9(program2);
10457
10671
  register8(program2);
10458
10672
  register7(program2);
10459
10673
  register10(program2);
10674
+ register12(program2);
10460
10675
  registerEvents(program2);
10461
10676
 
10462
10677
  // 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.188",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",