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.
- package/.pi/README.md +124 -124
- package/.pi/agents/architect.md +15 -15
- package/.pi/agents/coder.md +14 -14
- package/.pi/agents/designer.md +17 -17
- package/.pi/agents/orchestrator.md +22 -22
- package/.pi/agents/reviewer.md +16 -16
- package/.pi/extensions/oauth-router/README.md +125 -125
- package/.pi/extensions/oauth-router/commands.ts +380 -380
- package/.pi/extensions/oauth-router/config.ts +200 -200
- package/.pi/extensions/oauth-router/index.ts +41 -41
- package/.pi/extensions/oauth-router/oauth-flow.ts +154 -154
- package/.pi/extensions/oauth-router/oauth-store.ts +121 -121
- package/.pi/extensions/oauth-router/package.json +14 -14
- package/.pi/extensions/oauth-router/policies.ts +27 -27
- package/.pi/extensions/oauth-router/provider.ts +492 -492
- package/.pi/extensions/oauth-router/scripts/vibe-verify.py +98 -98
- package/.pi/extensions/oauth-router/state.ts +174 -174
- package/.pi/extensions/oauth-router/types.ts +153 -153
- package/.pi/extensions/takomi-runtime/command-text.ts +130 -130
- package/.pi/extensions/takomi-runtime/commands.ts +179 -179
- package/.pi/extensions/takomi-runtime/context-panel.ts +282 -282
- package/.pi/extensions/takomi-runtime/index.ts +1288 -1288
- package/.pi/extensions/takomi-runtime/profile.ts +114 -114
- package/.pi/extensions/takomi-runtime/routing-policy.ts +105 -105
- package/.pi/extensions/takomi-runtime/shared.ts +511 -492
- package/.pi/extensions/takomi-runtime/subagent-controller.ts +364 -364
- package/.pi/extensions/takomi-runtime/subagent-render.ts +501 -501
- package/.pi/extensions/takomi-runtime/subagent-types.ts +90 -83
- package/.pi/extensions/takomi-runtime/ui.ts +133 -133
- package/.pi/extensions/takomi-subagents/agent-aliases.ts +18 -18
- package/.pi/extensions/takomi-subagents/agents.ts +113 -113
- package/.pi/extensions/takomi-subagents/delegation-plan.ts +95 -95
- package/.pi/extensions/takomi-subagents/dispatch-helpers.ts +26 -26
- package/.pi/extensions/takomi-subagents/dispatch.ts +306 -215
- package/.pi/extensions/takomi-subagents/index.ts +76 -75
- package/.pi/extensions/takomi-subagents/live-updates.ts +136 -83
- package/.pi/extensions/takomi-subagents/native-render.ts +5 -142
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +228 -0
- package/.pi/extensions/takomi-subagents/tool-runner.ts +209 -209
- package/.pi/themes/takomi-noir.json +81 -81
- package/package.json +59 -59
- package/src/cli.js +14 -0
- package/src/doctor.js +87 -84
- package/src/pi-harness.js +355 -351
- package/src/pi-installer.js +193 -171
- package/src/pi-takomi-core/index.ts +4 -4
- package/src/pi-takomi-core/orchestration.ts +402 -402
- package/src/pi-takomi-core/routing.ts +93 -93
- package/src/pi-takomi-core/types.ts +173 -173
- package/src/pi-takomi-core/workflows.ts +299 -299
- package/src/skills-installer.js +101 -101
- package/src/update-check.js +140 -0
package/src/skills-installer.js
CHANGED
|
@@ -1,101 +1,101 @@
|
|
|
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
|
-
|
|
8
|
-
const HOME = os.homedir();
|
|
9
|
-
const TAKOMI_HOME = path.join(HOME, '.takomi');
|
|
10
|
-
export const SKILLS_MANIFEST_PATH = path.join(TAKOMI_HOME, 'skills-manifest.json');
|
|
11
|
-
export const SKILLS_ROOT = path.join(HOME, '.agents', 'skills');
|
|
12
|
-
|
|
13
|
-
function sha256(value) {
|
|
14
|
-
return crypto.createHash('sha256').update(value).digest('hex');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function hashDirectory(dir) {
|
|
18
|
-
if (!await fs.pathExists(dir)) return null;
|
|
19
|
-
const entries = [];
|
|
20
|
-
async function walk(current, prefix = '') {
|
|
21
|
-
const names = (await fs.readdir(current)).sort();
|
|
22
|
-
for (const name of names) {
|
|
23
|
-
const full = path.join(current, name);
|
|
24
|
-
const rel = path.join(prefix, name).replace(/\\/g, '/');
|
|
25
|
-
const stat = await fs.stat(full);
|
|
26
|
-
if (stat.isDirectory()) {
|
|
27
|
-
entries.push(`dir:${rel}`);
|
|
28
|
-
await walk(full, rel);
|
|
29
|
-
} else {
|
|
30
|
-
entries.push(`file:${rel}:${sha256(await fs.readFile(full))}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
await walk(dir);
|
|
35
|
-
return sha256(entries.join('\n'));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function listBundledSkills() {
|
|
39
|
-
const entries = await fs.readdir(PATHS.skills);
|
|
40
|
-
const skills = [];
|
|
41
|
-
for (const entry of entries) {
|
|
42
|
-
const stat = await fs.stat(path.join(PATHS.skills, entry));
|
|
43
|
-
if (stat.isDirectory()) skills.push(entry);
|
|
44
|
-
}
|
|
45
|
-
return skills.sort();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function readSkillsInstallManifest() {
|
|
49
|
-
try {
|
|
50
|
-
if (await fs.pathExists(SKILLS_MANIFEST_PATH)) return await fs.readJson(SKILLS_MANIFEST_PATH);
|
|
51
|
-
} catch {}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function writeSkillsInstallManifest(manifest) {
|
|
56
|
-
await fs.ensureDir(TAKOMI_HOME);
|
|
57
|
-
await fs.writeJson(SKILLS_MANIFEST_PATH, manifest, { spaces: 2 });
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export async function installBundledSkills(version = 'unknown') {
|
|
61
|
-
const skillNames = await listBundledSkills();
|
|
62
|
-
await fs.ensureDir(SKILLS_ROOT);
|
|
63
|
-
|
|
64
|
-
const installed = {};
|
|
65
|
-
for (const name of skillNames) {
|
|
66
|
-
const src = path.join(PATHS.skills, name);
|
|
67
|
-
const dest = path.join(SKILLS_ROOT, name);
|
|
68
|
-
if (await fs.pathExists(dest)) {
|
|
69
|
-
await fs.remove(dest);
|
|
70
|
-
}
|
|
71
|
-
await fs.copy(src, dest, { overwrite: true, errorOnExist: false });
|
|
72
|
-
installed[name] = await hashDirectory(dest);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const manifest = {
|
|
76
|
-
takomiVersion: version,
|
|
77
|
-
installedAt: new Date().toISOString(),
|
|
78
|
-
targetRoot: SKILLS_ROOT,
|
|
79
|
-
owned: installed,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
await writeSkillsInstallManifest(manifest);
|
|
83
|
-
return { targetRoot: SKILLS_ROOT, manifest, count: skillNames.length };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function validateSkillsInstall() {
|
|
87
|
-
const names = await listBundledSkills();
|
|
88
|
-
const missing = [];
|
|
89
|
-
for (const name of names) {
|
|
90
|
-
if (!await fs.pathExists(path.join(SKILLS_ROOT, name))) missing.push(name);
|
|
91
|
-
}
|
|
92
|
-
return { root: SKILLS_ROOT, expected: names.length, missing, ok: missing.length === 0 };
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function printSkillsInstallSummary(result, validation) {
|
|
96
|
-
console.log(pc.green('\n✔ Installed bundled Takomi skills'));
|
|
97
|
-
console.log(pc.white(` Root: ${result.targetRoot}`));
|
|
98
|
-
console.log(pc.white(` Manifest: ${SKILLS_MANIFEST_PATH}`));
|
|
99
|
-
console.log(pc.white(` Count: ${result.count}`));
|
|
100
|
-
console.log(pc.white(` Status: ${validation.ok ? 'ok' : `missing ${validation.missing.length}`}`));
|
|
101
|
-
}
|
|
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
|
+
|
|
8
|
+
const HOME = os.homedir();
|
|
9
|
+
const TAKOMI_HOME = path.join(HOME, '.takomi');
|
|
10
|
+
export const SKILLS_MANIFEST_PATH = path.join(TAKOMI_HOME, 'skills-manifest.json');
|
|
11
|
+
export const SKILLS_ROOT = path.join(HOME, '.agents', 'skills');
|
|
12
|
+
|
|
13
|
+
function sha256(value) {
|
|
14
|
+
return crypto.createHash('sha256').update(value).digest('hex');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function hashDirectory(dir) {
|
|
18
|
+
if (!await fs.pathExists(dir)) return null;
|
|
19
|
+
const entries = [];
|
|
20
|
+
async function walk(current, prefix = '') {
|
|
21
|
+
const names = (await fs.readdir(current)).sort();
|
|
22
|
+
for (const name of names) {
|
|
23
|
+
const full = path.join(current, name);
|
|
24
|
+
const rel = path.join(prefix, name).replace(/\\/g, '/');
|
|
25
|
+
const stat = await fs.stat(full);
|
|
26
|
+
if (stat.isDirectory()) {
|
|
27
|
+
entries.push(`dir:${rel}`);
|
|
28
|
+
await walk(full, rel);
|
|
29
|
+
} else {
|
|
30
|
+
entries.push(`file:${rel}:${sha256(await fs.readFile(full))}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await walk(dir);
|
|
35
|
+
return sha256(entries.join('\n'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function listBundledSkills() {
|
|
39
|
+
const entries = await fs.readdir(PATHS.skills);
|
|
40
|
+
const skills = [];
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const stat = await fs.stat(path.join(PATHS.skills, entry));
|
|
43
|
+
if (stat.isDirectory()) skills.push(entry);
|
|
44
|
+
}
|
|
45
|
+
return skills.sort();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function readSkillsInstallManifest() {
|
|
49
|
+
try {
|
|
50
|
+
if (await fs.pathExists(SKILLS_MANIFEST_PATH)) return await fs.readJson(SKILLS_MANIFEST_PATH);
|
|
51
|
+
} catch {}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function writeSkillsInstallManifest(manifest) {
|
|
56
|
+
await fs.ensureDir(TAKOMI_HOME);
|
|
57
|
+
await fs.writeJson(SKILLS_MANIFEST_PATH, manifest, { spaces: 2 });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function installBundledSkills(version = 'unknown') {
|
|
61
|
+
const skillNames = await listBundledSkills();
|
|
62
|
+
await fs.ensureDir(SKILLS_ROOT);
|
|
63
|
+
|
|
64
|
+
const installed = {};
|
|
65
|
+
for (const name of skillNames) {
|
|
66
|
+
const src = path.join(PATHS.skills, name);
|
|
67
|
+
const dest = path.join(SKILLS_ROOT, name);
|
|
68
|
+
if (await fs.pathExists(dest)) {
|
|
69
|
+
await fs.remove(dest);
|
|
70
|
+
}
|
|
71
|
+
await fs.copy(src, dest, { overwrite: true, errorOnExist: false });
|
|
72
|
+
installed[name] = await hashDirectory(dest);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const manifest = {
|
|
76
|
+
takomiVersion: version,
|
|
77
|
+
installedAt: new Date().toISOString(),
|
|
78
|
+
targetRoot: SKILLS_ROOT,
|
|
79
|
+
owned: installed,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
await writeSkillsInstallManifest(manifest);
|
|
83
|
+
return { targetRoot: SKILLS_ROOT, manifest, count: skillNames.length };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function validateSkillsInstall() {
|
|
87
|
+
const names = await listBundledSkills();
|
|
88
|
+
const missing = [];
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
if (!await fs.pathExists(path.join(SKILLS_ROOT, name))) missing.push(name);
|
|
91
|
+
}
|
|
92
|
+
return { root: SKILLS_ROOT, expected: names.length, missing, ok: missing.length === 0 };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function printSkillsInstallSummary(result, validation) {
|
|
96
|
+
console.log(pc.green('\n✔ Installed bundled Takomi skills'));
|
|
97
|
+
console.log(pc.white(` Root: ${result.targetRoot}`));
|
|
98
|
+
console.log(pc.white(` Manifest: ${SKILLS_MANIFEST_PATH}`));
|
|
99
|
+
console.log(pc.white(` Count: ${result.count}`));
|
|
100
|
+
console.log(pc.white(` Status: ${validation.ok ? 'ok' : `missing ${validation.missing.length}`}`));
|
|
101
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { spawnSync } from 'child_process';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
|
|
8
|
+
const CHECK_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
9
|
+
const CACHE_PATH = path.join(os.homedir(), '.takomi', 'update-check.json');
|
|
10
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/takomi/latest';
|
|
11
|
+
|
|
12
|
+
function parseVersion(version = '') {
|
|
13
|
+
const [core] = String(version).replace(/^v/, '').split('-');
|
|
14
|
+
return core.split('.').map((part) => Number.parseInt(part, 10) || 0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isNewerVersion(latest, current) {
|
|
18
|
+
const a = parseVersion(latest);
|
|
19
|
+
const b = parseVersion(current);
|
|
20
|
+
for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
|
|
21
|
+
const left = a[i] || 0;
|
|
22
|
+
const right = b[i] || 0;
|
|
23
|
+
if (left > right) return true;
|
|
24
|
+
if (left < right) return false;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function fetchLatestPackageInfo(timeoutMs = 2500) {
|
|
30
|
+
return new Promise((resolve) => {
|
|
31
|
+
const req = https.get(REGISTRY_URL, {
|
|
32
|
+
headers: {
|
|
33
|
+
'accept': 'application/json',
|
|
34
|
+
'user-agent': 'takomi-update-check',
|
|
35
|
+
},
|
|
36
|
+
timeout: timeoutMs,
|
|
37
|
+
}, (res) => {
|
|
38
|
+
let body = '';
|
|
39
|
+
res.setEncoding('utf8');
|
|
40
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
41
|
+
res.on('end', () => {
|
|
42
|
+
if (res.statusCode && res.statusCode >= 400) return resolve(null);
|
|
43
|
+
try {
|
|
44
|
+
resolve(JSON.parse(body));
|
|
45
|
+
} catch {
|
|
46
|
+
resolve(null);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
req.on('timeout', () => {
|
|
51
|
+
req.destroy();
|
|
52
|
+
resolve(null);
|
|
53
|
+
});
|
|
54
|
+
req.on('error', () => resolve(null));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readCache() {
|
|
59
|
+
try {
|
|
60
|
+
return await fs.readJson(CACHE_PATH);
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function writeCache(cache) {
|
|
67
|
+
try {
|
|
68
|
+
await fs.ensureDir(path.dirname(CACHE_PATH));
|
|
69
|
+
await fs.writeJson(CACHE_PATH, cache, { spaces: 2 });
|
|
70
|
+
} catch {
|
|
71
|
+
// Update checks must never block normal Takomi startup.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function getLatestTakomiVersion({ currentVersion, force = false } = {}) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const cache = await readCache();
|
|
78
|
+
if (!force && cache?.checkedAt && now - cache.checkedAt < CHECK_INTERVAL_MS) {
|
|
79
|
+
return cache;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const info = await fetchLatestPackageInfo();
|
|
83
|
+
const latestVersion = typeof info?.version === 'string' ? info.version : null;
|
|
84
|
+
const next = {
|
|
85
|
+
checkedAt: now,
|
|
86
|
+
currentVersion,
|
|
87
|
+
latestVersion,
|
|
88
|
+
updateAvailable: Boolean(latestVersion && currentVersion && isNewerVersion(latestVersion, currentVersion)),
|
|
89
|
+
};
|
|
90
|
+
await writeCache(next);
|
|
91
|
+
return next;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function notifyIfTakomiUpdateAvailable(currentVersion) {
|
|
95
|
+
if (process.env.TAKOMI_NO_UPDATE_CHECK === '1') return;
|
|
96
|
+
|
|
97
|
+
// Fire-and-forget by design: launching the Takomi harness must never wait on
|
|
98
|
+
// network, DNS, npm registry latency, or cache file IO.
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
getLatestTakomiVersion({ currentVersion })
|
|
101
|
+
.then((result) => {
|
|
102
|
+
if (!result?.updateAvailable) return;
|
|
103
|
+
console.log(pc.yellow(`\n⬆ Takomi ${result.latestVersion} is available (installed: ${currentVersion}).`));
|
|
104
|
+
console.log(pc.dim(' Run: takomi upgrade'));
|
|
105
|
+
console.log(pc.dim(' Disable this check with TAKOMI_NO_UPDATE_CHECK=1.\n'));
|
|
106
|
+
})
|
|
107
|
+
.catch(() => {
|
|
108
|
+
// Silent: update checks must never affect harness startup or usage.
|
|
109
|
+
});
|
|
110
|
+
}, 0).unref?.();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function printTakomiUpdateStatus(currentVersion) {
|
|
114
|
+
const result = await getLatestTakomiVersion({ currentVersion, force: true });
|
|
115
|
+
if (!result?.latestVersion) {
|
|
116
|
+
console.log(pc.yellow('Could not check the npm registry for Takomi updates.'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (result.updateAvailable) {
|
|
120
|
+
console.log(pc.yellow(`Takomi ${result.latestVersion} is available (installed: ${currentVersion}).`));
|
|
121
|
+
console.log(pc.dim('Run: takomi upgrade'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(pc.green(`Takomi is up to date (${currentVersion}).`));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function upgradeTakomiPackage() {
|
|
128
|
+
console.log(pc.cyan('Updating Takomi from npm...\n'));
|
|
129
|
+
const command = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
130
|
+
const result = spawnSync(command, ['install', '-g', 'takomi@latest'], {
|
|
131
|
+
stdio: 'inherit',
|
|
132
|
+
shell: process.platform === 'win32',
|
|
133
|
+
});
|
|
134
|
+
if (result.status === 0) {
|
|
135
|
+
console.log(pc.green('\nTakomi updated. Run `takomi --version` to confirm.'));
|
|
136
|
+
return 0;
|
|
137
|
+
}
|
|
138
|
+
console.log(pc.red('\nTakomi update failed. Try manually: npm install -g takomi@latest'));
|
|
139
|
+
return result.status || 1;
|
|
140
|
+
}
|