takomi 2.1.26 → 2.1.28
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 +10 -0
- package/.pi/agents/architect.md +0 -1
- package/.pi/agents/coder.md +0 -1
- package/.pi/agents/designer.md +0 -1
- package/.pi/agents/orchestrator.md +0 -1
- package/.pi/agents/reviewer.md +0 -1
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +1 -1
- package/.pi/extensions/takomi-context-manager/extension-conflicts.ts +14 -3
- package/.pi/extensions/takomi-context-manager/index.ts +6 -3
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +28 -13
- package/.pi/extensions/takomi-runtime/commands.ts +24 -7
- package/.pi/extensions/takomi-runtime/context-panel.ts +583 -282
- package/.pi/extensions/takomi-runtime/index.ts +101 -6
- package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +296 -0
- package/.pi/extensions/takomi-runtime/routing-policy.ts +67 -17
- package/.pi/extensions/takomi-runtime/takomi-stats.js +44 -16
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +12 -2
- package/.pi/extensions/takomi-subagents/tool-runner.ts +4 -2
- package/.pi/settings.json +18 -20
- package/README.md +34 -4
- package/package.json +4 -2
- package/src/cli.js +326 -33
- package/src/harness.js +132 -16
- package/src/pi-harness.js +27 -6
- package/src/pi-optional-features.js +195 -0
- package/src/postinstall.js +27 -0
- package/src/skills-catalog.js +245 -0
- package/src/skills-installer.js +244 -101
- package/src/skills-selection-tui.js +200 -0
- package/src/store.js +418 -240
- package/src/takomi-stats.js +44 -16
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import {
|
|
4
|
+
colorCategory,
|
|
5
|
+
getUncategorizedSkills,
|
|
6
|
+
listBundledSkillNames,
|
|
7
|
+
readSkillDescription,
|
|
8
|
+
SKILL_CATEGORIES,
|
|
9
|
+
} from './skills-catalog.js';
|
|
10
|
+
|
|
11
|
+
const CLEAR_SCREEN = '\x1b[2J\x1b[H';
|
|
12
|
+
const HIDE_CURSOR = '\x1b[?25l';
|
|
13
|
+
const SHOW_CURSOR = '\x1b[?25h';
|
|
14
|
+
|
|
15
|
+
function uniqueSkills(skills = []) {
|
|
16
|
+
return [...new Set(skills)].sort();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function terminalSupportsTui() {
|
|
20
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI && process.env.TAKOMI_SKILLS_TUI !== '0');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function buildTuiCategories() {
|
|
24
|
+
const bundled = new Set(await listBundledSkillNames());
|
|
25
|
+
const categories = SKILL_CATEGORIES.map((category) => ({
|
|
26
|
+
...category,
|
|
27
|
+
skills: category.skills.filter((skill) => bundled.has(skill)),
|
|
28
|
+
})).filter((category) => category.skills.length > 0);
|
|
29
|
+
|
|
30
|
+
const uncategorized = await getUncategorizedSkills();
|
|
31
|
+
if (uncategorized.length) {
|
|
32
|
+
categories.push({
|
|
33
|
+
id: '__uncategorized',
|
|
34
|
+
title: 'Other / Uncategorized',
|
|
35
|
+
color: 'yellow',
|
|
36
|
+
description: 'Bundled skills that are not assigned to a curated category yet.',
|
|
37
|
+
skills: uncategorized,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const descriptions = new Map();
|
|
42
|
+
for (const skill of uniqueSkills(categories.flatMap((category) => category.skills))) {
|
|
43
|
+
descriptions.set(skill, await readSkillDescription(skill));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { categories, descriptions };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getCategorySkillState(category, selected) {
|
|
50
|
+
const selectedCount = category.skills.filter((skill) => selected.has(skill)).length;
|
|
51
|
+
return {
|
|
52
|
+
selectedCount,
|
|
53
|
+
total: category.skills.length,
|
|
54
|
+
allSelected: selectedCount === category.skills.length,
|
|
55
|
+
noneSelected: selectedCount === 0,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function buildVisibleRows(categories, expanded) {
|
|
60
|
+
const rows = [];
|
|
61
|
+
for (const category of categories) {
|
|
62
|
+
rows.push({ type: 'category', category });
|
|
63
|
+
if (expanded.has(category.id)) {
|
|
64
|
+
for (const skill of category.skills) rows.push({ type: 'skill', category, skill });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return rows;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function trimLine(value, width) {
|
|
71
|
+
const plain = pc.stripColor ? pc.stripColor(value) : value.replace(/\x1b\[[0-9;]*m/g, '');
|
|
72
|
+
if (plain.length <= width) return value;
|
|
73
|
+
return plain.slice(0, Math.max(0, width - 1)) + '…';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function render({ categories, descriptions, expanded, selected, cursor, scroll, title }) {
|
|
77
|
+
const rows = buildVisibleRows(categories, expanded);
|
|
78
|
+
const width = Math.max(60, process.stdout.columns || 100);
|
|
79
|
+
const height = Math.max(16, process.stdout.rows || 30);
|
|
80
|
+
const bodyHeight = height - 8;
|
|
81
|
+
const visibleRows = rows.slice(scroll, scroll + bodyHeight);
|
|
82
|
+
const selectedTotal = selected.size;
|
|
83
|
+
|
|
84
|
+
const lines = [];
|
|
85
|
+
lines.push(pc.magenta('🧰 Takomi Skills Installation'));
|
|
86
|
+
lines.push(pc.dim(title || 'Custom category selector'));
|
|
87
|
+
lines.push(pc.dim('↑/↓ move Space select → expand ← collapse Enter continue q/Esc cancel'));
|
|
88
|
+
lines.push(pc.cyan(`Selected skills: ${selectedTotal}`));
|
|
89
|
+
lines.push('');
|
|
90
|
+
|
|
91
|
+
for (let index = 0; index < visibleRows.length; index++) {
|
|
92
|
+
const absoluteIndex = scroll + index;
|
|
93
|
+
const row = visibleRows[index];
|
|
94
|
+
const active = absoluteIndex === cursor;
|
|
95
|
+
const prefix = active ? pc.inverse('›') : ' ';
|
|
96
|
+
|
|
97
|
+
if (row.type === 'category') {
|
|
98
|
+
const state = getCategorySkillState(row.category, selected);
|
|
99
|
+
const marker = expanded.has(row.category.id) ? '▾' : '▸';
|
|
100
|
+
const checkbox = state.allSelected ? pc.green('[x]') : state.noneSelected ? pc.dim('[ ]') : pc.yellow('[-]');
|
|
101
|
+
const count = pc.dim(`${state.selectedCount}/${state.total}`);
|
|
102
|
+
const description = pc.dim(row.category.description || '');
|
|
103
|
+
lines.push(trimLine(`${prefix} ${marker} ${checkbox} ${colorCategory(row.category)} ${count} ${description}`, width));
|
|
104
|
+
} else {
|
|
105
|
+
const checked = selected.has(row.skill) ? pc.green('[x]') : pc.dim('[ ]');
|
|
106
|
+
const description = descriptions.get(row.skill) || '';
|
|
107
|
+
lines.push(trimLine(`${prefix} ${checked} ${pc.white(row.skill)} ${pc.dim(description)}`, width));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (scroll + bodyHeight < rows.length) lines.push(pc.dim(' … more below'));
|
|
112
|
+
lines.push('');
|
|
113
|
+
lines.push(pc.dim('Tip: selecting a category toggles all skills in that category. Expand to fine-tune individual skills.'));
|
|
114
|
+
|
|
115
|
+
process.stdout.write(CLEAR_SCREEN + HIDE_CURSOR + lines.join('\n'));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function clampCursor(cursor, rowCount) {
|
|
119
|
+
if (rowCount <= 0) return 0;
|
|
120
|
+
return Math.max(0, Math.min(cursor, rowCount - 1));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function scrollForCursor(cursor, scroll) {
|
|
124
|
+
const height = Math.max(16, process.stdout.rows || 30);
|
|
125
|
+
const bodyHeight = height - 8;
|
|
126
|
+
if (cursor < scroll) return cursor;
|
|
127
|
+
if (cursor >= scroll + bodyHeight) return cursor - bodyHeight + 1;
|
|
128
|
+
return scroll;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function promptSkillCategoryTui({ initialSelected = [], title = 'Custom Skills Selection' } = {}) {
|
|
132
|
+
if (!terminalSupportsTui()) return undefined;
|
|
133
|
+
|
|
134
|
+
const { categories, descriptions } = await buildTuiCategories();
|
|
135
|
+
const selected = new Set(initialSelected);
|
|
136
|
+
const expanded = new Set(initialSelected.length ? categories.filter((category) => category.skills.some((skill) => selected.has(skill))).map((category) => category.id) : ['core']);
|
|
137
|
+
let cursor = 0;
|
|
138
|
+
let scroll = 0;
|
|
139
|
+
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
const stdin = process.stdin;
|
|
142
|
+
const wasRaw = stdin.isRaw;
|
|
143
|
+
readline.emitKeypressEvents(stdin);
|
|
144
|
+
stdin.setRawMode?.(true);
|
|
145
|
+
stdin.resume();
|
|
146
|
+
|
|
147
|
+
const cleanup = (value) => {
|
|
148
|
+
stdin.off('keypress', onKeypress);
|
|
149
|
+
if (!wasRaw) stdin.setRawMode?.(false);
|
|
150
|
+
process.stdout.write(SHOW_CURSOR + '\n');
|
|
151
|
+
resolve(value);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const rerender = () => {
|
|
155
|
+
const rows = buildVisibleRows(categories, expanded);
|
|
156
|
+
cursor = clampCursor(cursor, rows.length);
|
|
157
|
+
scroll = scrollForCursor(cursor, scroll);
|
|
158
|
+
render({ categories, descriptions, expanded, selected, cursor, scroll, title });
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const toggleCategory = (category) => {
|
|
162
|
+
const state = getCategorySkillState(category, selected);
|
|
163
|
+
if (state.allSelected) {
|
|
164
|
+
for (const skill of category.skills) selected.delete(skill);
|
|
165
|
+
} else {
|
|
166
|
+
for (const skill of category.skills) selected.add(skill);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const onKeypress = (_str, key = {}) => {
|
|
171
|
+
const rows = buildVisibleRows(categories, expanded);
|
|
172
|
+
const row = rows[cursor];
|
|
173
|
+
|
|
174
|
+
if (key.name === 'c' && key.ctrl) return cleanup(null);
|
|
175
|
+
if (key.name === 'escape' || key.name === 'q') return cleanup(null);
|
|
176
|
+
if (key.name === 'return') return cleanup([...selected].sort());
|
|
177
|
+
|
|
178
|
+
if (key.name === 'up') cursor -= 1;
|
|
179
|
+
else if (key.name === 'down') cursor += 1;
|
|
180
|
+
else if (key.name === 'pageup') cursor -= Math.max(5, (process.stdout.rows || 30) - 10);
|
|
181
|
+
else if (key.name === 'pagedown') cursor += Math.max(5, (process.stdout.rows || 30) - 10);
|
|
182
|
+
else if (key.name === 'right' && row?.type === 'category') expanded.add(row.category.id);
|
|
183
|
+
else if (key.name === 'left' && row?.type === 'category') expanded.delete(row.category.id);
|
|
184
|
+
else if (key.name === 'left' && row?.type === 'skill') {
|
|
185
|
+
expanded.delete(row.category.id);
|
|
186
|
+
cursor = rows.findIndex((candidate) => candidate.type === 'category' && candidate.category.id === row.category.id);
|
|
187
|
+
} else if (key.name === 'space' && row?.type === 'category') {
|
|
188
|
+
toggleCategory(row.category);
|
|
189
|
+
} else if (key.name === 'space' && row?.type === 'skill') {
|
|
190
|
+
if (selected.has(row.skill)) selected.delete(row.skill);
|
|
191
|
+
else selected.add(row.skill);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
rerender();
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
stdin.on('keypress', onKeypress);
|
|
198
|
+
rerender();
|
|
199
|
+
});
|
|
200
|
+
}
|