takomi 2.1.2 → 2.1.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.
Files changed (52) hide show
  1. package/.pi/README.md +124 -124
  2. package/.pi/agents/architect.md +15 -15
  3. package/.pi/agents/coder.md +14 -14
  4. package/.pi/agents/designer.md +17 -17
  5. package/.pi/agents/orchestrator.md +22 -22
  6. package/.pi/agents/reviewer.md +16 -16
  7. package/.pi/extensions/oauth-router/README.md +125 -125
  8. package/.pi/extensions/oauth-router/commands.ts +380 -380
  9. package/.pi/extensions/oauth-router/config.ts +200 -200
  10. package/.pi/extensions/oauth-router/index.ts +41 -41
  11. package/.pi/extensions/oauth-router/oauth-flow.ts +154 -154
  12. package/.pi/extensions/oauth-router/oauth-store.ts +121 -121
  13. package/.pi/extensions/oauth-router/package.json +14 -14
  14. package/.pi/extensions/oauth-router/policies.ts +27 -27
  15. package/.pi/extensions/oauth-router/provider.ts +492 -492
  16. package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -98
  17. package/.pi/extensions/oauth-router/state.ts +174 -174
  18. package/.pi/extensions/oauth-router/types.ts +153 -153
  19. package/.pi/extensions/takomi-runtime/command-text.ts +130 -130
  20. package/.pi/extensions/takomi-runtime/commands.ts +179 -179
  21. package/.pi/extensions/takomi-runtime/context-panel.ts +282 -282
  22. package/.pi/extensions/takomi-runtime/index.ts +1288 -1288
  23. package/.pi/extensions/takomi-runtime/profile.ts +114 -114
  24. package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -105
  25. package/.pi/extensions/takomi-runtime/shared.ts +511 -492
  26. package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -364
  27. package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -501
  28. package/.pi/extensions/takomi-runtime/subagent-types.ts +90 -83
  29. package/.pi/extensions/takomi-runtime/ui.ts +133 -133
  30. package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -18
  31. package/.pi/extensions/takomi-subagents/agents.ts +113 -113
  32. package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -95
  33. package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -26
  34. package/.pi/extensions/takomi-subagents/dispatch.ts +306 -215
  35. package/.pi/extensions/takomi-subagents/index.ts +76 -75
  36. package/.pi/extensions/takomi-subagents/live-updates.ts +136 -83
  37. package/.pi/extensions/takomi-subagents/native-render.ts +5 -142
  38. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +228 -0
  39. package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -209
  40. package/.pi/themes/takomi-noir.json +81 -81
  41. package/package.json +59 -59
  42. package/src/cli.js +14 -0
  43. package/src/doctor.js +87 -84
  44. package/src/pi-harness.js +355 -351
  45. package/src/pi-installer.js +193 -171
  46. package/src/pi-takomi-core/index.ts +4 -4
  47. package/src/pi-takomi-core/orchestration.ts +402 -402
  48. package/src/pi-takomi-core/routing.ts +93 -93
  49. package/src/pi-takomi-core/types.ts +173 -173
  50. package/src/pi-takomi-core/workflows.ts +299 -299
  51. package/src/skills-installer.js +101 -101
  52. package/src/update-check.js +140 -0
package/src/pi-harness.js CHANGED
@@ -1,351 +1,355 @@
1
- import fs from 'fs-extra';
2
- import os from 'os';
3
- import path from 'path';
4
- import { spawn, spawnSync } from 'child_process';
5
- import pc from 'picocolors';
6
- import { PATHS } from './utils.js';
7
- import { STORE_PATH, MANIFEST_PATH } from './store.js';
8
-
9
- const TAKOMI_PACKAGE_DIR = PATHS.root;
10
-
11
- const HOME = os.homedir();
12
-
13
- function getPathEntries() {
14
- return (process.env.PATH || '').split(path.delimiter).filter(Boolean);
15
- }
16
-
17
- function commandExists(command) {
18
- const probe = process.platform === 'win32'
19
- ? spawnSync('where', [command], { stdio: 'pipe', encoding: 'utf8' })
20
- : spawnSync('which', [command], { stdio: 'pipe', encoding: 'utf8' });
21
-
22
- if (probe.status === 0) {
23
- const matches = probe.stdout.split(/\r?\n/).map(v => v.trim()).filter(Boolean);
24
- const preferred = process.platform === 'win32'
25
- ? matches.find((entry) => /\.(cmd|exe|bat)$/i.test(entry))
26
- : matches[0];
27
- return { found: true, path: preferred || matches[0] || null };
28
- }
29
-
30
- for (const entry of getPathEntries()) {
31
- const candidate = path.join(entry, process.platform === 'win32' ? `${command}.cmd` : command);
32
- if (fs.existsSync(candidate)) return { found: true, path: candidate };
33
- }
34
-
35
- return { found: false, path: null };
36
- }
37
-
38
- export function getPiAgentRoot(home = HOME) {
39
- return path.join(home, '.pi', 'agent');
40
- }
41
-
42
- export function getPiGlobalTargets(home = HOME) {
43
- const agentRoot = getPiAgentRoot(home);
44
- return {
45
- root: agentRoot,
46
- extensions: path.join(agentRoot, 'extensions'),
47
- prompts: path.join(agentRoot, 'prompts'),
48
- agents: path.join(agentRoot, 'agents'),
49
- themes: path.join(agentRoot, 'themes'),
50
- settings: path.join(agentRoot, 'settings.json'),
51
- takomi: path.join(agentRoot, 'takomi'),
52
- routingPolicy: path.join(agentRoot, 'takomi', 'model-routing.md'),
53
- };
54
- }
55
-
56
- export function getProjectPiTargets(cwd = process.cwd()) {
57
- return {
58
- root: path.join(cwd, '.pi'),
59
- extensions: path.join(cwd, '.pi', 'extensions'),
60
- prompts: path.join(cwd, '.pi', 'prompts'),
61
- agents: path.join(cwd, '.pi', 'agents'),
62
- themes: path.join(cwd, '.pi', 'themes'),
63
- settings: path.join(cwd, '.pi', 'settings.json'),
64
- takomiProfile: path.join(cwd, '.pi', 'takomi-profile.json'),
65
- routingPolicy: path.join(cwd, '.pi', 'takomi', 'model-routing.md'),
66
- };
67
- }
68
-
69
- export function getBundledPiAssetTargets() {
70
- const root = path.join(PATHS.root, '.pi');
71
- return {
72
- root,
73
- runtime: path.join(root, 'extensions', 'takomi-runtime'),
74
- subagents: path.join(root, 'extensions', 'takomi-subagents'),
75
- prompts: path.join(root, 'prompts'),
76
- agents: path.join(root, 'agents'),
77
- themes: path.join(root, 'themes'),
78
- settings: path.join(root, 'settings.json'),
79
- routingPolicy: path.join(root, 'takomi', 'model-routing.md'),
80
- profile: path.join(root, 'takomi-profile.json'),
81
- };
82
- }
83
-
84
- async function getPackageJson() {
85
- try {
86
- return await fs.readJson(PATHS.packageJson);
87
- } catch {
88
- return {};
89
- }
90
- }
91
-
92
- async function getPackageFileEntries() {
93
- const pkg = await getPackageJson();
94
- return Array.isArray(pkg.files) ? pkg.files : [];
95
- }
96
-
97
- function getGlobalNodeModulesRoot(home = HOME) {
98
- if (process.platform === 'win32') {
99
- return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'npm', 'node_modules');
100
- }
101
- return path.join(home, '.npm-global', 'lib', 'node_modules');
102
- }
103
-
104
- export async function inspectPiSubagentsDependency(home = HOME) {
105
- const pkg = await getPackageJson();
106
- const declaredVersion = pkg?.dependencies?.['pi-subagents'] || null;
107
- const localPackageJson = path.join(TAKOMI_PACKAGE_DIR, 'node_modules', 'pi-subagents', 'package.json');
108
- const globalPackageJson = path.join(getGlobalNodeModulesRoot(home), 'pi-subagents', 'package.json');
109
-
110
- let localVersion = null;
111
- let globalVersion = null;
112
-
113
- try {
114
- if (await fs.pathExists(localPackageJson)) {
115
- const localPkg = await fs.readJson(localPackageJson);
116
- localVersion = localPkg.version || null;
117
- }
118
- } catch {}
119
-
120
- try {
121
- if (await fs.pathExists(globalPackageJson)) {
122
- const globalPkg = await fs.readJson(globalPackageJson);
123
- globalVersion = globalPkg.version || null;
124
- }
125
- } catch {}
126
-
127
- const bin = commandExists('pi-subagents');
128
-
129
- return {
130
- declaredVersion,
131
- localInstalled: Boolean(localVersion),
132
- localVersion,
133
- localPackageJson,
134
- globalInstalled: Boolean(globalVersion),
135
- globalVersion,
136
- globalPackageJson,
137
- binaryInstalled: bin.found,
138
- binaryPath: bin.path,
139
- };
140
- }
141
-
142
- export async function detectPiCommand() {
143
- const binary = commandExists('pi');
144
- if (!binary.found) {
145
- return { installed: false, path: null, version: null };
146
- }
147
-
148
- const versionProbe = process.platform === 'win32'
149
- ? spawnSync('powershell', ['-NoProfile', '-Command', `& '${(binary.path || 'pi').replace(/'/g, "''")}' --version`], { stdio: 'pipe', encoding: 'utf8' })
150
- : spawnSync(binary.path || 'pi', ['--version'], { stdio: 'pipe', encoding: 'utf8' });
151
- const output = [versionProbe.stdout, versionProbe.stderr].filter(Boolean).join('\n').trim();
152
- return {
153
- installed: true,
154
- path: binary.path,
155
- version: versionProbe.status === 0 ? (output || null) : null,
156
- };
157
- }
158
-
159
- export async function inspectBundledPiAssets() {
160
- const targets = getBundledPiAssetTargets();
161
- const checks = {
162
- root: await fs.pathExists(targets.root),
163
- runtime: await fs.pathExists(targets.runtime),
164
- subagents: await fs.pathExists(targets.subagents),
165
- prompts: await fs.pathExists(targets.prompts),
166
- agents: await fs.pathExists(targets.agents),
167
- themes: await fs.pathExists(targets.themes),
168
- settings: await fs.pathExists(targets.settings),
169
- routingPolicy: await fs.pathExists(targets.routingPolicy),
170
- profile: await fs.pathExists(targets.profile),
171
- };
172
-
173
- const packageReady = await fs.pathExists(targets.root);
174
- const packageFiles = await getPackageFileEntries();
175
- const packageIncluded = packageFiles.includes('.pi') || packageFiles.some((entry) => entry.startsWith('.pi/')) || packageReady;
176
-
177
- return {
178
- targets,
179
- checks,
180
- packageReady,
181
- packageIncluded,
182
- };
183
- }
184
-
185
- export async function inspectInstalledTakomiPiHarness(home = HOME) {
186
- const targets = getPiGlobalTargets(home);
187
- const runtime = path.join(targets.extensions, 'takomi-runtime');
188
- const subagents = path.join(targets.extensions, 'takomi-subagents');
189
-
190
- return {
191
- targets,
192
- runtimeInstalled: await fs.pathExists(runtime),
193
- subagentsInstalled: await fs.pathExists(subagents),
194
- promptsInstalled: await fs.pathExists(targets.prompts),
195
- agentsInstalled: await fs.pathExists(targets.agents),
196
- themesInstalled: await fs.pathExists(targets.themes),
197
- settingsPresent: await fs.pathExists(targets.settings),
198
- routingPolicyPresent: await fs.pathExists(targets.routingPolicy),
199
- manifestPresent: await fs.pathExists(MANIFEST_PATH),
200
- storePresent: await fs.pathExists(STORE_PATH),
201
- };
202
- }
203
-
204
- function runCommand(command, args) {
205
- return spawnSync(command, args, { stdio: 'pipe', encoding: 'utf8', shell: process.platform === 'win32' });
206
- }
207
-
208
- export async function ensurePiInstalled() {
209
- const before = await detectPiCommand();
210
- if (before.installed) {
211
- return { ok: true, changed: false, report: 'Pi already available.' };
212
- }
213
-
214
- const attempts = [
215
- { command: 'npm', args: ['install', '-g', '@mariozechner/pi-coding-agent'] },
216
- { command: 'npm.cmd', args: ['install', '-g', '@mariozechner/pi-coding-agent'] },
217
- ];
218
-
219
- let lastError = 'Unknown install failure.';
220
- for (const attempt of attempts) {
221
- const result = runCommand(attempt.command, attempt.args);
222
- if (result.status === 0) {
223
- const after = await detectPiCommand();
224
- if (after.installed) {
225
- return {
226
- ok: true,
227
- changed: true,
228
- report: (result.stdout || result.stderr || 'Installed Pi.').trim(),
229
- };
230
- }
231
- lastError = 'npm install reported success, but pi was still not detected.';
232
- continue;
233
- }
234
- lastError = [result.stdout, result.stderr].filter(Boolean).join('\n').trim() || lastError;
235
- }
236
-
237
- return { ok: false, changed: false, report: lastError };
238
- }
239
-
240
- export function printPiInstallResult(result) {
241
- if (result.ok) {
242
- console.log(pc.green(`✔ ${result.changed ? 'Installed' : 'Validated'} Pi`));
243
- if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-4).join('\n')));
244
- return;
245
- }
246
- console.log(pc.red(' Failed to install Pi'));
247
- if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
248
- }
249
-
250
- export async function ensurePiSubagentsInstalled() {
251
- const before = await inspectPiSubagentsDependency();
252
- if (before.localInstalled || before.globalInstalled) {
253
- return { ok: true, changed: false, report: 'pi-subagents already available.' };
254
- }
255
-
256
- const attempts = [
257
- { command: 'npm', args: ['install', '-g', 'pi-subagents'] },
258
- { command: 'npm.cmd', args: ['install', '-g', 'pi-subagents'] },
259
- ];
260
-
261
- let lastError = 'Unknown install failure.';
262
- for (const attempt of attempts) {
263
- const result = runCommand(attempt.command, attempt.args);
264
- if (result.status === 0) {
265
- const after = await inspectPiSubagentsDependency();
266
- if (after.localInstalled || after.globalInstalled) {
267
- return {
268
- ok: true,
269
- changed: true,
270
- report: (result.stdout || result.stderr || 'Installed pi-subagents.').trim(),
271
- };
272
- }
273
- lastError = 'npm install reported success, but pi-subagents was still not detected.';
274
- continue;
275
- }
276
- lastError = [result.stdout, result.stderr].filter(Boolean).join('\n').trim() || lastError;
277
- }
278
-
279
- return { ok: false, changed: false, report: lastError };
280
- }
281
-
282
- export function printPiSubagentsInstallResult(result) {
283
- if (result.ok) {
284
- console.log(pc.green(`✔ ${result.changed ? 'Installed' : 'Validated'} pi-subagents`));
285
- if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-4).join('\n')));
286
- return;
287
- }
288
- console.log(pc.red(' Failed to install pi-subagents'));
289
- if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
290
- }
291
-
292
- export async function inspectPiHarnessEnvironment(cwd = process.cwd()) {
293
- const pi = await detectPiCommand();
294
- const bundled = await inspectBundledPiAssets();
295
- const installed = await inspectInstalledTakomiPiHarness();
296
- const piSubagents = await inspectPiSubagentsDependency();
297
- const project = getProjectPiTargets(cwd);
298
-
299
- return {
300
- pi,
301
- bundled,
302
- installed,
303
- piSubagents,
304
- project: {
305
- targets: project,
306
- settingsPresent: await fs.pathExists(project.settings),
307
- routingPolicyPresent: await fs.pathExists(project.routingPolicy),
308
- profilePresent: await fs.pathExists(project.takomiProfile),
309
- },
310
- };
311
- }
312
-
313
- export async function launchTakomiHarness(cwd = process.cwd()) {
314
- const report = await inspectPiHarnessEnvironment(cwd);
315
-
316
- if (!report.pi.installed) {
317
- console.log(pc.red('Pi is not installed.'));
318
- console.log(pc.dim('Run: takomi install pi'));
319
- return 1;
320
- }
321
-
322
- if (!report.installed.runtimeInstalled || !report.installed.subagentsInstalled) {
323
- console.log(pc.red('Takomi Pi harness is not fully installed.'));
324
- console.log(pc.dim('Run: takomi install pi'));
325
- return 1;
326
- }
327
-
328
- const env = {
329
- ...process.env,
330
- TAKOMI_HARNESS: '1',
331
- };
332
-
333
- return await new Promise((resolve) => {
334
- const child = process.platform === 'win32'
335
- ? spawn('cmd.exe', ['/d', '/s', '/c', 'pi'], {
336
- cwd,
337
- stdio: 'inherit',
338
- env,
339
- shell: false,
340
- })
341
- : spawn(report.pi.path || 'pi', [], {
342
- cwd,
343
- stdio: 'inherit',
344
- env,
345
- shell: false,
346
- });
347
-
348
- child.on('close', (code) => resolve(code ?? 0));
349
- child.on('error', () => resolve(1));
350
- });
351
- }
1
+ import fs from 'fs-extra';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { spawn, spawnSync } from 'child_process';
5
+ import pc from 'picocolors';
6
+ import { PATHS } from './utils.js';
7
+ import { STORE_PATH, MANIFEST_PATH } from './store.js';
8
+
9
+ const TAKOMI_PACKAGE_DIR = PATHS.root;
10
+
11
+ const HOME = os.homedir();
12
+
13
+ function getPathEntries() {
14
+ return (process.env.PATH || '').split(path.delimiter).filter(Boolean);
15
+ }
16
+
17
+ function commandExists(command) {
18
+ const probe = process.platform === 'win32'
19
+ ? spawnSync('where', [command], { stdio: 'pipe', encoding: 'utf8' })
20
+ : spawnSync('which', [command], { stdio: 'pipe', encoding: 'utf8' });
21
+
22
+ if (probe.status === 0) {
23
+ const matches = probe.stdout.split(/\r?\n/).map(v => v.trim()).filter(Boolean);
24
+ const preferred = process.platform === 'win32'
25
+ ? matches.find((entry) => /\.(cmd|exe|bat)$/i.test(entry))
26
+ : matches[0];
27
+ return { found: true, path: preferred || matches[0] || null };
28
+ }
29
+
30
+ for (const entry of getPathEntries()) {
31
+ const candidate = path.join(entry, process.platform === 'win32' ? `${command}.cmd` : command);
32
+ if (fs.existsSync(candidate)) return { found: true, path: candidate };
33
+ }
34
+
35
+ return { found: false, path: null };
36
+ }
37
+
38
+ export function getPiAgentRoot(home = HOME) {
39
+ return path.join(home, '.pi', 'agent');
40
+ }
41
+
42
+ export function getPiGlobalTargets(home = HOME) {
43
+ const agentRoot = getPiAgentRoot(home);
44
+ return {
45
+ root: agentRoot,
46
+ extensions: path.join(agentRoot, 'extensions'),
47
+ prompts: path.join(agentRoot, 'prompts'),
48
+ agents: path.join(agentRoot, 'agents'),
49
+ themes: path.join(agentRoot, 'themes'),
50
+ settings: path.join(agentRoot, 'settings.json'),
51
+ takomi: path.join(agentRoot, 'takomi'),
52
+ routingPolicy: path.join(agentRoot, 'takomi', 'model-routing.md'),
53
+ };
54
+ }
55
+
56
+ export function getProjectPiTargets(cwd = process.cwd()) {
57
+ return {
58
+ root: path.join(cwd, '.pi'),
59
+ extensions: path.join(cwd, '.pi', 'extensions'),
60
+ prompts: path.join(cwd, '.pi', 'prompts'),
61
+ agents: path.join(cwd, '.pi', 'agents'),
62
+ themes: path.join(cwd, '.pi', 'themes'),
63
+ settings: path.join(cwd, '.pi', 'settings.json'),
64
+ takomiProfile: path.join(cwd, '.pi', 'takomi-profile.json'),
65
+ routingPolicy: path.join(cwd, '.pi', 'takomi', 'model-routing.md'),
66
+ };
67
+ }
68
+
69
+ export function getBundledPiAssetTargets() {
70
+ const root = path.join(PATHS.root, '.pi');
71
+ return {
72
+ root,
73
+ runtime: path.join(root, 'extensions', 'takomi-runtime'),
74
+ subagents: path.join(root, 'extensions', 'takomi-subagents'),
75
+ prompts: path.join(root, 'prompts'),
76
+ agents: path.join(root, 'agents'),
77
+ themes: path.join(root, 'themes'),
78
+ settings: path.join(root, 'settings.json'),
79
+ routingPolicy: path.join(root, 'takomi', 'model-routing.md'),
80
+ profile: path.join(root, 'takomi-profile.json'),
81
+ };
82
+ }
83
+
84
+ async function getPackageJson() {
85
+ try {
86
+ return await fs.readJson(PATHS.packageJson);
87
+ } catch {
88
+ return {};
89
+ }
90
+ }
91
+
92
+ async function getPackageFileEntries() {
93
+ const pkg = await getPackageJson();
94
+ return Array.isArray(pkg.files) ? pkg.files : [];
95
+ }
96
+
97
+ function getGlobalNodeModulesRoot(home = HOME) {
98
+ if (process.platform === 'win32') {
99
+ return path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'npm', 'node_modules');
100
+ }
101
+ return path.join(home, '.npm-global', 'lib', 'node_modules');
102
+ }
103
+
104
+ export async function inspectPiSubagentsDependency(home = HOME) {
105
+ const pkg = await getPackageJson();
106
+ const declaredVersion = pkg?.dependencies?.['pi-subagents'] || null;
107
+ const localPackageJson = path.join(TAKOMI_PACKAGE_DIR, 'node_modules', 'pi-subagents', 'package.json');
108
+ const globalPackageJson = path.join(getGlobalNodeModulesRoot(home), 'pi-subagents', 'package.json');
109
+
110
+ let localVersion = null;
111
+ let globalVersion = null;
112
+
113
+ try {
114
+ if (await fs.pathExists(localPackageJson)) {
115
+ const localPkg = await fs.readJson(localPackageJson);
116
+ localVersion = localPkg.version || null;
117
+ }
118
+ } catch {}
119
+
120
+ try {
121
+ if (await fs.pathExists(globalPackageJson)) {
122
+ const globalPkg = await fs.readJson(globalPackageJson);
123
+ globalVersion = globalPkg.version || null;
124
+ }
125
+ } catch {}
126
+
127
+ const bin = commandExists('pi-subagents');
128
+
129
+ return {
130
+ declaredVersion,
131
+ localInstalled: Boolean(localVersion),
132
+ localVersion,
133
+ localPackageJson,
134
+ globalInstalled: Boolean(globalVersion),
135
+ globalVersion,
136
+ globalPackageJson,
137
+ binaryInstalled: bin.found,
138
+ binaryPath: bin.path,
139
+ };
140
+ }
141
+
142
+ export async function detectPiCommand() {
143
+ const binary = commandExists('pi');
144
+ if (!binary.found) {
145
+ return { installed: false, path: null, version: null };
146
+ }
147
+
148
+ const versionProbe = process.platform === 'win32'
149
+ ? spawnSync('powershell', ['-NoProfile', '-Command', `& '${(binary.path || 'pi').replace(/'/g, "''")}' --version`], { stdio: 'pipe', encoding: 'utf8' })
150
+ : spawnSync(binary.path || 'pi', ['--version'], { stdio: 'pipe', encoding: 'utf8' });
151
+ const output = [versionProbe.stdout, versionProbe.stderr].filter(Boolean).join('\n').trim();
152
+ return {
153
+ installed: true,
154
+ path: binary.path,
155
+ version: versionProbe.status === 0 ? (output || null) : null,
156
+ };
157
+ }
158
+
159
+ export async function inspectBundledPiAssets() {
160
+ const targets = getBundledPiAssetTargets();
161
+ const checks = {
162
+ root: await fs.pathExists(targets.root),
163
+ runtime: await fs.pathExists(targets.runtime),
164
+ subagents: await fs.pathExists(targets.subagents),
165
+ prompts: await fs.pathExists(targets.prompts),
166
+ agents: await fs.pathExists(targets.agents),
167
+ themes: await fs.pathExists(targets.themes),
168
+ settings: await fs.pathExists(targets.settings),
169
+ routingPolicy: await fs.pathExists(targets.routingPolicy),
170
+ profile: await fs.pathExists(targets.profile),
171
+ };
172
+
173
+ const packageReady = await fs.pathExists(targets.root);
174
+ const packageFiles = await getPackageFileEntries();
175
+ const packageIncluded = packageFiles.includes('.pi') || packageFiles.some((entry) => entry.startsWith('.pi/')) || packageReady;
176
+
177
+ return {
178
+ targets,
179
+ checks,
180
+ packageReady,
181
+ packageIncluded,
182
+ };
183
+ }
184
+
185
+ export async function inspectInstalledTakomiPiHarness(home = HOME) {
186
+ const targets = getPiGlobalTargets(home);
187
+ const runtime = path.join(targets.extensions, 'takomi-runtime');
188
+ const subagents = path.join(targets.extensions, 'takomi-subagents');
189
+ const core = path.join(path.dirname(targets.root), 'src', 'pi-takomi-core');
190
+ const piSubagentsModule = path.join(targets.root, 'node_modules', 'pi-subagents');
191
+
192
+ return {
193
+ targets,
194
+ runtimeInstalled: await fs.pathExists(runtime),
195
+ subagentsInstalled: await fs.pathExists(subagents),
196
+ coreInstalled: await fs.pathExists(core),
197
+ piSubagentsModuleInstalled: await fs.pathExists(piSubagentsModule),
198
+ promptsInstalled: await fs.pathExists(targets.prompts),
199
+ agentsInstalled: await fs.pathExists(targets.agents),
200
+ themesInstalled: await fs.pathExists(targets.themes),
201
+ settingsPresent: await fs.pathExists(targets.settings),
202
+ routingPolicyPresent: await fs.pathExists(targets.routingPolicy),
203
+ manifestPresent: await fs.pathExists(MANIFEST_PATH),
204
+ storePresent: await fs.pathExists(STORE_PATH),
205
+ };
206
+ }
207
+
208
+ function runCommand(command, args) {
209
+ return spawnSync(command, args, { stdio: 'pipe', encoding: 'utf8', shell: process.platform === 'win32' });
210
+ }
211
+
212
+ export async function ensurePiInstalled() {
213
+ const before = await detectPiCommand();
214
+ if (before.installed) {
215
+ return { ok: true, changed: false, report: 'Pi already available.' };
216
+ }
217
+
218
+ const attempts = [
219
+ { command: 'npm', args: ['install', '-g', '@mariozechner/pi-coding-agent'] },
220
+ { command: 'npm.cmd', args: ['install', '-g', '@mariozechner/pi-coding-agent'] },
221
+ ];
222
+
223
+ let lastError = 'Unknown install failure.';
224
+ for (const attempt of attempts) {
225
+ const result = runCommand(attempt.command, attempt.args);
226
+ if (result.status === 0) {
227
+ const after = await detectPiCommand();
228
+ if (after.installed) {
229
+ return {
230
+ ok: true,
231
+ changed: true,
232
+ report: (result.stdout || result.stderr || 'Installed Pi.').trim(),
233
+ };
234
+ }
235
+ lastError = 'npm install reported success, but pi was still not detected.';
236
+ continue;
237
+ }
238
+ lastError = [result.stdout, result.stderr].filter(Boolean).join('\n').trim() || lastError;
239
+ }
240
+
241
+ return { ok: false, changed: false, report: lastError };
242
+ }
243
+
244
+ export function printPiInstallResult(result) {
245
+ if (result.ok) {
246
+ console.log(pc.green(`✔ ${result.changed ? 'Installed' : 'Validated'} Pi`));
247
+ if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-4).join('\n')));
248
+ return;
249
+ }
250
+ console.log(pc.red('✗ Failed to install Pi'));
251
+ if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
252
+ }
253
+
254
+ export async function ensurePiSubagentsInstalled() {
255
+ const before = await inspectPiSubagentsDependency();
256
+ if (before.localInstalled || before.globalInstalled) {
257
+ return { ok: true, changed: false, report: 'pi-subagents already available.' };
258
+ }
259
+
260
+ const attempts = [
261
+ { command: 'npm', args: ['install', '-g', 'pi-subagents'] },
262
+ { command: 'npm.cmd', args: ['install', '-g', 'pi-subagents'] },
263
+ ];
264
+
265
+ let lastError = 'Unknown install failure.';
266
+ for (const attempt of attempts) {
267
+ const result = runCommand(attempt.command, attempt.args);
268
+ if (result.status === 0) {
269
+ const after = await inspectPiSubagentsDependency();
270
+ if (after.localInstalled || after.globalInstalled) {
271
+ return {
272
+ ok: true,
273
+ changed: true,
274
+ report: (result.stdout || result.stderr || 'Installed pi-subagents.').trim(),
275
+ };
276
+ }
277
+ lastError = 'npm install reported success, but pi-subagents was still not detected.';
278
+ continue;
279
+ }
280
+ lastError = [result.stdout, result.stderr].filter(Boolean).join('\n').trim() || lastError;
281
+ }
282
+
283
+ return { ok: false, changed: false, report: lastError };
284
+ }
285
+
286
+ export function printPiSubagentsInstallResult(result) {
287
+ if (result.ok) {
288
+ console.log(pc.green(`✔ ${result.changed ? 'Installed' : 'Validated'} pi-subagents`));
289
+ if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-4).join('\n')));
290
+ return;
291
+ }
292
+ console.log(pc.red('✗ Failed to install pi-subagents'));
293
+ if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
294
+ }
295
+
296
+ export async function inspectPiHarnessEnvironment(cwd = process.cwd()) {
297
+ const pi = await detectPiCommand();
298
+ const bundled = await inspectBundledPiAssets();
299
+ const installed = await inspectInstalledTakomiPiHarness();
300
+ const piSubagents = await inspectPiSubagentsDependency();
301
+ const project = getProjectPiTargets(cwd);
302
+
303
+ return {
304
+ pi,
305
+ bundled,
306
+ installed,
307
+ piSubagents,
308
+ project: {
309
+ targets: project,
310
+ settingsPresent: await fs.pathExists(project.settings),
311
+ routingPolicyPresent: await fs.pathExists(project.routingPolicy),
312
+ profilePresent: await fs.pathExists(project.takomiProfile),
313
+ },
314
+ };
315
+ }
316
+
317
+ export async function launchTakomiHarness(cwd = process.cwd()) {
318
+ const report = await inspectPiHarnessEnvironment(cwd);
319
+
320
+ if (!report.pi.installed) {
321
+ console.log(pc.red('Pi is not installed.'));
322
+ console.log(pc.dim('Run: takomi install pi'));
323
+ return 1;
324
+ }
325
+
326
+ if (!report.installed.runtimeInstalled || !report.installed.subagentsInstalled) {
327
+ console.log(pc.red('Takomi Pi harness is not fully installed.'));
328
+ console.log(pc.dim('Run: takomi install pi'));
329
+ return 1;
330
+ }
331
+
332
+ const env = {
333
+ ...process.env,
334
+ TAKOMI_HARNESS: '1',
335
+ };
336
+
337
+ return await new Promise((resolve) => {
338
+ const child = process.platform === 'win32'
339
+ ? spawn('cmd.exe', ['/d', '/s', '/c', 'pi'], {
340
+ cwd,
341
+ stdio: 'inherit',
342
+ env,
343
+ shell: false,
344
+ })
345
+ : spawn(report.pi.path || 'pi', [], {
346
+ cwd,
347
+ stdio: 'inherit',
348
+ env,
349
+ shell: false,
350
+ });
351
+
352
+ child.on('close', (code) => resolve(code ?? 0));
353
+ child.on('error', () => resolve(1));
354
+ });
355
+ }