@claude-collective/cli 0.8.0 → 0.13.1
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/CHANGELOG.md +138 -0
- package/README.md +26 -9
- package/config/skills-matrix.yaml +858 -0
- package/config/stacks.yaml +254 -0
- package/dist/{chunk-TOPAIL5W.js → chunk-3U3R4NCG.js} +2 -2
- package/dist/chunk-3U3R4NCG.js.map +1 -0
- package/dist/{chunk-YKXBGCFD.js → chunk-57Y5RALO.js} +10 -10
- package/dist/chunk-57Y5RALO.js.map +1 -0
- package/dist/{chunk-ED73HCW2.js → chunk-6DCSSORF.js} +37 -88
- package/dist/chunk-6DCSSORF.js.map +1 -0
- package/dist/{chunk-6LS7XO3H.js → chunk-6Q3Y7KVB.js} +2 -2
- package/dist/chunk-6Q3Y7KVB.js.map +1 -0
- package/dist/{chunk-A3J6IAXK.js → chunk-76DWXGQE.js} +4 -2
- package/dist/chunk-76DWXGQE.js.map +1 -0
- package/dist/{chunk-Q6DR5QUH.js → chunk-7Q44DMSP.js} +62 -27
- package/dist/chunk-7Q44DMSP.js.map +1 -0
- package/dist/chunk-ACNBKXXJ.js +321 -0
- package/dist/chunk-ACNBKXXJ.js.map +1 -0
- package/dist/{chunk-QGGSLMO3.js → chunk-B7CCVP6Q.js} +41 -9
- package/dist/chunk-B7CCVP6Q.js.map +1 -0
- package/dist/{chunk-LVKRVFYR.js → chunk-BDLUZVKU.js} +2 -2
- package/dist/chunk-BDLUZVKU.js.map +1 -0
- package/dist/{chunk-HNDT5QRB.js → chunk-CDX4W4DM.js} +3 -3
- package/dist/chunk-CDX4W4DM.js.map +1 -0
- package/dist/{chunk-MYAVQ23U.js → chunk-CJEHB4TB.js} +23 -9
- package/dist/chunk-CJEHB4TB.js.map +1 -0
- package/dist/{chunk-DKGL77IY.js → chunk-CPZOTVCI.js} +15 -14
- package/dist/chunk-CPZOTVCI.js.map +1 -0
- package/dist/{chunk-K7PTOVX4.js → chunk-D237EVNB.js} +32 -3
- package/dist/chunk-D237EVNB.js.map +1 -0
- package/dist/{chunk-Q2LH2DAB.js → chunk-DRXPNNPB.js} +19 -18
- package/dist/chunk-DRXPNNPB.js.map +1 -0
- package/dist/{chunk-Y3V43XCU.js → chunk-E3FJH4TF.js} +12 -8
- package/dist/chunk-E3FJH4TF.js.map +1 -0
- package/dist/{chunk-3HBTELJN.js → chunk-ED4E6Q2T.js} +10 -10
- package/dist/chunk-ED4E6Q2T.js.map +1 -0
- package/dist/{chunk-SYQ7R2JO.js → chunk-EHS3TWWP.js} +3 -3
- package/dist/chunk-EHS3TWWP.js.map +1 -0
- package/dist/{chunk-LQTST4WY.js → chunk-GDH553MV.js} +6 -6
- package/dist/chunk-GDH553MV.js.map +1 -0
- package/dist/{chunk-A65SBAAJ.js → chunk-HLJX2FTL.js} +31 -5
- package/dist/chunk-HLJX2FTL.js.map +1 -0
- package/dist/chunk-I2DSLOXZ.js +75 -0
- package/dist/chunk-I2DSLOXZ.js.map +1 -0
- package/dist/{chunk-SEBPPFUW.js → chunk-I4TPKIYX.js} +33 -18
- package/dist/chunk-I4TPKIYX.js.map +1 -0
- package/dist/{chunk-NGBFJJ7Q.js → chunk-IMDW5ZUP.js} +19 -11
- package/dist/chunk-IMDW5ZUP.js.map +1 -0
- package/dist/{chunk-U4VYHKPM.js → chunk-JIPWV2FX.js} +6 -6
- package/dist/chunk-JIPWV2FX.js.map +1 -0
- package/dist/{chunk-G2FBJOZG.js → chunk-K7EVM5LY.js} +4 -4
- package/dist/chunk-K7EVM5LY.js.map +1 -0
- package/dist/{chunk-MJSFR562.js → chunk-KAAEN2PO.js} +3 -3
- package/dist/chunk-KAAEN2PO.js.map +1 -0
- package/dist/{chunk-FNOYEXUE.js → chunk-LE6IY6IT.js} +22 -17
- package/dist/chunk-LE6IY6IT.js.map +1 -0
- package/dist/{chunk-CIY5UBRB.js → chunk-NDY25DTL.js} +6 -6
- package/dist/chunk-NDY25DTL.js.map +1 -0
- package/dist/{chunk-OLBOTK3O.js → chunk-P26A2K5N.js} +7 -7
- package/dist/chunk-P26A2K5N.js.map +1 -0
- package/dist/{chunk-DHFFRMF6.js → chunk-RTE64SJA.js} +2 -2
- package/dist/chunk-RTE64SJA.js.map +1 -0
- package/dist/{chunk-3ZCB5K33.js → chunk-SGJ23HIP.js} +14 -11
- package/dist/chunk-SGJ23HIP.js.map +1 -0
- package/dist/{chunk-C4ZTIYFR.js → chunk-SVYPSDWY.js} +10 -10
- package/dist/chunk-SVYPSDWY.js.map +1 -0
- package/dist/{chunk-MMDXNZPF.js → chunk-TKFPKEV3.js} +2 -2
- package/dist/chunk-TKFPKEV3.js.map +1 -0
- package/dist/{chunk-M7YCPFIX.js → chunk-UQTEPWU7.js} +2 -2
- package/dist/chunk-UQTEPWU7.js.map +1 -0
- package/dist/{chunk-QESUUPOE.js → chunk-V46GGCCI.js} +80 -27
- package/dist/chunk-V46GGCCI.js.map +1 -0
- package/dist/{chunk-UOWHJ6BE.js → chunk-X6QONICW.js} +6 -3
- package/dist/chunk-X6QONICW.js.map +1 -0
- package/dist/chunk-Y2LW7R3Y.js +23 -0
- package/dist/chunk-Y2LW7R3Y.js.map +1 -0
- package/dist/chunk-Z2CWURZ6.js +78 -0
- package/dist/chunk-Z2CWURZ6.js.map +1 -0
- package/dist/chunk-Z7G4B5HJ.js +377 -0
- package/dist/chunk-Z7G4B5HJ.js.map +1 -0
- package/dist/{chunk-XKEG3SCV.js → chunk-ZENYS6KW.js} +13 -9
- package/dist/chunk-ZENYS6KW.js.map +1 -0
- package/dist/{cli-v2 → cli}/defaults/agent-mappings.yaml +5 -5
- package/dist/commands/build/marketplace.js +9 -9
- package/dist/commands/build/marketplace.js.map +1 -1
- package/dist/commands/build/plugins.js +12 -12
- package/dist/commands/build/plugins.js.map +1 -1
- package/dist/commands/build/stack.js +15 -15
- package/dist/commands/build/stack.js.map +1 -1
- package/dist/commands/compile.js +21 -21
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/config/get.js +9 -8
- package/dist/commands/config/get.js.map +1 -1
- package/dist/commands/config/index.js +7 -6
- package/dist/commands/config/index.js.map +1 -1
- package/dist/commands/config/path.js +8 -7
- package/dist/commands/config/path.js.map +1 -1
- package/dist/commands/config/set-project.js +9 -8
- package/dist/commands/config/set-project.js.map +1 -1
- package/dist/commands/config/set.js +9 -8
- package/dist/commands/config/set.js.map +1 -1
- package/dist/commands/config/show.js +6 -5
- package/dist/commands/config/unset-project.js +9 -8
- package/dist/commands/config/unset-project.js.map +1 -1
- package/dist/commands/config/unset.js +9 -8
- package/dist/commands/config/unset.js.map +1 -1
- package/dist/commands/diff.js +12 -12
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.js +10 -10
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/edit.js +52 -49
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/eject.js +180 -97
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/import/skill.js +339 -0
- package/dist/commands/import/skill.js.map +1 -0
- package/dist/commands/info.js +9 -9
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.js +205 -78
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.js +9 -9
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/new/agent.js +19 -21
- package/dist/commands/new/agent.js.map +1 -1
- package/dist/commands/new/skill.js +11 -12
- package/dist/commands/new/skill.js.map +1 -1
- package/dist/commands/outdated.js +12 -12
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/search.js +205 -17
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/test-imports.js +18 -18
- package/dist/commands/test-imports.js.map +1 -1
- package/dist/commands/uninstall.js +58 -78
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +21 -21
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/validate.js +9 -9
- package/dist/commands/validate.js.map +1 -1
- package/dist/commands/version/bump.js +8 -8
- package/dist/commands/version/bump.js.map +1 -1
- package/dist/commands/version/index.js +8 -8
- package/dist/commands/version/index.js.map +1 -1
- package/dist/commands/version/set.js +7 -7
- package/dist/commands/version/set.js.map +1 -1
- package/dist/commands/version/show.js +8 -8
- package/dist/commands/version/show.js.map +1 -1
- package/dist/components/common/confirm.js +1 -1
- package/dist/components/common/message.js +1 -1
- package/dist/components/common/message.js.map +1 -1
- package/dist/components/common/spinner.js +1 -1
- package/dist/components/common/spinner.js.map +1 -1
- package/dist/components/skill-search/skill-search.js +10 -0
- package/dist/components/skill-search/skill-search.js.map +1 -0
- package/dist/components/wizard/category-grid.js +1 -1
- package/dist/components/wizard/category-grid.test.js +213 -80
- package/dist/components/wizard/category-grid.test.js.map +1 -1
- package/dist/components/wizard/section-progress.js +1 -1
- package/dist/components/wizard/section-progress.test.js +2 -2
- package/dist/components/wizard/section-progress.test.js.map +1 -1
- package/dist/components/wizard/step-approach.js +4 -3
- package/dist/components/wizard/step-build.js +4 -3
- package/dist/components/wizard/step-build.test.js +29 -17
- package/dist/components/wizard/step-build.test.js.map +1 -1
- package/dist/components/wizard/step-confirm.js +2 -1
- package/dist/components/wizard/step-refine.js +2 -1
- package/dist/components/wizard/step-refine.test.js +4 -3
- package/dist/components/wizard/step-refine.test.js.map +1 -1
- package/dist/components/wizard/step-stack-options.js +3 -3
- package/dist/components/wizard/step-stack.js +3 -3
- package/dist/components/wizard/wizard-footer.js +9 -0
- package/dist/components/wizard/wizard-footer.js.map +1 -0
- package/dist/components/wizard/wizard-tabs.js +1 -1
- package/dist/components/wizard/wizard.js +14 -12
- package/dist/config/skills-matrix.yaml +858 -0
- package/dist/config/stacks.yaml +254 -0
- package/dist/hooks/init.js +5 -4
- package/dist/hooks/init.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/stores/wizard-store.js +2 -2
- package/dist/stores/wizard-store.test.js +3 -3
- package/dist/stores/wizard-store.test.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-3HBTELJN.js.map +0 -1
- package/dist/chunk-3ZCB5K33.js.map +0 -1
- package/dist/chunk-6LS7XO3H.js.map +0 -1
- package/dist/chunk-A3J6IAXK.js.map +0 -1
- package/dist/chunk-A65SBAAJ.js.map +0 -1
- package/dist/chunk-ALEPJ6YN.js +0 -80
- package/dist/chunk-ALEPJ6YN.js.map +0 -1
- package/dist/chunk-C4ZTIYFR.js.map +0 -1
- package/dist/chunk-CIY5UBRB.js.map +0 -1
- package/dist/chunk-DHFFRMF6.js.map +0 -1
- package/dist/chunk-DKGL77IY.js.map +0 -1
- package/dist/chunk-ED73HCW2.js.map +0 -1
- package/dist/chunk-FNOYEXUE.js.map +0 -1
- package/dist/chunk-G2FBJOZG.js.map +0 -1
- package/dist/chunk-HNDT5QRB.js.map +0 -1
- package/dist/chunk-K7PTOVX4.js.map +0 -1
- package/dist/chunk-LQTST4WY.js.map +0 -1
- package/dist/chunk-LVKRVFYR.js.map +0 -1
- package/dist/chunk-M7YCPFIX.js.map +0 -1
- package/dist/chunk-MJSFR562.js.map +0 -1
- package/dist/chunk-MMDXNZPF.js.map +0 -1
- package/dist/chunk-MYAVQ23U.js.map +0 -1
- package/dist/chunk-NGBFJJ7Q.js.map +0 -1
- package/dist/chunk-OLBOTK3O.js.map +0 -1
- package/dist/chunk-PPNTD5LO.js +0 -330
- package/dist/chunk-PPNTD5LO.js.map +0 -1
- package/dist/chunk-Q2LH2DAB.js.map +0 -1
- package/dist/chunk-Q6DR5QUH.js.map +0 -1
- package/dist/chunk-QESUUPOE.js.map +0 -1
- package/dist/chunk-QGGSLMO3.js.map +0 -1
- package/dist/chunk-SEBPPFUW.js.map +0 -1
- package/dist/chunk-SYQ7R2JO.js.map +0 -1
- package/dist/chunk-TOPAIL5W.js.map +0 -1
- package/dist/chunk-U4VYHKPM.js.map +0 -1
- package/dist/chunk-UOWHJ6BE.js.map +0 -1
- package/dist/chunk-XKEG3SCV.js.map +0 -1
- package/dist/chunk-Y3V43XCU.js.map +0 -1
- package/dist/chunk-YKXBGCFD.js.map +0 -1
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CategoryGrid
|
|
4
|
+
} from "./chunk-Z7G4B5HJ.js";
|
|
2
5
|
import {
|
|
3
6
|
SectionProgress
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BDLUZVKU.js";
|
|
5
8
|
import {
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
WizardFooter
|
|
10
|
+
} from "./chunk-Y2LW7R3Y.js";
|
|
8
11
|
import {
|
|
9
12
|
init_esm_shims
|
|
10
13
|
} from "./chunk-DHET7RCE.js";
|
|
11
14
|
|
|
12
|
-
// src/cli
|
|
15
|
+
// src/cli/components/wizard/step-build.tsx
|
|
13
16
|
init_esm_shims();
|
|
14
17
|
import { useState } from "react";
|
|
15
18
|
import { Box, Text, useInput } from "ink";
|
|
16
19
|
|
|
17
|
-
// src/cli
|
|
20
|
+
// src/cli/lib/matrix-resolver.ts
|
|
18
21
|
init_esm_shims();
|
|
19
22
|
function resolveAlias(aliasOrId, matrix) {
|
|
20
23
|
return matrix.aliases[aliasOrId] || aliasOrId;
|
|
@@ -334,9 +337,11 @@ function getAvailableSkills(categoryId, currentSelections, matrix, options) {
|
|
|
334
337
|
return skillOptions;
|
|
335
338
|
}
|
|
336
339
|
|
|
337
|
-
// src/cli
|
|
340
|
+
// src/cli/components/wizard/step-build.tsx
|
|
338
341
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
339
342
|
var MIN_DOMAINS_FOR_PROGRESS = 2;
|
|
343
|
+
var FRAMEWORK_SUBCATEGORY_ID = "framework";
|
|
344
|
+
var WEB_DOMAIN_ID = "web";
|
|
340
345
|
function validateBuildStep(categories, selections) {
|
|
341
346
|
for (const category of categories) {
|
|
342
347
|
if (category.required) {
|
|
@@ -379,13 +384,48 @@ function getStateReason(skill) {
|
|
|
379
384
|
}
|
|
380
385
|
return void 0;
|
|
381
386
|
}
|
|
382
|
-
function
|
|
387
|
+
function isFrameworkSelected(selections) {
|
|
388
|
+
const frameworkSelections = selections[FRAMEWORK_SUBCATEGORY_ID] ?? [];
|
|
389
|
+
return frameworkSelections.length > 0;
|
|
390
|
+
}
|
|
391
|
+
function getSelectedFrameworks(selections, matrix) {
|
|
392
|
+
const frameworkSelections = selections[FRAMEWORK_SUBCATEGORY_ID] ?? [];
|
|
393
|
+
return frameworkSelections.map((alias) => {
|
|
394
|
+
return matrix.aliases[alias] ?? alias;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
function isCompatibleWithSelectedFrameworks(skillId, selectedFrameworkIds, matrix) {
|
|
398
|
+
const skill = matrix.skills[skillId];
|
|
399
|
+
if (!skill) return false;
|
|
400
|
+
if (skill.compatibleWith.length === 0) {
|
|
401
|
+
return true;
|
|
402
|
+
}
|
|
403
|
+
return selectedFrameworkIds.some(
|
|
404
|
+
(frameworkId) => skill.compatibleWith.includes(frameworkId)
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
function shouldShowSubcategory(_subcategoryId, _domain, _frameworkSelected) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
function buildCategoriesForDomain(domain, allSelections, matrix, expertMode, selections) {
|
|
411
|
+
const frameworkSelected = isFrameworkSelected(selections);
|
|
412
|
+
const selectedFrameworkIds = frameworkSelected ? getSelectedFrameworks(selections, matrix) : [];
|
|
383
413
|
const subcategories = Object.values(matrix.categories).filter((cat) => cat.domain === domain && cat.parent).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
384
|
-
const
|
|
414
|
+
const visibleSubcategories = subcategories.filter(
|
|
415
|
+
(cat) => shouldShowSubcategory(cat.id, domain, frameworkSelected)
|
|
416
|
+
);
|
|
417
|
+
const categoryRows = visibleSubcategories.map((cat) => {
|
|
385
418
|
const skillOptions = getAvailableSkills(cat.id, allSelections, matrix, {
|
|
386
419
|
expertMode
|
|
387
420
|
});
|
|
388
|
-
const
|
|
421
|
+
const filteredSkillOptions = domain === WEB_DOMAIN_ID && cat.id !== FRAMEWORK_SUBCATEGORY_ID && frameworkSelected ? skillOptions.filter(
|
|
422
|
+
(skill) => isCompatibleWithSelectedFrameworks(
|
|
423
|
+
skill.id,
|
|
424
|
+
selectedFrameworkIds,
|
|
425
|
+
matrix
|
|
426
|
+
)
|
|
427
|
+
) : skillOptions;
|
|
428
|
+
const options = filteredSkillOptions.map((skill) => ({
|
|
389
429
|
id: skill.alias || skill.id,
|
|
390
430
|
// Use alias for selection tracking
|
|
391
431
|
label: getDisplayLabel(skill),
|
|
@@ -402,7 +442,7 @@ function buildCategoriesForDomain(domain, allSelections, matrix, expertMode) {
|
|
|
402
442
|
options
|
|
403
443
|
};
|
|
404
444
|
});
|
|
405
|
-
return categoryRows;
|
|
445
|
+
return categoryRows.filter((row) => row.options.length > 0);
|
|
406
446
|
}
|
|
407
447
|
function getDomainDisplayName(domain) {
|
|
408
448
|
const displayNames = {
|
|
@@ -445,22 +485,16 @@ var Header = ({ domain, selectionCount }) => {
|
|
|
445
485
|
] })
|
|
446
486
|
] });
|
|
447
487
|
};
|
|
448
|
-
var Footer = ({
|
|
449
|
-
showContinueHint,
|
|
450
|
-
validationError
|
|
451
|
-
}) => {
|
|
488
|
+
var Footer = ({ validationError }) => {
|
|
452
489
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
453
490
|
validationError && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: validationError }) }),
|
|
454
|
-
/* @__PURE__ */
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
"\u2193",
|
|
462
|
-
" categories SPACE select TAB descriptions E expert ENTER continue ESC back"
|
|
463
|
-
] })
|
|
491
|
+
/* @__PURE__ */ jsx(
|
|
492
|
+
WizardFooter,
|
|
493
|
+
{
|
|
494
|
+
navigation: "\u2190/\u2192 options \u2191/\u2193 categories SPACE select TAB desc E expert",
|
|
495
|
+
action: "ENTER continue ESC back"
|
|
496
|
+
}
|
|
497
|
+
)
|
|
464
498
|
] });
|
|
465
499
|
};
|
|
466
500
|
var StepBuild = ({
|
|
@@ -488,7 +522,8 @@ var StepBuild = ({
|
|
|
488
522
|
domain,
|
|
489
523
|
allSelections,
|
|
490
524
|
matrix,
|
|
491
|
-
expertMode
|
|
525
|
+
expertMode,
|
|
526
|
+
selections
|
|
492
527
|
);
|
|
493
528
|
const selectionCount = countSelections(categories);
|
|
494
529
|
const showProgress = selectedDomains.length >= MIN_DOMAINS_FOR_PROGRESS;
|
|
@@ -534,7 +569,7 @@ var StepBuild = ({
|
|
|
534
569
|
onToggleExpertMode
|
|
535
570
|
}
|
|
536
571
|
),
|
|
537
|
-
/* @__PURE__ */ jsx(Footer, {
|
|
572
|
+
/* @__PURE__ */ jsx(Footer, { validationError })
|
|
538
573
|
] });
|
|
539
574
|
};
|
|
540
575
|
|
|
@@ -544,4 +579,4 @@ export {
|
|
|
544
579
|
getDisplayLabel,
|
|
545
580
|
StepBuild
|
|
546
581
|
};
|
|
547
|
-
//# sourceMappingURL=chunk-
|
|
582
|
+
//# sourceMappingURL=chunk-7Q44DMSP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/components/wizard/step-build.tsx","../src/cli/lib/matrix-resolver.ts"],"sourcesContent":["/**\n * StepBuild component - Main Build step for domain-based technology selection.\n *\n * Uses CategoryGrid for 2D grid selection and SectionProgress for multi-domain\n * progress indication. Replaces the old linear category->subcategory flow.\n *\n * This component is stateless - all state is managed via props from the parent\n * wizard component, following React's controlled component pattern.\n */\nimport React, { useState } from \"react\";\nimport { Box, Text, useInput } from \"ink\";\nimport type { MergedSkillsMatrix } from \"../../types-matrix.js\";\nimport { getAvailableSkills } from \"../../lib/matrix-resolver.js\";\nimport {\n CategoryGrid,\n type CategoryRow,\n type CategoryOption,\n type OptionState,\n} from \"./category-grid.js\";\nimport { SectionProgress } from \"./section-progress.js\";\nimport { WizardFooter } from \"./wizard-footer.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface StepBuildProps {\n /** Skills matrix for category/skill lookup */\n matrix: MergedSkillsMatrix;\n /** Current domain being configured (e.g., 'web', 'api') */\n domain: string;\n /** All selected domains (for progress indicator) */\n selectedDomains: string[];\n /** Current domain index (0-based) */\n currentDomainIndex: number;\n /** Current selections by subcategory */\n selections: Record<string, string[]>;\n /** All current selections (for state calculation across domains) */\n allSelections: string[];\n /** Grid focus state */\n focusedRow: number;\n focusedCol: number;\n /** UI toggles */\n showDescriptions: boolean;\n expertMode: boolean;\n /** Callbacks */\n onToggle: (subcategoryId: string, technologyId: string) => void;\n onFocusChange: (row: number, col: number) => void;\n onToggleDescriptions: () => void;\n onToggleExpertMode: () => void;\n onContinue: () => void;\n onBack: () => void;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Minimum number of domains to show progress indicator */\nconst MIN_DOMAINS_FOR_PROGRESS = 2;\n\n/** Framework subcategory ID for web domain (framework-first filtering) */\nconst FRAMEWORK_SUBCATEGORY_ID = \"framework\";\n\n/** Web domain ID where framework-first flow applies */\nconst WEB_DOMAIN_ID = \"web\";\n\n// =============================================================================\n// Validation\n// =============================================================================\n\nexport interface ValidationResult {\n valid: boolean;\n message?: string;\n}\n\n/**\n * Validate that required categories have at least one selection.\n */\nexport function validateBuildStep(\n categories: CategoryRow[],\n selections: Record<string, string[]>,\n): ValidationResult {\n for (const category of categories) {\n if (category.required) {\n const categorySelections = selections[category.id] || [];\n if (categorySelections.length === 0) {\n return {\n valid: false,\n message: `Please select a ${category.name}`,\n };\n }\n }\n }\n return { valid: true };\n}\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Compute option state from skill flags.\n */\nfunction computeOptionState(skill: {\n disabled: boolean;\n discouraged: boolean;\n recommended: boolean;\n}): OptionState {\n if (skill.disabled) {\n return \"disabled\";\n }\n if (skill.discouraged) {\n return \"discouraged\";\n }\n if (skill.recommended) {\n return \"recommended\";\n }\n return \"normal\";\n}\n\n/**\n * Get clean display label for a skill option.\n * Uses name with author suffix stripped for accurate display.\n * e.g., \"React (@vince)\" -> \"React\", \"SCSS Modules (@vince)\" -> \"SCSS Modules\"\n */\nexport function getDisplayLabel(skill: {\n alias?: string;\n name: string;\n}): string {\n // Strip author suffix like \" (@vince)\" from name\n // This preserves the original capitalization (e.g., \"SCSS Modules\" stays as-is)\n const authorPattern = /\\s*\\(@[^)]+\\)\\s*$/;\n return skill.name.replace(authorPattern, \"\");\n}\n\n/**\n * Get state reason from skill flags.\n */\nfunction getStateReason(skill: {\n disabled: boolean;\n disabledReason?: string;\n discouraged: boolean;\n discouragedReason?: string;\n recommended: boolean;\n recommendedReason?: string;\n}): string | undefined {\n if (skill.disabled && skill.disabledReason) {\n return skill.disabledReason;\n }\n if (skill.discouraged && skill.discouragedReason) {\n return skill.discouragedReason;\n }\n if (skill.recommended && skill.recommendedReason) {\n return skill.recommendedReason;\n }\n return undefined;\n}\n\n// =============================================================================\n// Framework-First Flow (Web Domain)\n// =============================================================================\n\n/**\n * Check if a framework is selected in the current selections.\n * Framework skills are in the \"framework\" subcategory.\n */\nfunction isFrameworkSelected(selections: Record<string, string[]>): boolean {\n const frameworkSelections = selections[FRAMEWORK_SUBCATEGORY_ID] ?? [];\n return frameworkSelections.length > 0;\n}\n\n/**\n * Get the selected framework skill ID(s) from selections.\n * Returns the full skill IDs (e.g., \"web-framework-react\").\n */\nfunction getSelectedFrameworks(\n selections: Record<string, string[]>,\n matrix: MergedSkillsMatrix,\n): string[] {\n const frameworkSelections = selections[FRAMEWORK_SUBCATEGORY_ID] ?? [];\n // Resolve aliases to full skill IDs\n return frameworkSelections.map((alias) => {\n return matrix.aliases[alias] ?? alias;\n });\n}\n\n/**\n * Check if a skill is compatible with any of the selected frameworks.\n * Uses the skill's compatibleWith field from metadata.\n */\nfunction isCompatibleWithSelectedFrameworks(\n skillId: string,\n selectedFrameworkIds: string[],\n matrix: MergedSkillsMatrix,\n): boolean {\n const skill = matrix.skills[skillId];\n if (!skill) return false;\n\n // If skill has no compatibleWith defined, assume it's compatible with all\n // (this allows legacy skills without metadata to still appear)\n if (skill.compatibleWith.length === 0) {\n return true;\n }\n\n // Check if any selected framework is in the skill's compatibleWith list\n return selectedFrameworkIds.some((frameworkId) =>\n skill.compatibleWith.includes(frameworkId),\n );\n}\n\n/**\n * Determine if a subcategory should be shown based on framework-first flow.\n *\n * IMPORTANT: All sections are ALWAYS visible. The \"locked\" state in CategoryGrid\n * handles dimming and preventing navigation until a framework is selected.\n * We do NOT hide sections.\n */\nfunction shouldShowSubcategory(\n _subcategoryId: string,\n _domain: string,\n _frameworkSelected: boolean,\n): boolean {\n // All sections are always visible\n // Locking (dimming + preventing navigation) is handled by CategoryGrid\n return true;\n}\n\n/**\n * Build CategoryRow[] from matrix for a specific domain.\n *\n * Filters subcategories by domain and builds options using getAvailableSkills.\n * For web domain, implements framework-first flow:\n * - Initially shows only \"framework\" subcategory\n * - After framework selection, shows skills compatible with selected framework\n */\nfunction buildCategoriesForDomain(\n domain: string,\n allSelections: string[],\n matrix: MergedSkillsMatrix,\n expertMode: boolean,\n selections: Record<string, string[]>,\n): CategoryRow[] {\n // Check framework selection for framework-first flow\n const frameworkSelected = isFrameworkSelected(selections);\n const selectedFrameworkIds = frameworkSelected\n ? getSelectedFrameworks(selections, matrix)\n : [];\n\n // Get subcategories for the current domain (categories with parent and matching domain)\n const subcategories = Object.values(matrix.categories)\n .filter((cat) => cat.domain === domain && cat.parent)\n .sort((a, b) => (a.order ?? 0) - (b.order ?? 0));\n\n // Filter subcategories based on framework-first flow\n const visibleSubcategories = subcategories.filter((cat) =>\n shouldShowSubcategory(cat.id, domain, frameworkSelected),\n );\n\n // Build CategoryRow for each visible subcategory\n const categoryRows: CategoryRow[] = visibleSubcategories.map((cat) => {\n // Get available skills with computed states\n const skillOptions = getAvailableSkills(cat.id, allSelections, matrix, {\n expertMode,\n });\n\n // For web domain (non-framework categories), filter by compatibility\n // Framework category itself doesn't need filtering\n const filteredSkillOptions =\n domain === WEB_DOMAIN_ID &&\n cat.id !== FRAMEWORK_SUBCATEGORY_ID &&\n frameworkSelected\n ? skillOptions.filter((skill) =>\n isCompatibleWithSelectedFrameworks(\n skill.id,\n selectedFrameworkIds,\n matrix,\n ),\n )\n : skillOptions;\n\n // Map skills to CategoryOption[]\n const options: CategoryOption[] = filteredSkillOptions.map((skill) => ({\n id: skill.alias || skill.id, // Use alias for selection tracking\n label: getDisplayLabel(skill), // Clean display name without author\n state: computeOptionState(skill),\n stateReason: getStateReason(skill),\n selected: skill.selected,\n }));\n\n return {\n id: cat.id,\n name: cat.name,\n required: cat.required ?? false,\n exclusive: cat.exclusive ?? true,\n options,\n };\n });\n\n // Filter out categories with no options (after compatibility filtering)\n return categoryRows.filter((row) => row.options.length > 0);\n}\n\n/**\n * Get display name for a domain.\n */\nfunction getDomainDisplayName(domain: string): string {\n const displayNames: Record<string, string> = {\n web: \"Web\",\n api: \"API\",\n cli: \"CLI\",\n mobile: \"Mobile\",\n shared: \"Shared\",\n };\n return (\n displayNames[domain] || domain.charAt(0).toUpperCase() + domain.slice(1)\n );\n}\n\n/**\n * Count selected options across categories.\n */\nfunction countSelections(categories: CategoryRow[]): {\n selected: number;\n total: number;\n} {\n let selected = 0;\n let total = 0;\n for (const category of categories) {\n for (const option of category.options) {\n if (option.state !== \"disabled\") {\n total++;\n if (option.selected) {\n selected++;\n }\n }\n }\n }\n return { selected, total };\n}\n\n// =============================================================================\n// Header Component (Domain info with selection count)\n// =============================================================================\n\ninterface HeaderProps {\n domain: string;\n selectionCount: { selected: number; total: number };\n}\n\nconst Header: React.FC<HeaderProps> = ({ domain, selectionCount }) => {\n return (\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Text bold>\n Configure your <Text color=\"cyan\">{getDomainDisplayName(domain)}</Text>{\" \"}\n stack:\n </Text>\n <Text dimColor>\n {selectionCount.selected}/{selectionCount.total} selected\n </Text>\n </Box>\n );\n};\n\n// =============================================================================\n// Footer Component (Keyboard Help)\n// =============================================================================\n\ninterface FooterProps {\n validationError?: string;\n}\n\nconst Footer: React.FC<FooterProps> = ({ validationError }) => {\n return (\n <Box flexDirection=\"column\" marginTop={1}>\n {/* Validation error message */}\n {validationError && (\n <Box marginBottom={1}>\n <Text color=\"yellow\">{validationError}</Text>\n </Box>\n )}\n\n {/* Keyboard shortcuts help - split layout */}\n <WizardFooter\n navigation={\n \"\\u2190/\\u2192 options \\u2191/\\u2193 categories SPACE select TAB desc E expert\"\n }\n action=\"ENTER continue ESC back\"\n />\n </Box>\n );\n};\n\n// =============================================================================\n// Main Component\n// =============================================================================\n\nexport const StepBuild: React.FC<StepBuildProps> = ({\n matrix,\n domain,\n selectedDomains,\n currentDomainIndex,\n selections,\n allSelections,\n focusedRow,\n focusedCol,\n showDescriptions,\n expertMode,\n onToggle,\n onFocusChange,\n onToggleDescriptions,\n onToggleExpertMode,\n onContinue,\n onBack,\n}) => {\n // Validation state for showing error messages\n const [validationError, setValidationError] = useState<string | undefined>(\n undefined,\n );\n\n // Build categories for the current domain (with framework-first filtering)\n const categories = buildCategoriesForDomain(\n domain,\n allSelections,\n matrix,\n expertMode,\n selections,\n );\n\n // Selection count for header\n const selectionCount = countSelections(categories);\n\n // Multi-domain progress\n const showProgress = selectedDomains.length >= MIN_DOMAINS_FOR_PROGRESS;\n const isLastDomain = currentDomainIndex === selectedDomains.length - 1;\n const nextDomain = isLastDomain\n ? undefined\n : selectedDomains[currentDomainIndex + 1];\n\n // Handle keyboard input for Enter and Escape\n useInput((input, key) => {\n if (key.return) {\n // Validate before continuing\n const validation = validateBuildStep(categories, selections);\n if (validation.valid) {\n setValidationError(undefined);\n onContinue();\n } else {\n setValidationError(validation.message);\n }\n } else if (key.escape) {\n setValidationError(undefined);\n onBack();\n }\n });\n\n return (\n <Box flexDirection=\"column\">\n {/* Header with domain and selection count */}\n <Header domain={domain} selectionCount={selectionCount} />\n\n {/* Progress indicator for multi-domain */}\n {showProgress && (\n <SectionProgress\n label=\"Domain\"\n current={getDomainDisplayName(domain)}\n index={currentDomainIndex + 1}\n total={selectedDomains.length}\n next={nextDomain ? getDomainDisplayName(nextDomain) : undefined}\n />\n )}\n\n {/* Category grid */}\n <CategoryGrid\n categories={categories}\n focusedRow={focusedRow}\n focusedCol={focusedCol}\n showDescriptions={showDescriptions}\n expertMode={expertMode}\n onToggle={onToggle}\n onFocusChange={onFocusChange}\n onToggleDescriptions={onToggleDescriptions}\n onToggleExpertMode={onToggleExpertMode}\n />\n\n {/* Footer with keyboard hints */}\n <Footer validationError={validationError} />\n </Box>\n );\n};\n","import type {\n MergedSkillsMatrix,\n ResolvedSkill,\n SkillOption,\n SelectionValidation,\n ValidationError,\n ValidationWarning,\n} from \"../types-matrix\";\n\nexport function resolveAlias(\n aliasOrId: string,\n matrix: MergedSkillsMatrix,\n): string {\n return matrix.aliases[aliasOrId] || aliasOrId;\n}\n\nexport function getDependentSkills(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n): string[] {\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) return [];\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n const dependents: string[] = [];\n\n for (const selectedId of resolvedSelections) {\n if (selectedId === fullId) continue;\n\n const selectedSkill = matrix.skills[selectedId];\n if (!selectedSkill) continue;\n\n for (const requirement of selectedSkill.requires) {\n if (requirement.needsAny) {\n const satisfiedReqs = requirement.skillIds.filter((reqId) =>\n resolvedSelections.includes(reqId),\n );\n if (satisfiedReqs.length === 1 && satisfiedReqs[0] === fullId) {\n dependents.push(selectedId);\n }\n } else {\n if (requirement.skillIds.includes(fullId)) {\n dependents.push(selectedId);\n }\n }\n }\n }\n\n return dependents;\n}\n\nexport interface SkillCheckOptions {\n expertMode?: boolean;\n}\n\nexport function isDisabled(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n options?: SkillCheckOptions,\n): boolean {\n if (options?.expertMode) {\n return false;\n }\n\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) {\n return false;\n }\n\n for (const selectedId of currentSelections) {\n const selectedFullId = resolveAlias(selectedId, matrix);\n\n if (skill.conflictsWith.some((c) => c.skillId === selectedFullId)) {\n return true;\n }\n\n const selectedSkill = matrix.skills[selectedFullId];\n if (\n selectedSkill &&\n selectedSkill.conflictsWith.some((c) => c.skillId === fullId)\n ) {\n return true;\n }\n }\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const requirement of skill.requires) {\n if (requirement.needsAny) {\n const hasAny = requirement.skillIds.some((reqId) =>\n resolvedSelections.includes(reqId),\n );\n if (!hasAny) {\n return true;\n }\n } else {\n const hasAll = requirement.skillIds.every((reqId) =>\n resolvedSelections.includes(reqId),\n );\n if (!hasAll) {\n return true;\n }\n }\n }\n\n return false;\n}\n\nexport function getDisableReason(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n): string | undefined {\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) {\n return undefined;\n }\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const selectedId of resolvedSelections) {\n const conflict = skill.conflictsWith.find((c) => c.skillId === selectedId);\n if (conflict) {\n const selectedSkill = matrix.skills[selectedId];\n const selectedName = selectedSkill?.name || selectedId;\n return `${conflict.reason} (conflicts with ${selectedName})`;\n }\n\n const selectedSkill = matrix.skills[selectedId];\n if (selectedSkill) {\n const reverseConflict = selectedSkill.conflictsWith.find(\n (c) => c.skillId === fullId,\n );\n if (reverseConflict) {\n const selectedName = selectedSkill.name;\n return `${reverseConflict.reason} (conflicts with ${selectedName})`;\n }\n }\n }\n\n for (const requirement of skill.requires) {\n if (requirement.needsAny) {\n const hasAny = requirement.skillIds.some((reqId) =>\n resolvedSelections.includes(reqId),\n );\n if (!hasAny) {\n const requiredNames = requirement.skillIds\n .map((id) => matrix.skills[id]?.name || id)\n .join(\" or \");\n return `${requirement.reason} (requires ${requiredNames})`;\n }\n } else {\n const missingIds = requirement.skillIds.filter(\n (reqId) => !resolvedSelections.includes(reqId),\n );\n if (missingIds.length > 0) {\n const missingNames = missingIds\n .map((id) => matrix.skills[id]?.name || id)\n .join(\", \");\n return `${requirement.reason} (requires ${missingNames})`;\n }\n }\n }\n\n return undefined;\n}\n\nexport function isDiscouraged(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n): boolean {\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) {\n return false;\n }\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const selectedId of resolvedSelections) {\n const selectedSkill = matrix.skills[selectedId];\n if (\n selectedSkill &&\n selectedSkill.discourages.some((d) => d.skillId === fullId)\n ) {\n return true;\n }\n\n if (skill.discourages.some((d) => d.skillId === selectedId)) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function getDiscourageReason(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n): string | undefined {\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) {\n return undefined;\n }\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const selectedId of resolvedSelections) {\n const selectedSkill = matrix.skills[selectedId];\n if (selectedSkill) {\n const discourage = selectedSkill.discourages.find(\n (d) => d.skillId === fullId,\n );\n if (discourage) {\n return discourage.reason;\n }\n }\n\n const reverseDiscourage = skill.discourages.find(\n (d) => d.skillId === selectedId,\n );\n if (reverseDiscourage) {\n return reverseDiscourage.reason;\n }\n }\n\n return undefined;\n}\n\nexport function isRecommended(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n): boolean {\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) {\n return false;\n }\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const selectedId of resolvedSelections) {\n const selectedSkill = matrix.skills[selectedId];\n if (\n selectedSkill &&\n selectedSkill.recommends.some((r) => r.skillId === fullId)\n ) {\n return true;\n }\n }\n\n return false;\n}\n\nexport function getRecommendReason(\n skillId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n): string | undefined {\n const fullId = resolveAlias(skillId, matrix);\n const skill = matrix.skills[fullId];\n\n if (!skill) {\n return undefined;\n }\n\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const selectedId of resolvedSelections) {\n const selectedSkill = matrix.skills[selectedId];\n if (selectedSkill) {\n const recommendation = selectedSkill.recommends.find(\n (r) => r.skillId === fullId,\n );\n if (recommendation) {\n return `${recommendation.reason} (recommended by ${selectedSkill.name})`;\n }\n }\n }\n\n return undefined;\n}\n\nexport function validateSelection(\n selections: string[],\n matrix: MergedSkillsMatrix,\n): SelectionValidation {\n const errors: ValidationError[] = [];\n const warnings: ValidationWarning[] = [];\n const resolvedSelections = selections.map((s) => resolveAlias(s, matrix));\n\n for (let i = 0; i < resolvedSelections.length; i++) {\n const skillA = matrix.skills[resolvedSelections[i]];\n if (!skillA) continue;\n\n for (let j = i + 1; j < resolvedSelections.length; j++) {\n const skillBId = resolvedSelections[j];\n const conflict = skillA.conflictsWith.find((c) => c.skillId === skillBId);\n if (conflict) {\n errors.push({\n type: \"conflict\",\n message: `${skillA.name} conflicts with ${matrix.skills[skillBId]?.name || skillBId}: ${conflict.reason}`,\n skills: [skillA.id, skillBId],\n });\n }\n }\n }\n\n for (const skillId of resolvedSelections) {\n const skill = matrix.skills[skillId];\n if (!skill) continue;\n\n for (const requirement of skill.requires) {\n if (requirement.needsAny) {\n const hasAny = requirement.skillIds.some((reqId) =>\n resolvedSelections.includes(reqId),\n );\n if (!hasAny) {\n errors.push({\n type: \"missing_requirement\",\n message: `${skill.name} requires one of: ${requirement.skillIds.map((id) => matrix.skills[id]?.name || id).join(\", \")}`,\n skills: [skillId, ...requirement.skillIds],\n });\n }\n } else {\n const missingIds = requirement.skillIds.filter(\n (reqId) => !resolvedSelections.includes(reqId),\n );\n if (missingIds.length > 0) {\n errors.push({\n type: \"missing_requirement\",\n message: `${skill.name} requires: ${missingIds.map((id) => matrix.skills[id]?.name || id).join(\", \")}`,\n skills: [skillId, ...missingIds],\n });\n }\n }\n }\n }\n\n const categorySelections = new Map<string, string[]>();\n for (const skillId of resolvedSelections) {\n const skill = matrix.skills[skillId];\n if (!skill) continue;\n\n const existing = categorySelections.get(skill.category) || [];\n existing.push(skillId);\n categorySelections.set(skill.category, existing);\n }\n\n for (const [categoryId, skillIds] of categorySelections.entries()) {\n if (skillIds.length > 1) {\n const category = matrix.categories[categoryId];\n if (category?.exclusive) {\n errors.push({\n type: \"category_exclusive\",\n message: `Category \"${category.name}\" only allows one selection, but multiple selected: ${skillIds.map((id) => matrix.skills[id]?.name || id).join(\", \")}`,\n skills: skillIds,\n });\n }\n }\n }\n\n for (const skillId of resolvedSelections) {\n const skill = matrix.skills[skillId];\n if (!skill) continue;\n\n for (const recommendation of skill.recommends) {\n if (!resolvedSelections.includes(recommendation.skillId)) {\n const recommendedSkill = matrix.skills[recommendation.skillId];\n if (recommendedSkill) {\n const hasConflict = recommendedSkill.conflictsWith.some((c) =>\n resolvedSelections.includes(c.skillId),\n );\n if (!hasConflict) {\n warnings.push({\n type: \"missing_recommendation\",\n message: `${skill.name} recommends ${recommendedSkill.name}: ${recommendation.reason}`,\n skills: [skillId, recommendation.skillId],\n });\n }\n }\n }\n }\n }\n\n for (const skillId of resolvedSelections) {\n const skill = matrix.skills[skillId];\n if (!skill || skill.providesSetupFor.length === 0) continue;\n\n const hasUsageSkill = skill.providesSetupFor.some((usageId) =>\n resolvedSelections.includes(usageId),\n );\n if (!hasUsageSkill) {\n warnings.push({\n type: \"unused_setup\",\n message: `Setup skill \"${skill.name}\" selected but no corresponding usage skills: ${skill.providesSetupFor.map((id) => matrix.skills[id]?.name || id).join(\", \")}`,\n skills: [skillId, ...skill.providesSetupFor],\n });\n }\n }\n\n return {\n valid: errors.length === 0,\n errors,\n warnings,\n };\n}\n\nexport function getAvailableSkills(\n categoryId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n options?: SkillCheckOptions,\n): SkillOption[] {\n const skillOptions: SkillOption[] = [];\n const resolvedSelections = currentSelections.map((s) =>\n resolveAlias(s, matrix),\n );\n\n for (const skill of Object.values(matrix.skills)) {\n if (skill.category !== categoryId) {\n continue;\n }\n\n const disabled = isDisabled(skill.id, currentSelections, matrix, options);\n const discouraged =\n !disabled && isDiscouraged(skill.id, currentSelections, matrix);\n const recommended =\n !disabled &&\n !discouraged &&\n isRecommended(skill.id, currentSelections, matrix);\n\n skillOptions.push({\n id: skill.id,\n alias: skill.alias,\n name: skill.name,\n description: skill.description,\n disabled,\n disabledReason: disabled\n ? getDisableReason(skill.id, currentSelections, matrix)\n : undefined,\n discouraged,\n discouragedReason: discouraged\n ? getDiscourageReason(skill.id, currentSelections, matrix)\n : undefined,\n recommended,\n recommendedReason: recommended\n ? getRecommendReason(skill.id, currentSelections, matrix)\n : undefined,\n selected: resolvedSelections.includes(skill.id),\n alternatives: skill.alternatives.map((a) => a.skillId),\n });\n }\n\n return skillOptions;\n}\n\nexport function getSkillsByCategory(\n categoryId: string,\n matrix: MergedSkillsMatrix,\n): ResolvedSkill[] {\n const skills: ResolvedSkill[] = [];\n\n for (const skill of Object.values(matrix.skills)) {\n if (skill.category === categoryId) {\n skills.push(skill);\n }\n }\n\n return skills;\n}\n\nexport function isCategoryAllDisabled(\n categoryId: string,\n currentSelections: string[],\n matrix: MergedSkillsMatrix,\n options?: SkillCheckOptions,\n): { disabled: boolean; reason?: string } {\n if (options?.expertMode) {\n return { disabled: false };\n }\n\n const skills = getSkillsByCategory(categoryId, matrix);\n\n if (skills.length === 0) {\n return { disabled: false };\n }\n\n const disabledSkills: Array<{ skillId: string; reason: string | undefined }> =\n [];\n\n for (const skill of skills) {\n if (isDisabled(skill.id, currentSelections, matrix, options)) {\n disabledSkills.push({\n skillId: skill.id,\n reason: getDisableReason(skill.id, currentSelections, matrix),\n });\n }\n }\n\n if (disabledSkills.length === skills.length) {\n const firstReason = disabledSkills[0]?.reason;\n const shortReason = firstReason?.split(\" (\")[0] || \"requirements not met\";\n return { disabled: true, reason: shortReason };\n }\n\n return { disabled: false };\n}\n\nexport function getSubcategories(\n parentCategoryId: string,\n matrix: MergedSkillsMatrix,\n): string[] {\n const subcategories: string[] = [];\n\n for (const category of Object.values(matrix.categories)) {\n if (category.parent === parentCategoryId) {\n subcategories.push(category.id);\n }\n }\n\n subcategories.sort((a, b) => {\n const catA = matrix.categories[a];\n const catB = matrix.categories[b];\n return (catA?.order ?? 0) - (catB?.order ?? 0);\n });\n\n return subcategories;\n}\n\nexport function getTopLevelCategories(matrix: MergedSkillsMatrix): string[] {\n const topLevel: string[] = [];\n\n for (const category of Object.values(matrix.categories)) {\n if (!category.parent) {\n topLevel.push(category.id);\n }\n }\n\n topLevel.sort((a, b) => {\n const catA = matrix.categories[a];\n const catB = matrix.categories[b];\n return (catA?.order ?? 0) - (catB?.order ?? 0);\n });\n\n return topLevel;\n}\n"],"mappings":"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","names":["selectedSkill"]}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
cliTheme
|
|
4
|
+
} from "./chunk-Z2CWURZ6.js";
|
|
5
|
+
import {
|
|
6
|
+
init_esm_shims
|
|
7
|
+
} from "./chunk-DHET7RCE.js";
|
|
8
|
+
|
|
9
|
+
// src/cli/components/skill-search/skill-search.tsx
|
|
10
|
+
init_esm_shims();
|
|
11
|
+
import { useState, useCallback, useMemo } from "react";
|
|
12
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
13
|
+
import { ThemeProvider } from "@inkjs/ui";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
var MAX_VISIBLE_RESULTS = 10;
|
|
16
|
+
var CHECKBOX_CHECKED = "[x]";
|
|
17
|
+
var CHECKBOX_UNCHECKED = "[ ]";
|
|
18
|
+
function matchesQuery(skill, query) {
|
|
19
|
+
if (!query.trim()) return true;
|
|
20
|
+
const lowerQuery = query.toLowerCase();
|
|
21
|
+
if (skill.name.toLowerCase().includes(lowerQuery)) return true;
|
|
22
|
+
if (skill.id.toLowerCase().includes(lowerQuery)) return true;
|
|
23
|
+
if (skill.alias?.toLowerCase().includes(lowerQuery)) return true;
|
|
24
|
+
if (skill.description.toLowerCase().includes(lowerQuery)) return true;
|
|
25
|
+
if (skill.category.toLowerCase().includes(lowerQuery)) return true;
|
|
26
|
+
if (skill.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))) {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
if (skill.sourceName.toLowerCase().includes(lowerQuery)) return true;
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
function truncate(text, maxLength) {
|
|
33
|
+
if (text.length <= maxLength) return text;
|
|
34
|
+
return text.slice(0, maxLength - 3) + "...";
|
|
35
|
+
}
|
|
36
|
+
var Header = ({ sourceCount }) => {
|
|
37
|
+
return /* @__PURE__ */ jsxs(
|
|
38
|
+
Box,
|
|
39
|
+
{
|
|
40
|
+
flexDirection: "row",
|
|
41
|
+
justifyContent: "space-between",
|
|
42
|
+
borderStyle: "single",
|
|
43
|
+
borderBottom: false,
|
|
44
|
+
borderLeft: false,
|
|
45
|
+
borderRight: false,
|
|
46
|
+
paddingX: 1,
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Search Skills" }),
|
|
49
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
50
|
+
sourceCount,
|
|
51
|
+
" source",
|
|
52
|
+
sourceCount !== 1 ? "s" : ""
|
|
53
|
+
] })
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
var SearchInput = ({ value, onChange }) => {
|
|
59
|
+
useInput((input, key) => {
|
|
60
|
+
if (key.backspace || key.delete) {
|
|
61
|
+
onChange(value.slice(0, -1));
|
|
62
|
+
} else if (!key.ctrl && !key.meta && input && input.length === 1) {
|
|
63
|
+
const charCode = input.charCodeAt(0);
|
|
64
|
+
if (charCode >= 32 && charCode <= 126) {
|
|
65
|
+
onChange(value + input);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
return /* @__PURE__ */ jsxs(Box, { paddingX: 1, paddingY: 1, children: [
|
|
70
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
71
|
+
">",
|
|
72
|
+
" "
|
|
73
|
+
] }),
|
|
74
|
+
/* @__PURE__ */ jsx(Text, { children: value }),
|
|
75
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "_" })
|
|
76
|
+
] });
|
|
77
|
+
};
|
|
78
|
+
var ResultItem = ({
|
|
79
|
+
skill,
|
|
80
|
+
isSelected,
|
|
81
|
+
isFocused
|
|
82
|
+
}) => {
|
|
83
|
+
const checkbox = isSelected ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED;
|
|
84
|
+
const displayName = skill.alias || skill.id;
|
|
85
|
+
const descriptionWidth = 30;
|
|
86
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
|
|
87
|
+
/* @__PURE__ */ jsxs(
|
|
88
|
+
Text,
|
|
89
|
+
{
|
|
90
|
+
color: isFocused ? "cyan" : isSelected ? "green" : void 0,
|
|
91
|
+
bold: isFocused,
|
|
92
|
+
backgroundColor: isFocused ? "#333333" : void 0,
|
|
93
|
+
children: [
|
|
94
|
+
" ",
|
|
95
|
+
checkbox,
|
|
96
|
+
" "
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
),
|
|
100
|
+
/* @__PURE__ */ jsx(Box, { width: 14, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncate(skill.sourceName, 12) }) }),
|
|
101
|
+
/* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(
|
|
102
|
+
Text,
|
|
103
|
+
{
|
|
104
|
+
color: isFocused ? "cyan" : void 0,
|
|
105
|
+
bold: isFocused || isSelected,
|
|
106
|
+
children: truncate(displayName, 22)
|
|
107
|
+
}
|
|
108
|
+
) }),
|
|
109
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: truncate(skill.description, descriptionWidth) })
|
|
110
|
+
] });
|
|
111
|
+
};
|
|
112
|
+
var ResultsList = ({
|
|
113
|
+
results,
|
|
114
|
+
selectedIds,
|
|
115
|
+
focusedIndex,
|
|
116
|
+
scrollOffset
|
|
117
|
+
}) => {
|
|
118
|
+
const visibleResults = results.slice(
|
|
119
|
+
scrollOffset,
|
|
120
|
+
scrollOffset + MAX_VISIBLE_RESULTS
|
|
121
|
+
);
|
|
122
|
+
if (results.length === 0) {
|
|
123
|
+
return /* @__PURE__ */ jsx(Box, { paddingX: 1, paddingY: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No skills found matching your search." }) });
|
|
124
|
+
}
|
|
125
|
+
return /* @__PURE__ */ jsx(
|
|
126
|
+
Box,
|
|
127
|
+
{
|
|
128
|
+
flexDirection: "column",
|
|
129
|
+
borderStyle: "single",
|
|
130
|
+
borderTop: false,
|
|
131
|
+
borderBottom: false,
|
|
132
|
+
paddingX: 1,
|
|
133
|
+
children: visibleResults.map((skill, index) => {
|
|
134
|
+
const actualIndex = scrollOffset + index;
|
|
135
|
+
return /* @__PURE__ */ jsx(
|
|
136
|
+
ResultItem,
|
|
137
|
+
{
|
|
138
|
+
skill,
|
|
139
|
+
isSelected: selectedIds.has(skill.id),
|
|
140
|
+
isFocused: actualIndex === focusedIndex
|
|
141
|
+
},
|
|
142
|
+
skill.id
|
|
143
|
+
);
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
};
|
|
148
|
+
var StatusBar = ({
|
|
149
|
+
resultCount,
|
|
150
|
+
selectedCount
|
|
151
|
+
}) => {
|
|
152
|
+
return /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
153
|
+
resultCount,
|
|
154
|
+
" result",
|
|
155
|
+
resultCount !== 1 ? "s" : "",
|
|
156
|
+
selectedCount > 0 && /* @__PURE__ */ jsxs(Text, { color: "green", children: [
|
|
157
|
+
" | ",
|
|
158
|
+
selectedCount,
|
|
159
|
+
" selected"
|
|
160
|
+
] })
|
|
161
|
+
] }) });
|
|
162
|
+
};
|
|
163
|
+
var Footer = ({ hasSelection }) => {
|
|
164
|
+
return /* @__PURE__ */ jsxs(
|
|
165
|
+
Box,
|
|
166
|
+
{
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
justifyContent: "center",
|
|
169
|
+
gap: 2,
|
|
170
|
+
borderStyle: "single",
|
|
171
|
+
borderTop: false,
|
|
172
|
+
borderLeft: false,
|
|
173
|
+
borderRight: false,
|
|
174
|
+
paddingX: 1,
|
|
175
|
+
marginTop: 1,
|
|
176
|
+
children: [
|
|
177
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
178
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: "j/k" }),
|
|
179
|
+
" navigate"
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
182
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: "SPACE" }),
|
|
183
|
+
" select"
|
|
184
|
+
] }),
|
|
185
|
+
hasSelection && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
186
|
+
/* @__PURE__ */ jsx(Text, { color: "green", children: "ENTER" }),
|
|
187
|
+
" import"
|
|
188
|
+
] }),
|
|
189
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
190
|
+
/* @__PURE__ */ jsx(Text, { color: "white", children: "c" }),
|
|
191
|
+
" copy link"
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
194
|
+
/* @__PURE__ */ jsx(Text, { color: "yellow", children: "ESC" }),
|
|
195
|
+
" cancel"
|
|
196
|
+
] })
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
};
|
|
201
|
+
var SkillSearch = ({
|
|
202
|
+
skills,
|
|
203
|
+
sourceCount,
|
|
204
|
+
initialQuery = "",
|
|
205
|
+
onComplete,
|
|
206
|
+
onCancel
|
|
207
|
+
}) => {
|
|
208
|
+
const { exit } = useApp();
|
|
209
|
+
const [query, setQuery] = useState(initialQuery);
|
|
210
|
+
const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
211
|
+
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
212
|
+
const [scrollOffset, setScrollOffset] = useState(0);
|
|
213
|
+
const [copiedMessage, setCopiedMessage] = useState(null);
|
|
214
|
+
const filteredResults = useMemo(() => {
|
|
215
|
+
return skills.filter((skill) => matchesQuery(skill, query));
|
|
216
|
+
}, [skills, query]);
|
|
217
|
+
const safeFocusedIndex = Math.min(
|
|
218
|
+
focusedIndex,
|
|
219
|
+
Math.max(0, filteredResults.length - 1)
|
|
220
|
+
);
|
|
221
|
+
const focusedSkill = filteredResults[safeFocusedIndex];
|
|
222
|
+
const handleQueryChange = useCallback((newQuery) => {
|
|
223
|
+
setQuery(newQuery);
|
|
224
|
+
setFocusedIndex(0);
|
|
225
|
+
setScrollOffset(0);
|
|
226
|
+
}, []);
|
|
227
|
+
const toggleSelection = useCallback((skillId) => {
|
|
228
|
+
setSelectedIds((prev) => {
|
|
229
|
+
const next = new Set(prev);
|
|
230
|
+
if (next.has(skillId)) {
|
|
231
|
+
next.delete(skillId);
|
|
232
|
+
} else {
|
|
233
|
+
next.add(skillId);
|
|
234
|
+
}
|
|
235
|
+
return next;
|
|
236
|
+
});
|
|
237
|
+
}, []);
|
|
238
|
+
const copySkillLink = useCallback(async (skill) => {
|
|
239
|
+
const link = skill.sourceUrl ? `${skill.sourceUrl}/${skill.id}` : skill.id;
|
|
240
|
+
try {
|
|
241
|
+
const encoded = Buffer.from(link).toString("base64");
|
|
242
|
+
process.stdout.write(`\x1B]52;c;${encoded}\x07`);
|
|
243
|
+
setCopiedMessage(`Copied: ${link}`);
|
|
244
|
+
setTimeout(() => setCopiedMessage(null), 2e3);
|
|
245
|
+
} catch {
|
|
246
|
+
setCopiedMessage(`Link: ${link}`);
|
|
247
|
+
setTimeout(() => setCopiedMessage(null), 3e3);
|
|
248
|
+
}
|
|
249
|
+
}, []);
|
|
250
|
+
useInput((input, key) => {
|
|
251
|
+
if (key.escape) {
|
|
252
|
+
onCancel();
|
|
253
|
+
exit();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (key.return) {
|
|
257
|
+
if (selectedIds.size > 0) {
|
|
258
|
+
const selectedSkills = filteredResults.filter(
|
|
259
|
+
(s) => selectedIds.has(s.id)
|
|
260
|
+
);
|
|
261
|
+
onComplete({
|
|
262
|
+
selectedSkills,
|
|
263
|
+
cancelled: false
|
|
264
|
+
});
|
|
265
|
+
exit();
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (input === " " && focusedSkill) {
|
|
270
|
+
toggleSelection(focusedSkill.id);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if ((input === "c" || input === "C") && focusedSkill) {
|
|
274
|
+
void copySkillLink(focusedSkill);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const isUp = key.upArrow || input === "k";
|
|
278
|
+
const isDown = key.downArrow || input === "j";
|
|
279
|
+
if (isUp && safeFocusedIndex > 0) {
|
|
280
|
+
const newIndex = safeFocusedIndex - 1;
|
|
281
|
+
setFocusedIndex(newIndex);
|
|
282
|
+
if (newIndex < scrollOffset) {
|
|
283
|
+
setScrollOffset(newIndex);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (isDown && safeFocusedIndex < filteredResults.length - 1) {
|
|
287
|
+
const newIndex = safeFocusedIndex + 1;
|
|
288
|
+
setFocusedIndex(newIndex);
|
|
289
|
+
if (newIndex >= scrollOffset + MAX_VISIBLE_RESULTS) {
|
|
290
|
+
setScrollOffset(newIndex - MAX_VISIBLE_RESULTS + 1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
return /* @__PURE__ */ jsx(ThemeProvider, { theme: cliTheme, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
295
|
+
/* @__PURE__ */ jsx(Header, { sourceCount }),
|
|
296
|
+
/* @__PURE__ */ jsx(SearchInput, { value: query, onChange: handleQueryChange }),
|
|
297
|
+
/* @__PURE__ */ jsx(
|
|
298
|
+
ResultsList,
|
|
299
|
+
{
|
|
300
|
+
results: filteredResults,
|
|
301
|
+
selectedIds,
|
|
302
|
+
focusedIndex: safeFocusedIndex,
|
|
303
|
+
scrollOffset
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
/* @__PURE__ */ jsx(
|
|
307
|
+
StatusBar,
|
|
308
|
+
{
|
|
309
|
+
resultCount: filteredResults.length,
|
|
310
|
+
selectedCount: selectedIds.size
|
|
311
|
+
}
|
|
312
|
+
),
|
|
313
|
+
copiedMessage && /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "green", children: copiedMessage }) }),
|
|
314
|
+
/* @__PURE__ */ jsx(Footer, { hasSelection: selectedIds.size > 0 })
|
|
315
|
+
] }) });
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export {
|
|
319
|
+
SkillSearch
|
|
320
|
+
};
|
|
321
|
+
//# sourceMappingURL=chunk-ACNBKXXJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/components/skill-search/skill-search.tsx"],"sourcesContent":["/**\n * Interactive skill search component.\n *\n * Features:\n * - Live text filtering as you type\n * - Multi-select checkboxes\n * - Keyboard navigation (arrows, vim keys, space, enter, c, esc)\n * - Source attribution for each skill\n *\n * Keyboard controls:\n * - Up/Down/k/j: Navigate results\n * - Space: Toggle selection\n * - Enter: Import selected skills\n * - c: Copy link of focused skill\n * - Esc: Cancel/exit\n */\nimport React, { useState, useCallback, useMemo } from \"react\";\nimport { Box, Text, useApp, useInput } from \"ink\";\nimport { ThemeProvider } from \"@inkjs/ui\";\nimport { cliTheme } from \"../themes/default.js\";\nimport type { ResolvedSkill } from \"../../types-matrix.js\";\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface SourcedSkill extends ResolvedSkill {\n /** Source name where this skill came from */\n sourceName: string;\n /** Source URL for reference */\n sourceUrl?: string;\n}\n\nexport interface SkillSearchResult {\n /** Skills selected for import */\n selectedSkills: SourcedSkill[];\n /** Whether the user cancelled */\n cancelled: boolean;\n}\n\nexport interface SkillSearchProps {\n /** All available skills from all sources */\n skills: SourcedSkill[];\n /** Total number of sources */\n sourceCount: number;\n /** Initial search query (from command args) */\n initialQuery?: string;\n /** Called when user completes selection (Enter) */\n onComplete: (result: SkillSearchResult) => void;\n /** Called when user cancels (Esc) */\n onCancel: () => void;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/** Maximum results to show at once */\nconst MAX_VISIBLE_RESULTS = 10;\n\n/** Checkbox display */\nconst CHECKBOX_CHECKED = \"[x]\";\nconst CHECKBOX_UNCHECKED = \"[ ]\";\n\n// =============================================================================\n// Helper Functions\n// =============================================================================\n\n/**\n * Match skill against search query (case-insensitive substring)\n */\nfunction matchesQuery(skill: SourcedSkill, query: string): boolean {\n if (!query.trim()) return true;\n\n const lowerQuery = query.toLowerCase();\n\n // Match against name\n if (skill.name.toLowerCase().includes(lowerQuery)) return true;\n\n // Match against ID\n if (skill.id.toLowerCase().includes(lowerQuery)) return true;\n\n // Match against alias\n if (skill.alias?.toLowerCase().includes(lowerQuery)) return true;\n\n // Match against description\n if (skill.description.toLowerCase().includes(lowerQuery)) return true;\n\n // Match against category\n if (skill.category.toLowerCase().includes(lowerQuery)) return true;\n\n // Match against tags\n if (skill.tags.some((tag) => tag.toLowerCase().includes(lowerQuery))) {\n return true;\n }\n\n // Match against source name\n if (skill.sourceName.toLowerCase().includes(lowerQuery)) return true;\n\n return false;\n}\n\n/**\n * Truncate text with ellipsis\n */\nfunction truncate(text: string, maxLength: number): string {\n if (text.length <= maxLength) return text;\n return text.slice(0, maxLength - 3) + \"...\";\n}\n\n// =============================================================================\n// Sub-Components\n// =============================================================================\n\ninterface HeaderProps {\n sourceCount: number;\n}\n\nconst Header: React.FC<HeaderProps> = ({ sourceCount }) => {\n return (\n <Box\n flexDirection=\"row\"\n justifyContent=\"space-between\"\n borderStyle=\"single\"\n borderBottom={false}\n borderLeft={false}\n borderRight={false}\n paddingX={1}\n >\n <Text bold color=\"cyan\">\n Search Skills\n </Text>\n <Text dimColor>\n {sourceCount} source{sourceCount !== 1 ? \"s\" : \"\"}\n </Text>\n </Box>\n );\n};\n\ninterface SearchInputProps {\n value: string;\n onChange: (value: string) => void;\n}\n\nconst SearchInput: React.FC<SearchInputProps> = ({ value, onChange }) => {\n // Handle text input\n useInput((input, key) => {\n if (key.backspace || key.delete) {\n onChange(value.slice(0, -1));\n } else if (!key.ctrl && !key.meta && input && input.length === 1) {\n // Only handle printable characters\n const charCode = input.charCodeAt(0);\n if (charCode >= 32 && charCode <= 126) {\n onChange(value + input);\n }\n }\n });\n\n return (\n <Box paddingX={1} paddingY={1}>\n <Text color=\"cyan\">{\">\"} </Text>\n <Text>{value}</Text>\n <Text color=\"cyan\">_</Text>\n </Box>\n );\n};\n\ninterface ResultItemProps {\n skill: SourcedSkill;\n isSelected: boolean;\n isFocused: boolean;\n}\n\nconst ResultItem: React.FC<ResultItemProps> = ({\n skill,\n isSelected,\n isFocused,\n}) => {\n const checkbox = isSelected ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED;\n const displayName = skill.alias || skill.id;\n const descriptionWidth = 30;\n\n return (\n <Box flexDirection=\"row\" gap={1}>\n <Text\n color={isFocused ? \"cyan\" : isSelected ? \"green\" : undefined}\n bold={isFocused}\n backgroundColor={isFocused ? \"#333333\" : undefined}\n >\n {\" \"}\n {checkbox}{\" \"}\n </Text>\n <Box width={14}>\n <Text dimColor>{truncate(skill.sourceName, 12)}</Text>\n </Box>\n <Box width={24}>\n <Text\n color={isFocused ? \"cyan\" : undefined}\n bold={isFocused || isSelected}\n >\n {truncate(displayName, 22)}\n </Text>\n </Box>\n <Text dimColor>{truncate(skill.description, descriptionWidth)}</Text>\n </Box>\n );\n};\n\ninterface ResultsListProps {\n results: SourcedSkill[];\n selectedIds: Set<string>;\n focusedIndex: number;\n scrollOffset: number;\n}\n\nconst ResultsList: React.FC<ResultsListProps> = ({\n results,\n selectedIds,\n focusedIndex,\n scrollOffset,\n}) => {\n const visibleResults = results.slice(\n scrollOffset,\n scrollOffset + MAX_VISIBLE_RESULTS,\n );\n\n if (results.length === 0) {\n return (\n <Box paddingX={1} paddingY={1}>\n <Text dimColor>No skills found matching your search.</Text>\n </Box>\n );\n }\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"single\"\n borderTop={false}\n borderBottom={false}\n paddingX={1}\n >\n {visibleResults.map((skill, index) => {\n const actualIndex = scrollOffset + index;\n return (\n <ResultItem\n key={skill.id}\n skill={skill}\n isSelected={selectedIds.has(skill.id)}\n isFocused={actualIndex === focusedIndex}\n />\n );\n })}\n </Box>\n );\n};\n\ninterface StatusBarProps {\n resultCount: number;\n selectedCount: number;\n}\n\nconst StatusBar: React.FC<StatusBarProps> = ({\n resultCount,\n selectedCount,\n}) => {\n return (\n <Box paddingX={1}>\n <Text dimColor>\n {resultCount} result{resultCount !== 1 ? \"s\" : \"\"}\n {selectedCount > 0 && (\n <Text color=\"green\"> | {selectedCount} selected</Text>\n )}\n </Text>\n </Box>\n );\n};\n\ninterface FooterProps {\n hasSelection: boolean;\n}\n\nconst Footer: React.FC<FooterProps> = ({ hasSelection }) => {\n return (\n <Box\n flexDirection=\"row\"\n justifyContent=\"center\"\n gap={2}\n borderStyle=\"single\"\n borderTop={false}\n borderLeft={false}\n borderRight={false}\n paddingX={1}\n marginTop={1}\n >\n <Text dimColor>\n <Text color=\"white\">j/k</Text> navigate\n </Text>\n <Text dimColor>\n <Text color=\"white\">SPACE</Text> select\n </Text>\n {hasSelection && (\n <Text dimColor>\n <Text color=\"green\">ENTER</Text> import\n </Text>\n )}\n <Text dimColor>\n <Text color=\"white\">c</Text> copy link\n </Text>\n <Text dimColor>\n <Text color=\"yellow\">ESC</Text> cancel\n </Text>\n </Box>\n );\n};\n\n// =============================================================================\n// Main Component\n// =============================================================================\n\nexport const SkillSearch: React.FC<SkillSearchProps> = ({\n skills,\n sourceCount,\n initialQuery = \"\",\n onComplete,\n onCancel,\n}) => {\n const { exit } = useApp();\n\n // State\n const [query, setQuery] = useState(initialQuery);\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n const [focusedIndex, setFocusedIndex] = useState(0);\n const [scrollOffset, setScrollOffset] = useState(0);\n const [copiedMessage, setCopiedMessage] = useState<string | null>(null);\n\n // Filter results based on query\n const filteredResults = useMemo(() => {\n return skills.filter((skill) => matchesQuery(skill, query));\n }, [skills, query]);\n\n // Ensure focus stays in bounds when results change\n const safeFocusedIndex = Math.min(\n focusedIndex,\n Math.max(0, filteredResults.length - 1),\n );\n\n // Get the currently focused skill\n const focusedSkill = filteredResults[safeFocusedIndex];\n\n // Handle query change\n const handleQueryChange = useCallback((newQuery: string) => {\n setQuery(newQuery);\n setFocusedIndex(0);\n setScrollOffset(0);\n }, []);\n\n // Toggle selection\n const toggleSelection = useCallback((skillId: string) => {\n setSelectedIds((prev) => {\n const next = new Set(prev);\n if (next.has(skillId)) {\n next.delete(skillId);\n } else {\n next.add(skillId);\n }\n return next;\n });\n }, []);\n\n // Copy skill link to clipboard\n const copySkillLink = useCallback(async (skill: SourcedSkill) => {\n const link = skill.sourceUrl ? `${skill.sourceUrl}/${skill.id}` : skill.id;\n\n try {\n // Use native clipboard API via process.stdout.write with OSC 52\n // This is a cross-platform terminal clipboard method\n const encoded = Buffer.from(link).toString(\"base64\");\n process.stdout.write(`\\x1b]52;c;${encoded}\\x07`);\n setCopiedMessage(`Copied: ${link}`);\n setTimeout(() => setCopiedMessage(null), 2000);\n } catch {\n setCopiedMessage(`Link: ${link}`);\n setTimeout(() => setCopiedMessage(null), 3000);\n }\n }, []);\n\n // Handle keyboard navigation\n useInput((input, key) => {\n // Escape to cancel\n if (key.escape) {\n onCancel();\n exit();\n return;\n }\n\n // Enter to import selected\n if (key.return) {\n if (selectedIds.size > 0) {\n const selectedSkills = filteredResults.filter((s) =>\n selectedIds.has(s.id),\n );\n onComplete({\n selectedSkills,\n cancelled: false,\n });\n exit();\n }\n return;\n }\n\n // Space to toggle selection\n if (input === \" \" && focusedSkill) {\n toggleSelection(focusedSkill.id);\n return;\n }\n\n // c to copy link\n if ((input === \"c\" || input === \"C\") && focusedSkill) {\n void copySkillLink(focusedSkill);\n return;\n }\n\n // Navigation\n const isUp = key.upArrow || input === \"k\";\n const isDown = key.downArrow || input === \"j\";\n\n if (isUp && safeFocusedIndex > 0) {\n const newIndex = safeFocusedIndex - 1;\n setFocusedIndex(newIndex);\n // Scroll up if needed\n if (newIndex < scrollOffset) {\n setScrollOffset(newIndex);\n }\n }\n\n if (isDown && safeFocusedIndex < filteredResults.length - 1) {\n const newIndex = safeFocusedIndex + 1;\n setFocusedIndex(newIndex);\n // Scroll down if needed\n if (newIndex >= scrollOffset + MAX_VISIBLE_RESULTS) {\n setScrollOffset(newIndex - MAX_VISIBLE_RESULTS + 1);\n }\n }\n });\n\n return (\n <ThemeProvider theme={cliTheme}>\n <Box flexDirection=\"column\">\n <Header sourceCount={sourceCount} />\n <SearchInput value={query} onChange={handleQueryChange} />\n <ResultsList\n results={filteredResults}\n selectedIds={selectedIds}\n focusedIndex={safeFocusedIndex}\n scrollOffset={scrollOffset}\n />\n <StatusBar\n resultCount={filteredResults.length}\n selectedCount={selectedIds.size}\n />\n {copiedMessage && (\n <Box paddingX={1}>\n <Text color=\"green\">{copiedMessage}</Text>\n </Box>\n )}\n <Footer hasSelection={selectedIds.size > 0} />\n </Box>\n </ThemeProvider>\n );\n};\n"],"mappings":"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","names":[]}
|