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
package/src/cli.js
CHANGED
|
@@ -33,6 +33,8 @@ import {
|
|
|
33
33
|
initGlobalStore,
|
|
34
34
|
getManifest,
|
|
35
35
|
writeManifest,
|
|
36
|
+
buildStoreSkillsReconcilePlan,
|
|
37
|
+
buildStoreWorkflowsReconcilePlan,
|
|
36
38
|
populateSkills,
|
|
37
39
|
populateWorkflows,
|
|
38
40
|
populateAgentYamls,
|
|
@@ -43,7 +45,26 @@ import {
|
|
|
43
45
|
import { runDoctor } from './doctor.js';
|
|
44
46
|
import { ensurePiInstalled, ensurePiSubagentsInstalled, launchTakomiHarness, printPiInstallResult, printPiSubagentsInstallResult, updatePiManagedPackages, printPiManagedPackageUpdateResult } from './pi-harness.js';
|
|
45
47
|
import { installPiHarnessAssets, printPiInstallSummary, syncPiHarnessAssets, validatePiHarnessInstall } from './pi-installer.js';
|
|
46
|
-
import {
|
|
48
|
+
import { offerPiOptionalFeatures } from './pi-optional-features.js';
|
|
49
|
+
import {
|
|
50
|
+
buildSkillsReconcilePlan,
|
|
51
|
+
getInstalledTakomiSkillNames,
|
|
52
|
+
installBundledSkills,
|
|
53
|
+
printSkillsInstallSummary,
|
|
54
|
+
validateSkillsInstall,
|
|
55
|
+
SKILLS_ROOT,
|
|
56
|
+
} from './skills-installer.js';
|
|
57
|
+
import {
|
|
58
|
+
colorCategory,
|
|
59
|
+
CORE_SKILLS,
|
|
60
|
+
getSkillChoices,
|
|
61
|
+
getSkillsForCategories,
|
|
62
|
+
getUncategorizedSkills,
|
|
63
|
+
getValidCoreSkills,
|
|
64
|
+
listBundledSkillNames,
|
|
65
|
+
SKILL_CATEGORIES,
|
|
66
|
+
} from './skills-catalog.js';
|
|
67
|
+
import { promptSkillCategoryTui } from './skills-selection-tui.js';
|
|
47
68
|
import { notifyIfTakomiUpdateAvailable, printTakomiUpdateStatus, upgradeTakomiPackage } from './update-check.js';
|
|
48
69
|
import { printTakomiStats } from './takomi-stats.js';
|
|
49
70
|
|
|
@@ -172,13 +193,7 @@ async function init() {
|
|
|
172
193
|
// Handle Skills
|
|
173
194
|
if (response.skillMode === 'core') {
|
|
174
195
|
console.log(pc.green('✔ Downloading Core Skills...'));
|
|
175
|
-
|
|
176
|
-
'takomi',
|
|
177
|
-
'ai-sdk', 'code-review', 'component-analysis',
|
|
178
|
-
'nextjs-standards', 'security-audit', 'spawn-task',
|
|
179
|
-
'stitch', 'sync-docs'
|
|
180
|
-
];
|
|
181
|
-
await copySpecificSkills(coreSkills, skillsDest);
|
|
196
|
+
await copySpecificSkills(CORE_SKILLS, skillsDest);
|
|
182
197
|
} else if (response.skillMode === 'all') {
|
|
183
198
|
console.log(pc.green('✔ Downloading all skills...'));
|
|
184
199
|
await copyAllSkills(skillsDest);
|
|
@@ -219,11 +234,182 @@ async function init() {
|
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
|
|
237
|
+
function formatCoreSkillsSummary() {
|
|
238
|
+
return CORE_SKILLS.join(', ');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async function promptCustomSkillSelection(initialSelected = [], options = {}) {
|
|
242
|
+
const tuiSelection = await promptSkillCategoryTui({
|
|
243
|
+
initialSelected,
|
|
244
|
+
title: options.title || 'Custom Skills Selection',
|
|
245
|
+
});
|
|
246
|
+
if (Array.isArray(tuiSelection)) return tuiSelection;
|
|
247
|
+
if (tuiSelection === null) return null;
|
|
248
|
+
|
|
249
|
+
const initialSet = new Set(initialSelected);
|
|
250
|
+
const uncategorized = await getUncategorizedSkills();
|
|
251
|
+
const categoryChoices = SKILL_CATEGORIES.map((category) => ({
|
|
252
|
+
title: colorCategory(category),
|
|
253
|
+
value: category.id,
|
|
254
|
+
selected: category.skills.some((skill) => initialSet.has(skill)),
|
|
255
|
+
description: category.description,
|
|
256
|
+
}));
|
|
257
|
+
|
|
258
|
+
if (uncategorized.length) {
|
|
259
|
+
categoryChoices.push({
|
|
260
|
+
title: pc.dim('[Other / Uncategorized]'),
|
|
261
|
+
value: '__uncategorized',
|
|
262
|
+
selected: uncategorized.some((skill) => initialSet.has(skill)),
|
|
263
|
+
description: 'Bundled skills that are not assigned to a curated category yet.',
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const categoryResponse = await prompts({
|
|
268
|
+
type: 'multiselect',
|
|
269
|
+
name: 'categories',
|
|
270
|
+
message: 'Select skill categories:',
|
|
271
|
+
choices: categoryChoices,
|
|
272
|
+
hint: '- Space to select. Return to continue',
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!categoryResponse.categories) return null;
|
|
276
|
+
|
|
277
|
+
const selected = new Set();
|
|
278
|
+
const selectedCategories = categoryResponse.categories;
|
|
279
|
+
|
|
280
|
+
for (const categoryId of selectedCategories) {
|
|
281
|
+
const category = SKILL_CATEGORIES.find((item) => item.id === categoryId);
|
|
282
|
+
const skillNames = categoryId === '__uncategorized'
|
|
283
|
+
? uncategorized
|
|
284
|
+
: (await getSkillsForCategories([categoryId]));
|
|
285
|
+
const categoryHadInitialSelection = skillNames.some((skill) => initialSet.has(skill));
|
|
286
|
+
const defaultSelected = options.selectAllForNewCategories && !categoryHadInitialSelection
|
|
287
|
+
? skillNames
|
|
288
|
+
: skillNames.filter((skill) => initialSet.has(skill));
|
|
289
|
+
|
|
290
|
+
const skillResponse = await prompts({
|
|
291
|
+
type: 'multiselect',
|
|
292
|
+
name: 'skills',
|
|
293
|
+
message: `Select skills in ${category?.title || 'Other / Uncategorized'}:`,
|
|
294
|
+
choices: await getSkillChoices(skillNames, defaultSelected),
|
|
295
|
+
hint: '- Space to select. Return to continue',
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (!skillResponse.skills) return null;
|
|
299
|
+
for (const skill of skillResponse.skills) selected.add(skill);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return [...selected].sort();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function confirmSkillRemovalIfNeeded(selectedSkills, mode) {
|
|
306
|
+
const plan = await buildSkillsReconcilePlan(selectedSkills);
|
|
307
|
+
if (plan.toRemove.length === 0) return true;
|
|
308
|
+
|
|
309
|
+
console.log(pc.yellow('\nTakomi will remove deselected Takomi-managed skills only:'));
|
|
310
|
+
for (const skill of plan.toRemove) console.log(pc.dim(` - ${skill}`));
|
|
311
|
+
console.log(pc.dim('\nManual skills and modified Takomi skills are preserved.'));
|
|
312
|
+
|
|
313
|
+
const response = await prompts({
|
|
314
|
+
type: 'confirm',
|
|
315
|
+
name: 'confirm',
|
|
316
|
+
message: `Remove ${plan.toRemove.length} Takomi-managed skill${plan.toRemove.length === 1 ? '' : 's'} for "${mode}"?`,
|
|
317
|
+
initial: false,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
return Boolean(response.confirm);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function promptSkillsInstallSelection() {
|
|
324
|
+
const envMode = process.env.TAKOMI_SKILLS_MODE;
|
|
325
|
+
if (envMode) {
|
|
326
|
+
const mode = envMode.toLowerCase();
|
|
327
|
+
if (mode === 'core') return { mode, selectedSkills: await getValidCoreSkills() };
|
|
328
|
+
if (mode === 'all') return { mode, selectedSkills: await listBundledSkillNames() };
|
|
329
|
+
if (mode === 'none') return { mode, selectedSkills: [] };
|
|
330
|
+
if (mode === 'leave-as-is' || mode === 'leave') return { mode: 'leave-as-is', leaveAsIs: true };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const installed = await getInstalledTakomiSkillNames();
|
|
334
|
+
const hasTakomiSkills = installed.length > 0;
|
|
335
|
+
const choices = hasTakomiSkills
|
|
336
|
+
? [
|
|
337
|
+
{ title: 'Leave As Is', value: 'leave-as-is', selected: true, description: `Recommended: keep ${installed.length} Takomi-managed skill${installed.length === 1 ? '' : 's'} unchanged.` },
|
|
338
|
+
{ title: 'Present Custom', value: 'present-custom', description: 'Review your current Takomi-managed skills and adjust selections.' },
|
|
339
|
+
{ title: 'Core Skills', value: 'core', description: `[Recommended defaults] ${formatCoreSkillsSummary()}` },
|
|
340
|
+
{ title: 'Custom', value: 'custom', description: 'Choose categories and individual skills.' },
|
|
341
|
+
{ title: 'All Skills', value: 'all', description: 'Install every bundled Takomi skill.' },
|
|
342
|
+
{ title: 'None', value: 'none', description: 'Disable Takomi-managed skills after confirmation.' },
|
|
343
|
+
]
|
|
344
|
+
: [
|
|
345
|
+
{ title: 'Core Skills', value: 'core', selected: true, description: `[Recommended defaults] ${formatCoreSkillsSummary()}` },
|
|
346
|
+
{ title: 'Custom', value: 'custom', description: 'Choose categories and individual skills.' },
|
|
347
|
+
{ title: 'All Skills', value: 'all', description: 'Install every bundled Takomi skill.' },
|
|
348
|
+
{ title: 'None', value: 'none', description: 'Do not install Takomi skills.' },
|
|
349
|
+
];
|
|
350
|
+
|
|
351
|
+
const response = await prompts({
|
|
352
|
+
type: 'select',
|
|
353
|
+
name: 'mode',
|
|
354
|
+
message: 'Skills Installation',
|
|
355
|
+
choices,
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
if (!response.mode) return null;
|
|
359
|
+
|
|
360
|
+
if (response.mode === 'leave-as-is') {
|
|
361
|
+
return { mode: response.mode, leaveAsIs: true, ownedCount: installed.length };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
let selectedSkills = [];
|
|
365
|
+
if (response.mode === 'core') {
|
|
366
|
+
selectedSkills = await getValidCoreSkills();
|
|
367
|
+
} else if (response.mode === 'all') {
|
|
368
|
+
const confirmAll = await prompts({
|
|
369
|
+
type: 'confirm',
|
|
370
|
+
name: 'confirm',
|
|
371
|
+
message: 'Install every bundled skill? This may add skill discovery noise in supported harnesses.',
|
|
372
|
+
initial: false,
|
|
373
|
+
});
|
|
374
|
+
if (!confirmAll.confirm) return { mode: 'leave-as-is', leaveAsIs: true, ownedCount: installed.length };
|
|
375
|
+
selectedSkills = await listBundledSkillNames();
|
|
376
|
+
} else if (response.mode === 'none') {
|
|
377
|
+
selectedSkills = [];
|
|
378
|
+
} else if (response.mode === 'present-custom') {
|
|
379
|
+
const customSelection = await promptCustomSkillSelection(installed, { selectAllForNewCategories: true, title: 'Present Custom Skills' });
|
|
380
|
+
if (!customSelection) return null;
|
|
381
|
+
selectedSkills = customSelection;
|
|
382
|
+
} else if (response.mode === 'custom') {
|
|
383
|
+
const customSelection = await promptCustomSkillSelection([], { selectAllForNewCategories: true, title: 'Custom Skills Selection' });
|
|
384
|
+
if (!customSelection) return null;
|
|
385
|
+
selectedSkills = customSelection;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const confirmedRemoval = await confirmSkillRemovalIfNeeded(selectedSkills, response.mode);
|
|
389
|
+
if (!confirmedRemoval) {
|
|
390
|
+
return { mode: 'leave-as-is', leaveAsIs: true, ownedCount: installed.length };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return { mode: response.mode, selectedSkills };
|
|
394
|
+
}
|
|
395
|
+
|
|
222
396
|
async function installSkillsTarget() {
|
|
223
397
|
console.log(pc.magenta('🧰 Takomi Skills Install\n'));
|
|
224
398
|
try {
|
|
225
|
-
const
|
|
226
|
-
|
|
399
|
+
const selection = await promptSkillsInstallSelection();
|
|
400
|
+
if (!selection) return;
|
|
401
|
+
if (selection.leaveAsIs) {
|
|
402
|
+
printSkillsInstallSummary({
|
|
403
|
+
leaveAsIs: true,
|
|
404
|
+
targetRoot: SKILLS_ROOT,
|
|
405
|
+
ownedCount: selection.ownedCount || (await getInstalledTakomiSkillNames()).length,
|
|
406
|
+
});
|
|
407
|
+
console.log(pc.dim('\nGlobal skills were not changed.\n'));
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const result = await installBundledSkills(program.version(), selection);
|
|
412
|
+
const validation = await validateSkillsInstall(selection.selectedSkills);
|
|
227
413
|
printSkillsInstallSummary(result, validation);
|
|
228
414
|
console.log(pc.dim('\nGlobal skills are ready for Pi or other supported harnesses.\n'));
|
|
229
415
|
} catch (error) {
|
|
@@ -232,12 +418,13 @@ async function installSkillsTarget() {
|
|
|
232
418
|
}
|
|
233
419
|
}
|
|
234
420
|
|
|
235
|
-
async function installAllTargets() {
|
|
236
|
-
await installPiTarget();
|
|
421
|
+
async function installAllTargets(options = {}) {
|
|
422
|
+
await installPiTarget(options);
|
|
237
423
|
await installSkillsTarget();
|
|
238
424
|
}
|
|
239
425
|
|
|
240
|
-
async function installPiTarget() {
|
|
426
|
+
async function installPiTarget(options = {}) {
|
|
427
|
+
const { offerOptionalFeatures = true } = options;
|
|
241
428
|
console.log(pc.magenta('🧭 Pi Harness Preflight\n'));
|
|
242
429
|
const report = await runDoctor({ version: program.version() });
|
|
243
430
|
|
|
@@ -247,7 +434,7 @@ async function installPiTarget() {
|
|
|
247
434
|
printPiInstallResult(installResult);
|
|
248
435
|
if (!installResult.ok) {
|
|
249
436
|
console.log(pc.yellow('\nPi harness install stopped because Pi could not be installed.'));
|
|
250
|
-
console.log(pc.dim('Retry manually with: npm install -g @
|
|
437
|
+
console.log(pc.dim('Retry manually with: npm install -g @earendil-works/pi-coding-agent\n'));
|
|
251
438
|
return;
|
|
252
439
|
}
|
|
253
440
|
}
|
|
@@ -268,6 +455,10 @@ async function installPiTarget() {
|
|
|
268
455
|
const result = await installPiHarnessAssets(program.version());
|
|
269
456
|
const validation = await validatePiHarnessInstall();
|
|
270
457
|
printPiInstallSummary(result, validation);
|
|
458
|
+
if (offerOptionalFeatures) {
|
|
459
|
+
console.log(pc.cyan('\n🧩 Optional Takomi Pi feature packs...\n'));
|
|
460
|
+
await offerPiOptionalFeatures({ interactive: true });
|
|
461
|
+
}
|
|
271
462
|
console.log(pc.cyan('\n📦 Checking Pi-managed package updates...\n'));
|
|
272
463
|
printPiManagedPackageUpdateResult(await updatePiManagedPackages());
|
|
273
464
|
console.log(pc.dim('\nNext: cd <project> && takomi\n'));
|
|
@@ -277,6 +468,16 @@ async function installPiTarget() {
|
|
|
277
468
|
}
|
|
278
469
|
}
|
|
279
470
|
|
|
471
|
+
async function installPiOptionalFeaturesTarget() {
|
|
472
|
+
console.log(pc.magenta('🧩 Takomi Optional Pi Features\n'));
|
|
473
|
+
const pi = await ensurePiInstalled();
|
|
474
|
+
printPiInstallResult(pi);
|
|
475
|
+
if (!pi.ok) return;
|
|
476
|
+
await offerPiOptionalFeatures({ interactive: true });
|
|
477
|
+
console.log(pc.cyan('\n📦 Checking Pi-managed package updates...\n'));
|
|
478
|
+
printPiManagedPackageUpdateResult(await updatePiManagedPackages());
|
|
479
|
+
}
|
|
480
|
+
|
|
280
481
|
async function syncPiTarget() {
|
|
281
482
|
console.log(pc.magenta('📡 Takomi Pi Sync\n'));
|
|
282
483
|
try {
|
|
@@ -294,8 +495,18 @@ async function syncPiTarget() {
|
|
|
294
495
|
async function syncSkillsTarget() {
|
|
295
496
|
console.log(pc.magenta('📡 Takomi Skills Sync\n'));
|
|
296
497
|
try {
|
|
297
|
-
const
|
|
298
|
-
|
|
498
|
+
const selection = await promptSkillsInstallSelection();
|
|
499
|
+
if (!selection) return;
|
|
500
|
+
if (selection.leaveAsIs) {
|
|
501
|
+
printSkillsInstallSummary({
|
|
502
|
+
leaveAsIs: true,
|
|
503
|
+
targetRoot: SKILLS_ROOT,
|
|
504
|
+
ownedCount: selection.ownedCount || (await getInstalledTakomiSkillNames()).length,
|
|
505
|
+
});
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const result = await installBundledSkills(program.version(), selection);
|
|
509
|
+
const validation = await validateSkillsInstall(selection.selectedSkills);
|
|
299
510
|
printSkillsInstallSummary(result, validation);
|
|
300
511
|
} catch (error) {
|
|
301
512
|
console.log(pc.red('\nSkills sync failed.'));
|
|
@@ -347,7 +558,7 @@ async function upgrade(target = 'all') {
|
|
|
347
558
|
}
|
|
348
559
|
|
|
349
560
|
if (normalizedTarget === 'pi') {
|
|
350
|
-
await installPiTarget();
|
|
561
|
+
await installPiTarget({ offerOptionalFeatures: false });
|
|
351
562
|
return;
|
|
352
563
|
}
|
|
353
564
|
|
|
@@ -356,13 +567,13 @@ async function upgrade(target = 'all') {
|
|
|
356
567
|
return;
|
|
357
568
|
}
|
|
358
569
|
|
|
359
|
-
await installAllTargets();
|
|
570
|
+
await installAllTargets({ offerOptionalFeatures: false });
|
|
360
571
|
console.log(pc.magenta('\n✨ Fully upgraded. Next: run `takomi` from your project.\n'));
|
|
361
572
|
}
|
|
362
573
|
|
|
363
574
|
function printUnsupportedInstallTarget(target) {
|
|
364
575
|
console.log(pc.yellow(`Unsupported install target: ${target}`));
|
|
365
|
-
console.log(pc.dim('Supported targets right now: pi, skills, all'));
|
|
576
|
+
console.log(pc.dim('Supported targets right now: pi, pi-features, skills, all'));
|
|
366
577
|
console.log(pc.dim('Use plain "takomi install" for the existing interactive global installer.\n'));
|
|
367
578
|
}
|
|
368
579
|
|
|
@@ -386,6 +597,10 @@ async function install(target) {
|
|
|
386
597
|
await installSkillsTarget();
|
|
387
598
|
return;
|
|
388
599
|
}
|
|
600
|
+
if (target === 'pi-features' || target === 'features' || target === 'optional') {
|
|
601
|
+
await installPiOptionalFeaturesTarget();
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
389
604
|
if (target === 'all') {
|
|
390
605
|
await installAllTargets();
|
|
391
606
|
return;
|
|
@@ -426,6 +641,11 @@ async function install(target) {
|
|
|
426
641
|
if (!harnessResponse.harnesses || harnessResponse.harnesses.length === 0) return;
|
|
427
642
|
|
|
428
643
|
const selectedHarnesses = detected.filter(h => harnessResponse.harnesses.includes(h.id));
|
|
644
|
+
const existingStoreManifest = await getManifest();
|
|
645
|
+
const existingStoreOwnedSkills = Object.keys(existingStoreManifest.bundledOwned?.skills || {});
|
|
646
|
+
const existingStoreOwnedWorkflows = Object.keys(existingStoreManifest.bundledOwned?.workflows || {});
|
|
647
|
+
const hasExistingStoreSkills = existingStoreOwnedSkills.length > 0;
|
|
648
|
+
const hasExistingStoreWorkflows = existingStoreOwnedWorkflows.length > 0;
|
|
429
649
|
|
|
430
650
|
// 3. Ask what to install
|
|
431
651
|
const contentResponse = await prompts([
|
|
@@ -447,13 +667,15 @@ async function install(target) {
|
|
|
447
667
|
name: 'skillMode',
|
|
448
668
|
message: 'Which skill pack?',
|
|
449
669
|
choices: [
|
|
450
|
-
{ title: '
|
|
670
|
+
...(hasExistingStoreSkills ? [{ title: 'Leave As Is (Recommended)', value: 'leave-as-is', description: `Keep ${existingStoreOwnedSkills.length} Takomi-managed store skill${existingStoreOwnedSkills.length === 1 ? '' : 's'} unchanged.` }] : []),
|
|
671
|
+
...(hasExistingStoreSkills ? [{ title: 'Present Custom', value: 'present-custom', description: 'Review current Takomi-managed store skills and adjust selections.' }] : []),
|
|
672
|
+
{ title: 'Core (Recommended defaults)', value: 'core', description: 'takomi, sync-docs, ai-sdk, git-commit-generation...' },
|
|
451
673
|
{ title: `All (${(await getSkills()).length} skills)`, value: 'all' },
|
|
452
674
|
{ title: 'Custom selection', value: 'custom' },
|
|
453
675
|
],
|
|
454
676
|
},
|
|
455
677
|
{
|
|
456
|
-
type:
|
|
678
|
+
type: null,
|
|
457
679
|
name: 'selectedSkills',
|
|
458
680
|
message: 'Select skills:',
|
|
459
681
|
choices: async () => {
|
|
@@ -468,6 +690,7 @@ async function install(target) {
|
|
|
468
690
|
name: 'workflowMode',
|
|
469
691
|
message: 'Which workflow pack?',
|
|
470
692
|
choices: [
|
|
693
|
+
...(hasExistingStoreWorkflows ? [{ title: 'Leave As Is (Recommended)', value: 'leave-as-is', description: `Keep ${existingStoreOwnedWorkflows.length} Takomi-managed workflow${existingStoreOwnedWorkflows.length === 1 ? '' : 's'} unchanged.` }] : []),
|
|
471
694
|
{ title: 'Core (16 essentials)', value: 'core', description: 'vibe-build, mode-architect...' },
|
|
472
695
|
{ title: `All (${(await getWorkflows()).length} workflows)`, value: 'all' },
|
|
473
696
|
{ title: 'All (Exclude Legacy)', value: 'no-legacy' },
|
|
@@ -495,19 +718,73 @@ async function install(target) {
|
|
|
495
718
|
|
|
496
719
|
// 5. Populate store from package assets
|
|
497
720
|
if (contentResponse.content.includes('skills')) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
:
|
|
501
|
-
|
|
502
|
-
|
|
721
|
+
let skillMode = contentResponse.skillMode;
|
|
722
|
+
if (contentResponse.skillMode === 'leave-as-is') {
|
|
723
|
+
console.log(pc.green(` ✔ Left ${existingStoreOwnedSkills.length} Takomi-managed store skill${existingStoreOwnedSkills.length === 1 ? '' : 's'} unchanged`));
|
|
724
|
+
} else {
|
|
725
|
+
if (contentResponse.skillMode === 'present-custom') {
|
|
726
|
+
const customSelection = await promptCustomSkillSelection(existingStoreOwnedSkills, { selectAllForNewCategories: true, title: 'Present Global Store Custom Skills' });
|
|
727
|
+
if (!customSelection) return;
|
|
728
|
+
skillMode = customSelection;
|
|
729
|
+
} else if (contentResponse.skillMode === 'custom') {
|
|
730
|
+
const customSelection = await promptCustomSkillSelection([], { selectAllForNewCategories: true, title: 'Global Store Custom Skills' });
|
|
731
|
+
if (!customSelection) return;
|
|
732
|
+
skillMode = customSelection;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const plan = await buildStoreSkillsReconcilePlan(skillMode);
|
|
736
|
+
if (plan.toRemove.length > 0) {
|
|
737
|
+
console.log(pc.yellow('\nTakomi will remove deselected Takomi-managed skills from the global store only:'));
|
|
738
|
+
for (const skill of plan.toRemove) console.log(pc.dim(` - ${skill}`));
|
|
739
|
+
console.log(pc.dim('\nManual/imported store skills and modified Takomi skills are preserved.'));
|
|
740
|
+
const confirmStorePrune = await prompts({
|
|
741
|
+
type: 'confirm',
|
|
742
|
+
name: 'confirm',
|
|
743
|
+
message: `Remove ${plan.toRemove.length} Takomi-managed store skill${plan.toRemove.length === 1 ? '' : 's'}?`,
|
|
744
|
+
initial: false,
|
|
745
|
+
});
|
|
746
|
+
if (!confirmStorePrune.confirm) {
|
|
747
|
+
console.log(pc.green(' ✔ Left global store skills unchanged'));
|
|
748
|
+
skillMode = null;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
if (skillMode) {
|
|
753
|
+
const skills = await populateSkills(skillMode);
|
|
754
|
+
console.log(pc.green(` ✔ ${skills.length} skills loaded into store`));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
503
757
|
}
|
|
504
758
|
|
|
505
759
|
if (contentResponse.content.includes('workflows')) {
|
|
506
|
-
|
|
760
|
+
let workflowMode = contentResponse.workflowMode === 'custom'
|
|
507
761
|
? contentResponse.selectedWorkflows
|
|
508
762
|
: contentResponse.workflowMode;
|
|
509
|
-
|
|
510
|
-
|
|
763
|
+
if (contentResponse.workflowMode === 'leave-as-is') {
|
|
764
|
+
console.log(pc.green(` ✔ Left ${existingStoreOwnedWorkflows.length} Takomi-managed workflow${existingStoreOwnedWorkflows.length === 1 ? '' : 's'} unchanged`));
|
|
765
|
+
} else {
|
|
766
|
+
const plan = await buildStoreWorkflowsReconcilePlan(workflowMode);
|
|
767
|
+
if (plan.toRemove.length > 0) {
|
|
768
|
+
console.log(pc.yellow('\nTakomi will remove deselected Takomi-managed workflows from the global store only:'));
|
|
769
|
+
for (const workflow of plan.toRemove) console.log(pc.dim(` - ${workflow}`));
|
|
770
|
+
console.log(pc.dim('\nManual/imported store workflows and modified Takomi workflows are preserved.'));
|
|
771
|
+
const confirmWorkflowPrune = await prompts({
|
|
772
|
+
type: 'confirm',
|
|
773
|
+
name: 'confirm',
|
|
774
|
+
message: `Remove ${plan.toRemove.length} Takomi-managed workflow${plan.toRemove.length === 1 ? '' : 's'}?`,
|
|
775
|
+
initial: false,
|
|
776
|
+
});
|
|
777
|
+
if (!confirmWorkflowPrune.confirm) {
|
|
778
|
+
console.log(pc.green(' ✔ Left global store workflows unchanged'));
|
|
779
|
+
workflowMode = null;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
if (workflowMode) {
|
|
784
|
+
const workflows = await populateWorkflows(workflowMode);
|
|
785
|
+
console.log(pc.green(` ✔ ${workflows.length} workflows loaded into store`));
|
|
786
|
+
}
|
|
787
|
+
}
|
|
511
788
|
}
|
|
512
789
|
|
|
513
790
|
if (contentResponse.content.includes('yamls')) {
|
|
@@ -517,13 +794,21 @@ async function install(target) {
|
|
|
517
794
|
|
|
518
795
|
// 6. Sync store to all selected harnesses
|
|
519
796
|
console.log(pc.cyan('\n📡 Syncing to harnesses...\n'));
|
|
520
|
-
await
|
|
797
|
+
let manifest = await getManifest();
|
|
798
|
+
const syncSummary = await syncToAllHarnesses(selectedHarnesses, STORE_PATH, {
|
|
799
|
+
useOwnership: true,
|
|
800
|
+
owned: manifest.harnessOwned,
|
|
801
|
+
});
|
|
521
802
|
|
|
522
803
|
// 7. Update manifest
|
|
523
|
-
|
|
804
|
+
manifest = await getManifest();
|
|
524
805
|
manifest.linkedHarnesses = selectedHarnesses.map(h => h.id);
|
|
525
806
|
manifest.installed.skills = await getStoreSkills();
|
|
526
807
|
manifest.installed.workflows = await getStoreWorkflows();
|
|
808
|
+
manifest.harnessOwned = manifest.harnessOwned || {};
|
|
809
|
+
for (const harness of selectedHarnesses) {
|
|
810
|
+
manifest.harnessOwned[harness.id] = syncSummary[harness.id]?.owned || manifest.harnessOwned[harness.id] || {};
|
|
811
|
+
}
|
|
527
812
|
await writeManifest(manifest);
|
|
528
813
|
|
|
529
814
|
// 8. Summary
|
|
@@ -594,10 +879,17 @@ async function sync(target) {
|
|
|
594
879
|
const selectedHarnesses = detected.filter(h => response.harnesses.includes(h.id));
|
|
595
880
|
|
|
596
881
|
console.log(pc.cyan('\n📡 Syncing from global store...\n'));
|
|
597
|
-
await syncToAllHarnesses(selectedHarnesses, STORE_PATH
|
|
882
|
+
const syncSummary = await syncToAllHarnesses(selectedHarnesses, STORE_PATH, {
|
|
883
|
+
useOwnership: true,
|
|
884
|
+
owned: manifest.harnessOwned,
|
|
885
|
+
});
|
|
598
886
|
|
|
599
887
|
// Update manifest with current harnesses
|
|
600
888
|
manifest.linkedHarnesses = [...new Set([...manifest.linkedHarnesses, ...response.harnesses])];
|
|
889
|
+
manifest.harnessOwned = manifest.harnessOwned || {};
|
|
890
|
+
for (const harness of selectedHarnesses) {
|
|
891
|
+
manifest.harnessOwned[harness.id] = syncSummary[harness.id]?.owned || manifest.harnessOwned[harness.id] || {};
|
|
892
|
+
}
|
|
601
893
|
await writeManifest(manifest);
|
|
602
894
|
|
|
603
895
|
const skills = await getStoreSkills();
|
|
@@ -813,6 +1105,7 @@ Primary flow:
|
|
|
813
1105
|
|
|
814
1106
|
Examples:
|
|
815
1107
|
takomi setup pi Set up the Pi harness
|
|
1108
|
+
takomi setup pi-features Add optional Pi feature packs
|
|
816
1109
|
takomi setup skills Install bundled skills
|
|
817
1110
|
takomi setup project
|
|
818
1111
|
takomi refresh One-command maintenance
|
|
@@ -825,7 +1118,7 @@ Legacy aliases still work:
|
|
|
825
1118
|
|
|
826
1119
|
program
|
|
827
1120
|
.command('setup [target]')
|
|
828
|
-
.description('Set up Takomi: guided setup, or setup pi|skills|project|all')
|
|
1121
|
+
.description('Set up Takomi: guided setup, or setup pi|pi-features|skills|project|all')
|
|
829
1122
|
.action(async (target) => {
|
|
830
1123
|
if (target === 'project') {
|
|
831
1124
|
await init();
|