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.
Files changed (45) hide show
  1. package/ARCHITECTURE.md +244 -0
  2. package/CHANGELOG.md +418 -0
  3. package/README.md +394 -0
  4. package/banner.png +0 -0
  5. package/config-overlay.ts +172 -0
  6. package/config.ts +178 -0
  7. package/crew/agents/crew-docs-scout.md +55 -0
  8. package/crew/agents/crew-gap-analyst.md +105 -0
  9. package/crew/agents/crew-github-scout.md +111 -0
  10. package/crew/agents/crew-interview-generator.md +79 -0
  11. package/crew/agents/crew-plan-sync.md +64 -0
  12. package/crew/agents/crew-practice-scout.md +62 -0
  13. package/crew/agents/crew-repo-scout.md +65 -0
  14. package/crew/agents/crew-reviewer.md +58 -0
  15. package/crew/agents/crew-web-scout.md +85 -0
  16. package/crew/agents/crew-worker.md +95 -0
  17. package/crew/agents.ts +200 -0
  18. package/crew/handlers/interview.ts +211 -0
  19. package/crew/handlers/plan.ts +358 -0
  20. package/crew/handlers/review.ts +341 -0
  21. package/crew/handlers/status.ts +257 -0
  22. package/crew/handlers/sync.ts +232 -0
  23. package/crew/handlers/task.ts +511 -0
  24. package/crew/handlers/work.ts +289 -0
  25. package/crew/id-allocator.ts +44 -0
  26. package/crew/index.ts +229 -0
  27. package/crew/state.ts +116 -0
  28. package/crew/store.ts +480 -0
  29. package/crew/types.ts +164 -0
  30. package/crew/utils/artifacts.ts +65 -0
  31. package/crew/utils/config.ts +104 -0
  32. package/crew/utils/discover.ts +170 -0
  33. package/crew/utils/install.ts +373 -0
  34. package/crew/utils/progress.ts +107 -0
  35. package/crew/utils/result.ts +16 -0
  36. package/crew/utils/truncate.ts +79 -0
  37. package/crew-overlay.ts +259 -0
  38. package/handlers.ts +799 -0
  39. package/index.ts +591 -0
  40. package/lib.ts +232 -0
  41. package/overlay.ts +687 -0
  42. package/package.json +20 -0
  43. package/skills/pi-messenger-crew/SKILL.md +140 -0
  44. package/store.ts +1068 -0
  45. package/tsconfig.json +19 -0
package/lib.ts ADDED
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Pi Messenger - Types and Pure Utilities
3
+ */
4
+
5
+ import type * as fs from "node:fs";
6
+ import { basename, isAbsolute, resolve, relative } from "node:path";
7
+
8
+ // =============================================================================
9
+ // Types
10
+ // =============================================================================
11
+
12
+ export interface FileReservation {
13
+ pattern: string;
14
+ reason?: string;
15
+ since: string;
16
+ }
17
+
18
+ export interface AgentRegistration {
19
+ name: string;
20
+ pid: number;
21
+ sessionId: string;
22
+ cwd: string;
23
+ model: string;
24
+ startedAt: string;
25
+ reservations?: FileReservation[];
26
+ gitBranch?: string;
27
+ spec?: string;
28
+ }
29
+
30
+ export interface AgentMailMessage {
31
+ id: string;
32
+ from: string;
33
+ to: string;
34
+ text: string;
35
+ timestamp: string;
36
+ replyTo: string | null;
37
+ }
38
+
39
+ export interface ReservationConflict {
40
+ path: string;
41
+ agent: string;
42
+ pattern: string;
43
+ reason?: string;
44
+ registration: AgentRegistration;
45
+ }
46
+
47
+ export interface MessengerState {
48
+ agentName: string;
49
+ registered: boolean;
50
+ watcher: fs.FSWatcher | null;
51
+ watcherRetries: number;
52
+ watcherRetryTimer: ReturnType<typeof setTimeout> | null;
53
+ watcherDebounceTimer: ReturnType<typeof setTimeout> | null;
54
+ reservations: FileReservation[];
55
+ chatHistory: Map<string, AgentMailMessage[]>;
56
+ unreadCounts: Map<string, number>;
57
+ broadcastHistory: AgentMailMessage[];
58
+ seenSenders: Map<string, string>; // name -> sessionId (detects agent restarts)
59
+ gitBranch?: string;
60
+ spec?: string;
61
+ scopeToFolder: boolean; // Only see agents in same cwd
62
+ }
63
+
64
+ export interface Dirs {
65
+ base: string;
66
+ registry: string;
67
+ inbox: string;
68
+ }
69
+
70
+ export interface ClaimEntry {
71
+ agent: string;
72
+ sessionId: string;
73
+ pid: number;
74
+ claimedAt: string;
75
+ reason?: string;
76
+ }
77
+
78
+ export interface CompletionEntry {
79
+ completedBy: string;
80
+ completedAt: string;
81
+ notes?: string;
82
+ }
83
+
84
+ export type SpecClaims = Record<string, ClaimEntry>;
85
+ export type SpecCompletions = Record<string, CompletionEntry>;
86
+ export type AllClaims = Record<string, SpecClaims>;
87
+ export type AllCompletions = Record<string, SpecCompletions>;
88
+
89
+ // =============================================================================
90
+ // Constants
91
+ // =============================================================================
92
+
93
+ export const MAX_WATCHER_RETRIES = 5;
94
+ export const MAX_CHAT_HISTORY = 50;
95
+
96
+ const AGENT_COLORS = [
97
+ "38;2;178;129;214", // purple
98
+ "38;2;215;135;175", // pink
99
+ "38;2;254;188;56", // gold
100
+ "38;2;137;210;129", // green
101
+ "38;2;0;175;175", // cyan
102
+ "38;2;23;143;185", // blue
103
+ "38;2;228;192;15", // yellow
104
+ "38;2;255;135;135", // coral
105
+ ];
106
+
107
+ const ADJECTIVES = [
108
+ "Swift", "Bright", "Calm", "Dark", "Epic", "Fast", "Gold", "Happy",
109
+ "Iron", "Jade", "Keen", "Loud", "Mint", "Nice", "Oak", "Pure",
110
+ "Quick", "Red", "Sage", "True", "Ultra", "Vivid", "Wild", "Young", "Zen"
111
+ ];
112
+
113
+ const NOUNS = [
114
+ "Arrow", "Bear", "Castle", "Dragon", "Eagle", "Falcon", "Grove", "Hawk",
115
+ "Ice", "Jaguar", "Knight", "Lion", "Moon", "Nova", "Owl", "Phoenix",
116
+ "Quartz", "Raven", "Storm", "Tiger", "Union", "Viper", "Wolf", "Xenon", "Yak", "Zenith"
117
+ ];
118
+
119
+ // =============================================================================
120
+ // Pure Utilities
121
+ // =============================================================================
122
+
123
+ export function generateMemorableName(): string {
124
+ const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
125
+ const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
126
+ return adj + noun;
127
+ }
128
+
129
+ export function isProcessAlive(pid: number): boolean {
130
+ try {
131
+ process.kill(pid, 0);
132
+ return true;
133
+ } catch {
134
+ return false;
135
+ }
136
+ }
137
+
138
+ export function isValidAgentName(name: string): boolean {
139
+ if (!name || name.length > 50) return false;
140
+ return /^[a-zA-Z0-9_][a-zA-Z0-9_-]*$/.test(name);
141
+ }
142
+
143
+ export function formatRelativeTime(timestamp: string): string {
144
+ const diff = Date.now() - new Date(timestamp).getTime();
145
+ const seconds = Math.floor(diff / 1000);
146
+ const minutes = Math.floor(seconds / 60);
147
+ const hours = Math.floor(minutes / 60);
148
+
149
+ if (hours > 0) return `${hours}h ago`;
150
+ if (minutes > 0) return `${minutes}m ago`;
151
+ return "just now";
152
+ }
153
+
154
+ export function pathMatchesReservation(filePath: string, pattern: string): boolean {
155
+ if (pattern.endsWith("/")) {
156
+ return filePath.startsWith(pattern) || filePath + "/" === pattern;
157
+ }
158
+ return filePath === pattern;
159
+ }
160
+
161
+ export function stripAnsiCodes(text: string): string {
162
+ // eslint-disable-next-line no-control-regex
163
+ return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
164
+ }
165
+
166
+ const colorCache = new Map<string, string>();
167
+
168
+ export function agentColorCode(name: string): string {
169
+ const cached = colorCache.get(name);
170
+ if (cached) return cached;
171
+
172
+ let hash = 0;
173
+ for (const char of name) hash = ((hash << 5) - hash) + char.charCodeAt(0);
174
+ const color = AGENT_COLORS[Math.abs(hash) % AGENT_COLORS.length];
175
+ colorCache.set(name, color);
176
+ return color;
177
+ }
178
+
179
+ export function coloredAgentName(name: string): string {
180
+ return `\x1b[${agentColorCode(name)}m${name}\x1b[0m`;
181
+ }
182
+
183
+ export function extractFolder(cwd: string): string {
184
+ return basename(cwd) || cwd;
185
+ }
186
+
187
+ export function resolveSpecPath(specPath: string, cwd: string): string {
188
+ if (isAbsolute(specPath)) return specPath;
189
+ return resolve(cwd, specPath);
190
+ }
191
+
192
+ export function displaySpecPath(absPath: string, cwd: string): string {
193
+ try {
194
+ const rel = relative(cwd, absPath);
195
+ if (rel === "") return ".";
196
+ if (!rel.startsWith("..") && !isAbsolute(rel)) {
197
+ return "./" + rel;
198
+ }
199
+ } catch {
200
+ // Ignore and fall back to absolute
201
+ }
202
+ return absPath;
203
+ }
204
+
205
+ export function truncatePathLeft(filePath: string, maxLen: number): string {
206
+ if (filePath.length <= maxLen) return filePath;
207
+ if (maxLen <= 1) return '…';
208
+ const truncated = filePath.slice(-(maxLen - 1));
209
+ const slashIdx = truncated.indexOf('/');
210
+ if (slashIdx > 0) {
211
+ return '…' + truncated.slice(slashIdx);
212
+ }
213
+ return '…' + truncated;
214
+ }
215
+
216
+ export type DisplayMode = "same-folder-branch" | "same-folder" | "different";
217
+
218
+ export function getDisplayMode(agents: AgentRegistration[]): DisplayMode {
219
+ if (agents.length === 0) return "different";
220
+
221
+ const folders = agents.map(a => extractFolder(a.cwd));
222
+ const uniqueFolders = new Set(folders);
223
+
224
+ if (uniqueFolders.size > 1) return "different";
225
+
226
+ const branches = agents.map(a => a.gitBranch).filter(Boolean);
227
+ const uniqueBranches = new Set(branches);
228
+
229
+ if (uniqueBranches.size <= 1) return "same-folder-branch";
230
+
231
+ return "same-folder";
232
+ }