@pugi/cli 0.1.0-beta.2 → 0.1.0-beta.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.
@@ -1,41 +1,26 @@
1
- [?25l▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
2
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
3
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
4
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
5
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
6
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
7
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
8
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
9
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
10
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
11
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
12
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
13
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
14
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
15
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
16
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
17
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
18
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
19
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
20
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
21
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
22
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
23
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
24
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
25
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
26
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
27
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
28
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
29
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
30
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
31
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
32
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
33
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
34
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
35
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
36
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
37
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
38
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
39
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
40
- ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
1
+ [?25l 
2
+ 
3
+ 
4
+ 
5
+ 
6
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
7
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
8
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
9
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
10
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
11
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
12
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
13
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
14
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ ▄ 
15
+ ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄ 
16
+ ▄▄▄▄▄▄▄▄▄▄▄ ▄ 
17
+ 
18
+ ▄▄▄▄▄ ▄ ▄▄▄▄ ▄ 
19
+ ▄ ▄ ▄ ▄ ▄ ▄▄ ▄ ▄ ▄ 
20
+ ▄ ▄▄▄▄ ▄▄▄▄▄▄ ▄ ▄▄▄▄ 
21
+ ▄▄▄ 
22
+ 
23
+ 
24
+ 
25
+ 
41
26
  [?25h
@@ -22,10 +22,7 @@ import { getJobRegistry, summarizeJobsForPrompt, } from '../jobs/registry.js';
22
22
  const COMMON_LOCAL_FIRST_PREAMBLE = [
23
23
  'You are the Pugi CLI agent running locally inside the operator\'s repository.',
24
24
  'The local filesystem is the source of truth. Every change you make is committed locally; nothing is uploaded by default (ADR-0037 local-first).',
25
- 'You have a tool registry: read, write, edit, grep, glob, bash, apply_patch, lsp_hover, lsp_definition, lsp_references, lsp_diagnostics, worktree_create, worktree_promote, worktree_drop. Call tools to inspect and modify the workspace.',
26
- 'Use lsp_hover / lsp_definition / lsp_references when you need precise symbol intel — they are faster and more reliable than grep for typed languages (TS/JS/Python/Go/Rust). Fall back to grep when the LSP server is unavailable.',
27
- 'Use apply_patch when you have a complete unified diff in hand (e.g. forwarded from an external review). For incremental edits, prefer edit/write over apply_patch — the failure modes are clearer.',
28
- 'Use worktree_create + worktree_promote when a multi-file change needs validation before landing on the operator\'s working tree. Drop the worktree with worktree_drop when done; never leave scratch worktrees around.',
25
+ 'You have a tool registry: read, write, edit, grep, glob, bash. Call tools to inspect and modify the workspace.',
29
26
  'Cite file paths relative to the workspace root. Keep edits minimal and reversible.',
30
27
  'When you are done, return a single final text answer that the operator can read on the CLI.',
31
28
  ].join(' ');
@@ -577,6 +577,18 @@ export class ReplSession {
577
577
  await this.dispatchStop(verdict.persona);
578
578
  return verdict;
579
579
  }
580
+ case 'delegate': {
581
+ // α7.5 Phase 1: surface the dispatch intent inline. The actual
582
+ // wire shape (POST /api/pugi/sessions/:id/delegate) requires the
583
+ // SDK transport extension that ships alongside this PR; the
584
+ // REPL session module wires the call when the matching transport
585
+ // method lands (paired CLI follow-up). Today we surface the
586
+ // delegation intent in the transcript so the operator sees the
587
+ // verdict echo for muscle-memory before the round-trip lights up.
588
+ this.appendSystemLine(`delegate ${verdict.persona}: ${verdict.brief.length > 80 ? `${verdict.brief.slice(0, 77)}...` : verdict.brief}`);
589
+ this.appendSystemLine('Run `pugi delegate <slug> "<brief>"` from a fresh shell while the REPL transport wiring lands.');
590
+ return verdict;
591
+ }
580
592
  case 'dispatch': {
581
593
  await this.dispatchBrief(verdict.brief);
582
594
  return verdict;
@@ -1382,21 +1394,8 @@ export class ReplSession {
1382
1394
  void this.recreateSessionSilently();
1383
1395
  return;
1384
1396
  }
1385
- // α6.14.4 CEO dogfood 2026-05-25 (parity with Claude Code):
1386
- // collapse the repeated "Stream interrupted (fetch failed).
1387
- // Reconnecting." spam. The status bar already shows
1388
- // connection='reconnecting' AND the attempt counter; pushing
1389
- // a fresh transcript row per attempt fills the screen with
1390
- // noise. Only emit the system line for the FIRST drop of a
1391
- // run; subsequent reconnects update the status bar silently
1392
- // until either success (clears the connection state) or the
1393
- // give-up path in scheduleReconnect prints the final hint.
1394
- const wasOnline = this.state.connection === 'on_watch'
1395
- || this.state.connection === 'connecting';
1396
1397
  this.patch({ connection: 'reconnecting' });
1397
- if (wasOnline) {
1398
- this.appendSystemLine(`Stream interrupted (${this.errorMessage(error)}). Reconnecting...`);
1399
- }
1398
+ this.appendSystemLine(`Stream interrupted (${this.errorMessage(error)}). Reconnecting.`);
1400
1399
  this.scheduleReconnect();
1401
1400
  },
1402
1401
  });
@@ -52,6 +52,7 @@ export const SLASH_COMMAND_HELP = Object.freeze([
52
52
  // Workforce dispatch
53
53
  { name: 'brief', args: '<text>', gloss: 'Dispatch a brief to the workforce', group: 'Workforce dispatch' },
54
54
  { name: 'agents', args: '', gloss: 'List the on-watch agent roster', group: 'Workforce dispatch' },
55
+ { name: 'delegate', args: '<slug> <brief>', gloss: 'Dispatch a brief to one Tier 1 specialist (α7.5)', group: 'Workforce dispatch' },
55
56
  { name: 'stop', args: '<persona>', gloss: 'Stop one agent by persona slug', group: 'Workforce dispatch' },
56
57
  { name: 'jobs', args: '', gloss: 'List background jobs', group: 'Workforce dispatch' },
57
58
  { name: 'ask', args: '<question>', gloss: 'Surface a yes/no modal locally (α6.3 forcing question)', group: 'Workforce dispatch' },
@@ -135,6 +136,35 @@ export function parseSlashCommand(input) {
135
136
  case 'roster': {
136
137
  return { kind: 'roster' };
137
138
  }
139
+ case 'delegate': {
140
+ // tail must start with the persona slug followed by the brief.
141
+ // Slug accepts only the closed-set lowercase ASCII pattern the
142
+ // server-side persona registry enforces; anything else surfaces
143
+ // as a usage error so the operator sees the typo before the
144
+ // round-trip.
145
+ const space = tail.indexOf(' ');
146
+ if (space === -1 || space === 0) {
147
+ return {
148
+ kind: 'error',
149
+ message: 'Usage: /delegate <slug> <one-sentence brief>',
150
+ };
151
+ }
152
+ const persona = tail.slice(0, space).toLowerCase();
153
+ const brief = tail.slice(space + 1).trim();
154
+ if (!/^[a-z_]+$/.test(persona)) {
155
+ return {
156
+ kind: 'error',
157
+ message: `/delegate slug must be lowercase ASCII; got '${persona}'`,
158
+ };
159
+ }
160
+ if (brief.length === 0) {
161
+ return {
162
+ kind: 'error',
163
+ message: 'Usage: /delegate <slug> <one-sentence brief>',
164
+ };
165
+ }
166
+ return { kind: 'delegate', persona, brief };
167
+ }
138
168
  case 'stop':
139
169
  case 'kill': {
140
170
  if (tail.length === 0) {
@@ -17,8 +17,10 @@ import { toolRegistry, toolSchemaBundleHashInput } from '../tools/registry.js';
17
17
  import { webFetchTool } from '../tools/web-fetch.js';
18
18
  import { emptyIndex, rebuildIndex, readIndex, upsertArtifact, writeIndex, } from '../core/index-store.js';
19
19
  import { signatureForPlanReview } from '../core/repl/ask.js';
20
- import { buildRuntimeConfig, loadRuntimeConfig, pollDeviceFlow, pugiHandoffBundleSchema, pugiSyncDryRunPlanSchema, pugiSyncPrivacyModeSchema, pugiSyncRequestSchema, pugiSyncUploadPlanSchema, pugiTripleReviewRequestSchema, startDeviceFlow, submitSync, submitTripleReview, } from '@pugi/sdk';
20
+ import { buildRuntimeConfig, fetchPersonaRoster, loadRuntimeConfig, openPugiSession, pollDeviceFlow, pugiHandoffBundleSchema, pugiSyncDryRunPlanSchema, pugiSyncPrivacyModeSchema, pugiSyncRequestSchema, pugiSyncUploadPlanSchema, pugiTripleReviewRequestSchema, startDeviceFlow, submitDelegate, submitSync, submitTripleReview, } from '@pugi/sdk';
21
21
  import { PUGI_TAGLINE } from '@pugi/personas';
22
+ import { resolveRoster, renderRosterTable } from './commands/roster.js';
23
+ import { runDelegateCommand } from './commands/delegate.js';
22
24
  import { clearApiKey, DEFAULT_API_URL, listStoredCredentials, maskApiKey, normalizeApiUrl, purgeAllCredentials, readCredentialsFile, resolveActiveCredential, storeApiKey, switchActiveAccount, } from '../core/credentials.js';
23
25
  import { runDeployCommand } from '../commands/deploy.js';
24
26
  import { runJobsCommand } from '../commands/jobs.js';
@@ -28,9 +30,6 @@ import { runUndoCommand } from './commands/undo.js';
28
30
  import { runBudgetCommand } from './commands/budget.js';
29
31
  import { runSkillsCommand } from './commands/skills.js';
30
32
  import { runAgentsCommand } from './commands/agents.js';
31
- import { runLspCommand } from './commands/lsp.js';
32
- import { runPatchCommand } from './commands/patch.js';
33
- import { runWorktreeCommand } from './commands/worktree.js';
34
33
  import { resolveWorkspaceLabel } from '../core/repl/workspace-context.js';
35
34
  import { runReviewConsensus } from './commands/review-consensus.js';
36
35
  import { FtsSyntaxError, SqliteSessionStore, resolveProjectStoreDir } from '../core/repl/store/index.js';
@@ -47,7 +46,7 @@ import { dispatchEdit, } from '../core/edits/index.js';
47
46
  * packages/pugi-sdk/package.json); the publish workflow validates the
48
47
  * three are in lockstep.
49
48
  */
50
- const PUGI_CLI_VERSION = "0.1.0-beta.2";
49
+ const PUGI_CLI_VERSION = "0.1.0-beta.4";
51
50
  const handlers = {
52
51
  accounts,
53
52
  agents: dispatchAgents,
@@ -56,6 +55,7 @@ const handlers = {
56
55
  budget: dispatchBudget,
57
56
  code: runEngineTask('code'),
58
57
  config: dispatchConfig,
58
+ delegate: dispatchDelegate,
59
59
  deploy: dispatchDeploy,
60
60
  doctor,
61
61
  explain: runEngineTask('explain'),
@@ -67,13 +67,12 @@ const handlers = {
67
67
  jobs,
68
68
  login,
69
69
  logout,
70
- lsp: dispatchLsp,
71
- patch: dispatchPatch,
72
70
  plan: runEngineTask('plan'),
73
71
  'plan-review': dispatchPlanReview,
74
72
  privacy: dispatchPrivacy,
75
73
  review,
76
74
  resume,
75
+ roster: dispatchRoster,
77
76
  sessions,
78
77
  skills: dispatchSkills,
79
78
  sync,
@@ -81,7 +80,6 @@ const handlers = {
81
80
  version,
82
81
  web: dispatchWeb,
83
82
  whoami,
84
- worktree: dispatchWorktree,
85
83
  };
86
84
  /**
87
85
  * α6.3 `pugi ask "<question>"` — surface the office-hours forcing-question
@@ -252,6 +250,59 @@ async function dispatchPrivacy(args, flags, _session) {
252
250
  writeOutput: (payload, text) => writeOutput(flags, payload, text),
253
251
  });
254
252
  }
253
+ /**
254
+ * `pugi roster` - α7.5 Phase 1.
255
+ *
256
+ * List the live Tier 1 personas with display name, role, and routing
257
+ * tag. Walks the remote /api/pugi/sessions/roster endpoint when a
258
+ * credential is available; falls back to the local @pugi/personas
259
+ * roster when offline so the operator can still see who is on the team.
260
+ */
261
+ async function dispatchRoster(_args, flags, _session) {
262
+ const credential = resolveActiveCredential();
263
+ const config = credential
264
+ ? buildRuntimeConfig({ apiUrl: credential.apiUrl, apiKey: credential.apiKey })
265
+ : null;
266
+ const { rows, warning } = await resolveRoster(config);
267
+ const payload = {
268
+ ok: true,
269
+ personas: rows,
270
+ warning,
271
+ };
272
+ const text = (warning ? `# warning: ${warning}\n\n` : '') +
273
+ renderRosterTable(rows);
274
+ writeOutput(flags, payload, text);
275
+ }
276
+ /**
277
+ * `pugi delegate <slug> "<brief>"` - α7.5 Phase 1.
278
+ *
279
+ * Open a fresh REPL session and POST the brief to one Tier 1 persona,
280
+ * bypassing Mira's coordinator pass. Non-interactive: the CLI prints
281
+ * the dispatch id on success and exits; the operator (or a script) can
282
+ * subscribe to the session stream separately if they want the live
283
+ * lifecycle. Interactive operators use `/delegate` from inside the REPL
284
+ * instead so the dispatch lifecycle surfaces inline.
285
+ */
286
+ async function dispatchDelegate(args, flags, _session) {
287
+ await runDelegateCommand(args, {
288
+ workspaceCwd: process.cwd(),
289
+ writeOutput: (payload, text) => writeOutput(flags, payload, text),
290
+ resolveConfig: () => {
291
+ const credential = resolveActiveCredential();
292
+ if (!credential)
293
+ return null;
294
+ return buildRuntimeConfig({ apiUrl: credential.apiUrl, apiKey: credential.apiKey });
295
+ },
296
+ fetchRoster: fetchPersonaRoster,
297
+ submitDelegate,
298
+ openSession: async (config, workspaceCwd) => {
299
+ const result = await openPugiSession(config, { workspaceCwd });
300
+ if (result.status === 'ok')
301
+ return { sessionId: result.response.sessionId };
302
+ return { error: `${result.status}: ${result.message}` };
303
+ },
304
+ });
305
+ }
255
306
  async function dispatchUndo(args, flags, session) {
256
307
  await runUndoCommand(args, {
257
308
  workspaceRoot: process.cwd(),
@@ -320,46 +371,6 @@ async function dispatchWeb(args, flags, _session) {
320
371
  }
321
372
  writeOutput(flags, result, `# ${result.title}\n# ${result.url}\n# fetched ${result.fetched_at}\n\n${result.content_md}`);
322
373
  }
323
- /**
324
- * α7.7: `pugi lsp <op> <file> [args]` — direct LSP queries. Delegated
325
- * to the standalone runner in `./commands/lsp.ts` so the giant cli.ts
326
- * dispatch table stays narrow. The runner spawns + tears down the LSP
327
- * server per invocation (no daemon yet — that ships in α7.7b).
328
- */
329
- async function dispatchLsp(args, flags, _session) {
330
- const result = await runLspCommand(args, { cwd: process.cwd(), json: flags.json });
331
- if (flags.json)
332
- console.log(result.text);
333
- else
334
- console.log(result.text);
335
- if (result.exitCode !== 0)
336
- process.exitCode = result.exitCode;
337
- }
338
- /**
339
- * α7.7: `pugi patch` — apply a unified-diff patch from stdin or a file.
340
- * Routes through the same security gate as the Layer A/B/C applicators
341
- * (see `src/core/edits/security-gate.ts`). Exit codes mirror the
342
- * security taxonomy so CI loops can alert on hostile patches without
343
- * confusing them with operator typos.
344
- */
345
- async function dispatchPatch(args, flags, _session) {
346
- const result = await runPatchCommand(args, { cwd: process.cwd(), json: flags.json });
347
- console.log(result.text);
348
- if (result.exitCode !== 0)
349
- process.exitCode = result.exitCode;
350
- }
351
- /**
352
- * α7.7: `pugi worktree <op>` — manual scratch worktree management.
353
- * The `pugi build` and `pugi review --consensus` paths use the same
354
- * primitives internally (`createWorktree` / `promoteWorktree`); this
355
- * surface is the operator escape hatch for debug + experiment flows.
356
- */
357
- async function dispatchWorktree(args, flags, _session) {
358
- const result = await runWorktreeCommand(args, { cwd: process.cwd(), json: flags.json });
359
- console.log(result.text);
360
- if (result.exitCode !== 0)
361
- process.exitCode = result.exitCode;
362
- }
363
374
  export async function runCli(argv) {
364
375
  const { command, args, flags, isBareInvocation } = parseArgs(argv);
365
376
  // Bare `pugi` on a TTY enters the REPL-by-default agentic session
@@ -573,6 +584,10 @@ async function help(_args, flags, _session) {
573
584
  ' pugi ask "<question>" Surface a yes/no question modal locally.',
574
585
  ' pugi plan-review <task> Generate + present a plan-review modal.',
575
586
  '',
587
+ 'Persona dispatch (α7.5):',
588
+ ' pugi roster List the live Tier 1 personas + roles.',
589
+ ' pugi delegate <slug> "<brief>" Dispatch a brief to one specialist.',
590
+ '',
576
591
  'Deploy:',
577
592
  ' pugi deploy --target vercel <vercelProject> --project <id>',
578
593
  ' Trigger a Vercel deployment from the bound Git source.',
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Run the delegate command. Parses args, validates the brief, opens a
3
+ * fresh session, and POSTs the delegate brief.
4
+ *
5
+ * Sets `process.exitCode` instead of throwing so the caller (cli.ts
6
+ * handlers table) does not need a try/catch wrapper.
7
+ */
8
+ export async function runDelegateCommand(args, ctx) {
9
+ const slug = args[0];
10
+ const brief = args.slice(1).join(' ').trim();
11
+ if (!slug || !brief) {
12
+ ctx.writeOutput({
13
+ ok: false,
14
+ error: 'Usage: pugi delegate <persona-slug> "<one-sentence brief>"',
15
+ }, 'Usage: pugi delegate <persona-slug> "<one-sentence brief>"');
16
+ process.exitCode = 2;
17
+ return;
18
+ }
19
+ // Brief size cap mirrors the server-side DTO (8000 chars). The local
20
+ // gate lets the operator know about the truncation before we waste a
21
+ // round-trip on a too-large brief.
22
+ if (brief.length > 8000) {
23
+ ctx.writeOutput({
24
+ ok: false,
25
+ error: `brief is ${brief.length} chars; max 8000`,
26
+ }, `pugi delegate: brief is ${brief.length} chars; max 8000.`);
27
+ process.exitCode = 2;
28
+ return;
29
+ }
30
+ const config = ctx.resolveConfig();
31
+ if (!config) {
32
+ ctx.writeOutput({
33
+ ok: false,
34
+ error: 'no Pugi credential configured; run `pugi login` first',
35
+ }, 'pugi delegate: no credential configured. Run `pugi login` first.');
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ const opened = await ctx.openSession(config, ctx.workspaceCwd);
40
+ if ('error' in opened) {
41
+ ctx.writeOutput({ ok: false, error: opened.error }, `pugi delegate: session open failed: ${opened.error}`);
42
+ process.exitCode = 1;
43
+ return;
44
+ }
45
+ const result = await ctx.submitDelegate(config, opened.sessionId, {
46
+ persona: slug,
47
+ brief,
48
+ });
49
+ switch (result.status) {
50
+ case 'ok':
51
+ ctx.writeOutput({
52
+ ok: true,
53
+ sessionId: opened.sessionId,
54
+ dispatchId: result.response.dispatchId,
55
+ personaSlug: result.response.personaSlug,
56
+ }, `dispatched ${result.response.personaSlug} (dispatchId=${result.response.dispatchId}); stream via GET /sessions/${opened.sessionId}/stream.`);
57
+ return;
58
+ case 'unknown_persona':
59
+ ctx.writeOutput({ ok: false, error: result.message, code: result.code }, `pugi delegate: ${result.message}`);
60
+ process.exitCode = 3;
61
+ return;
62
+ case 'quota_exceeded':
63
+ ctx.writeOutput({ ok: false, error: result.message, code: result.code }, `pugi delegate: ${result.message}`);
64
+ process.exitCode = 4;
65
+ return;
66
+ case 'endpoint_missing':
67
+ ctx.writeOutput({
68
+ ok: false,
69
+ error: 'runtime does not expose POST /api/pugi/sessions/:id/delegate (upgrade admin-api to α7.5+)',
70
+ code: result.code,
71
+ }, 'pugi delegate: runtime does not expose the delegate endpoint. Upgrade admin-api to α7.5+.');
72
+ process.exitCode = 1;
73
+ return;
74
+ case 'unauthenticated':
75
+ case 'failed':
76
+ ctx.writeOutput({ ok: false, error: result.message, code: result.code }, `pugi delegate: ${result.message}`);
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+ }
81
+ //# sourceMappingURL=delegate.js.map