@cleocode/adapters 2026.4.31 → 2026.4.35

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 (99) hide show
  1. package/dist/index.js +641 -5
  2. package/dist/index.js.map +4 -4
  3. package/dist/providers/claude-code/adapter.js +184 -0
  4. package/dist/providers/claude-code/adapter.js.map +1 -0
  5. package/dist/providers/claude-code/context-monitor.js +159 -0
  6. package/dist/providers/claude-code/context-monitor.js.map +1 -0
  7. package/dist/providers/claude-code/hooks.js +286 -0
  8. package/dist/providers/claude-code/hooks.js.map +1 -0
  9. package/dist/providers/claude-code/index.js +41 -0
  10. package/dist/providers/claude-code/index.js.map +1 -0
  11. package/dist/providers/claude-code/install.js +199 -0
  12. package/dist/providers/claude-code/install.js.map +1 -0
  13. package/dist/providers/claude-code/paths.js +41 -0
  14. package/dist/providers/claude-code/paths.js.map +1 -0
  15. package/dist/providers/claude-code/spawn.js +171 -0
  16. package/dist/providers/claude-code/spawn.js.map +1 -0
  17. package/dist/providers/claude-code/statusline.js +130 -0
  18. package/dist/providers/claude-code/statusline.js.map +1 -0
  19. package/dist/providers/claude-code/task-sync.js +119 -0
  20. package/dist/providers/claude-code/task-sync.js.map +1 -0
  21. package/dist/providers/claude-code/transport.js +29 -0
  22. package/dist/providers/claude-code/transport.js.map +1 -0
  23. package/dist/providers/codex/adapter.js +146 -0
  24. package/dist/providers/codex/adapter.js.map +1 -0
  25. package/dist/providers/codex/hooks.js +113 -0
  26. package/dist/providers/codex/hooks.js.map +1 -0
  27. package/dist/providers/codex/index.js +39 -0
  28. package/dist/providers/codex/index.js.map +1 -0
  29. package/dist/providers/codex/install.js +124 -0
  30. package/dist/providers/codex/install.js.map +1 -0
  31. package/dist/providers/cursor/adapter.js +151 -0
  32. package/dist/providers/cursor/adapter.js.map +1 -0
  33. package/dist/providers/cursor/hooks.js +208 -0
  34. package/dist/providers/cursor/hooks.js.map +1 -0
  35. package/dist/providers/cursor/index.js +36 -0
  36. package/dist/providers/cursor/index.js.map +1 -0
  37. package/dist/providers/cursor/install.js +180 -0
  38. package/dist/providers/cursor/install.js.map +1 -0
  39. package/dist/providers/cursor/spawn.js +59 -0
  40. package/dist/providers/cursor/spawn.js.map +1 -0
  41. package/dist/providers/gemini-cli/adapter.js +158 -0
  42. package/dist/providers/gemini-cli/adapter.js.map +1 -0
  43. package/dist/providers/gemini-cli/hooks.js +128 -0
  44. package/dist/providers/gemini-cli/hooks.js.map +1 -0
  45. package/dist/providers/gemini-cli/index.js +39 -0
  46. package/dist/providers/gemini-cli/index.js.map +1 -0
  47. package/dist/providers/gemini-cli/install.js +124 -0
  48. package/dist/providers/gemini-cli/install.js.map +1 -0
  49. package/dist/providers/kimi/adapter.js +145 -0
  50. package/dist/providers/kimi/adapter.js.map +1 -0
  51. package/dist/providers/kimi/hooks.js +79 -0
  52. package/dist/providers/kimi/hooks.js.map +1 -0
  53. package/dist/providers/kimi/index.js +39 -0
  54. package/dist/providers/kimi/index.js.map +1 -0
  55. package/dist/providers/kimi/install.js +124 -0
  56. package/dist/providers/kimi/install.js.map +1 -0
  57. package/dist/providers/opencode/adapter.js +166 -0
  58. package/dist/providers/opencode/adapter.js.map +1 -0
  59. package/dist/providers/opencode/hooks.js +206 -0
  60. package/dist/providers/opencode/hooks.js.map +1 -0
  61. package/dist/providers/opencode/index.js +37 -0
  62. package/dist/providers/opencode/index.js.map +1 -0
  63. package/dist/providers/opencode/install.js +115 -0
  64. package/dist/providers/opencode/install.js.map +1 -0
  65. package/dist/providers/opencode/spawn.js +241 -0
  66. package/dist/providers/opencode/spawn.js.map +1 -0
  67. package/dist/providers/pi/adapter.d.ts +102 -0
  68. package/dist/providers/pi/adapter.d.ts.map +1 -0
  69. package/dist/providers/pi/adapter.js +220 -0
  70. package/dist/providers/pi/adapter.js.map +1 -0
  71. package/dist/providers/pi/hooks.d.ts +149 -0
  72. package/dist/providers/pi/hooks.d.ts.map +1 -0
  73. package/dist/providers/pi/hooks.js +223 -0
  74. package/dist/providers/pi/hooks.js.map +1 -0
  75. package/dist/providers/pi/index.d.ts +36 -0
  76. package/dist/providers/pi/index.d.ts.map +1 -0
  77. package/dist/providers/pi/index.js +38 -0
  78. package/dist/providers/pi/index.js.map +1 -0
  79. package/dist/providers/pi/install.d.ts +68 -0
  80. package/dist/providers/pi/install.d.ts.map +1 -0
  81. package/dist/providers/pi/install.js +175 -0
  82. package/dist/providers/pi/install.js.map +1 -0
  83. package/dist/providers/pi/spawn.d.ts +68 -0
  84. package/dist/providers/pi/spawn.d.ts.map +1 -0
  85. package/dist/providers/pi/spawn.js +187 -0
  86. package/dist/providers/pi/spawn.js.map +1 -0
  87. package/dist/providers/shared/transcript-reader.js +124 -0
  88. package/dist/providers/shared/transcript-reader.js.map +1 -0
  89. package/dist/registry.d.ts.map +1 -1
  90. package/dist/registry.js +92 -0
  91. package/dist/registry.js.map +1 -0
  92. package/package.json +3 -3
  93. package/src/providers/pi/adapter.ts +240 -0
  94. package/src/providers/pi/hooks.ts +232 -0
  95. package/src/providers/pi/index.ts +41 -0
  96. package/src/providers/pi/install.ts +189 -0
  97. package/src/providers/pi/manifest.json +40 -0
  98. package/src/providers/pi/spawn.ts +207 -0
  99. package/src/registry.ts +6 -1
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Pi Spawn Provider
3
+ *
4
+ * Implements AdapterSpawnProvider for Pi coding agent CLI.
5
+ *
6
+ * Uses the `pi` CLI (or the path from `PI_CLI_PATH` env var) to spawn
7
+ * subagent processes with prompts written to temporary files. Processes
8
+ * run detached and are tracked by PID for listing and termination.
9
+ *
10
+ * Pi detection: `PI_CLI_PATH` env var or `which pi`.
11
+ *
12
+ * @task T553
13
+ */
14
+
15
+ import { exec, spawn as nodeSpawn } from 'node:child_process';
16
+ import { unlink, writeFile } from 'node:fs/promises';
17
+ import { promisify } from 'node:util';
18
+ import type { AdapterSpawnProvider, SpawnContext, SpawnResult } from '@cleocode/contracts';
19
+ import { getErrorMessage } from '@cleocode/contracts';
20
+
21
+ const execAsync = promisify(exec);
22
+
23
+ /** Internal tracking entry for a spawned process. */
24
+ interface TrackedProcess {
25
+ pid: number;
26
+ taskId: string;
27
+ startTime: string;
28
+ }
29
+
30
+ /**
31
+ * Resolve the Pi CLI executable path.
32
+ *
33
+ * Honours `PI_CLI_PATH` env var when set, otherwise uses `pi` (expects
34
+ * the binary on PATH).
35
+ */
36
+ function getPiCliPath(): string {
37
+ return process.env['PI_CLI_PATH'] ?? 'pi';
38
+ }
39
+
40
+ /**
41
+ * Spawn provider for Pi coding agent.
42
+ *
43
+ * Spawns detached Pi CLI processes for subagent execution. Each spawn
44
+ * writes its prompt to a temporary file, then runs the Pi CLI with the
45
+ * prompt file as the primary argument as a detached, unref'd child process.
46
+ *
47
+ * @remarks
48
+ * Prompts are written to temporary files under `/tmp/` and cleaned up
49
+ * after the child process exits. Processes are tracked by instance ID in
50
+ * an in-memory map and verified via `kill(pid, 0)` liveness checks.
51
+ * All failures are best-effort and non-blocking.
52
+ */
53
+ export class PiSpawnProvider implements AdapterSpawnProvider {
54
+ /** Map of instance IDs to tracked process info. */
55
+ private processMap = new Map<string, TrackedProcess>();
56
+
57
+ /**
58
+ * Check if the Pi CLI is available.
59
+ *
60
+ * Checks `PI_CLI_PATH` env var first, then tries `which pi`.
61
+ *
62
+ * @returns true if the Pi CLI is accessible
63
+ */
64
+ async canSpawn(): Promise<boolean> {
65
+ const cliPath = getPiCliPath();
66
+ try {
67
+ if (cliPath !== 'pi') {
68
+ // Custom path — check if it exists
69
+ const { stdout } = await execAsync(`test -x "${cliPath}" && echo ok`);
70
+ return stdout.trim() === 'ok';
71
+ }
72
+ await execAsync('which pi');
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Spawn a subagent via Pi CLI.
81
+ *
82
+ * Writes the prompt to a temporary file and spawns a detached Pi
83
+ * process. The process runs independently of the parent.
84
+ *
85
+ * @param context - Spawn context with taskId, prompt, and options
86
+ * @returns Spawn result with instance ID and status
87
+ */
88
+ async spawn(context: SpawnContext): Promise<SpawnResult> {
89
+ const instanceId = `pi-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
90
+ const startTime = new Date().toISOString();
91
+ let tmpFile: string | undefined;
92
+
93
+ try {
94
+ tmpFile = `/tmp/pi-spawn-${instanceId}.txt`;
95
+ await writeFile(tmpFile, context.prompt, 'utf-8');
96
+
97
+ const cliPath = getPiCliPath();
98
+ const args = [tmpFile];
99
+ const spawnOpts: Parameters<typeof nodeSpawn>[2] = {
100
+ detached: true,
101
+ stdio: 'ignore',
102
+ };
103
+
104
+ if (context.workingDirectory) {
105
+ spawnOpts.cwd = context.workingDirectory;
106
+ }
107
+
108
+ const child = nodeSpawn(cliPath, args, spawnOpts);
109
+ child.unref();
110
+
111
+ if (child.pid) {
112
+ this.processMap.set(instanceId, {
113
+ pid: child.pid,
114
+ taskId: context.taskId,
115
+ startTime,
116
+ });
117
+ }
118
+
119
+ const capturedTmpFile = tmpFile;
120
+ child.on('exit', async () => {
121
+ this.processMap.delete(instanceId);
122
+ try {
123
+ await unlink(capturedTmpFile);
124
+ } catch {
125
+ // Ignore cleanup errors
126
+ }
127
+ });
128
+
129
+ return {
130
+ instanceId,
131
+ taskId: context.taskId,
132
+ providerId: 'pi',
133
+ status: 'running',
134
+ startTime,
135
+ };
136
+ } catch (error) {
137
+ console.error(`[PiSpawnProvider] Failed to spawn: ${getErrorMessage(error)}`);
138
+
139
+ if (tmpFile) {
140
+ try {
141
+ await unlink(tmpFile);
142
+ } catch {
143
+ // Ignore cleanup errors
144
+ }
145
+ }
146
+
147
+ return {
148
+ instanceId,
149
+ taskId: context.taskId,
150
+ providerId: 'pi',
151
+ status: 'failed',
152
+ startTime,
153
+ endTime: new Date().toISOString(),
154
+ error: getErrorMessage(error),
155
+ };
156
+ }
157
+ }
158
+
159
+ /**
160
+ * List currently running Pi subagent processes.
161
+ *
162
+ * Checks each tracked process via kill(pid, 0) to verify it is still alive.
163
+ * Dead processes are automatically cleaned from the tracking map.
164
+ *
165
+ * @returns Array of spawn results for running processes
166
+ */
167
+ async listRunning(): Promise<SpawnResult[]> {
168
+ const running: SpawnResult[] = [];
169
+
170
+ for (const [instanceId, tracked] of this.processMap.entries()) {
171
+ try {
172
+ process.kill(tracked.pid, 0);
173
+ running.push({
174
+ instanceId,
175
+ taskId: tracked.taskId,
176
+ providerId: 'pi',
177
+ status: 'running',
178
+ startTime: tracked.startTime,
179
+ });
180
+ } catch {
181
+ this.processMap.delete(instanceId);
182
+ }
183
+ }
184
+
185
+ return running;
186
+ }
187
+
188
+ /**
189
+ * Terminate a running spawn by instance ID.
190
+ *
191
+ * Sends SIGTERM to the tracked process. If the process is not found
192
+ * or has already exited, this is a no-op.
193
+ *
194
+ * @param instanceId - ID of the spawn instance to terminate
195
+ */
196
+ async terminate(instanceId: string): Promise<void> {
197
+ const tracked = this.processMap.get(instanceId);
198
+ if (!tracked) return;
199
+
200
+ try {
201
+ process.kill(tracked.pid, 'SIGTERM');
202
+ } catch {
203
+ // Process may have already exited
204
+ }
205
+ this.processMap.delete(instanceId);
206
+ }
207
+ }
package/src/registry.ts CHANGED
@@ -47,7 +47,7 @@ export interface AdapterManifest {
47
47
  }
48
48
 
49
49
  /** Known provider IDs bundled with @cleocode/adapters. */
50
- const PROVIDER_IDS = ['claude-code', 'opencode', 'cursor'] as const;
50
+ const PROVIDER_IDS = ['claude-code', 'opencode', 'cursor', 'pi'] as const;
51
51
 
52
52
  /**
53
53
  * Get the manifests for all bundled provider adapters.
@@ -127,5 +127,10 @@ export async function discoverProviders(): Promise<Map<string, () => Promise<unk
127
127
  return new CursorAdapter();
128
128
  });
129
129
 
130
+ providers.set('pi', async () => {
131
+ const { PiAdapter } = await import('./providers/pi/index.js');
132
+ return new PiAdapter();
133
+ });
134
+
130
135
  return providers;
131
136
  }