pi-messenger 0.7.3
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/ARCHITECTURE.md +244 -0
- package/CHANGELOG.md +418 -0
- package/README.md +394 -0
- package/banner.png +0 -0
- package/config-overlay.ts +172 -0
- package/config.ts +178 -0
- package/crew/agents/crew-docs-scout.md +55 -0
- package/crew/agents/crew-gap-analyst.md +105 -0
- package/crew/agents/crew-github-scout.md +111 -0
- package/crew/agents/crew-interview-generator.md +79 -0
- package/crew/agents/crew-plan-sync.md +64 -0
- package/crew/agents/crew-practice-scout.md +62 -0
- package/crew/agents/crew-repo-scout.md +65 -0
- package/crew/agents/crew-reviewer.md +58 -0
- package/crew/agents/crew-web-scout.md +85 -0
- package/crew/agents/crew-worker.md +95 -0
- package/crew/agents.ts +200 -0
- package/crew/handlers/interview.ts +211 -0
- package/crew/handlers/plan.ts +358 -0
- package/crew/handlers/review.ts +341 -0
- package/crew/handlers/status.ts +257 -0
- package/crew/handlers/sync.ts +232 -0
- package/crew/handlers/task.ts +511 -0
- package/crew/handlers/work.ts +289 -0
- package/crew/id-allocator.ts +44 -0
- package/crew/index.ts +229 -0
- package/crew/state.ts +116 -0
- package/crew/store.ts +480 -0
- package/crew/types.ts +164 -0
- package/crew/utils/artifacts.ts +65 -0
- package/crew/utils/config.ts +104 -0
- package/crew/utils/discover.ts +170 -0
- package/crew/utils/install.ts +373 -0
- package/crew/utils/progress.ts +107 -0
- package/crew/utils/result.ts +16 -0
- package/crew/utils/truncate.ts +79 -0
- package/crew-overlay.ts +259 -0
- package/handlers.ts +799 -0
- package/index.ts +591 -0
- package/lib.ts +232 -0
- package/overlay.ts +687 -0
- package/package.json +20 -0
- package/skills/pi-messenger-crew/SKILL.md +140 -0
- package/store.ts +1068 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
<p>
|
|
2
|
+
<img src="banner.png" alt="pi-messenger" width="1100">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# Pi Messenger
|
|
6
|
+
|
|
7
|
+
**What if multiple agents in different terminals sharing a folder could talk to each other like they're in a chat room?** Join, see who's online. Claim tasks, reserve files, send messages. Built on [Pi's](https://github.com/badlogic/pi-mono) extension system. No daemon, no server, just files.
|
|
8
|
+
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[]()
|
|
11
|
+
|
|
12
|
+
> ⚠️ **Beta** - Core messaging and file reservations are stable. **Crew task orchestration** (plan/work/review) is newer and not fully tested yet. Please [open an issue](https://github.com/nicobailon/pi-messenger/issues) if you encounter problems.
|
|
13
|
+
|
|
14
|
+
Pi-messenger adds a `pi_messenger` tool that **agents use** for coordination. You don't type these commands - you ask your agent to do things, and it calls `pi_messenger` behind the scenes.
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Multi-Agent Coordination
|
|
19
|
+
|
|
20
|
+
Once joined (manually or via auto-join config), agents can coordinate:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
pi_messenger({ action: "reserve", paths: ["src/auth/"], reason: "Refactoring" })
|
|
24
|
+
// → Reserved src/auth/ - other agents will be blocked
|
|
25
|
+
|
|
26
|
+
// ... does the work ...
|
|
27
|
+
|
|
28
|
+
pi_messenger({ action: "release" })
|
|
29
|
+
// → Released all reservations
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> **Tip:** Set `autoRegister: true` in your config to auto-join on startup. Otherwise, agents join with `pi_messenger({ action: "join" })`.
|
|
33
|
+
|
|
34
|
+
### Crew Task Orchestration
|
|
35
|
+
|
|
36
|
+
Ask your agent to plan and execute from a PRD:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
pi_messenger({ action: "plan" })
|
|
40
|
+
// → Scouts analyze codebase, gap-analyst creates tasks
|
|
41
|
+
|
|
42
|
+
pi_messenger({ action: "work", autonomous: true })
|
|
43
|
+
// → Workers execute tasks in waves until done
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> **Note:** Crew agents (scouts, workers, reviewers) automatically join the mesh as their first action.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
Copy to your extensions directory and restart pi:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
~/.pi/agent/extensions/pi-messenger/
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
After joining, your agent name appears in the status bar:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
msg: SwiftRaven (2 peers) ●3
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
**Discovery** - Agents register with memorable names (SwiftRaven, IronKnight). See who's active, what model they're using, which git branch they're on.
|
|
65
|
+
|
|
66
|
+
**Messaging** - Send messages between agents. Recipients wake up immediately and see the message as a steering prompt. Great for handoffs and coordination.
|
|
67
|
+
|
|
68
|
+
**File Reservations** - Claim files or directories. Other agents get blocked with a clear message telling them who to coordinate with. Auto-releases on exit.
|
|
69
|
+
|
|
70
|
+
**Swarm Coordination** - Multiple agents work on the same spec file. Claim tasks atomically, mark them complete, see who's doing what.
|
|
71
|
+
|
|
72
|
+
## Crew: Task Orchestration
|
|
73
|
+
|
|
74
|
+
Crew provides multi-agent task orchestration with a simplified PRD-based workflow.
|
|
75
|
+
|
|
76
|
+
### Basic Workflow
|
|
77
|
+
|
|
78
|
+
1. **Plan** - Scouts analyze your codebase and PRD, gap-analyst creates tasks
|
|
79
|
+
2. **Work** - Workers implement tasks in parallel waves
|
|
80
|
+
3. **Review** - Reviewer checks each implementation
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// Plan from your PRD (auto-discovers PRD.md, SPEC.md, etc.)
|
|
84
|
+
pi_messenger({ action: "plan" })
|
|
85
|
+
|
|
86
|
+
// Or specify PRD path explicitly
|
|
87
|
+
pi_messenger({ action: "plan", prd: "docs/PRD.md" })
|
|
88
|
+
|
|
89
|
+
// Execute tasks (spawns parallel workers)
|
|
90
|
+
pi_messenger({ action: "work" })
|
|
91
|
+
|
|
92
|
+
// Or run autonomously until done/blocked
|
|
93
|
+
pi_messenger({ action: "work", autonomous: true })
|
|
94
|
+
|
|
95
|
+
// Review a specific task
|
|
96
|
+
pi_messenger({ action: "review", target: "task-1" })
|
|
97
|
+
// → SHIP ✅ or NEEDS_WORK 🔄
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Crew API
|
|
101
|
+
|
|
102
|
+
**Planning**
|
|
103
|
+
| Action | Description | Example |
|
|
104
|
+
|--------|-------------|---------|
|
|
105
|
+
| `plan` | Create plan from PRD | `{ action: "plan" }` or `{ action: "plan", prd: "..." }` |
|
|
106
|
+
| `status` | Show progress | `{ action: "status" }` |
|
|
107
|
+
|
|
108
|
+
**Work Execution**
|
|
109
|
+
| Action | Description | Example |
|
|
110
|
+
|--------|-------------|---------|
|
|
111
|
+
| `work` | Run ready tasks | `{ action: "work" }` |
|
|
112
|
+
| `work` (auto) | Run until done/blocked | `{ action: "work", autonomous: true }` |
|
|
113
|
+
|
|
114
|
+
**Task Management**
|
|
115
|
+
| Action | Description | Example |
|
|
116
|
+
|--------|-------------|---------|
|
|
117
|
+
| `task.show` | Show task details | `{ action: "task.show", id: "task-1" }` |
|
|
118
|
+
| `task.list` | List all tasks | `{ action: "task.list" }` |
|
|
119
|
+
| `task.start` | Start task | `{ action: "task.start", id: "task-1" }` |
|
|
120
|
+
| `task.done` | Complete task | `{ action: "task.done", id: "task-1", summary: "..." }` |
|
|
121
|
+
| `task.block` | Block task | `{ action: "task.block", id: "task-1", reason: "..." }` |
|
|
122
|
+
| `task.unblock` | Unblock task | `{ action: "task.unblock", id: "task-1" }` |
|
|
123
|
+
| `task.ready` | List ready tasks | `{ action: "task.ready" }` |
|
|
124
|
+
| `task.reset` | Reset task | `{ action: "task.reset", id: "task-1", cascade: true }` |
|
|
125
|
+
|
|
126
|
+
**Review**
|
|
127
|
+
| Action | Description | Example |
|
|
128
|
+
|--------|-------------|---------|
|
|
129
|
+
| `review` | Review implementation | `{ action: "review", target: "task-1" }` |
|
|
130
|
+
|
|
131
|
+
**Maintenance**
|
|
132
|
+
| Action | Description | Example |
|
|
133
|
+
|--------|-------------|---------|
|
|
134
|
+
| `crew.status` | Overall status | `{ action: "crew.status" }` |
|
|
135
|
+
| `crew.validate` | Validate plan | `{ action: "crew.validate" }` |
|
|
136
|
+
| `crew.agents` | List crew agents | `{ action: "crew.agents" }` |
|
|
137
|
+
| `crew.install` | Install crew agents | `{ action: "crew.install" }` |
|
|
138
|
+
|
|
139
|
+
### Planning Workflow
|
|
140
|
+
|
|
141
|
+
The `plan` action orchestrates a multi-agent analysis:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
145
|
+
│ Your Project │
|
|
146
|
+
│ ├── PRD.md ◄── Scouts discover and read these │
|
|
147
|
+
│ ├── DESIGN.md │
|
|
148
|
+
│ ├── src/ │
|
|
149
|
+
│ └── ... │
|
|
150
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
151
|
+
│
|
|
152
|
+
▼
|
|
153
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
154
|
+
│ Phase 1: Scouts (parallel) │
|
|
155
|
+
│ ├── crew-repo-scout → Analyzes codebase structure │
|
|
156
|
+
│ ├── crew-docs-scout → Reads project documentation │
|
|
157
|
+
│ ├── crew-practice-scout → Finds coding conventions │
|
|
158
|
+
│ ├── crew-web-scout → Searches web for best practices │
|
|
159
|
+
│ └── crew-github-scout → Examines real repos via gh CLI │
|
|
160
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
161
|
+
│
|
|
162
|
+
▼
|
|
163
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
164
|
+
│ Phase 2: Gap Analyst │
|
|
165
|
+
│ └── Synthesizes findings → Creates task breakdown │
|
|
166
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
167
|
+
│
|
|
168
|
+
▼
|
|
169
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
170
|
+
│ Result: Tasks with Dependencies │
|
|
171
|
+
│ ├── task-1: Setup types (no deps) │
|
|
172
|
+
│ ├── task-2: Core logic (depends on task-1) │
|
|
173
|
+
│ ├── task-3: API endpoints (depends on task-1) │
|
|
174
|
+
│ └── task-4: Tests (depends on task-2, task-3) │
|
|
175
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**No special format required** - just put your docs in the project. Scouts will find and read markdown files, READMEs, and code comments.
|
|
179
|
+
|
|
180
|
+
### Autonomous Mode
|
|
181
|
+
|
|
182
|
+
Run tasks continuously until completion:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
pi_messenger({ action: "work", autonomous: true })
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Autonomous mode:
|
|
189
|
+
- Executes waves of parallel workers
|
|
190
|
+
- Reviews each task after completion
|
|
191
|
+
- Auto-blocks on failure
|
|
192
|
+
- Stops when all tasks done or blocked
|
|
193
|
+
- Respects `maxWaves` limit (default: 50)
|
|
194
|
+
|
|
195
|
+
### Crew Overlay Tab
|
|
196
|
+
|
|
197
|
+
The `/messenger` overlay includes a Crew tab showing task status:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
╭─ Messenger ── SwiftRaven ── 2 peers ─────────────────╮
|
|
201
|
+
│ Agents │ ▸ Crew (2/5) │ ● GoldFalcon │ + All │
|
|
202
|
+
├──────────────────────────────────────────────────────┤
|
|
203
|
+
│ │
|
|
204
|
+
│ 📋 docs/PRD.md [2/5] │
|
|
205
|
+
│ │
|
|
206
|
+
│ ✓ task-1 Setup OAuth config │
|
|
207
|
+
│ ✓ task-2 Implement token storage │
|
|
208
|
+
│ ● task-3 Add Google provider (SwiftRaven) │
|
|
209
|
+
│ ○ task-4 Add GitHub provider → task-2 │
|
|
210
|
+
│ ○ task-5 Write tests → task-3, task-4 │
|
|
211
|
+
│ │
|
|
212
|
+
├──────────────────────────────────────────────────────┤
|
|
213
|
+
│ ● AUTO Wave 2 │ 2/5 done │ 1 ready │ ⏱️ 3:42 │
|
|
214
|
+
╰──────────────────────────────────────────────────────╯
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Crew Data Storage
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
.pi/messenger/crew/
|
|
221
|
+
├── plan.json # Plan metadata (PRD path, progress)
|
|
222
|
+
├── plan.md # Gap analyst output
|
|
223
|
+
├── tasks/
|
|
224
|
+
│ ├── task-1.json # Task metadata
|
|
225
|
+
│ ├── task-1.md # Task specification
|
|
226
|
+
│ └── ...
|
|
227
|
+
├── artifacts/ # Debug artifacts
|
|
228
|
+
└── config.json # Project-level crew config
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Crew Configuration
|
|
232
|
+
|
|
233
|
+
Add to `~/.pi/agent/pi-messenger.json`:
|
|
234
|
+
|
|
235
|
+
```json
|
|
236
|
+
{
|
|
237
|
+
"crew": {
|
|
238
|
+
"concurrency": { "scouts": 4, "workers": 2 },
|
|
239
|
+
"review": { "enabled": true, "maxIterations": 3 },
|
|
240
|
+
"work": { "maxAttemptsPerTask": 5, "maxWaves": 50 }
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
| Setting | Description | Default |
|
|
246
|
+
|---------|-------------|---------|
|
|
247
|
+
| `concurrency.scouts` | Max parallel scouts during planning | `4` |
|
|
248
|
+
| `concurrency.workers` | Max parallel workers during work | `2` |
|
|
249
|
+
| `review.enabled` | Auto-review tasks after completion | `true` |
|
|
250
|
+
| `review.maxIterations` | Max review cycles before blocking | `3` |
|
|
251
|
+
| `work.maxAttemptsPerTask` | Retries before blocking a task | `5` |
|
|
252
|
+
| `work.maxWaves` | Max waves in autonomous mode | `50` |
|
|
253
|
+
|
|
254
|
+
### Crew Install
|
|
255
|
+
|
|
256
|
+
Crew agents are **auto-installed** on first use of `plan`, `work`, or `review`. To manually install or update:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
pi_messenger({ action: "crew.install" })
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**What gets installed:**
|
|
263
|
+
- **10 agents** in `~/.pi/agent/agents/` (scouts, analysts, worker, reviewer)
|
|
264
|
+
- **1 skill** in `~/.pi/agent/skills/` (pi-messenger-crew quick reference)
|
|
265
|
+
|
|
266
|
+
To remove:
|
|
267
|
+
```typescript
|
|
268
|
+
pi_messenger({ action: "crew.uninstall" })
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Chat Overlay
|
|
272
|
+
|
|
273
|
+
`/messenger` opens an interactive chat UI:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
╭─ Messenger ── SwiftRaven ── 2 peers ────────────────╮
|
|
277
|
+
│ ▸ Agents │ ● GoldFalcon │ ● IronKnight (1) │ + All │
|
|
278
|
+
├─────────────────────────────────────────────────────┤
|
|
279
|
+
│ ./feature-spec.md: │
|
|
280
|
+
│ SwiftRaven (you) TASK-03 Implementing auth │
|
|
281
|
+
│ GoldFalcon TASK-04 API endpoints │
|
|
282
|
+
├─────────────────────────────────────────────────────┤
|
|
283
|
+
│ > Agents overview [Tab] [Enter] │
|
|
284
|
+
╰─────────────────────────────────────────────────────╯
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
| Key | Action |
|
|
288
|
+
|-----|--------|
|
|
289
|
+
| `Tab` / `←` `→` | Switch tabs |
|
|
290
|
+
| `↑` `↓` | Scroll history |
|
|
291
|
+
| `Enter` | Send message |
|
|
292
|
+
| `Esc` | Close |
|
|
293
|
+
|
|
294
|
+
## Tool Reference
|
|
295
|
+
|
|
296
|
+
### Action-Based API (Recommended)
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
pi_messenger({
|
|
300
|
+
action: string, // Action to perform
|
|
301
|
+
|
|
302
|
+
// Plan
|
|
303
|
+
prd?: string, // PRD file path
|
|
304
|
+
|
|
305
|
+
// Task identifiers
|
|
306
|
+
id?: string, // Task ID (task-N)
|
|
307
|
+
target?: string, // Target for review
|
|
308
|
+
|
|
309
|
+
// Creation
|
|
310
|
+
title?: string, // For task.create
|
|
311
|
+
dependsOn?: string[], // Task dependencies
|
|
312
|
+
|
|
313
|
+
// Completion
|
|
314
|
+
summary?: string, // For task.done
|
|
315
|
+
|
|
316
|
+
// Work options
|
|
317
|
+
autonomous?: boolean, // Run continuously
|
|
318
|
+
concurrency?: number, // Override concurrency
|
|
319
|
+
|
|
320
|
+
// Reset
|
|
321
|
+
cascade?: boolean, // Reset dependent tasks too
|
|
322
|
+
})
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Legacy API
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
pi_messenger({
|
|
329
|
+
// Join
|
|
330
|
+
join?: boolean, // Join the agent mesh
|
|
331
|
+
spec?: string, // Spec file to work on
|
|
332
|
+
|
|
333
|
+
// Swarm
|
|
334
|
+
swarm?: boolean, // Get swarm status
|
|
335
|
+
claim?: string, // Claim a task
|
|
336
|
+
unclaim?: string, // Release without completing
|
|
337
|
+
complete?: string, // Mark task complete
|
|
338
|
+
notes?: string, // Completion notes
|
|
339
|
+
|
|
340
|
+
// Messaging
|
|
341
|
+
to?: string | string[], // Recipient(s)
|
|
342
|
+
broadcast?: boolean, // Send to all
|
|
343
|
+
message?: string, // Message text
|
|
344
|
+
|
|
345
|
+
// Reservations
|
|
346
|
+
reserve?: string[], // Paths to reserve
|
|
347
|
+
reason?: string, // Why reserving/claiming
|
|
348
|
+
release?: string[] | true, // Release reservations
|
|
349
|
+
|
|
350
|
+
// Other
|
|
351
|
+
rename?: string, // Change your name
|
|
352
|
+
list?: boolean, // List active agents
|
|
353
|
+
})
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## Configuration
|
|
357
|
+
|
|
358
|
+
Create `~/.pi/agent/pi-messenger.json`:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"autoRegister": false,
|
|
363
|
+
"autoRegisterPaths": ["~/projects/team-collab"],
|
|
364
|
+
"scopeToFolder": false
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
| Setting | Description | Default |
|
|
369
|
+
|---------|-------------|---------|
|
|
370
|
+
| `autoRegister` | Join mesh on startup | `false` |
|
|
371
|
+
| `autoRegisterPaths` | Folders where auto-join is enabled | `[]` |
|
|
372
|
+
| `scopeToFolder` | Only see agents in same directory | `false` |
|
|
373
|
+
|
|
374
|
+
## How It Works
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
~/.pi/agent/messenger/
|
|
378
|
+
├── registry/ # Agent registrations (PID, cwd, model, spec)
|
|
379
|
+
├── inbox/ # Message delivery
|
|
380
|
+
├── claims.json # Active task claims
|
|
381
|
+
├── completions.json # Completed tasks
|
|
382
|
+
└── swarm.lock # Atomic lock for claims
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
File-based coordination. No daemon. Dead agents detected via PID and cleaned up automatically.
|
|
386
|
+
|
|
387
|
+
## Credits
|
|
388
|
+
|
|
389
|
+
- **[mcp_agent_mail](https://github.com/Dicklesworthstone/mcp_agent_mail)** by [@doodlestein](https://x.com/doodlestein) - Inspiration for agent-to-agent messaging
|
|
390
|
+
- **[Pi coding agent](https://github.com/badlogic/pi-mono/)** by [@badlogicgames](https://x.com/badlogicgames)
|
|
391
|
+
|
|
392
|
+
## License
|
|
393
|
+
|
|
394
|
+
MIT
|
package/banner.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi Messenger - Config Overlay Component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Component, Focusable, TUI } from "@mariozechner/pi-tui";
|
|
6
|
+
import { matchesKey, truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|
7
|
+
import type { Theme } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
import { getAutoRegisterPaths, saveAutoRegisterPaths, matchesAutoRegisterPath } from "./config.js";
|
|
9
|
+
|
|
10
|
+
export class MessengerConfigOverlay implements Component, Focusable {
|
|
11
|
+
readonly width = 60;
|
|
12
|
+
focused = false;
|
|
13
|
+
|
|
14
|
+
private paths: string[];
|
|
15
|
+
private selectedIndex = 0;
|
|
16
|
+
private dirty = false;
|
|
17
|
+
private statusMessage = "";
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private tui: TUI,
|
|
21
|
+
private theme: Theme,
|
|
22
|
+
private done: () => void
|
|
23
|
+
) {
|
|
24
|
+
this.paths = getAutoRegisterPaths();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
handleInput(data: string): void {
|
|
28
|
+
if (matchesKey(data, "escape") || matchesKey(data, "q")) {
|
|
29
|
+
if (this.dirty) {
|
|
30
|
+
saveAutoRegisterPaths(this.paths);
|
|
31
|
+
}
|
|
32
|
+
this.done();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (matchesKey(data, "a")) {
|
|
37
|
+
this.addCurrentPath();
|
|
38
|
+
this.tui.requestRender();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (matchesKey(data, "d") || matchesKey(data, "backspace")) {
|
|
43
|
+
this.deleteSelected();
|
|
44
|
+
this.tui.requestRender();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (matchesKey(data, "up")) {
|
|
49
|
+
if (this.paths.length > 0) {
|
|
50
|
+
this.selectedIndex = Math.max(0, this.selectedIndex - 1);
|
|
51
|
+
this.tui.requestRender();
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (matchesKey(data, "down")) {
|
|
57
|
+
if (this.paths.length > 0) {
|
|
58
|
+
this.selectedIndex = Math.min(this.paths.length - 1, this.selectedIndex + 1);
|
|
59
|
+
this.tui.requestRender();
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private addCurrentPath(): void {
|
|
66
|
+
const cwd = process.cwd();
|
|
67
|
+
if (this.paths.includes(cwd)) {
|
|
68
|
+
this.statusMessage = "Already in list";
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.paths.push(cwd);
|
|
72
|
+
this.selectedIndex = this.paths.length - 1;
|
|
73
|
+
this.dirty = true;
|
|
74
|
+
this.statusMessage = "Added current folder";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private deleteSelected(): void {
|
|
78
|
+
if (this.paths.length === 0) return;
|
|
79
|
+
|
|
80
|
+
const removed = this.paths[this.selectedIndex];
|
|
81
|
+
this.paths.splice(this.selectedIndex, 1);
|
|
82
|
+
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.paths.length - 1));
|
|
83
|
+
this.dirty = true;
|
|
84
|
+
this.statusMessage = `Removed: ${removed.split("/").pop()}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
render(_width: number): string[] {
|
|
88
|
+
const w = this.width;
|
|
89
|
+
const innerW = w - 2;
|
|
90
|
+
const lines: string[] = [];
|
|
91
|
+
const cwd = process.cwd();
|
|
92
|
+
const isCurrentInList = matchesAutoRegisterPath(cwd, this.paths);
|
|
93
|
+
|
|
94
|
+
const border = (s: string) => this.theme.fg("dim", s);
|
|
95
|
+
const pad = (s: string, len: number) => s + " ".repeat(Math.max(0, len - visibleWidth(s)));
|
|
96
|
+
const row = (content: string) => border("│") + pad(" " + content, innerW) + border("│");
|
|
97
|
+
const emptyRow = () => border("│") + " ".repeat(innerW) + border("│");
|
|
98
|
+
|
|
99
|
+
// Top border with title
|
|
100
|
+
const titleText = " Messenger Config ";
|
|
101
|
+
const borderLen = innerW - titleText.length;
|
|
102
|
+
const leftBorder = Math.floor(borderLen / 2);
|
|
103
|
+
const rightBorder = borderLen - leftBorder;
|
|
104
|
+
lines.push(border("╭" + "─".repeat(leftBorder)) + this.theme.fg("accent", titleText) + border("─".repeat(rightBorder) + "╮"));
|
|
105
|
+
|
|
106
|
+
lines.push(emptyRow());
|
|
107
|
+
|
|
108
|
+
// Current folder status
|
|
109
|
+
const cwdDisplay = truncateToWidth(cwd, Math.max(10, innerW - 20));
|
|
110
|
+
lines.push(row(`Current folder: ${cwdDisplay}`));
|
|
111
|
+
const statusColor = isCurrentInList ? "accent" : "dim";
|
|
112
|
+
lines.push(row(`Auto-register: ${this.theme.fg(statusColor, isCurrentInList ? "YES" : "NO")}`));
|
|
113
|
+
|
|
114
|
+
lines.push(emptyRow());
|
|
115
|
+
|
|
116
|
+
// Divider
|
|
117
|
+
lines.push(border("├" + "─".repeat(innerW) + "┤"));
|
|
118
|
+
|
|
119
|
+
lines.push(emptyRow());
|
|
120
|
+
lines.push(row(this.theme.fg("dim", "Auto-register paths:")));
|
|
121
|
+
lines.push(emptyRow());
|
|
122
|
+
|
|
123
|
+
if (this.paths.length === 0) {
|
|
124
|
+
lines.push(row(this.theme.fg("dim", " (none configured)")));
|
|
125
|
+
} else {
|
|
126
|
+
for (let i = 0; i < this.paths.length; i++) {
|
|
127
|
+
const path = this.paths[i];
|
|
128
|
+
const isSelected = i === this.selectedIndex;
|
|
129
|
+
const isCurrent = path === cwd;
|
|
130
|
+
|
|
131
|
+
const marker = isSelected ? this.theme.fg("accent", "▸") : " ";
|
|
132
|
+
const suffix = isCurrent ? this.theme.fg("dim", " (current)") : "";
|
|
133
|
+
const pathDisplay = truncateToWidth(path, Math.max(10, innerW - 15));
|
|
134
|
+
|
|
135
|
+
if (isSelected) {
|
|
136
|
+
lines.push(row(`${marker} ${this.theme.fg("accent", pathDisplay)}${suffix}`));
|
|
137
|
+
} else {
|
|
138
|
+
lines.push(row(`${marker} ${pathDisplay}${suffix}`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
lines.push(emptyRow());
|
|
144
|
+
|
|
145
|
+
// Divider
|
|
146
|
+
lines.push(border("├" + "─".repeat(innerW) + "┤"));
|
|
147
|
+
|
|
148
|
+
lines.push(emptyRow());
|
|
149
|
+
|
|
150
|
+
// Status message
|
|
151
|
+
if (this.statusMessage) {
|
|
152
|
+
lines.push(row(this.theme.fg("accent", this.statusMessage)));
|
|
153
|
+
} else {
|
|
154
|
+
lines.push(emptyRow());
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Help
|
|
158
|
+
const help = "a add d delete ↑↓ navigate Esc save & close";
|
|
159
|
+
lines.push(row(this.theme.fg("dim", help)));
|
|
160
|
+
|
|
161
|
+
// Bottom border
|
|
162
|
+
lines.push(border("╰" + "─".repeat(innerW) + "╯"));
|
|
163
|
+
|
|
164
|
+
return lines;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
invalidate(): void {
|
|
168
|
+
this.statusMessage = "";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
dispose(): void {}
|
|
172
|
+
}
|