@voidlabs.gg/voidshift 0.4.3 → 0.4.4

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 CHANGED
@@ -50,15 +50,24 @@ Five agents, installed into Claude Code (and Codex if you use it):
50
50
  | QA | CI, then code review, then a usability check in a headless browser |
51
51
 
52
52
  Use them yourself (`/vs-plan ENG-123` in Claude Code), or let a **workflow**
53
- run them. A workflow starts when one of your Linear cards gets a label, runs
54
- agents in order in a project (a repo on your Mac), and sends work back to the
55
- step that made it when a check fails:
53
+ run them. A workflow starts when one of your Linear or VoidTrack cards gets a
54
+ label, or on a schedule, runs agents in order in a project (a repo on your
55
+ machine), and sends work back to the step that made it when a check fails:
56
56
 
57
57
  ```
58
58
  when ENG + vs-plan: Dev → QA in tether-relay
59
59
  ```
60
60
 
61
- The runner checks Linear every minute, works in its own copy of the repo so
61
+ Triggers, as `--trigger` takes them:
62
+
63
+ ```
64
+ ENG:vs-plan # a Linear card in ENG assigned to you gets vs-plan
65
+ voidtrack:ORG:vs-start # the same for VoidTrack (vsf agents key --voidtrack)
66
+ "cron:0 5 * * 1:Update dependencies" # every Monday 05:00, no card, on its own branch
67
+ ```
68
+
69
+ A schedule fires once when due (a missed one is made up within a day) and
70
+ `vsf agents run --workflow NAME` fires it now. The runner checks every minute, works in its own copy of the repo so
62
71
  your checkout is never touched, and keeps two labels on the card as it goes: the
63
72
  stage (`vs-planning`, `vs-plan-review`, `vs-dev`, `vs-qa`, then `vs-qa-verified`
64
73
  or `vs-parked`) and the round (`vs-v1`, `vs-v2`). On Linear it posts the plan and
@@ -63,8 +63,10 @@ export interface WorkflowItem {
63
63
  name: string;
64
64
  triggers: {
65
65
  source: string;
66
- team: string;
67
- label: string;
66
+ team?: string;
67
+ label?: string;
68
+ schedule?: string;
69
+ task?: string;
68
70
  }[];
69
71
  steps: string[];
70
72
  maxRounds: number;
@@ -8,7 +8,7 @@ import { useBackGuard, useEquivalent, useHints, useKeys, useScreen } from "../co
8
8
  import { theme } from "../theme.js";
9
9
  import { Panel, Select, TextInput } from "../widgets.js";
10
10
  import { tilde } from "../format.js";
11
- import { matchWarnings, stepLabel, stepName, triggerText, warningText, sameTriggerLike } from "./workflows.js";
11
+ import { matchWarnings, stepLabel, stepName, triggerSpec, triggerText, warningText, sameTriggerLike } from "./workflows.js";
12
12
  const h = React.createElement;
13
13
  export function formReducer(s, a) {
14
14
  switch (a.type) {
@@ -38,7 +38,7 @@ export function formReducer(s, a) {
38
38
  }
39
39
  export function initialForm(w, name, projects) {
40
40
  if (w)
41
- return { name, triggers: w.triggers.map((t) => ({ team: t.team, label: t.label })), steps: [...w.steps], project: w.project, rounds: w.maxRounds };
41
+ return { name, triggers: w.triggers.map((t) => ({ ...t })), steps: [...w.steps], project: w.project, rounds: w.maxRounds };
42
42
  return { name: '', triggers: [], steps: [], project: projects.length === 1 ? projects[0] : '', rounds: 3 };
43
43
  }
44
44
  const FALLBACK_PRODUCERS = ['vs-plan', 'vs-dev'];
@@ -95,7 +95,7 @@ export function saveArgs(s, isNew, catalog) {
95
95
  'workflow',
96
96
  isNew ? 'add' : 'edit',
97
97
  s.name.trim(),
98
- ...s.triggers.flatMap((t) => ['--trigger', `${t.team}:${t.label}`]),
98
+ ...s.triggers.flatMap((t) => ['--trigger', triggerSpec(t)]),
99
99
  '--steps',
100
100
  s.steps.map((id) => stepName(catalog, id)).join(','),
101
101
  '--project',
@@ -105,17 +105,19 @@ export function saveArgs(s, isNew, catalog) {
105
105
  ];
106
106
  }
107
107
  export const renameArgs = (from, to) => ['agents', 'workflow', 'rename', from, to];
108
- // A trigger from the two text fields, or a complaint.
108
+ // A trigger from the two text fields, or a complaint. A team written as
109
+ // voidtrack:ORG is a VoidTrack team; schedules are added with the CLI.
109
110
  export function makeTrigger(team, label) {
110
- const t = team.trim().toUpperCase();
111
+ const voidtrack = /^voidtrack:/i.test(team.trim());
112
+ const t = team.trim().replace(/^voidtrack:/i, '').toUpperCase();
111
113
  const l = label.trim();
112
114
  if (!t)
113
- return { error: 'Give the Linear team, like ENG.' };
115
+ return { error: 'Give the team, like ENG, or voidtrack:ORG for VoidTrack.' };
114
116
  if (t.includes(':') || /\s/.test(t))
115
- return { error: 'The team is a short key like ENG.' };
117
+ return { error: 'The team is a short key like ENG, or voidtrack:ORG.' };
116
118
  if (!l)
117
119
  return { error: 'Give the label, like vs-plan.' };
118
- return { trigger: { team: t, label: l } };
120
+ return { trigger: { source: voidtrack ? 'voidtrack' : 'linear', team: t, label: l } };
119
121
  }
120
122
  // Warnings for triggers this workflow shares with another. In place when
121
123
  // editing, last in order when new.
@@ -348,7 +350,7 @@ function WorkflowForm(props) {
348
350
  // Triggers
349
351
  h(Box, null, h(Label, { text: 'Starts when', focused: where === 'triggers' }), h(Box, { flexDirection: 'column', flexGrow: 1 }, form.triggers.length === 0 ? h(Text, { color: theme.dim }, ' no trigger yet') : null, ...form.triggers.map((t, i) => {
350
352
  const on = where === 'triggers' && i === tCursor && adding === null;
351
- return h(Text, { key: `${t.team}:${t.label}`, backgroundColor: on ? theme.selected : undefined, bold: on, color: on ? theme.bold : undefined }, ` ${triggerText(t)} `);
353
+ return h(Text, { key: triggerSpec(t), backgroundColor: on ? theme.selected : undefined, bold: on, color: on ? theme.bold : undefined }, ` ${triggerText(t)} `);
352
354
  }), adding !== null
353
355
  ? h(Box, { flexDirection: 'column' }, h(Box, null, h(Text, { color: theme.dim }, ' team '), h(TextInput, { value: newTeam, onChange: setNewTeam, focus: adding === 'team', placeholder: 'ENG', onSubmit: () => setAdding('label') }), h(Text, { color: theme.dim }, ' label '), h(TextInput, { value: newLabel, onChange: setNewLabel, focus: adding === 'label', placeholder: 'vs-plan', onSubmit: commitTrigger })), addError ? h(Text, { color: theme.red }, ` ${addError}`) : null)
354
356
  : null), h(Box, { flexShrink: 0, marginLeft: 2 }, h(Text, { color: theme.dim }, where === 'triggers' ? 'a add trigger · x remove' : ''))), h(Text, null, ' '),
@@ -4,10 +4,14 @@ import type { Screen } from '../screens.ts';
4
4
  export declare function stepLabel(catalog: StepsCatalog | null, id: string): string;
5
5
  export declare function stepName(catalog: StepsCatalog | null, id: string): string;
6
6
  export interface TriggerLike {
7
- team: string;
8
- label: string;
7
+ source?: string;
8
+ team?: string;
9
+ label?: string;
10
+ schedule?: string;
11
+ task?: string;
9
12
  }
10
13
  export declare const triggerText: (t: TriggerLike) => string;
14
+ export declare const triggerSpec: (t: TriggerLike) => string;
11
15
  export declare const triggersText: (triggers: TriggerLike[]) => string;
12
16
  export declare const sameTriggerLike: (a: TriggerLike, b: TriggerLike) => boolean;
13
17
  export declare const stepsText: (catalog: StepsCatalog | null, steps: string[]) => string;
@@ -28,9 +28,11 @@ export function stepLabel(catalog, id) {
28
28
  export function stepName(catalog, id) {
29
29
  return catalog?.steps.find((s) => s.id === id)?.name ?? id.replace(/^vs-/, '');
30
30
  }
31
- export const triggerText = (t) => `${t.team} + ${t.label}`;
31
+ export const triggerText = (t) => t.source === 'cron' ? `cron ${t.schedule}` : `${t.source === 'voidtrack' ? 'VoidTrack ' : ''}${t.team} + ${t.label}`;
32
+ // The form `--trigger` takes.
33
+ export const triggerSpec = (t) => t.source === 'cron' ? `cron:${t.schedule}:${t.task}` : t.source === 'voidtrack' ? `voidtrack:${t.team}:${t.label}` : `${t.team}:${t.label}`;
32
34
  export const triggersText = (triggers) => triggers.map(triggerText).join(', ');
33
- export const sameTriggerLike = (a, b) => a.team === b.team && a.label === b.label;
35
+ export const sameTriggerLike = (a, b) => triggerSpec(a) === triggerSpec(b);
34
36
  export const stepsText = (catalog, steps) => steps.map((s) => stepLabel(catalog, s)).join(' → ');
35
37
  // Two workflows that start on the same TEAM:LABEL: only the first in order
36
38
  // starts the card. One entry per (loser, winner) pair.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voidlabs.gg/voidshift",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "One CLI for the Void dev tools: keep your Mac awake, run agents in the background, shared skills and memory for Claude Code and Codex.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {