@cleocode/adapters 2026.4.31 → 2026.4.36
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/dist/index.js +641 -5
- package/dist/index.js.map +4 -4
- package/dist/providers/claude-code/adapter.js +184 -0
- package/dist/providers/claude-code/adapter.js.map +1 -0
- package/dist/providers/claude-code/context-monitor.js +159 -0
- package/dist/providers/claude-code/context-monitor.js.map +1 -0
- package/dist/providers/claude-code/hooks.js +286 -0
- package/dist/providers/claude-code/hooks.js.map +1 -0
- package/dist/providers/claude-code/index.js +41 -0
- package/dist/providers/claude-code/index.js.map +1 -0
- package/dist/providers/claude-code/install.js +199 -0
- package/dist/providers/claude-code/install.js.map +1 -0
- package/dist/providers/claude-code/paths.js +41 -0
- package/dist/providers/claude-code/paths.js.map +1 -0
- package/dist/providers/claude-code/spawn.js +171 -0
- package/dist/providers/claude-code/spawn.js.map +1 -0
- package/dist/providers/claude-code/statusline.js +130 -0
- package/dist/providers/claude-code/statusline.js.map +1 -0
- package/dist/providers/claude-code/task-sync.js +119 -0
- package/dist/providers/claude-code/task-sync.js.map +1 -0
- package/dist/providers/claude-code/transport.js +29 -0
- package/dist/providers/claude-code/transport.js.map +1 -0
- package/dist/providers/codex/adapter.js +146 -0
- package/dist/providers/codex/adapter.js.map +1 -0
- package/dist/providers/codex/hooks.js +113 -0
- package/dist/providers/codex/hooks.js.map +1 -0
- package/dist/providers/codex/index.js +39 -0
- package/dist/providers/codex/index.js.map +1 -0
- package/dist/providers/codex/install.js +124 -0
- package/dist/providers/codex/install.js.map +1 -0
- package/dist/providers/cursor/adapter.js +151 -0
- package/dist/providers/cursor/adapter.js.map +1 -0
- package/dist/providers/cursor/hooks.js +208 -0
- package/dist/providers/cursor/hooks.js.map +1 -0
- package/dist/providers/cursor/index.js +36 -0
- package/dist/providers/cursor/index.js.map +1 -0
- package/dist/providers/cursor/install.js +180 -0
- package/dist/providers/cursor/install.js.map +1 -0
- package/dist/providers/cursor/spawn.js +59 -0
- package/dist/providers/cursor/spawn.js.map +1 -0
- package/dist/providers/gemini-cli/adapter.js +158 -0
- package/dist/providers/gemini-cli/adapter.js.map +1 -0
- package/dist/providers/gemini-cli/hooks.js +128 -0
- package/dist/providers/gemini-cli/hooks.js.map +1 -0
- package/dist/providers/gemini-cli/index.js +39 -0
- package/dist/providers/gemini-cli/index.js.map +1 -0
- package/dist/providers/gemini-cli/install.js +124 -0
- package/dist/providers/gemini-cli/install.js.map +1 -0
- package/dist/providers/kimi/adapter.js +145 -0
- package/dist/providers/kimi/adapter.js.map +1 -0
- package/dist/providers/kimi/hooks.js +79 -0
- package/dist/providers/kimi/hooks.js.map +1 -0
- package/dist/providers/kimi/index.js +39 -0
- package/dist/providers/kimi/index.js.map +1 -0
- package/dist/providers/kimi/install.js +124 -0
- package/dist/providers/kimi/install.js.map +1 -0
- package/dist/providers/opencode/adapter.js +166 -0
- package/dist/providers/opencode/adapter.js.map +1 -0
- package/dist/providers/opencode/hooks.js +206 -0
- package/dist/providers/opencode/hooks.js.map +1 -0
- package/dist/providers/opencode/index.js +37 -0
- package/dist/providers/opencode/index.js.map +1 -0
- package/dist/providers/opencode/install.js +115 -0
- package/dist/providers/opencode/install.js.map +1 -0
- package/dist/providers/opencode/spawn.js +241 -0
- package/dist/providers/opencode/spawn.js.map +1 -0
- package/dist/providers/pi/adapter.d.ts +102 -0
- package/dist/providers/pi/adapter.d.ts.map +1 -0
- package/dist/providers/pi/adapter.js +220 -0
- package/dist/providers/pi/adapter.js.map +1 -0
- package/dist/providers/pi/hooks.d.ts +149 -0
- package/dist/providers/pi/hooks.d.ts.map +1 -0
- package/dist/providers/pi/hooks.js +223 -0
- package/dist/providers/pi/hooks.js.map +1 -0
- package/dist/providers/pi/index.d.ts +36 -0
- package/dist/providers/pi/index.d.ts.map +1 -0
- package/dist/providers/pi/index.js +38 -0
- package/dist/providers/pi/index.js.map +1 -0
- package/dist/providers/pi/install.d.ts +68 -0
- package/dist/providers/pi/install.d.ts.map +1 -0
- package/dist/providers/pi/install.js +175 -0
- package/dist/providers/pi/install.js.map +1 -0
- package/dist/providers/pi/spawn.d.ts +68 -0
- package/dist/providers/pi/spawn.d.ts.map +1 -0
- package/dist/providers/pi/spawn.js +187 -0
- package/dist/providers/pi/spawn.js.map +1 -0
- package/dist/providers/shared/transcript-reader.js +124 -0
- package/dist/providers/shared/transcript-reader.js.map +1 -0
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +92 -0
- package/dist/registry.js.map +1 -0
- package/package.json +3 -3
- package/src/providers/pi/adapter.ts +240 -0
- package/src/providers/pi/hooks.ts +232 -0
- package/src/providers/pi/index.ts +41 -0
- package/src/providers/pi/install.ts +189 -0
- package/src/providers/pi/manifest.json +40 -0
- package/src/providers/pi/spawn.ts +207 -0
- 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
|
}
|