tines 0.0.111 → 0.0.113

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 +172 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3408,6 +3408,17 @@ function actorLabel(actor) {
3408
3408
  }
3409
3409
  return actor.api_key_name ? `${actor.user_name} via ${actor.api_key_name}` : actor.user_name;
3410
3410
  }
3411
+ var LABEL_COLORS = [
3412
+ "slate",
3413
+ "red",
3414
+ "orange",
3415
+ "amber",
3416
+ "green",
3417
+ "teal",
3418
+ "blue",
3419
+ "violet",
3420
+ "pink"
3421
+ ];
3411
3422
  var PROMPT_MAX_BYTES = 32 * 1024;
3412
3423
  var SKILL_MAX_TOTAL_BYTES = 100 * 1024;
3413
3424
  var AGENT_GUIDELINES_NAME = "agent-guidelines";
@@ -3590,6 +3601,11 @@ var DESCRIBERS = {
3590
3601
  "issue.comment_deleted": () => [text("deleted a comment on"), selfRef()],
3591
3602
  "issue.link_added": linkSegments,
3592
3603
  "issue.link_removed": linkSegments,
3604
+ "issue.labeled": (_ev, p) => [text("labeled"), selfRef(), name(p.name)],
3605
+ "issue.unlabeled": (_ev, p) => [text("removed label"), name(p.name), text("from"), selfRef()],
3606
+ "label.created": (ev, p) => [text(`${action(ev.type)} label`), name(p.name)],
3607
+ "label.updated": (ev, p) => [text(`${action(ev.type)} label`), name(p.name)],
3608
+ "label.deleted": (ev, p) => [text(`${action(ev.type)} label`), name(p.name)],
3593
3609
  "issue.parked": (_ev, p) => [
3594
3610
  text("parked"),
3595
3611
  selfRef(),
@@ -3712,7 +3728,9 @@ var ApiError = class extends Error {
3712
3728
  function query(params) {
3713
3729
  const search = new URLSearchParams();
3714
3730
  for (const [key, value] of Object.entries(params)) {
3715
- if (value !== void 0 && value !== "" && value !== null) search.set(key, String(value));
3731
+ if (value === void 0 || value === "" || value === null) continue;
3732
+ if (Array.isArray(value)) for (const v of value) search.append(key, String(v));
3733
+ else search.set(key, String(value));
3716
3734
  }
3717
3735
  const s = search.toString();
3718
3736
  return s ? `?${s}` : "";
@@ -3776,6 +3794,12 @@ function createApiClient(options) {
3776
3794
  deleteWorkflow: (id, body) => request("DELETE", `/api/v1/workflows/${id}`, body),
3777
3795
  // Issues
3778
3796
  listIssues: (filters = {}) => get(`/api/v1/issues${query(filters)}`),
3797
+ listLabels: () => get("/api/v1/labels"),
3798
+ createLabel: (body) => request("POST", "/api/v1/labels", body),
3799
+ updateLabel: (labelRef, body) => request("PATCH", `/api/v1/labels/${encodeURIComponent(labelRef)}`, body),
3800
+ deleteLabel: (labelRef) => request("DELETE", `/api/v1/labels/${encodeURIComponent(labelRef)}`),
3801
+ addIssueLabels: (issueId, labels) => request("POST", `/api/v1/issues/${issueId}/labels`, { labels }),
3802
+ removeIssueLabel: (issueId, labelRef) => request("DELETE", `/api/v1/issues/${issueId}/labels/${encodeURIComponent(labelRef)}`),
3779
3803
  listProjectIssues: (projectId, filters = {}) => get(`/api/v1/projects/${projectId}/issues${query(filters)}`),
3780
3804
  createIssue: (projectId, body) => request("POST", `/api/v1/projects/${projectId}/issues`, body),
3781
3805
  getIssue: (id) => get(`/api/v1/issues/${id}`),
@@ -4764,6 +4788,9 @@ function printIssueDetail(issue) {
4764
4788
  `own state: ${issue.state.name} (${issue.state.category}) \u2014 dormant while this is a duplicate`
4765
4789
  );
4766
4790
  }
4791
+ if (issue.labels.length > 0) {
4792
+ console.log(`labels: ${issue.labels.map((l) => l.name).join(", ")}`);
4793
+ }
4767
4794
  console.log(`id: ${issue.id}`);
4768
4795
  printIssueLinks(issue.links);
4769
4796
  if (issue.description) {
@@ -4850,7 +4877,7 @@ function register2(program3) {
4850
4877
  issues.command("list").description("List issues across projects (hides done issues unless --all)").option("-p, --project <name>", "filter by project name or id").option("-s, --state <name>", "filter by state name or id").option("-c, --category <cat>", "filter by state category").option("-w, --workflow <id-or-name>", "filter by workflow").option("-a, --all", "include issues in done states").option(
4851
4878
  "--ready",
4852
4879
  "only issues that are actionable now (not done, not a duplicate, no open blockers)"
4853
- ).option("-q, --search <text>", "search titles and descriptions").option("--brief", "omit description bodies (saves tokens when scanning)")
4880
+ ).option("-q, --search <text>", "search titles and descriptions").option("-l, --label <name>", "filter by label; repeat to require all of them", collect, []).option("--brief", "omit description bodies (saves tokens when scanning)")
4854
4881
  ).action(
4855
4882
  async (opts) => {
4856
4883
  const api = client(opts);
@@ -4864,6 +4891,7 @@ function register2(program3) {
4864
4891
  hide_done: !opts.all,
4865
4892
  ready: opts.ready,
4866
4893
  q: opts.search,
4894
+ label: opts.label,
4867
4895
  // The table below never prints descriptions, so --brief only
4868
4896
  // ever changes --json; it is off by default so existing
4869
4897
  // consumers of the JSON keep the field.
@@ -4883,7 +4911,11 @@ function register2(program3) {
4883
4911
  i.effective_state.name,
4884
4912
  i.effective_state.category,
4885
4913
  timestamp(i.last_activity_at),
4886
- [i.open_blockers.length > 0 ? "blocked" : "", i.duplicate_of ? "dup" : ""].filter(Boolean).join(" ")
4914
+ [
4915
+ i.open_blockers.length > 0 ? "blocked" : "",
4916
+ i.duplicate_of ? "dup" : "",
4917
+ ...i.labels.map((l) => `[${l.name}]`)
4918
+ ].filter(Boolean).join(" ")
4887
4919
  ])
4888
4920
  ]);
4889
4921
  });
@@ -4902,7 +4934,12 @@ function register2(program3) {
4902
4934
  ).option(
4903
4935
  "--at <when>",
4904
4936
  "preset time of day HH:MM (default 09:00); for hourly, the minute past the hour :MM (default :00)"
4905
- ).option("--on <when>", "weekday (weekly) or day of month (monthly)").option("--cron <expr>", "5-field cron expression (alternative to --every/--at/--on)").option("--tz <iana>", "schedule timezone (defaults to the system timezone)").option("--if-closed", "only create a new instance when all previous instances are closed").option("--schedule-name <name>", "schedule name, unique per project (defaults to the title)")
4937
+ ).option("--on <when>", "weekday (weekly) or day of month (monthly)").option("--cron <expr>", "5-field cron expression (alternative to --every/--at/--on)").option("--tz <iana>", "schedule timezone (defaults to the system timezone)").option("--if-closed", "only create a new instance when all previous instances are closed").option("--schedule-name <name>", "schedule name, unique per project (defaults to the title)").option(
4938
+ "-l, --label <name>",
4939
+ "attach a label; repeat for several (created if new)",
4940
+ collect,
4941
+ []
4942
+ )
4906
4943
  ).action(
4907
4944
  async (projectRef, opts) => {
4908
4945
  const description = opts.description !== void 0 ? readBodyValue(opts.description) : void 0;
@@ -4924,11 +4961,12 @@ function register2(program3) {
4924
4961
  description,
4925
4962
  workflow_id: workflowId,
4926
4963
  state: opts.state,
4927
- schedule
4964
+ schedule,
4965
+ labels: opts.label
4928
4966
  });
4929
4967
  if (opts.json) return printJson(issue);
4930
4968
  console.log(
4931
- `created ${issue.project_name}/#${issue.number} "${issue.title}" in state "${issue.state.name}"`
4969
+ `created ${issue.project_name}/#${issue.number} "${issue.title}" in state "${issue.state.name}"` + (issue.labels.length > 0 ? ` labeled ${issue.labels.map((l) => l.name).join(", ")}` : "")
4932
4970
  );
4933
4971
  if (issue.schedule) {
4934
4972
  console.log(
@@ -5021,6 +5059,47 @@ function register2(program3) {
5021
5059
  if (opts.json) return printJson({ id: commentId, deleted: true });
5022
5060
  console.log(`deleted comment ${commentId} from ${issue.project_name}/#${issue.number}`);
5023
5061
  });
5062
+ withCommon(
5063
+ issues.command("label <ref> <name...>").description("Add labels to an issue (a label that does not exist yet is created)")
5064
+ ).action(async (ref, names, opts) => {
5065
+ const api = client(opts);
5066
+ const issue = await resolveIssue(api, ref);
5067
+ const res = await api.addIssueLabels(issue.id, names);
5068
+ if (opts.json) return printJson(res);
5069
+ const target = `${issue.project_name}/#${issue.number}`;
5070
+ if (res.added.length === 0) {
5071
+ console.log(`${target} already had ${names.join(", ")} \u2014 nothing to do`);
5072
+ } else {
5073
+ console.log(`labeled ${target}: ${res.added.map((l) => l.name).join(", ")}`);
5074
+ }
5075
+ if (res.created.length > 0) {
5076
+ console.log(
5077
+ `created new label${res.created.length > 1 ? "s" : ""} ${res.created.map((l) => l.name).join(", ")}`
5078
+ );
5079
+ }
5080
+ });
5081
+ withCommon(
5082
+ issues.command("unlabel <ref> <name...>").description("Remove labels from an issue")
5083
+ ).action(async (ref, names, opts) => {
5084
+ const api = client(opts);
5085
+ const issue = await resolveIssue(api, ref);
5086
+ const removed = [];
5087
+ const missing = [];
5088
+ for (const name2 of names) {
5089
+ try {
5090
+ await api.removeIssueLabel(issue.id, name2);
5091
+ removed.push(name2);
5092
+ } catch (err) {
5093
+ if (err instanceof ApiError && err.status === 404) missing.push(name2);
5094
+ else throw err;
5095
+ }
5096
+ }
5097
+ if (opts.json) return printJson({ removed, missing });
5098
+ const target = `${issue.project_name}/#${issue.number}`;
5099
+ if (removed.length > 0) console.log(`unlabeled ${target}: ${removed.join(", ")}`);
5100
+ if (missing.length > 0) console.log(`not on ${target}: ${missing.join(", ")}`);
5101
+ if (removed.length === 0 && missing.length === 0) console.log("nothing to do");
5102
+ });
5024
5103
  withCommon(
5025
5104
  issues.command("block <blocker> <blocked>").description("Record that <blocker> blocks <blocked> (advisory: transitions stay allowed)")
5026
5105
  ).action(async (blockerRef, blockedRef, opts) => {
@@ -5548,6 +5627,79 @@ start one: tines journal append ${issue.project_name}/${issue.number} "- <date>:
5548
5627
  );
5549
5628
  }
5550
5629
 
5630
+ // src/commands/labels.ts
5631
+ import { createInterface } from "node:readline/promises";
5632
+ function parseLabelColor(value) {
5633
+ if (value === void 0) return void 0;
5634
+ if (!LABEL_COLORS.includes(value)) {
5635
+ die(`unknown color "${value}" \u2014 pick one of: ${LABEL_COLORS.join(", ")}`);
5636
+ }
5637
+ return value;
5638
+ }
5639
+ function register4(program3) {
5640
+ const labels = program3.command("labels").description("Manage the label library");
5641
+ withCommon(
5642
+ labels.command("list").description("List labels with how many issues carry each")
5643
+ ).action(async (opts) => {
5644
+ const res = await client(opts).listLabels();
5645
+ if (opts.json) return printJson(res);
5646
+ if (res.items.length === 0) return console.log("no labels");
5647
+ table([
5648
+ ["NAME", "COLOR", "ISSUES", "DESCRIPTION"],
5649
+ ...res.items.map((l) => [l.name, l.color, String(l.issue_count), l.description])
5650
+ ]);
5651
+ });
5652
+ withCommon(
5653
+ labels.command("create <name>").description("Create a label").option(
5654
+ "--color <key>",
5655
+ `chip color (${LABEL_COLORS.join(", ")}); defaults to one derived from the name`
5656
+ ).option("-d, --description <text>", "what the label means")
5657
+ ).action(async (name2, opts) => {
5658
+ const color = parseLabelColor(opts.color);
5659
+ const label = await client(opts).createLabel({ name: name2, color, description: opts.description });
5660
+ if (opts.json) return printJson(label);
5661
+ console.log(`created label "${label.name}" (${label.color})`);
5662
+ });
5663
+ withCommon(
5664
+ labels.command("edit <name>").description("Rename or restyle a label").option("--name <new>", "new name").option("--color <key>", `chip color (${LABEL_COLORS.join(", ")})`).option("-d, --description <text>", "what the label means")
5665
+ ).action(
5666
+ async (name2, opts) => {
5667
+ if (opts.name === void 0 && opts.color === void 0 && opts.description === void 0) {
5668
+ die("nothing to change: pass --name, --color, or --description");
5669
+ }
5670
+ const label = await client(opts).updateLabel(name2, {
5671
+ name: opts.name,
5672
+ color: parseLabelColor(opts.color),
5673
+ description: opts.description
5674
+ });
5675
+ if (opts.json) return printJson(label);
5676
+ console.log(`updated label "${label.name}" (${label.color})`);
5677
+ }
5678
+ );
5679
+ withCommon(
5680
+ labels.command("delete <name>").description("Delete a label and detach it from every issue").option("-y, --yes", "skip the confirmation")
5681
+ ).action(async (name2, opts) => {
5682
+ const api = client(opts);
5683
+ if (!opts.yes) {
5684
+ const existing = (await api.listLabels()).items.find(
5685
+ (l) => l.name.toLowerCase() === name2.toLowerCase() || l.id === name2
5686
+ );
5687
+ if (!existing) die(`no such label: ${name2}`);
5688
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
5689
+ const answer = await rl.question(
5690
+ `Delete label "${existing.name}"? It is on ${existing.issue_count} issue${existing.issue_count === 1 ? "" : "s"}. [y/N] `
5691
+ );
5692
+ rl.close();
5693
+ if (!/^y(es)?$/i.test(answer.trim())) die("aborted");
5694
+ }
5695
+ const res = await api.deleteLabel(name2);
5696
+ if (opts.json) return printJson(res);
5697
+ console.log(
5698
+ `deleted label "${name2}" (was on ${res.issue_count} issue${res.issue_count === 1 ? "" : "s"})`
5699
+ );
5700
+ });
5701
+ }
5702
+
5551
5703
  // src/commands/login.ts
5552
5704
  import { readFileSync as readFileSync5 } from "node:fs";
5553
5705
  function maskKey(key) {
@@ -5559,7 +5711,7 @@ function readKeyArg(value) {
5559
5711
  if (!key) die("no API key on stdin");
5560
5712
  return key;
5561
5713
  }
5562
- function register4(program3) {
5714
+ function register5(program3) {
5563
5715
  program3.command("login").description("Store the API URL and key for this machine, so commands need no env vars").option("-u, --url <url>", `base URL of the Tines API to store (default ${DEFAULT_URL})`).option("--api-key <key>", 'API key from Settings \u2192 API keys ("-" reads it from stdin)').option("--no-verify", "store without checking the key against the API").action(async (opts) => {
5564
5716
  if (!opts.url && !opts.apiKey) die("pass --url and/or --api-key");
5565
5717
  const dir = defaultConfigDir();
@@ -5650,7 +5802,7 @@ function registerEvents(program3) {
5650
5802
  }
5651
5803
 
5652
5804
  // src/commands/projects.ts
5653
- function register5(program3) {
5805
+ function register6(program3) {
5654
5806
  const projects = program3.command("projects").description("Manage projects");
5655
5807
  withList(projects.command("list").description("List projects")).action(async (opts) => {
5656
5808
  const api = client(opts);
@@ -5744,7 +5896,7 @@ async function resolveRoutingScope(api, opts) {
5744
5896
  if (opts.state) parts.push(`state ${opts.state}`);
5745
5897
  return { projectId, stateId, label: parts.length > 0 ? parts.join(" \xB7 ") : "global" };
5746
5898
  }
5747
- function register6(program3) {
5899
+ function register7(program3) {
5748
5900
  const routing = program3.command("routing").description(
5749
5901
  "Scoped routing rules: which runner takes which issues (most specific scope wins)"
5750
5902
  );
@@ -6869,7 +7021,7 @@ function printTierTable(runner) {
6869
7021
  );
6870
7022
  }
6871
7023
  }
6872
- function register7(program3) {
7024
+ function register8(program3) {
6873
7025
  const runners = program3.command("runners").description("Manage the runner registry");
6874
7026
  withCommon(runners.command("list").description("List runners")).action(
6875
7027
  async (opts) => {
@@ -7273,7 +7425,7 @@ function register7(program3) {
7273
7425
  }
7274
7426
 
7275
7427
  // src/commands/schedules.ts
7276
- import { createInterface } from "node:readline/promises";
7428
+ import { createInterface as createInterface2 } from "node:readline/promises";
7277
7429
  async function resolveSchedule(api, ref) {
7278
7430
  const { project, name: name2 } = parseScheduleRef(ref);
7279
7431
  const proj = await resolveProject(api, project);
@@ -7302,7 +7454,7 @@ title template: ${s.title_template}`);
7302
7454
  for (const line of s.description_template.split("\n")) console.log(` ${line}`);
7303
7455
  }
7304
7456
  }
7305
- function register8(program3) {
7457
+ function register9(program3) {
7306
7458
  const schedules = program3.command("schedules").description("Manage scheduled tasks (addressed as <project>/<name>)");
7307
7459
  withList(
7308
7460
  schedules.command("list").description("List scheduled tasks (hides paused schedules unless --all)").option("-p, --project <name>", "filter by project name or id").option("-a, --all", "include paused schedules")
@@ -7437,7 +7589,7 @@ recent instances:`);
7437
7589
  const api = client(opts);
7438
7590
  const schedule = await resolveSchedule(api, ref);
7439
7591
  if (!opts.yes) {
7440
- const rl = createInterface({ input: process.stdin, output: process.stdout });
7592
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
7441
7593
  const answer = await rl.question(
7442
7594
  `Delete schedule "${scheduleRef(schedule)}"? Its ${schedule.run_count} existing issue${schedule.run_count === 1 ? "" : "s"} will be kept. [y/N] `
7443
7595
  );
@@ -7450,7 +7602,7 @@ recent instances:`);
7450
7602
  }
7451
7603
 
7452
7604
  // src/commands/supervisor.ts
7453
- function register9(program3) {
7605
+ function register10(program3) {
7454
7606
  const supervisor = program3.command("supervisor").description("The automation kill switch, quota policy, and attempt limit");
7455
7607
  withCommon(
7456
7608
  supervisor.command("status").description("One-screen overview: kill switch, quota, utilization, runners")
@@ -7645,7 +7797,7 @@ function printWorkflowDetail(wf) {
7645
7797
  for (const w of wf.warnings ?? []) console.log(`
7646
7798
  warning: ${w}`);
7647
7799
  }
7648
- function register10(program3) {
7800
+ function register11(program3) {
7649
7801
  const workflows = program3.command("workflows").description("Manage the workflow library");
7650
7802
  withList(workflows.command("list").description("List the workflow library")).action(
7651
7803
  async (opts) => {
@@ -7730,16 +7882,17 @@ see \`tines workflows create --help\` for the expected shape`
7730
7882
  var program2 = new Command();
7731
7883
  program2.name("tines").description("CLI for Tines").version(cliVersion()).enablePositionalOptions();
7732
7884
  registerTime(program2);
7733
- register4(program2);
7734
7885
  register5(program2);
7735
- register10(program2);
7886
+ register6(program2);
7887
+ register11(program2);
7736
7888
  register2(program2);
7889
+ register4(program2);
7737
7890
  register(program2);
7738
7891
  register3(program2);
7892
+ register9(program2);
7739
7893
  register8(program2);
7740
7894
  register7(program2);
7741
- register6(program2);
7742
- register9(program2);
7895
+ register10(program2);
7743
7896
  registerEvents(program2);
7744
7897
 
7745
7898
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tines",
3
- "version": "0.0.111",
3
+ "version": "0.0.113",
4
4
  "description": "CLI for Tines, an orchestration layer for AI agents",
5
5
  "repository": {
6
6
  "type": "git",