takomi 2.0.6 → 2.1.0

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 (76) hide show
  1. package/.pi/README.md +124 -0
  2. package/.pi/agents/architect.md +16 -0
  3. package/.pi/agents/coder.md +15 -0
  4. package/.pi/agents/designer.md +18 -0
  5. package/.pi/agents/orchestrator.md +23 -0
  6. package/.pi/agents/reviewer.md +17 -0
  7. package/.pi/extensions/oauth-router/README.md +125 -0
  8. package/.pi/extensions/oauth-router/commands.ts +380 -0
  9. package/.pi/extensions/oauth-router/config.ts +200 -0
  10. package/.pi/extensions/oauth-router/index.ts +41 -0
  11. package/.pi/extensions/oauth-router/oauth-flow.ts +154 -0
  12. package/.pi/extensions/oauth-router/oauth-store.ts +121 -0
  13. package/.pi/extensions/oauth-router/package.json +14 -0
  14. package/.pi/extensions/oauth-router/policies.ts +27 -0
  15. package/.pi/extensions/oauth-router/provider.ts +492 -0
  16. package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -0
  17. package/.pi/extensions/oauth-router/state.ts +174 -0
  18. package/.pi/extensions/oauth-router/types.ts +153 -0
  19. package/.pi/extensions/takomi-runtime/command-text.ts +130 -0
  20. package/.pi/extensions/takomi-runtime/commands.ts +179 -0
  21. package/.pi/extensions/takomi-runtime/context-panel.ts +282 -0
  22. package/.pi/extensions/takomi-runtime/index.ts +1288 -0
  23. package/.pi/extensions/takomi-runtime/profile.ts +114 -0
  24. package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -0
  25. package/.pi/extensions/takomi-runtime/shared.ts +492 -0
  26. package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -0
  27. package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -0
  28. package/.pi/extensions/takomi-runtime/subagent-types.ts +83 -0
  29. package/.pi/extensions/takomi-runtime/ui.ts +133 -0
  30. package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -0
  31. package/.pi/extensions/takomi-subagents/agents.ts +113 -0
  32. package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -0
  33. package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -0
  34. package/.pi/extensions/takomi-subagents/dispatch.ts +215 -0
  35. package/.pi/extensions/takomi-subagents/index.ts +75 -0
  36. package/.pi/extensions/takomi-subagents/live-updates.ts +83 -0
  37. package/.pi/extensions/takomi-subagents/native-render.ts +174 -0
  38. package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -0
  39. package/.pi/prompts/build-prompt.md +199 -0
  40. package/.pi/prompts/design-prompt.md +134 -0
  41. package/.pi/prompts/genesis-prompt.md +133 -0
  42. package/.pi/prompts/orch-prompt.md +144 -0
  43. package/.pi/prompts/prime-prompt.md +80 -0
  44. package/.pi/prompts/takomi-prompt.md +96 -0
  45. package/.pi/prompts/vibe-primeAgent.md +97 -0
  46. package/.pi/prompts/vibe-spawnTask.md +133 -0
  47. package/.pi/prompts/vibe-syncDocs.md +100 -0
  48. package/.pi/themes/takomi-noir.json +81 -0
  49. package/README.md +61 -27
  50. package/assets/.agent/skills/21st-dev-components/21st-handoff.md +146 -0
  51. package/assets/.agent/skills/21st-dev-components/SKILL.md +198 -0
  52. package/assets/.agent/skills/21st-dev-components/references/categories.md +91 -0
  53. package/assets/.agent/skills/21st-dev-components/references/manual-handoff-template.md +79 -0
  54. package/assets/.agent/skills/21st-dev-components/references/section-detection-rubric.md +59 -0
  55. package/assets/.agent/skills/21st-dev-components/scripts/_shared.mjs +304 -0
  56. package/assets/.agent/skills/21st-dev-components/scripts/build-manual-handoff-template.mjs +115 -0
  57. package/assets/.agent/skills/21st-dev-components/scripts/fetch-21st-source.mjs +65 -0
  58. package/assets/.agent/skills/21st-dev-components/scripts/resolve-21st-component.mjs +115 -0
  59. package/assets/.agent/skills/pr-comment-fix/SKILL.md +182 -0
  60. package/assets/.agent/skills/takomi/SKILL.md +59 -59
  61. package/package.json +58 -41
  62. package/src/cli.js +165 -15
  63. package/src/doctor.js +84 -0
  64. package/src/harness.js +13 -0
  65. package/src/pi-harness.js +351 -0
  66. package/src/pi-installer.js +171 -0
  67. package/src/pi-takomi-core/index.ts +4 -0
  68. package/src/pi-takomi-core/orchestration.ts +402 -0
  69. package/src/pi-takomi-core/routing.ts +93 -0
  70. package/src/pi-takomi-core/types.ts +173 -0
  71. package/src/pi-takomi-core/workflows.ts +299 -0
  72. package/src/skills-installer.js +101 -0
  73. package/src/utils.js +479 -447
  74. package/assets/.agent/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-311.pyc +0 -0
  75. package/assets/.agent/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-311.pyc +0 -0
  76. package/assets/.agent/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-311.pyc +0 -0
package/src/doctor.js ADDED
@@ -0,0 +1,84 @@
1
+ import fs from 'fs-extra';
2
+ import pc from 'picocolors';
3
+ import { inspectPiHarnessEnvironment } from './pi-harness.js';
4
+ import { getStoreSkills, isStoreInitialized } from './store.js';
5
+ import { PI_MANIFEST_PATH } from './pi-installer.js';
6
+ import { SKILLS_MANIFEST_PATH, SKILLS_ROOT } from './skills-installer.js';
7
+
8
+ function status(ok, label, value) {
9
+ const icon = ok ? pc.green('✔') : pc.yellow('✗');
10
+ const suffix = value ? ` ${pc.dim(value)}` : '';
11
+ return ` ${icon} ${label}${suffix}`;
12
+ }
13
+
14
+ export async function runDoctor({ version, cwd = process.cwd() } = {}) {
15
+ const report = await inspectPiHarnessEnvironment(cwd);
16
+ const storeReady = await isStoreInitialized();
17
+ const storeSkills = storeReady ? await getStoreSkills() : [];
18
+
19
+ console.log(pc.magenta('🩺 Takomi Doctor\n'));
20
+ console.log(status(true, 'Takomi CLI', version || 'unknown'));
21
+ console.log(status(report.pi.installed, 'Pi command', report.pi.installed ? `${report.pi.path}${report.pi.version ? ` (${report.pi.version})` : ''}` : 'missing'));
22
+ console.log(status(report.bundled.packageReady, 'Bundled .pi assets', report.bundled.targets.root));
23
+ console.log(status(report.bundled.checks.runtime, 'Bundled takomi-runtime', report.bundled.targets.runtime));
24
+ console.log(status(report.bundled.checks.subagents, 'Bundled takomi-subagents', report.bundled.targets.subagents));
25
+ console.log(status(report.bundled.packageIncluded, 'Package includes .pi assets', report.bundled.packageIncluded ? 'yes' : 'no'));
26
+ console.log(status(report.installed.runtimeInstalled, 'Installed takomi-runtime', report.installed.targets.extensions));
27
+ console.log(status(report.installed.subagentsInstalled, 'Installed takomi-subagents', report.installed.targets.extensions));
28
+ console.log(status(await fs.pathExists(`${report.installed.targets.extensions}/oauth-router`), 'Installed oauth-router', `${report.installed.targets.extensions}`));
29
+ console.log(status(report.installed.agentsInstalled, 'Installed agents dir', report.installed.targets.agents));
30
+ console.log(status(report.installed.themesInstalled, 'Installed themes dir', report.installed.targets.themes));
31
+ console.log(status(await fs.pathExists(PI_MANIFEST_PATH), 'Pi install manifest', PI_MANIFEST_PATH));
32
+ console.log(status(report.piSubagents.declaredVersion !== null, 'pi-subagents dependency declared', report.piSubagents.declaredVersion || 'missing'));
33
+ console.log(status(report.piSubagents.localInstalled || report.piSubagents.globalInstalled, 'pi-subagents package available', report.piSubagents.localInstalled
34
+ ? `${report.piSubagents.localPackageJson}${report.piSubagents.localVersion ? ` (${report.piSubagents.localVersion})` : ''}`
35
+ : report.piSubagents.globalInstalled
36
+ ? `${report.piSubagents.globalPackageJson}${report.piSubagents.globalVersion ? ` (${report.piSubagents.globalVersion})` : ''}`
37
+ : 'missing'));
38
+ console.log(status(report.piSubagents.binaryInstalled, 'pi-subagents installer binary', report.piSubagents.binaryPath || 'missing'));
39
+ console.log(status(report.installed.routingPolicyPresent || report.project.routingPolicyPresent, 'Routing policy', report.project.routingPolicyPresent ? report.project.targets.routingPolicy : report.installed.targets.routingPolicy));
40
+ console.log(status(await fs.pathExists(SKILLS_ROOT), 'Installed skills root', SKILLS_ROOT));
41
+ console.log(status(await fs.pathExists(SKILLS_MANIFEST_PATH), 'Skills install manifest', SKILLS_MANIFEST_PATH));
42
+ console.log(status(storeReady, 'Global Takomi store', storeReady ? `${storeSkills.length} skills` : 'missing'));
43
+
44
+ console.log(pc.cyan('\nRecommendations\n'));
45
+
46
+ if (!report.pi.installed) {
47
+ console.log(pc.white(' - Install Pi first: npm install -g @mariozechner/pi-coding-agent'));
48
+ }
49
+
50
+ if (!report.bundled.packageIncluded) {
51
+ console.log(pc.white(' - Package .pi assets before enabling takomi install pi in releases.'));
52
+ }
53
+
54
+ if (!report.installed.runtimeInstalled || !report.installed.subagentsInstalled) {
55
+ console.log(pc.white(' - Run takomi install pi after the package-safe Pi asset layout is finalized.'));
56
+ }
57
+
58
+ if (!report.piSubagents.declaredVersion) {
59
+ console.log(pc.white(' - Add pi-subagents to takomi package dependencies so subagent support is bundled intentionally.'));
60
+ }
61
+
62
+ if (!report.piSubagents.localInstalled && !report.piSubagents.globalInstalled) {
63
+ console.log(pc.white(' - Install pi-subagents: npm install -g pi-subagents'));
64
+ }
65
+
66
+ if (!report.project.routingPolicyPresent && !report.installed.routingPolicyPresent) {
67
+ console.log(pc.white(' - Add a routing policy at .pi/takomi/model-routing.md when ready.'));
68
+ }
69
+
70
+ if (!await fs.pathExists(PI_MANIFEST_PATH) && report.installed.runtimeInstalled) {
71
+ console.log(pc.white(' - Re-run takomi install pi to write the Pi asset manifest.'));
72
+ }
73
+
74
+ if (!await fs.pathExists(SKILLS_ROOT)) {
75
+ console.log(pc.white(' - Run takomi install skills to install the bundled Takomi skills.'));
76
+ }
77
+
78
+ if (!storeReady) {
79
+ console.log(pc.white(' - Legacy global command center is not initialized. Run takomi install for current behavior.'));
80
+ }
81
+
82
+ console.log('');
83
+ return report;
84
+ }
package/src/harness.js CHANGED
@@ -73,6 +73,19 @@ export const HARNESS_MAP = {
73
73
  },
74
74
  },
75
75
 
76
+ codex: {
77
+ name: 'Codex',
78
+ rootPath: path.join(HOME, '.codex'),
79
+ detect() {
80
+ return fs.existsSync(this.rootPath);
81
+ },
82
+ targets: {
83
+ skills: path.join(HOME, '.codex', 'skills'),
84
+ workflows: null,
85
+ yamls: null,
86
+ },
87
+ },
88
+
76
89
  cursor: {
77
90
  name: 'Cursor',
78
91
  rootPath: path.join(HOME, '.cursor'),
@@ -0,0 +1,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
+
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
+ }
@@ -0,0 +1,171 @@
1
+ import fs from 'fs-extra';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import crypto from 'crypto';
5
+ import pc from 'picocolors';
6
+ import { PATHS } from './utils.js';
7
+ import { getPiGlobalTargets } from './pi-harness.js';
8
+
9
+ const HOME = os.homedir();
10
+ const TAKOMI_HOME = path.join(HOME, '.takomi');
11
+ export const PI_MANIFEST_PATH = path.join(TAKOMI_HOME, 'pi-manifest.json');
12
+
13
+ function sha256(value) {
14
+ return crypto.createHash('sha256').update(value).digest('hex');
15
+ }
16
+
17
+ async function hashPath(targetPath) {
18
+ if (!await fs.pathExists(targetPath)) return null;
19
+ const stat = await fs.stat(targetPath);
20
+ if (stat.isFile()) {
21
+ const buf = await fs.readFile(targetPath);
22
+ return sha256(buf);
23
+ }
24
+
25
+ const entries = [];
26
+ async function walk(dir, prefix = '') {
27
+ const names = (await fs.readdir(dir)).sort();
28
+ for (const name of names) {
29
+ const full = path.join(dir, name);
30
+ const rel = path.join(prefix, name).replace(/\\/g, '/');
31
+ const st = await fs.stat(full);
32
+ if (st.isDirectory()) {
33
+ entries.push(`dir:${rel}`);
34
+ await walk(full, rel);
35
+ } else {
36
+ const buf = await fs.readFile(full);
37
+ entries.push(`file:${rel}:${sha256(buf)}`);
38
+ }
39
+ }
40
+ }
41
+ await walk(targetPath);
42
+ return sha256(entries.join('\n'));
43
+ }
44
+
45
+ async function pathIsSameSymlink(dest, src) {
46
+ try {
47
+ const stat = await fs.lstat(dest);
48
+ if (!stat.isSymbolicLink()) return false;
49
+ const target = await fs.readlink(dest);
50
+ const resolvedTarget = path.resolve(path.dirname(dest), target);
51
+ return path.resolve(src) === resolvedTarget;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ async function prepareOwnedTarget(src, dest) {
58
+ if (await pathIsSameSymlink(dest, src)) return 'symlink';
59
+ if (!await fs.pathExists(dest)) return 'copy';
60
+ const stat = await fs.lstat(dest);
61
+ if (stat.isSymbolicLink()) {
62
+ await fs.remove(dest);
63
+ return 'copy';
64
+ }
65
+ return 'copy';
66
+ }
67
+
68
+ async function copyOwnedDirectory(src, dest) {
69
+ await fs.ensureDir(path.dirname(dest));
70
+ const mode = await prepareOwnedTarget(src, dest);
71
+ if (mode === 'symlink') return hashPath(src);
72
+ await fs.copy(src, dest, { overwrite: true });
73
+ return hashPath(dest);
74
+ }
75
+
76
+ async function copyOwnedFile(src, dest) {
77
+ await fs.ensureDir(path.dirname(dest));
78
+ const mode = await prepareOwnedTarget(src, dest);
79
+ if (mode === 'symlink') return hashPath(src);
80
+ await fs.copy(src, dest, { overwrite: true });
81
+ return hashPath(dest);
82
+ }
83
+
84
+ export async function readPiInstallManifest() {
85
+ try {
86
+ if (await fs.pathExists(PI_MANIFEST_PATH)) return await fs.readJson(PI_MANIFEST_PATH);
87
+ } catch {}
88
+ return null;
89
+ }
90
+
91
+ export async function writePiInstallManifest(manifest) {
92
+ await fs.ensureDir(TAKOMI_HOME);
93
+ await fs.writeJson(PI_MANIFEST_PATH, manifest, { spaces: 2 });
94
+ }
95
+
96
+ export async function installPiHarnessAssets(version = 'unknown') {
97
+ const srcRoot = PATHS.pi;
98
+ const targets = getPiGlobalTargets();
99
+
100
+ const copied = {};
101
+
102
+ const readmeSrc = path.join(srcRoot, 'README.md');
103
+ if (await fs.pathExists(readmeSrc)) {
104
+ copied.readme = await copyOwnedFile(readmeSrc, path.join(targets.root, 'README.md'));
105
+ }
106
+
107
+ const extensionNames = ['takomi-runtime', 'takomi-subagents', 'oauth-router'];
108
+ for (const name of extensionNames) {
109
+ const src = path.join(srcRoot, 'extensions', name);
110
+ if (await fs.pathExists(src)) {
111
+ copied[`extension:${name}`] = await copyOwnedDirectory(src, path.join(targets.extensions, name));
112
+ }
113
+ }
114
+
115
+ const owned = {
116
+ prompts: { src: path.join(srcRoot, 'prompts'), dest: targets.prompts, type: 'dir' },
117
+ agents: { src: path.join(srcRoot, 'agents'), dest: targets.agents, type: 'dir' },
118
+ themes: { src: path.join(srcRoot, 'themes'), dest: targets.themes, type: 'dir' },
119
+ };
120
+
121
+ for (const [key, entry] of Object.entries(owned)) {
122
+ if (!await fs.pathExists(entry.src)) continue;
123
+ copied[key] = entry.type === 'dir'
124
+ ? await copyOwnedDirectory(entry.src, entry.dest)
125
+ : await copyOwnedFile(entry.src, entry.dest);
126
+ }
127
+
128
+ const manifest = {
129
+ takomiVersion: version,
130
+ installedAt: new Date().toISOString(),
131
+ targetRoot: targets.root,
132
+ owned: copied,
133
+ preserved: {
134
+ settings: targets.settings,
135
+ routingPolicy: targets.routingPolicy,
136
+ userStateDir: targets.takomi,
137
+ },
138
+ };
139
+
140
+ await writePiInstallManifest(manifest);
141
+ return { targets, manifest };
142
+ }
143
+
144
+ export async function syncPiHarnessAssets(version = 'unknown') {
145
+ return installPiHarnessAssets(version);
146
+ }
147
+
148
+ export async function validatePiHarnessInstall() {
149
+ const targets = getPiGlobalTargets();
150
+ return {
151
+ runtime: await fs.pathExists(path.join(targets.extensions, 'takomi-runtime')),
152
+ subagents: await fs.pathExists(path.join(targets.extensions, 'takomi-subagents')),
153
+ oauthRouter: await fs.pathExists(path.join(targets.extensions, 'oauth-router')),
154
+ prompts: await fs.pathExists(targets.prompts),
155
+ agents: await fs.pathExists(targets.agents),
156
+ themes: await fs.pathExists(targets.themes),
157
+ readme: await fs.pathExists(path.join(targets.root, 'README.md')),
158
+ settingsPreserved: !await fs.pathExists(path.join(PATHS.pi, 'settings.json')) || true,
159
+ };
160
+ }
161
+
162
+ export function printPiInstallSummary(result, validation) {
163
+ console.log(pc.green('\n✔ Installed Takomi Pi harness assets'));
164
+ console.log(pc.white(` Root: ${result.targets.root}`));
165
+ console.log(pc.white(` Manifest: ${PI_MANIFEST_PATH}`));
166
+ console.log(pc.white(` Extensions: ${validation.runtime && validation.subagents && validation.oauthRouter ? 'ok' : 'check needed'}`));
167
+ console.log(pc.white(` Prompts: ${validation.prompts ? 'ok' : 'missing'}`));
168
+ console.log(pc.white(` Agents: ${validation.agents ? 'ok' : 'missing'}`));
169
+ console.log(pc.white(` Themes: ${validation.themes ? 'ok' : 'missing'}`));
170
+ console.log(pc.dim('\nPreserved user-owned config: settings.json, takomi/model-routing.md, runtime session state.'));
171
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./types";
2
+ export * from "./workflows";
3
+ export * from "./routing";
4
+ export * from "./orchestration";