@detergent-software/conductor 1.0.1-dev.1 → 1.1.0-dev.1

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 (112) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +11 -0
  3. package/build/brief-cli.js.map +1 -1
  4. package/build/brief.js +24 -9
  5. package/build/brief.js.map +1 -1
  6. package/build/broker-cli.js +5 -1
  7. package/build/broker-cli.js.map +1 -1
  8. package/build/broker.js +19 -5
  9. package/build/broker.js.map +1 -1
  10. package/build/checkout-sync.js +265 -0
  11. package/build/checkout-sync.js.map +1 -0
  12. package/build/claude.js +3 -1
  13. package/build/claude.js.map +1 -1
  14. package/build/cli.js +114 -31
  15. package/build/cli.js.map +1 -1
  16. package/build/client.js +3 -1
  17. package/build/client.js.map +1 -1
  18. package/build/conditions.js +4 -2
  19. package/build/conditions.js.map +1 -1
  20. package/build/config.js +13 -1
  21. package/build/config.js.map +1 -1
  22. package/build/console.js +45 -8
  23. package/build/console.js.map +1 -1
  24. package/build/credential.js +4 -1
  25. package/build/credential.js.map +1 -1
  26. package/build/daemon.js +56 -7
  27. package/build/daemon.js.map +1 -1
  28. package/build/events.js +4 -1
  29. package/build/events.js.map +1 -1
  30. package/build/help.js +85 -42
  31. package/build/help.js.map +1 -1
  32. package/build/history.js +216 -52
  33. package/build/history.js.map +1 -1
  34. package/build/inbox.js +23 -10
  35. package/build/inbox.js.map +1 -1
  36. package/build/manager.js +84 -18
  37. package/build/manager.js.map +1 -1
  38. package/build/mod.js +3 -1
  39. package/build/mod.js.map +1 -1
  40. package/build/note.js +12 -5
  41. package/build/note.js.map +1 -1
  42. package/build/notes.js +8 -5
  43. package/build/notes.js.map +1 -1
  44. package/build/notify.js +111 -24
  45. package/build/notify.js.map +1 -1
  46. package/build/overrides.js +24 -6
  47. package/build/overrides.js.map +1 -1
  48. package/build/profile.js.map +1 -1
  49. package/build/pushover.js +4 -1
  50. package/build/pushover.js.map +1 -1
  51. package/build/queue.js +18 -3
  52. package/build/queue.js.map +1 -1
  53. package/build/registry.js.map +1 -1
  54. package/build/rule-actions.js +173 -36
  55. package/build/rule-actions.js.map +1 -1
  56. package/build/rule-input.js +3 -1
  57. package/build/rule-input.js.map +1 -1
  58. package/build/rules-service.js +41 -11
  59. package/build/rules-service.js.map +1 -1
  60. package/build/rules.js +86 -14
  61. package/build/rules.js.map +1 -1
  62. package/build/scheduler.js +48 -8
  63. package/build/scheduler.js.map +1 -1
  64. package/build/server.js +40 -10
  65. package/build/server.js.map +1 -1
  66. package/build/spend.js.map +1 -1
  67. package/build/store.js +71 -16
  68. package/build/store.js.map +1 -1
  69. package/build/token.js +3 -1
  70. package/build/token.js.map +1 -1
  71. package/build/trackers.js +3 -1
  72. package/build/trackers.js.map +1 -1
  73. package/build/upgrade.js.map +1 -1
  74. package/build/wait.js +3 -1
  75. package/build/wait.js.map +1 -1
  76. package/build/watchers/ado-api.js +5 -2
  77. package/build/watchers/ado-api.js.map +1 -1
  78. package/build/watchers/ado.js +18 -6
  79. package/build/watchers/ado.js.map +1 -1
  80. package/build/watchers/boards-brief.js +9 -3
  81. package/build/watchers/boards-brief.js.map +1 -1
  82. package/build/watchers/boards-queue.js +10 -5
  83. package/build/watchers/boards-queue.js.map +1 -1
  84. package/build/watchers/boards-writes.js +11 -3
  85. package/build/watchers/boards-writes.js.map +1 -1
  86. package/build/watchers/boards.js +12 -3
  87. package/build/watchers/boards.js.map +1 -1
  88. package/build/watchers/conflicts.js +11 -3
  89. package/build/watchers/conflicts.js.map +1 -1
  90. package/build/watchers/github.js +6 -2
  91. package/build/watchers/github.js.map +1 -1
  92. package/build/watchers/health.js.map +1 -1
  93. package/build/watchers/index.js +22 -4
  94. package/build/watchers/index.js.map +1 -1
  95. package/build/watchers/linear-api.js +1 -1
  96. package/build/watchers/linear-api.js.map +1 -1
  97. package/build/watchers/linear-labels.js +3 -1
  98. package/build/watchers/linear-labels.js.map +1 -1
  99. package/build/watchers/linear-queue.js +10 -4
  100. package/build/watchers/linear-queue.js.map +1 -1
  101. package/build/watchers/linear.js +13 -4
  102. package/build/watchers/linear.js.map +1 -1
  103. package/build/watchers/npm.js.map +1 -1
  104. package/build/watchers/source.js +1 -1
  105. package/build/watchers/source.js.map +1 -1
  106. package/build/why.js +7 -3
  107. package/build/why.js.map +1 -1
  108. package/build/worktrees.js +2 -1
  109. package/build/worktrees.js.map +1 -1
  110. package/mod/hooks/model.ts +90 -25
  111. package/mod/hooks/register.tsx +172 -62
  112. package/package.json +11 -12
@@ -101,11 +101,20 @@ async function endpoint($: EngineInterface): Promise<Endpoint> {
101
101
  }
102
102
 
103
103
  // One call on the daemon's API (127.0.0.1, with its token). Throws when none runs.
104
- async function callApi($: EngineInterface, method: 'GET' | 'POST', path: string, body?: unknown, at?: Endpoint): Promise<ApiAnswer> {
104
+ async function callApi(
105
+ $: EngineInterface,
106
+ method: 'GET' | 'POST',
107
+ path: string,
108
+ body?: unknown,
109
+ at?: Endpoint,
110
+ ): Promise<ApiAnswer> {
105
111
  const found = at ?? (await endpoint($));
106
112
  const answer = await $.http.fetch(`http://127.0.0.1:${found.port}${path}`, {
107
113
  body: body === undefined ? undefined : JSON.stringify(body),
108
- headers: { authorization: `Bearer ${found.token}`, ...(body === undefined ? {} : { 'content-type': 'application/json' }) },
114
+ headers: {
115
+ authorization: `Bearer ${found.token}`,
116
+ ...(body === undefined ? {} : { 'content-type': 'application/json' }),
117
+ },
109
118
  method,
110
119
  });
111
120
  let parsed: unknown = null;
@@ -130,7 +139,13 @@ async function refresh($: EngineInterface): Promise<ConductorSnapshot> {
130
139
  ]);
131
140
  next = snapshotOf(fleet, inbox, readAt, notify);
132
141
  } catch (error) {
133
- next = { error: error instanceof Error ? error.message : String(error), inbox: [], notify: null, readAt, workers: [] };
142
+ next = {
143
+ error: error instanceof Error ? error.message : String(error),
144
+ inbox: [],
145
+ notify: null,
146
+ readAt,
147
+ workers: [],
148
+ };
134
149
  }
135
150
  await update($, snapshot, () => next);
136
151
  return next;
@@ -177,7 +192,9 @@ async function onLine($: EngineInterface, text: string): Promise<void> {
177
192
  return;
178
193
  }
179
194
  } catch (error) {
180
- $.ui.log(`conductor: ${line.kind} failed: ${error instanceof Error ? error.message : String(error)}`, { to: 'debug' });
195
+ $.ui.log(`conductor: ${line.kind} failed: ${error instanceof Error ? error.message : String(error)}`, {
196
+ to: 'debug',
197
+ });
181
198
  }
182
199
  }
183
200
 
@@ -198,9 +215,15 @@ async function push($: EngineInterface, message: string): Promise<void> {
198
215
  $.ui.toast(warning);
199
216
  }
200
217
  try {
201
- await callApi($, 'POST', '/notify/report', { channel: 'remote-control', result: outcome.detail, state: reportedState(outcome) });
218
+ await callApi($, 'POST', '/notify/report', {
219
+ channel: 'remote-control',
220
+ result: outcome.detail,
221
+ state: reportedState(outcome),
222
+ });
202
223
  } catch (error) {
203
- $.ui.log(`conductor: notify report failed: ${error instanceof Error ? error.message : String(error)}`, { to: 'debug' });
224
+ $.ui.log(`conductor: notify report failed: ${error instanceof Error ? error.message : String(error)}`, {
225
+ to: 'debug',
226
+ });
204
227
  }
205
228
  }
206
229
 
@@ -238,7 +261,9 @@ async function onIdle($: EngineInterface): Promise<void> {
238
261
  await $.command.run({ command: 'clear' });
239
262
  } catch (error) {
240
263
  await update($, cleared, () => null);
241
- $.ui.log(`conductor: the idle /clear failed: ${error instanceof Error ? error.message : String(error)}`, { to: 'debug' });
264
+ $.ui.log(`conductor: the idle /clear failed: ${error instanceof Error ? error.message : String(error)}`, {
265
+ to: 'debug',
266
+ });
242
267
  return;
243
268
  } finally {
244
269
  isClearing = false;
@@ -250,7 +275,9 @@ async function onIdle($: EngineInterface): Promise<void> {
250
275
  try {
251
276
  outcome = pingOutcome(await $.model.fork({ prompt: WARM_PROMPT }), contextTokens);
252
277
  } catch (error) {
253
- $.ui.log(`conductor: the cache ping failed: ${error instanceof Error ? error.message : String(error)}`, { to: 'debug' });
278
+ $.ui.log(`conductor: the cache ping failed: ${error instanceof Error ? error.message : String(error)}`, {
279
+ to: 'debug',
280
+ });
254
281
  outcome = { kind: 'failed', usage: null };
255
282
  }
256
283
  if (outcome.kind === 'nothing') return;
@@ -266,7 +293,9 @@ async function reportCache($: EngineInterface, body: Record<string, unknown>): P
266
293
  try {
267
294
  await callApi($, 'POST', '/console/cache', body);
268
295
  } catch (error) {
269
- $.ui.log(`conductor: console cache report failed: ${error instanceof Error ? error.message : String(error)}`, { to: 'debug' });
296
+ $.ui.log(`conductor: console cache report failed: ${error instanceof Error ? error.message : String(error)}`, {
297
+ to: 'debug',
298
+ });
270
299
  }
271
300
  }
272
301
 
@@ -291,12 +320,19 @@ async function follow($: EngineInterface): Promise<void> {
291
320
  $.ui.log(`conductor: no ${cli}; the console polls instead of following events`, { to: 'debug' });
292
321
  return;
293
322
  }
294
- for (let quickExits = 0; quickExits < 3; ) {
323
+ for (let quickExits = 0; quickExits < 3;) {
295
324
  const startedAt = await $.clock.now();
296
325
  let buffer = '';
297
326
  try {
298
327
  // --console: while this stream is open, the daemon knows escalations have a console to wake.
299
- const argv = ['node', cli, 'events', '--follow', '--console', ...(lastEventId === null ? [] : ['--since', String(lastEventId)])];
328
+ const argv = [
329
+ 'node',
330
+ cli,
331
+ 'events',
332
+ '--follow',
333
+ '--console',
334
+ ...(lastEventId === null ? [] : ['--since', String(lastEventId)]),
335
+ ];
300
336
  for await (const chunk of $.process.spawn({ argv })) {
301
337
  if (chunk.stream !== 'stdout') continue;
302
338
  buffer += chunk.text;
@@ -307,7 +343,9 @@ async function follow($: EngineInterface): Promise<void> {
307
343
  }
308
344
  }
309
345
  } catch (error) {
310
- $.ui.log(`conductor: conduct events failed: ${error instanceof Error ? error.message : String(error)}`, { to: 'debug' });
346
+ $.ui.log(`conductor: conduct events failed: ${error instanceof Error ? error.message : String(error)}`, {
347
+ to: 'debug',
348
+ });
311
349
  }
312
350
  quickExits = (await $.clock.now()) - startedAt < 10_000 ? quickExits + 1 : 0;
313
351
  await new Promise<void>((done) => $.clock.after(RESTART_MS, done));
@@ -316,7 +354,11 @@ async function follow($: EngineInterface): Promise<void> {
316
354
  }
317
355
 
318
356
  // Jason's answer to an inbox item, as `conduct approve|revise|done` posts it; the line to show.
319
- async function answer($: EngineInterface, id: number, body: { action: 'approve' | 'done' } | { action: 'revise'; text: string }): Promise<string> {
357
+ async function answer(
358
+ $: EngineInterface,
359
+ id: number,
360
+ body: { action: 'approve' | 'done' } | { action: 'revise'; text: string },
361
+ ): Promise<string> {
320
362
  try {
321
363
  const result = await callApi($, 'POST', `/inbox/${id}/resolve`, body);
322
364
  refreshSoon($);
@@ -359,7 +401,11 @@ async function attach($: EngineInterface, id: number, name: string): Promise<str
359
401
  }
360
402
  }
361
403
 
362
- async function copy($: EngineInterface, text: string, surface: Parameters<EngineInterface['ui']['copy']>[0]['surface']): Promise<void> {
404
+ async function copy(
405
+ $: EngineInterface,
406
+ text: string,
407
+ surface: Parameters<EngineInterface['ui']['copy']>[0]['surface'],
408
+ ): Promise<void> {
363
409
  const copied = await $.ui.copy({ surface, text });
364
410
  $.ui.toast(copied.isCopied ? `copied: ${text}` : `not copied: ${text}`);
365
411
  }
@@ -375,15 +421,45 @@ async function openPane($: EngineInterface): Promise<void> {
375
421
  }
376
422
 
377
423
  async function registerCommands($: EngineInterface): Promise<void> {
378
- await $.command.register({ description: 'Open the conductor pane: the inbox, the fleet and spend', name: 'conductor' });
379
- await $.command.register({ description: 'What waits on you in the conductor, with commands and links', name: 'inbox' });
380
- await $.command.register({ argumentHint: '<item | #id>', description: "Approve an item's plan (posts approve as you)", name: 'approve' });
381
- await $.command.register({ argumentHint: '<item | #id> <what to change>', description: 'Ask for a revised plan (posts revise: … as you)', name: 'revise' });
382
- await $.command.register({ argumentHint: '<item | worker>', description: 'Wake a parked worker and open it in a tab', name: 'attach' });
383
- await $.command.register({ argumentHint: '[<profile> [--until <when>]]', description: 'Show or switch the notification profile (default, away, focus …)', name: 'notify' });
384
- await $.command.register({ argumentHint: '[<project>] [--until <when>]', description: 'No new launches in a project (every project without one)', name: 'pause' });
424
+ await $.command.register({
425
+ description: 'Open the conductor pane: the inbox, the fleet and spend',
426
+ name: 'conductor',
427
+ });
428
+ await $.command.register({
429
+ description: 'What waits on you in the conductor, with commands and links',
430
+ name: 'inbox',
431
+ });
432
+ await $.command.register({
433
+ argumentHint: '<item | #id>',
434
+ description: "Approve an item's plan (posts approve as you)",
435
+ name: 'approve',
436
+ });
437
+ await $.command.register({
438
+ argumentHint: '<item | #id> <what to change>',
439
+ description: 'Ask for a revised plan (posts revise: … as you)',
440
+ name: 'revise',
441
+ });
442
+ await $.command.register({
443
+ argumentHint: '<item | worker>',
444
+ description: 'Wake a parked worker and open it in a tab',
445
+ name: 'attach',
446
+ });
447
+ await $.command.register({
448
+ argumentHint: '[<profile> [--until <when>]]',
449
+ description: 'Show or switch the notification profile (default, away, focus …)',
450
+ name: 'notify',
451
+ });
452
+ await $.command.register({
453
+ argumentHint: '[<project>] [--until <when>]',
454
+ description: 'No new launches in a project (every project without one)',
455
+ name: 'pause',
456
+ });
385
457
  await $.command.register({ argumentHint: '[<project>]', description: 'Launch again after /pause', name: 'unpause' });
386
- await $.command.register({ argumentHint: '<project> [--until <when>]', description: "Put a project's items first (until midnight without --until)", name: 'focus-project' });
458
+ await $.command.register({
459
+ argumentHint: '<project> [--until <when>]',
460
+ description: "Put a project's items first (until midnight without --until)",
461
+ name: 'focus-project',
462
+ });
387
463
  }
388
464
 
389
465
  export const register: Register = (on) => {
@@ -519,15 +595,15 @@ export const register: Register = (on) => {
519
595
  const clearedBy = await read($, cleared);
520
596
  const withWarning = (row: RenderElement): RenderElement =>
521
597
  isWarned ? (
522
- <Box flexDirection="column">
598
+ <Box flexDirection='column'>
523
599
  {clearedBy === null ? (
524
- <Box key="no-skill">
525
- <Text bold color="warning">
600
+ <Box key='no-skill'>
601
+ <Text bold color='warning'>
526
602
  {NO_SKILL_WARNING}
527
603
  </Text>
528
604
  </Box>
529
605
  ) : (
530
- <Box key="cleared">
606
+ <Box key='cleared'>
531
607
  <Text dimColor>{clearedText(clearedBy)}</Text>
532
608
  </Box>
533
609
  )}
@@ -537,33 +613,34 @@ export const register: Register = (on) => {
537
613
  row
538
614
  );
539
615
  const snap = await read($, snapshot);
540
- if (snap === null || snap.error !== null || snap.inbox.length === 0) return isWarned ? withWarning(await next(e)) : next(e);
616
+ if (snap === null || snap.error !== null || snap.inbox.length === 0)
617
+ return isWarned ? withWarning(await next(e)) : next(e);
541
618
  const entries = bandEntries(snap.inbox);
542
619
  const confirmId = await read($, confirming);
543
620
  const pending = entries.find((x) => x.act === 'approve' && x.id === confirmId);
544
621
  if (pending) {
545
622
  return withWarning(
546
- <Box columnGap={2} flexWrap="wrap">
623
+ <Box columnGap={2} flexWrap='wrap'>
547
624
  <Text bold>conductor</Text>
548
625
  <Text>
549
626
  {pending.label}? {pending.text}
550
627
  </Text>
551
628
  <Button
552
- hotkey="y"
553
- key="confirm"
554
- label="y approve"
629
+ hotkey='y'
630
+ key='confirm'
631
+ label='y approve'
555
632
  onPress={async () => {
556
633
  await update($, confirming, () => null);
557
634
  $.ui.toast(await answer($, pending.id, { action: 'approve' }));
558
635
  }}
559
- variant="primary"
636
+ variant='primary'
560
637
  />
561
- <Button hotkey="n" key="cancel" label="n cancel" onPress={() => update($, confirming, () => null)} />
638
+ <Button hotkey='n' key='cancel' label='n cancel' onPress={() => update($, confirming, () => null)} />
562
639
  </Box>,
563
640
  );
564
641
  }
565
642
  return withWarning(
566
- <Box columnGap={2} flexWrap="wrap">
643
+ <Box columnGap={2} flexWrap='wrap'>
567
644
  <Text bold>conductor · inbox {snap.inbox.length}</Text>
568
645
  {entries.map((x) => (
569
646
  <Button
@@ -578,7 +655,7 @@ export const register: Register = (on) => {
578
655
  />
579
656
  ))}
580
657
  {snap.inbox.length > entries.length && <Text dimColor>+{snap.inbox.length - entries.length} more</Text>}
581
- <Button hotkey="o" key="open-pane" label="o panel" onPress={() => openPane($)} />
658
+ <Button hotkey='o' key='open-pane' label='o panel' onPress={() => openPane($)} />
582
659
  </Box>,
583
660
  );
584
661
  });
@@ -590,27 +667,28 @@ export const register: Register = (on) => {
590
667
  const snap = await read($, snapshot);
591
668
  const open = await read($, expanded);
592
669
  const now = await $.clock.now();
593
- const rule = (title: string): string => `─ ${title} ${'─'.repeat(Math.max(0, e.props.bodyColumns - title.length - 3))}`;
670
+ const rule = (title: string): string =>
671
+ `─ ${title} ${'─'.repeat(Math.max(0, e.props.bodyColumns - title.length - 3))}`;
594
672
  if (snap === null) return <Text dimColor>Reading the conductor…</Text>;
595
673
  if (snap.error !== null) {
596
674
  return (
597
- <Box flexDirection="column">
675
+ <Box flexDirection='column'>
598
676
  <Text bold>{TITLE}</Text>
599
677
  <Text>{snap.error}</Text>
600
678
  </Box>
601
679
  );
602
680
  }
603
681
  return (
604
- <Box flexDirection="column">
682
+ <Box flexDirection='column'>
605
683
  <Text bold>
606
684
  {TITLE} <Text dimColor>{statusText(snap)}</Text>
607
685
  </Text>
608
686
  {snap.notify !== null && (
609
- <Box columnGap={2} flexWrap="wrap">
687
+ <Box columnGap={2} flexWrap='wrap'>
610
688
  {e.surface !== 'mobile' && 'Select' in ui ? (
611
689
  <ui.Select
612
- key="notify-profile"
613
- label="notify "
690
+ key='notify-profile'
691
+ label='notify '
614
692
  onSelect={async (name) => $.ui.toast(await switchProfile($, name))}
615
693
  options={snap.notify.profiles.map((p) => ({ value: p }))}
616
694
  value={snap.notify.profile}
@@ -631,61 +709,93 @@ export const register: Register = (on) => {
631
709
  </Text>
632
710
  </Box>
633
711
  )}
634
- <Text dimColor wrap="truncate">
712
+ <Text dimColor wrap='truncate'>
635
713
  {rule(`Inbox (${snap.inbox.length})`)}
636
714
  </Text>
637
715
  {snap.inbox.length === 0 && <Text dimColor>Nothing waits on you.</Text>}
638
716
  {snap.inbox.map((item, i) => (
639
- <Box flexDirection="column" key={`inbox-${item.id}`} marginBottom={1}>
717
+ <Box flexDirection='column' key={`inbox-${item.id}`} marginBottom={1}>
640
718
  <Text>
641
- [{i + 1}] <Text bold>{item.item ?? `#${item.id}`}</Text> {item.kind} <Text dimColor>{age(item.raisedAt, now)}</Text>
719
+ [{i + 1}] <Text bold>{item.item ?? `#${item.id}`}</Text> {item.kind}{' '}
720
+ <Text dimColor>{age(item.raisedAt, now)}</Text>
642
721
  </Text>
643
722
  <Text>{item.text}</Text>
644
- <Box columnGap={2} flexWrap="wrap">
723
+ <Box columnGap={2} flexWrap='wrap'>
645
724
  {item.kind === 'plan' && item.item !== null && (
646
725
  <Button
647
726
  key={`approve-${item.id}`}
648
- label="approve"
727
+ label='approve'
649
728
  onPress={async () => $.ui.toast(await answer($, item.id, { action: 'approve' }))}
650
- variant="primary"
729
+ variant='primary'
651
730
  />
652
731
  )}
653
732
  {item.kind === 'plan' && item.item !== null && (
654
- <Button key={`revise-${item.id}`} label="revise" onPress={() => $.prompt.fill({ text: `/revise ${item.item ?? ''} ` })} />
733
+ <Button
734
+ key={`revise-${item.id}`}
735
+ label='revise'
736
+ onPress={() => $.prompt.fill({ text: `/revise ${item.item ?? ''} ` })}
737
+ />
655
738
  )}
656
739
  {item.commands.slice(0, 2).map((command, n) => (
657
- <Button key={`copy-${item.id}-${n}`} label={`copy: ${command}`} onPress={(press) => copy($, command, press.surface)} />
740
+ <Button
741
+ key={`copy-${item.id}-${n}`}
742
+ label={`copy: ${command}`}
743
+ onPress={(press) => copy($, command, press.surface)}
744
+ />
658
745
  ))}
659
746
  {item.links.slice(0, 2).map((href, n) => (
660
- <Link href={href} key={`link-${item.id}-${n}`} label="open" />
747
+ <Link href={href} key={`link-${item.id}-${n}`} label='open' />
661
748
  ))}
662
749
  {item.kind !== 'plan' && (
663
- <Button key={`done-${item.id}`} label="done" onPress={async () => $.ui.toast(await answer($, item.id, { action: 'done' }))} />
750
+ <Button
751
+ key={`done-${item.id}`}
752
+ label='done'
753
+ onPress={async () => $.ui.toast(await answer($, item.id, { action: 'done' }))}
754
+ />
664
755
  )}
665
756
  </Box>
666
757
  </Box>
667
758
  ))}
668
759
  {projectGroups(snap.workers).map((group) => (
669
- <Box flexDirection="column" key={`project-${group.project}`}>
760
+ <Box flexDirection='column' key={`project-${group.project}`}>
670
761
  <Box columnGap={1}>
671
- <Text dimColor wrap="truncate">
672
- {rule(`${group.project} · ${group.running}/${group.rows.length} running`).slice(0, Math.max(10, e.props.bodyColumns - 18))}
762
+ <Text dimColor wrap='truncate'>
763
+ {rule(`${group.project} · ${group.running}/${group.rows.length} running`).slice(
764
+ 0,
765
+ Math.max(10, e.props.bodyColumns - 18),
766
+ )}
673
767
  </Text>
674
- <Button key={`pause-${group.project}`} label="pause" onPress={async () => $.ui.toast(await override($, 'pause', group.project))} />
675
- <Button key={`focus-${group.project}`} label="focus" onPress={async () => $.ui.toast(await override($, 'focus', group.project))} />
768
+ <Button
769
+ key={`pause-${group.project}`}
770
+ label='pause'
771
+ onPress={async () => $.ui.toast(await override($, 'pause', group.project))}
772
+ />
773
+ <Button
774
+ key={`focus-${group.project}`}
775
+ label='focus'
776
+ onPress={async () => $.ui.toast(await override($, 'focus', group.project))}
777
+ />
676
778
  </Box>
677
779
  {group.rows.map((w) => (
678
- <Box flexDirection="column" key={`worker-${w.id}`}>
679
- <Button key={`row-${w.id}`} onPress={() => update($, expanded, (name) => (name === w.name ? null : w.name))} plain>
780
+ <Box flexDirection='column' key={`worker-${w.id}`}>
781
+ <Button
782
+ key={`row-${w.id}`}
783
+ onPress={() => update($, expanded, (name) => (name === w.name ? null : w.name))}
784
+ plain
785
+ >
680
786
  {glyph(w.status)} {w.name} {w.phase ?? ''}{' '}
681
787
  <Text dimColor>
682
788
  {workerDoing(w)} · {age(w.updatedAt, now)} · {tokens(w.spendToday)} tok
683
789
  </Text>
684
790
  </Button>
685
791
  {open === w.name && (
686
- <Box columnGap={2} flexWrap="wrap" marginLeft={2}>
687
- <Button key={`attach-${w.id}`} label="attach" onPress={async () => $.ui.toast(await attach($, w.id, w.name))} />
688
- <Button key={`why-${w.id}`} label="why" onPress={async () => $.ui.toast(await why($, w.item))} />
792
+ <Box columnGap={2} flexWrap='wrap' marginLeft={2}>
793
+ <Button
794
+ key={`attach-${w.id}`}
795
+ label='attach'
796
+ onPress={async () => $.ui.toast(await attach($, w.id, w.name))}
797
+ />
798
+ <Button key={`why-${w.id}`} label='why' onPress={async () => $.ui.toast(await why($, w.item))} />
689
799
  <Text dimColor>
690
800
  {w.status}
691
801
  {w.waits.length > 0 ? ` · waits on ${w.waits.join(', ')}` : ''}
@@ -696,7 +806,7 @@ export const register: Register = (on) => {
696
806
  ))}
697
807
  </Box>
698
808
  ))}
699
- <Text dimColor wrap="truncate">
809
+ <Text dimColor wrap='truncate'>
700
810
  {rule(`Today: ${tokens(spendToday(snap.workers))} tokens · ${snap.workers.length} workers`)}
701
811
  </Text>
702
812
  </Box>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@detergent-software/conductor",
3
- "version": "1.0.1-dev.1",
3
+ "version": "1.1.0-dev.1",
4
4
  "description": "Conductor: the local daemon that runs conducted projects' work loops (conduct serve) and its conduct CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,8 @@
17
17
  "scripts": {
18
18
  "build": "tsc",
19
19
  "dev": "tsc --watch",
20
+ "format": "prettier --write \"src/**/*.{ts,tsx}\" \"mod/**/*.{ts,tsx}\" \"scripts/**/*.mjs\"",
21
+ "format:check": "prettier --check \"src/**/*.{ts,tsx}\" \"mod/**/*.{ts,tsx}\" \"scripts/**/*.mjs\"",
20
22
  "lint": "eslint src/",
21
23
  "lint:fix": "eslint src/ --fix --cache",
22
24
  "test": "vitest run",
@@ -41,22 +43,19 @@
41
43
  "zod": "^4.0.0"
42
44
  },
43
45
  "devDependencies": {
44
- "@eslint/js": "^9.0.0",
46
+ "@atk/eslint-config": "workspace:*",
47
+ "@atk/tsconfig": "workspace:*",
45
48
  "@semantic-release/changelog": "^6.0.3",
46
49
  "@semantic-release/commit-analyzer": "^13.0.1",
47
50
  "@semantic-release/git": "^10.0.1",
48
51
  "@semantic-release/release-notes-generator": "^14.1.0",
49
- "@types/node": "^24.0.0",
50
- "@types/semver": "^7.5.0",
51
- "@vitest/eslint-plugin": "^1.0.0",
52
+ "@types/node": "catalog:",
53
+ "@types/semver": "catalog:",
52
54
  "conventional-changelog-conventionalcommits": "^9.1.0",
53
- "eslint": "^9.0.0",
54
- "eslint-config-prettier": "^10.0.0",
55
- "eslint-plugin-perfectionist": "^5.0.0",
56
- "prettier": "^3.4.0",
55
+ "eslint": "catalog:",
56
+ "prettier": "catalog:",
57
57
  "semantic-release": "^25.0.3",
58
- "typescript": "^5.7.0",
59
- "typescript-eslint": "^8.0.0",
60
- "vitest": "^4.0.0"
58
+ "typescript": "catalog:",
59
+ "vitest": "catalog:"
61
60
  }
62
61
  }