agents-inc 0.167.0 → 0.170.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +18 -15
  3. package/dist/{chunk-ZR4YPKXQ.js → chunk-3G7X2WKI.js} +4 -4
  4. package/dist/{chunk-JOEA52NR.js → chunk-44TE3RKH.js} +46 -2
  5. package/dist/chunk-44TE3RKH.js.map +1 -0
  6. package/dist/{chunk-V5WXIM2F.js → chunk-52HDHJ7I.js} +11 -19
  7. package/dist/chunk-52HDHJ7I.js.map +1 -0
  8. package/dist/{chunk-V3I7VQ7N.js → chunk-6HNXYYVF.js} +39 -22
  9. package/dist/chunk-6HNXYYVF.js.map +1 -0
  10. package/dist/{chunk-MAWNVT2C.js → chunk-6ZRH5RHC.js} +10 -11
  11. package/dist/chunk-6ZRH5RHC.js.map +1 -0
  12. package/dist/{chunk-G6UREQ4V.js → chunk-7RDMTR64.js} +14 -34
  13. package/dist/chunk-7RDMTR64.js.map +1 -0
  14. package/dist/{chunk-GE3WKJ6Q.js → chunk-BMRUNMPV.js} +4 -4
  15. package/dist/{chunk-VAPTVR3J.js → chunk-CLPUWOSA.js} +4 -4
  16. package/dist/{chunk-VAPTVR3J.js.map → chunk-CLPUWOSA.js.map} +1 -1
  17. package/dist/{chunk-HWEKHVLE.js → chunk-F2BHT5C4.js} +9 -7
  18. package/dist/chunk-F2BHT5C4.js.map +1 -0
  19. package/dist/{chunk-ZXKBERQU.js → chunk-F5EBEATU.js} +2 -2
  20. package/dist/{chunk-PMJWD27C.js → chunk-HED2DAXZ.js} +3 -3
  21. package/dist/{chunk-SAYZWCH6.js → chunk-KOLDW6SF.js} +3 -3
  22. package/dist/{chunk-SAYZWCH6.js.map → chunk-KOLDW6SF.js.map} +1 -1
  23. package/dist/{chunk-A4KDXNOT.js → chunk-MQ6CGZNJ.js} +171 -522
  24. package/dist/chunk-MQ6CGZNJ.js.map +1 -0
  25. package/dist/{chunk-G4JK7WVO.js → chunk-NCWDK6L7.js} +115 -196
  26. package/dist/chunk-NCWDK6L7.js.map +1 -0
  27. package/dist/{chunk-VRXX4D6K.js → chunk-NEZ4OAOL.js} +1 -1
  28. package/dist/chunk-NEZ4OAOL.js.map +1 -0
  29. package/dist/chunk-NIHUOWE4.js +106 -0
  30. package/dist/chunk-NIHUOWE4.js.map +1 -0
  31. package/dist/{chunk-OI2H6M5I.js → chunk-NQW25DCF.js} +2 -2
  32. package/dist/chunk-NQW25DCF.js.map +1 -0
  33. package/dist/{chunk-25MPBIK3.js → chunk-OYJQUA3D.js} +3 -3
  34. package/dist/{chunk-25MPBIK3.js.map → chunk-OYJQUA3D.js.map} +1 -1
  35. package/dist/{chunk-ENGYJCTF.js → chunk-PJWGTNOX.js} +220 -254
  36. package/dist/chunk-PJWGTNOX.js.map +1 -0
  37. package/dist/{chunk-NBXXFIIW.js → chunk-RIPSGNLU.js} +3 -3
  38. package/dist/chunk-RIPSGNLU.js.map +1 -0
  39. package/dist/{chunk-Y47EV74W.js → chunk-SZYQISA2.js} +10 -13
  40. package/dist/chunk-SZYQISA2.js.map +1 -0
  41. package/dist/{chunk-TODCQFRB.js → chunk-USVBYQSQ.js} +2 -2
  42. package/dist/{chunk-Y3VIRVVE.js → chunk-V3KTSP3Q.js} +50 -42
  43. package/dist/chunk-V3KTSP3Q.js.map +1 -0
  44. package/dist/{chunk-PXWG7KFO.js → chunk-WTWMGH2C.js} +2 -2
  45. package/dist/commands/build/marketplace.js +23 -22
  46. package/dist/commands/build/marketplace.js.map +1 -1
  47. package/dist/commands/build/plugins.js +8 -9
  48. package/dist/commands/build/plugins.js.map +1 -1
  49. package/dist/commands/compile.js +19 -20
  50. package/dist/commands/compile.js.map +1 -1
  51. package/dist/commands/doctor.js +17 -18
  52. package/dist/commands/doctor.js.map +1 -1
  53. package/dist/commands/edit.js +138 -488
  54. package/dist/commands/edit.js.map +1 -1
  55. package/dist/commands/eject.js +21 -33
  56. package/dist/commands/eject.js.map +1 -1
  57. package/dist/commands/init.js +17 -45
  58. package/dist/commands/list.js +19 -17
  59. package/dist/commands/list.js.map +1 -1
  60. package/dist/commands/new/marketplace.js +11 -11
  61. package/dist/commands/new/marketplace.js.map +1 -1
  62. package/dist/commands/search.js +12 -13
  63. package/dist/commands/search.js.map +1 -1
  64. package/dist/commands/share.js +11 -12
  65. package/dist/commands/share.js.map +1 -1
  66. package/dist/commands/uninstall.js +18 -20
  67. package/dist/commands/uninstall.js.map +1 -1
  68. package/dist/commands/update.js +8 -9
  69. package/dist/commands/update.js.map +1 -1
  70. package/dist/components/common/removal-plan-confirm.js +2 -2
  71. package/dist/components/common/scroll-affordance.js +9 -0
  72. package/dist/components/common/select-list.js +2 -3
  73. package/dist/components/common/skill-agent-summary.js +11 -0
  74. package/dist/components/common/spinner.js +1 -1
  75. package/dist/{chunk-2VUYFTUB.js → components/common/summary-panel.js} +57 -31
  76. package/dist/components/common/summary-panel.js.map +1 -0
  77. package/dist/config-exports.js +1 -1
  78. package/dist/hooks/init.js +18 -46
  79. package/dist/hooks/init.js.map +1 -1
  80. package/dist/load-agent-defs-YMDWBFDP.js +14 -0
  81. package/dist/recompile-project-agents-SZ76WNTW.js +18 -0
  82. package/dist/{source-loader-7KZ3MD5O.js → source-loader-C5SQXZXI.js} +5 -5
  83. package/dist/src/agents/meta/codex-keeper/playbook.md +2 -3
  84. package/package.json +6 -4
  85. package/src/agents/meta/codex-keeper/playbook.md +2 -3
  86. package/src/schemas/project-config.schema.json +1 -1
  87. package/assets/demo.tape +0 -266
  88. package/dist/chunk-2VUYFTUB.js.map +0 -1
  89. package/dist/chunk-4NDBUJHK.js +0 -237
  90. package/dist/chunk-4NDBUJHK.js.map +0 -1
  91. package/dist/chunk-6L75SQZU.js +0 -168
  92. package/dist/chunk-6L75SQZU.js.map +0 -1
  93. package/dist/chunk-6LF4CQBY.js +0 -51
  94. package/dist/chunk-6LF4CQBY.js.map +0 -1
  95. package/dist/chunk-7JNJ52CD.js +0 -1179
  96. package/dist/chunk-7JNJ52CD.js.map +0 -1
  97. package/dist/chunk-A4KDXNOT.js.map +0 -1
  98. package/dist/chunk-DAIEQJIF.js +0 -48
  99. package/dist/chunk-DAIEQJIF.js.map +0 -1
  100. package/dist/chunk-ENGYJCTF.js.map +0 -1
  101. package/dist/chunk-G4JK7WVO.js.map +0 -1
  102. package/dist/chunk-G6UREQ4V.js.map +0 -1
  103. package/dist/chunk-HHIYT3PI.js +0 -53
  104. package/dist/chunk-HHIYT3PI.js.map +0 -1
  105. package/dist/chunk-HWEKHVLE.js.map +0 -1
  106. package/dist/chunk-JOEA52NR.js.map +0 -1
  107. package/dist/chunk-JQRJQ4SS.js +0 -118
  108. package/dist/chunk-JQRJQ4SS.js.map +0 -1
  109. package/dist/chunk-LFWLEDUG.js +0 -59
  110. package/dist/chunk-LFWLEDUG.js.map +0 -1
  111. package/dist/chunk-MAWNVT2C.js.map +0 -1
  112. package/dist/chunk-MPTNC32T.js +0 -92
  113. package/dist/chunk-MPTNC32T.js.map +0 -1
  114. package/dist/chunk-NBXXFIIW.js.map +0 -1
  115. package/dist/chunk-OI2H6M5I.js.map +0 -1
  116. package/dist/chunk-PNB3524H.js +0 -197
  117. package/dist/chunk-PNB3524H.js.map +0 -1
  118. package/dist/chunk-QHDPLLK6.js +0 -35
  119. package/dist/chunk-QHDPLLK6.js.map +0 -1
  120. package/dist/chunk-RAHSOJQ2.js +0 -25
  121. package/dist/chunk-RAHSOJQ2.js.map +0 -1
  122. package/dist/chunk-RZDXDMT6.js +0 -23
  123. package/dist/chunk-RZDXDMT6.js.map +0 -1
  124. package/dist/chunk-SCOQ34YM.js +0 -17
  125. package/dist/chunk-SCOQ34YM.js.map +0 -1
  126. package/dist/chunk-SXVHSOTH.js +0 -268
  127. package/dist/chunk-SXVHSOTH.js.map +0 -1
  128. package/dist/chunk-T6UWJAB3.js +0 -28
  129. package/dist/chunk-T6UWJAB3.js.map +0 -1
  130. package/dist/chunk-TSIU7DME.js +0 -43
  131. package/dist/chunk-TSIU7DME.js.map +0 -1
  132. package/dist/chunk-TTY6DPJN.js +0 -27
  133. package/dist/chunk-TTY6DPJN.js.map +0 -1
  134. package/dist/chunk-TZ2YJJKK.js +0 -292
  135. package/dist/chunk-TZ2YJJKK.js.map +0 -1
  136. package/dist/chunk-V3I7VQ7N.js.map +0 -1
  137. package/dist/chunk-V5WXIM2F.js.map +0 -1
  138. package/dist/chunk-VRXX4D6K.js.map +0 -1
  139. package/dist/chunk-WTJ3AY33.js +0 -31
  140. package/dist/chunk-WTJ3AY33.js.map +0 -1
  141. package/dist/chunk-WVUDLKIO.js +0 -121
  142. package/dist/chunk-WVUDLKIO.js.map +0 -1
  143. package/dist/chunk-WWIFNPJG.js +0 -317
  144. package/dist/chunk-WWIFNPJG.js.map +0 -1
  145. package/dist/chunk-XQHZPINL.js +0 -89
  146. package/dist/chunk-XQHZPINL.js.map +0 -1
  147. package/dist/chunk-XZEVWPX6.js +0 -230
  148. package/dist/chunk-XZEVWPX6.js.map +0 -1
  149. package/dist/chunk-Y3VIRVVE.js.map +0 -1
  150. package/dist/chunk-Y47EV74W.js.map +0 -1
  151. package/dist/components/wizard/category-grid.js +0 -13
  152. package/dist/components/wizard/checkbox-grid.js +0 -12
  153. package/dist/components/wizard/domain-selection.js +0 -18
  154. package/dist/components/wizard/run-wizard-session.js +0 -40
  155. package/dist/components/wizard/run-wizard-session.js.map +0 -1
  156. package/dist/components/wizard/scroll-affordance.js +0 -9
  157. package/dist/components/wizard/skill-agent-summary.js +0 -13
  158. package/dist/components/wizard/source-grid.js +0 -16
  159. package/dist/components/wizard/source-grid.js.map +0 -1
  160. package/dist/components/wizard/stack-selection.js +0 -17
  161. package/dist/components/wizard/stack-selection.js.map +0 -1
  162. package/dist/components/wizard/step-agents.js +0 -18
  163. package/dist/components/wizard/step-agents.js.map +0 -1
  164. package/dist/components/wizard/step-build.js +0 -18
  165. package/dist/components/wizard/step-build.js.map +0 -1
  166. package/dist/components/wizard/step-confirm.js +0 -18
  167. package/dist/components/wizard/step-confirm.js.map +0 -1
  168. package/dist/components/wizard/step-sources.js +0 -19
  169. package/dist/components/wizard/step-sources.js.map +0 -1
  170. package/dist/components/wizard/step-stack.js +0 -18
  171. package/dist/components/wizard/step-stack.js.map +0 -1
  172. package/dist/components/wizard/summary-panel.js +0 -17
  173. package/dist/components/wizard/summary-panel.js.map +0 -1
  174. package/dist/components/wizard/toast.js +0 -9
  175. package/dist/components/wizard/toast.js.map +0 -1
  176. package/dist/components/wizard/wizard-layout.js +0 -22
  177. package/dist/components/wizard/wizard-layout.js.map +0 -1
  178. package/dist/components/wizard/wizard-tabs.js +0 -11
  179. package/dist/components/wizard/wizard-tabs.js.map +0 -1
  180. package/dist/components/wizard/wizard.js +0 -39
  181. package/dist/components/wizard/wizard.js.map +0 -1
  182. package/dist/load-agent-defs-3C27ENC7.js +0 -14
  183. package/dist/load-agent-defs-3C27ENC7.js.map +0 -1
  184. package/dist/recompile-project-agents-DGNBCU3N.js +0 -18
  185. package/dist/recompile-project-agents-DGNBCU3N.js.map +0 -1
  186. package/dist/source-loader-7KZ3MD5O.js.map +0 -1
  187. package/dist/stores/wizard-store.js +0 -24
  188. package/dist/stores/wizard-store.js.map +0 -1
  189. /package/assets/{demo.gif → legacy-wizard.gif} +0 -0
  190. /package/dist/{chunk-ZR4YPKXQ.js.map → chunk-3G7X2WKI.js.map} +0 -0
  191. /package/dist/{chunk-GE3WKJ6Q.js.map → chunk-BMRUNMPV.js.map} +0 -0
  192. /package/dist/{chunk-ZXKBERQU.js.map → chunk-F5EBEATU.js.map} +0 -0
  193. /package/dist/{chunk-PMJWD27C.js.map → chunk-HED2DAXZ.js.map} +0 -0
  194. /package/dist/{chunk-TODCQFRB.js.map → chunk-USVBYQSQ.js.map} +0 -0
  195. /package/dist/{chunk-PXWG7KFO.js.map → chunk-WTWMGH2C.js.map} +0 -0
  196. /package/dist/components/{wizard → common}/scroll-affordance.js.map +0 -0
  197. /package/dist/components/{wizard → common}/skill-agent-summary.js.map +0 -0
  198. /package/dist/{components/wizard/category-grid.js.map → load-agent-defs-YMDWBFDP.js.map} +0 -0
  199. /package/dist/{components/wizard/checkbox-grid.js.map → recompile-project-agents-SZ76WNTW.js.map} +0 -0
  200. /package/dist/{components/wizard/domain-selection.js.map → source-loader-C5SQXZXI.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/category-grid.tsx","../src/cli/components/hooks/use-category-grid-input.ts"],"sourcesContent":["import React, { useCallback, useEffect } from \"react\";\n\nimport { Box, Text } from \"ink\";\n\nimport { CLI_COLORS } from \"../../consts.js\";\nimport { getSkillById } from \"../../lib/matrix/matrix-provider.js\";\nimport type { Category, OptionState, SkillId, SkillScope } from \"../../types/index.js\";\nimport { useCategoryGridInput } from \"../hooks/use-category-grid-input.js\";\nimport { useFocusedListItem } from \"../hooks/use-focused-list-item.js\";\nimport { useSectionScroll } from \"../hooks/use-section-scroll.js\";\n\nexport type CategoryOption = {\n id: SkillId;\n state: OptionState;\n selected: boolean;\n local?: boolean;\n installed?: boolean;\n scope?: SkillScope;\n /** Secondary scope badge shown alongside primary (e.g. after G->P toggle, excluded tombstone) */\n secondaryScope?: SkillScope;\n source?: string;\n /** True when selected but has unmet dependency requirements (shown dimmed) */\n hasUnmetRequirements?: boolean;\n /** Explains unmet requirements (shown in label when D pressed) */\n unmetRequirementsReason?: string;\n /** Display name of the skill that requires this one (e.g. \"Next.js\") */\n requiredBy?: string;\n};\n\nexport type CategoryRow = {\n id: Category;\n displayName: string;\n exclusive: boolean;\n options: CategoryOption[];\n};\n\nexport type CategoryGridProps = {\n categories: CategoryRow[];\n /** Available height in terminal lines for the scrollable viewport. 0 = no constraint. */\n availableHeight?: number;\n showLabels: boolean;\n onToggle: (categoryId: Category, technologyId: SkillId) => void;\n onToggleLabels: () => void;\n /** Optional initial focus row (default: 0). Use with React `key` to reset. */\n defaultFocusedRow?: number;\n /** Optional initial focus col (default: 0). Use with React `key` to reset. */\n defaultFocusedCol?: number;\n /** Optional callback fired whenever the focused position changes */\n onFocusChange?: (row: number, col: number) => void;\n /** Optional callback fired with the resolved SkillId of the focused cell */\n onFocusedSkillChange?: (skillId: SkillId | null) => void;\n};\n\n/** Next option index stepping cyclically from `currentIndex`; empty rows stay put. */\nconst wrapOptionIndex = (length: number, currentIndex: number, direction: 1 | -1): number => {\n if (length === 0) return currentIndex;\n return (currentIndex + direction + length) % length;\n};\n\ntype SkillTagProps = {\n option: CategoryOption;\n isFocused: boolean;\n showLabels: boolean;\n};\n\n/**\n * The verdict word and the reason behind it — `(incompatible: conflicts with Vue\n * Composition Api)`. Every advisory state but `normal` carries a reason, so the\n * status discriminant narrows to the two that have one to state.\n *\n * The word leads rather than the reason: it is the only carrier of the verdict\n * once colour is off, a `discourages` rule's reason is free-form author prose\n * that need not identify itself as a discouragement, and `CELL_ANNOTATION` in\n * `e2e/pages/steps/build-step.ts` anchors cell-label parsing on that keyword.\n *\n * Nothing is elided. A long reason grows the tag and wraps inside its border,\n * which is what `unmetRequirementsReason` above already does.\n */\nconst advisoryAnnotation = (state: OptionState): string | null =>\n state.status === \"normal\" ? null : `(${state.status}: ${state.reason})`;\n\nconst getCompatibilityLabel = (option: CategoryOption): string | null => {\n if (option.selected && option.hasUnmetRequirements && option.unmetRequirementsReason) {\n return `(${option.unmetRequirementsReason})`;\n }\n if (option.selected) return null;\n if (option.requiredBy) return `(required by ${option.requiredBy})`;\n return advisoryAnnotation(option.state);\n};\n\nconst STATUS_COLORS: Partial<Record<OptionState[\"status\"], string>> = {\n incompatible: CLI_COLORS.ERROR,\n discouraged: CLI_COLORS.WARNING,\n};\n\n/** Selected wins, then advisory status; `fallback` covers the plain \"normal\" state. */\nfunction resolveTagColor(option: CategoryOption, fallback: string): string {\n if (option.selected) return CLI_COLORS.PRIMARY;\n return STATUS_COLORS[option.state.status] ?? fallback;\n}\n\nconst SkillTag: React.FC<SkillTagProps> = ({ option, isFocused, showLabels }) => {\n const textColor = resolveTagColor(option, CLI_COLORS.DIM);\n const hasRequiredBy = option.selected && !!option.requiredBy;\n const hasUnmetDeps = option.selected && !!option.hasUnmetRequirements;\n const isDimmed = hasUnmetDeps || hasRequiredBy;\n const showCompatibility = hasRequiredBy || hasUnmetDeps || (showLabels && isFocused);\n const compatibilityLabel = showCompatibility ? getCompatibilityLabel(option) : null;\n\n return (\n <Box\n marginRight={1}\n borderColor={isFocused ? resolveTagColor(option, CLI_COLORS.UNFOCUSED) : textColor}\n borderDimColor={!isFocused}\n borderStyle=\"single\"\n flexShrink={0}\n paddingLeft={1}\n flexDirection=\"row\"\n >\n <>\n {option.scope && (\n <>\n {[option.scope, option.secondaryScope].filter(Boolean).map((badgeScope, i) => (\n <Text key={i} color={CLI_COLORS.WARNING} backgroundColor={CLI_COLORS.LABEL_BG}>\n {badgeScope === \"global\" ? \" G \" : \" P \"}\n </Text>\n ))}\n <Text> </Text>\n </>\n )}\n <Text color={textColor} bold dimColor={isDimmed}>\n {getSkillById(option.id).displayName}{\" \"}\n </Text>\n {compatibilityLabel && (\n <Text color={textColor} dimColor>\n {compatibilityLabel}{\" \"}\n </Text>\n )}\n </>\n </Box>\n );\n};\n\ntype CategorySectionProps = {\n isFirst: boolean;\n category: CategoryRow;\n isFocused: boolean;\n focusedOptionIndex: number;\n showLabels: boolean;\n};\n\nconst CategorySection: React.FC<CategorySectionProps> = ({\n isFirst,\n category,\n isFocused,\n focusedOptionIndex,\n showLabels,\n}) => {\n const { options } = category;\n const selectedCount = options.filter((o) => o.selected).length;\n\n const selectionCounter = category.exclusive ? `(${selectedCount} of 1)` : null;\n\n return (\n <Box flexDirection=\"column\" marginTop={isFirst ? 0 : 1}>\n <Box flexDirection=\"row\">\n {isFocused ? (\n <Text color={CLI_COLORS.WHITE} backgroundColor={CLI_COLORS.LABEL_BG}>\n {` ${category.displayName}${selectionCounter ? ` ${selectionCounter}` : \"\"} `}\n </Text>\n ) : (\n <>\n <Text color={CLI_COLORS.NEUTRAL}>{category.displayName}</Text>\n {selectionCounter && <Text dimColor> {selectionCounter}</Text>}\n </>\n )}\n </Box>\n\n <Box flexDirection=\"row\" flexWrap=\"wrap\" marginTop={0}>\n {options.map((option, index) => (\n <SkillTag\n key={option.id}\n option={option}\n isFocused={isFocused && index === focusedOptionIndex}\n showLabels={showLabels}\n />\n ))}\n </Box>\n </Box>\n );\n};\n\nexport const CategoryGrid: React.FC<CategoryGridProps> = ({\n categories,\n availableHeight = 0,\n showLabels,\n onToggle,\n onToggleLabels,\n defaultFocusedRow = 0,\n defaultFocusedCol = 0,\n onFocusChange,\n onFocusedSkillChange,\n}) => {\n const getColCount = useCallback(\n (row: number): number => categories[row]?.options.length ?? 0,\n [categories],\n );\n\n const findValidCol = useCallback(\n (row: number, currentCol: number, direction: 1 | -1): number =>\n wrapOptionIndex(categories[row]?.options.length ?? 0, currentCol, direction),\n [categories],\n );\n\n const handleFocusChange = useCallback(\n (row: number, col: number) => {\n if (showLabels) onToggleLabels();\n onFocusChange?.(row, col);\n },\n [showLabels, onToggleLabels, onFocusChange],\n );\n\n const { focusedRow, focusedCol, currentFocus, setFocused, moveFocus } = useFocusedListItem(\n categories.length,\n getColCount,\n {\n wrap: true,\n findValidCol,\n onChange: handleFocusChange,\n initialRow: defaultFocusedRow,\n initialCol: defaultFocusedCol,\n },\n );\n\n useCategoryGridInput({\n categories,\n focusedRow,\n focusedCol,\n currentFocus,\n setFocused,\n moveFocus,\n onToggle,\n onToggleLabels,\n });\n\n // Report the focused cell's skill on mount and whenever it changes — including\n // category reshapes that shift the cell without a navigation event. useFocusedListItem\n // only fires onChange during navigation, so the initially highlighted cell would\n // otherwise never reach the store, leaving focusedSkillId out of sync with the visual\n // focus and making the `s` scope hotkey no-op until the first arrow key.\n const focusedSkillId = categories[focusedRow]?.options[focusedCol]?.id ?? null;\n useEffect(() => {\n onFocusedSkillChange?.(focusedSkillId);\n }, [focusedSkillId, onFocusedSkillChange]);\n\n // `hiddenAbove` / `hiddenBelow` are discarded ON PURPOSE — the build grid has\n // no `ScrollAffordance` and is not getting one (owner decision). On a grid\n // this dense a half-cut card already says \"there is more\"; a counted hint\n // would be noise, and it would cost the viewport a row to say what the\n // clipped card says for free. Do not add one on the strength of the counts\n // merely being available here.\n const { setSectionRef, scrollEnabled, scrollTopPx } = useSectionScroll({\n sectionCount: categories.length,\n focusedIndex: focusedRow,\n availableHeight,\n });\n\n if (categories.length === 0) {\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>No categories to display.</Text>\n </Box>\n );\n }\n\n const noShrink = scrollEnabled ? { flexShrink: 0 } : {};\n\n const sectionElements = categories.map((category, index) => (\n <Box key={category.id} ref={(el) => setSectionRef(index, el)} {...noShrink}>\n <CategorySection\n category={category}\n isFocused={index === focusedRow}\n focusedOptionIndex={focusedCol}\n showLabels={showLabels}\n isFirst={index === 0}\n />\n </Box>\n ));\n\n return (\n <Box\n flexDirection=\"column\"\n {...(scrollEnabled\n ? { height: availableHeight, overflow: \"hidden\" as const }\n : { flexGrow: 1 })}\n >\n <Box flexDirection=\"column\" marginTop={scrollTopPx > 0 ? -scrollTopPx : 0} {...noShrink}>\n {sectionElements}\n </Box>\n </Box>\n );\n};\n","import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { useInput, type Key } from \"ink\";\n\nimport type { Category, SkillId } from \"../../types/index.js\";\nimport type { CategoryRow } from \"../wizard/category-grid.js\";\nimport type { Direction, FocusedPosition } from \"./use-focused-list-item.js\";\nimport { HOTKEY_TOGGLE_LABELS, KEY_SPACE, isHotkey } from \"../wizard/hotkeys.js\";\n\n/** Find next section index (wrapping forward) */\nconst findNextIndex = (items: { length: number }, currentIndex: number): number => {\n const length = items.length;\n if (length === 0) return currentIndex;\n return (currentIndex + 1) % length;\n};\n\ntype UseCategoryGridInputOptions = {\n categories: CategoryRow[];\n focusedRow: number;\n focusedCol: number;\n currentFocus: () => FocusedPosition;\n setFocused: (row: number, col: number) => void;\n moveFocus: (direction: Direction) => void;\n onToggle: (categoryId: Category, technologyId: SkillId) => void;\n onToggleLabels: () => void;\n};\n\nexport function useCategoryGridInput({\n categories,\n focusedRow,\n focusedCol,\n currentFocus,\n setFocused,\n moveFocus,\n onToggle,\n onToggleLabels,\n}: UseCategoryGridInputOptions): void {\n const currentRow = categories[focusedRow];\n // Memoised so the effect below keys on the row actually changing — the `[]`\n // fallback used to be a fresh array every render, re-running the effect for\n // nothing whenever no row was focused.\n const currentOptions = useMemo(() => currentRow?.options ?? [], [currentRow]);\n\n // Adjust column when current row's options change externally (e.g. option becomes disabled)\n useEffect(() => {\n if (!currentRow) return;\n\n const maxCol = currentOptions.length - 1;\n if (focusedCol > maxCol) {\n const newCol = Math.max(0, maxCol);\n setFocused(focusedRow, newCol);\n }\n }, [focusedRow, currentOptions, focusedCol, setFocused, currentRow]);\n\n // Store the latest handler in a ref so that the useInput effect never needs to\n // re-register on the event emitter. This avoids a stale-closure race condition\n // where, after a domain switch (CategoryGrid remount via key={activeDomain}),\n // the useInput effect may not yet have re-registered the updated handler when\n // the first keypress arrives — causing the first space press to be silently lost.\n const handlerRef = useRef<((input: string, key: Key) => void) | null>(null);\n handlerRef.current = (input: string, key: Key) => {\n if (key.tab && key.shift) {\n onToggleLabels();\n return;\n }\n\n // Tab and Space read the focus as it stands now rather than as the last render painted it:\n // a key that follows a move in the same chunk is handled before that move renders.\n if (key.tab && !key.shift) {\n const { row } = currentFocus();\n const nextSection = findNextIndex(categories, row);\n if (nextSection !== row) {\n setFocused(nextSection, 0);\n }\n return;\n }\n\n if (isHotkey(input, HOTKEY_TOGGLE_LABELS)) {\n onToggleLabels();\n return;\n }\n\n if (input === KEY_SPACE) {\n const { row, col } = currentFocus();\n const category = categories[row];\n const option = category?.options[col];\n if (category && option) {\n onToggle(category.id, option.id);\n }\n return;\n }\n\n const isLeft = key.leftArrow || input === \"h\";\n const isRight = key.rightArrow || input === \"l\";\n const isUp = key.upArrow || input === \"k\";\n const isDown = key.downArrow || input === \"j\";\n\n if (isLeft) {\n moveFocus(\"left\");\n } else if (isRight) {\n moveFocus(\"right\");\n } else if (isUp) {\n moveFocus(\"up\");\n } else if (isDown) {\n moveFocus(\"down\");\n }\n };\n\n // Stable handler reference — never changes, so useInput's effect registers once\n const stableHandler = useCallback((input: string, key: Key) => {\n handlerRef.current?.(input, key);\n }, []);\n\n useInput(stableHandler);\n}\n"],"mappings":"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","names":["useCallback","useEffect","useCallback","useEffect"]}
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- CLI_COLORS
4
- } from "./chunk-ENGYJCTF.js";
5
-
6
- // src/cli/components/wizard/toast.tsx
7
- import { Box, Text } from "ink";
8
- import "react";
9
- import { jsx, jsxs } from "react/jsx-runtime";
10
- var TITLE_HORIZONTAL_PADDING = 6;
11
- var Toast = ({ children }) => {
12
- const padding = " ".repeat(children.length + TITLE_HORIZONTAL_PADDING);
13
- const paddingHalf = " ".repeat(TITLE_HORIZONTAL_PADDING / 2);
14
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", position: "absolute", children: [
15
- /* @__PURE__ */ jsx(Text, { backgroundColor: CLI_COLORS.TOAST_BG, children: padding }),
16
- /* @__PURE__ */ jsxs(Text, { bold: true, backgroundColor: CLI_COLORS.TOAST_BG, color: CLI_COLORS.TOAST_FG, children: [
17
- paddingHalf,
18
- children,
19
- paddingHalf
20
- ] }),
21
- /* @__PURE__ */ jsx(Text, { backgroundColor: CLI_COLORS.TOAST_BG, children: padding })
22
- ] });
23
- };
24
-
25
- export {
26
- Toast
27
- };
28
- //# sourceMappingURL=chunk-T6UWJAB3.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/toast.tsx"],"sourcesContent":["import { Box, Text } from \"ink\";\nimport React from \"react\";\nimport { CLI_COLORS } from \"../../consts.js\";\n\nconst TITLE_HORIZONTAL_PADDING = 6;\n\ntype ToastProps = {\n children: string;\n};\n\nexport const Toast: React.FC<ToastProps> = ({ children }) => {\n const padding = \" \".repeat(children.length + TITLE_HORIZONTAL_PADDING);\n const paddingHalf = \" \".repeat(TITLE_HORIZONTAL_PADDING / 2);\n\n return (\n <Box flexDirection=\"column\" position=\"absolute\">\n <Text backgroundColor={CLI_COLORS.TOAST_BG}>{padding}</Text>\n <Text bold backgroundColor={CLI_COLORS.TOAST_BG} color={CLI_COLORS.TOAST_FG}>\n {paddingHalf}\n {children}\n {paddingHalf}\n </Text>\n <Text backgroundColor={CLI_COLORS.TOAST_BG}>{padding}</Text>\n </Box>\n );\n};\n"],"mappings":";;;;;;AAAA,SAAS,KAAK,YAAY;AAC1B,OAAkB;AAeZ,cACA,YADA;AAZN,IAAM,2BAA2B;AAM1B,IAAM,QAA8B,CAAC,EAAE,SAAS,MAAM;AAC3D,QAAM,UAAU,IAAI,OAAO,SAAS,SAAS,wBAAwB;AACrE,QAAM,cAAc,IAAI,OAAO,2BAA2B,CAAC;AAE3D,SACE,qBAAC,OAAI,eAAc,UAAS,UAAS,YACnC;AAAA,wBAAC,QAAK,iBAAiB,WAAW,UAAW,mBAAQ;AAAA,IACrD,qBAAC,QAAK,MAAI,MAAC,iBAAiB,WAAW,UAAU,OAAO,WAAW,UAChE;AAAA;AAAA,MACA;AAAA,MACA;AAAA,OACH;AAAA,IACA,oBAAC,QAAK,iBAAiB,WAAW,UAAW,mBAAQ;AAAA,KACvD;AAEJ;","names":[]}
@@ -1,43 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/cli/components/hooks/use-measured-height.ts
4
- import { useRef, useState, useEffect } from "react";
5
- import { measureElement, useStdout } from "ink";
6
- function useMeasuredHeight() {
7
- const ref = useRef(null);
8
- const [measuredHeight, setMeasuredHeight] = useState(0);
9
- const { stdout } = useStdout();
10
- const measure = () => {
11
- if (ref.current) {
12
- const { height } = measureElement(ref.current);
13
- setMeasuredHeight((prev) => prev !== height ? height : prev);
14
- }
15
- };
16
- useEffect(() => {
17
- measure();
18
- });
19
- useEffect(() => {
20
- const RETRY_DELAYS_MS = [0, 16, 50];
21
- const timers = [];
22
- for (const delay of RETRY_DELAYS_MS) {
23
- timers.push(setTimeout(measure, delay));
24
- }
25
- return () => {
26
- for (const timer of timers) {
27
- clearTimeout(timer);
28
- }
29
- };
30
- }, []);
31
- useEffect(() => {
32
- stdout.on("resize", measure);
33
- return () => {
34
- stdout.off("resize", measure);
35
- };
36
- }, [stdout]);
37
- return { ref, measuredHeight };
38
- }
39
-
40
- export {
41
- useMeasuredHeight
42
- };
43
- //# sourceMappingURL=chunk-TSIU7DME.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/hooks/use-measured-height.ts"],"sourcesContent":["import { useRef, useState, useEffect } from \"react\";\nimport { type DOMElement, measureElement, useStdout } from \"ink\";\n\n/**\n * Measures the computed height of a Box element using Ink's Yoga layout engine.\n *\n * Returns a ref to attach to a Box with `flexGrow={1}` and the measured height.\n * The Box must be inside a parent chain with a constrained height (e.g., an\n * explicit `height` prop on an ancestor) so Yoga can compute the remaining space.\n *\n * Returns 0 before the first layout pass. Re-measures on terminal resize.\n */\nexport function useMeasuredHeight(): {\n ref: React.Ref<DOMElement>;\n measuredHeight: number;\n} {\n const ref = useRef<DOMElement>(null);\n const [measuredHeight, setMeasuredHeight] = useState(0);\n const { stdout } = useStdout();\n\n const measure = () => {\n if (ref.current) {\n const { height } = measureElement(ref.current);\n setMeasuredHeight((prev) => (prev !== height ? height : prev));\n }\n };\n\n // Re-measure after every render so late-mounted refs (e.g., Sources\n // step switching from \"choice\" to \"customize\" view) get picked up.\n // The state setter avoids unnecessary updates when height is unchanged.\n useEffect(() => {\n measure();\n });\n\n // Yoga may not have computed the layout on the first render, so\n // measureElement() returns 0. Retry with increasing delays to give\n // Yoga time to resolve the full flex constraint chain.\n useEffect(() => {\n const RETRY_DELAYS_MS = [0, 16, 50];\n const timers: ReturnType<typeof setTimeout>[] = [];\n\n for (const delay of RETRY_DELAYS_MS) {\n timers.push(setTimeout(measure, delay));\n }\n\n return () => {\n for (const timer of timers) {\n clearTimeout(timer);\n }\n };\n // Deps are deliberately empty: this is a mount-only retry ladder. `measure`\n // is re-created every render, so listing it would re-arm the timers on every\n // render instead of once.\n }, []);\n\n useEffect(() => {\n stdout.on(\"resize\", measure);\n return () => {\n stdout.off(\"resize\", measure);\n };\n }, [stdout]);\n\n return { ref, measuredHeight };\n}\n"],"mappings":";;;AAAA,SAAS,QAAQ,UAAU,iBAAiB;AAC5C,SAA0B,gBAAgB,iBAAiB;AAWpD,SAAS,oBAGd;AACA,QAAM,MAAM,OAAmB,IAAI;AACnC,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAS,CAAC;AACtD,QAAM,EAAE,OAAO,IAAI,UAAU;AAE7B,QAAM,UAAU,MAAM;AACpB,QAAI,IAAI,SAAS;AACf,YAAM,EAAE,OAAO,IAAI,eAAe,IAAI,OAAO;AAC7C,wBAAkB,CAAC,SAAU,SAAS,SAAS,SAAS,IAAK;AAAA,IAC/D;AAAA,EACF;AAKA,YAAU,MAAM;AACd,YAAQ;AAAA,EACV,CAAC;AAKD,YAAU,MAAM;AACd,UAAM,kBAAkB,CAAC,GAAG,IAAI,EAAE;AAClC,UAAM,SAA0C,CAAC;AAEjD,eAAW,SAAS,iBAAiB;AACnC,aAAO,KAAK,WAAW,SAAS,KAAK,CAAC;AAAA,IACxC;AAEA,WAAO,MAAM;AACX,iBAAW,SAAS,QAAQ;AAC1B,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EAIF,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,WAAO,GAAG,UAAU,OAAO;AAC3B,WAAO,MAAM;AACX,aAAO,IAAI,UAAU,OAAO;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,SAAO,EAAE,KAAK,eAAe;AAC/B;","names":[]}
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- computeRowScrollTop
4
- } from "./chunk-WVUDLKIO.js";
5
- import {
6
- SCROLL_VIEWPORT
7
- } from "./chunk-ENGYJCTF.js";
8
-
9
- // src/cli/components/hooks/use-row-scroll.ts
10
- import { useRef } from "react";
11
- function useRowScroll({
12
- focusedIndex,
13
- itemCount,
14
- availableHeight
15
- }) {
16
- const scrollTopRef = useRef(0);
17
- const scrollEnabled = availableHeight > 0 && availableHeight >= SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS && itemCount > availableHeight;
18
- if (scrollEnabled) {
19
- scrollTopRef.current = computeRowScrollTop(focusedIndex, scrollTopRef.current, availableHeight);
20
- }
21
- return { scrollEnabled, scrollTop: scrollTopRef.current };
22
- }
23
-
24
- export {
25
- useRowScroll
26
- };
27
- //# sourceMappingURL=chunk-TTY6DPJN.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/hooks/use-row-scroll.ts"],"sourcesContent":["import { useRef } from \"react\";\nimport { SCROLL_VIEWPORT } from \"../../consts.js\";\nimport { computeRowScrollTop } from \"./use-section-scroll.js\";\n\nexport type UseRowScrollOptions = {\n focusedIndex: number;\n itemCount: number;\n availableHeight: number;\n};\n\nexport type UseRowScrollResult = {\n scrollEnabled: boolean;\n scrollTop: number;\n};\n\n/**\n * Row-based scroll hook for views with uniform 1-line rows.\n *\n * Manages a scroll offset (in rows) to keep the focused row visible within\n * a constrained viewport. Extracted from the identical scroll plumbing in\n * checkbox-grid.tsx, step-agents.tsx, and stack-selection.tsx.\n *\n * Deliberately exposes NO hidden-line counts — unlike `useSectionScroll`, which\n * computes them for the `ScrollAffordance` the Sources grid renders. Every\n * consumer here (Domains, Agents, Stack) clips silently by owner decision: a\n * half-cut row is self-evident, and a counted hint would cost the viewport a row\n * to say the same thing. Adding the counts is a product change, not a gap.\n */\nexport function useRowScroll({\n focusedIndex,\n itemCount,\n availableHeight,\n}: UseRowScrollOptions): UseRowScrollResult {\n const scrollTopRef = useRef(0);\n\n const scrollEnabled =\n availableHeight > 0 &&\n availableHeight >= SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS &&\n itemCount > availableHeight;\n\n if (scrollEnabled) {\n scrollTopRef.current = computeRowScrollTop(focusedIndex, scrollTopRef.current, availableHeight);\n }\n\n return { scrollEnabled, scrollTop: scrollTopRef.current };\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,cAAc;AA4BhB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAA4C;AAC1C,QAAM,eAAe,OAAO,CAAC;AAE7B,QAAM,gBACJ,kBAAkB,KAClB,mBAAmB,gBAAgB,qBACnC,YAAY;AAEd,MAAI,eAAe;AACjB,iBAAa,UAAU,oBAAoB,cAAc,aAAa,SAAS,eAAe;AAAA,EAChG;AAEA,SAAO,EAAE,eAAe,WAAW,aAAa,QAAQ;AAC1D;","names":[]}
@@ -1,292 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- ScrollAffordance
4
- } from "./chunk-SAYZWCH6.js";
5
- import {
6
- useFocusedListItem
7
- } from "./chunk-XQHZPINL.js";
8
- import {
9
- KEY_SPACE
10
- } from "./chunk-WTJ3AY33.js";
11
- import {
12
- useSectionScroll
13
- } from "./chunk-WVUDLKIO.js";
14
- import {
15
- getSkillById
16
- } from "./chunk-V5WXIM2F.js";
17
- import {
18
- CLI_COLORS,
19
- INSTALL_MODE_CELL_LABELS,
20
- UI_SYMBOLS
21
- } from "./chunk-ENGYJCTF.js";
22
-
23
- // src/cli/components/wizard/source-grid.tsx
24
- import { useCallback, useMemo } from "react";
25
- import { Box, Text, useInput } from "ink";
26
- import { jsx, jsxs } from "react/jsx-runtime";
27
- var SKILL_NAME_WIDTH = 26;
28
- var INSTALL_MODE_COL_WIDTH = 18;
29
- var SCOPE_COL_WIDTH = 11;
30
- var SCOPE_ROW_HEADERS = {
31
- global: "Global",
32
- project: "Project"
33
- };
34
- var ROW_MARKER_BLANK = " ";
35
- var SOURCE_GRID_MIN_VIEWPORT_ROWS = 1;
36
- function isRowInert(row) {
37
- return !!row.readOnly || !!row.disabled;
38
- }
39
- function rowStatusMarker(row) {
40
- if (row.readOnly) return `${UI_SYMBOLS.LOCK} `;
41
- if (row.disabled) return `${UI_SYMBOLS.REMOVED} `;
42
- if (row.added) return `${UI_SYMBOLS.ADDED} `;
43
- return ROW_MARKER_BLANK;
44
- }
45
- function rowDiffColor(row) {
46
- if (row.disabled) return CLI_COLORS.ERROR;
47
- if (row.added) return CLI_COLORS.SUCCESS;
48
- return void 0;
49
- }
50
- function rowLabelColor(row) {
51
- return rowDiffColor(row) ?? CLI_COLORS.NEUTRAL;
52
- }
53
- function focusedRowLabelColor(row) {
54
- return rowDiffColor(row) ?? CLI_COLORS.WHITE;
55
- }
56
- var SourceTag = ({ option, isFocused, readOnly, disabled }) => {
57
- if (readOnly || disabled) {
58
- return /* @__PURE__ */ jsx(Box, { width: INSTALL_MODE_COL_WIDTH, children: /* @__PURE__ */ jsxs(
59
- Text,
60
- {
61
- ...disabled && { color: CLI_COLORS.ERROR },
62
- bold: option.selected,
63
- dimColor: !disabled && !option.selected,
64
- children: [
65
- `${UI_SYMBOLS.CHEVRON_SPACER} `,
66
- INSTALL_MODE_CELL_LABELS[option.mode]
67
- ]
68
- }
69
- ) });
70
- }
71
- const textColor = option.selected ? CLI_COLORS.PRIMARY : CLI_COLORS.WHITE;
72
- const isBold = isFocused || option.selected;
73
- const prefix = isFocused ? `${UI_SYMBOLS.CHEVRON} ` : `${UI_SYMBOLS.CHEVRON_SPACER} `;
74
- return /* @__PURE__ */ jsx(Box, { width: INSTALL_MODE_COL_WIDTH, children: /* @__PURE__ */ jsxs(Text, { color: textColor, bold: isBold, dimColor: !option.selected && !isFocused, children: [
75
- prefix,
76
- INSTALL_MODE_CELL_LABELS[option.mode]
77
- ] }) });
78
- };
79
- var SourceSection = ({ row, isFocused, focusedOptionIndex }) => {
80
- const isInert = isRowInert(row);
81
- const effectiveFocused = isInert ? false : isFocused;
82
- const statusMarker = rowStatusMarker(row);
83
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
84
- /* @__PURE__ */ jsx(Box, { width: SKILL_NAME_WIDTH, children: effectiveFocused ? (
85
- // The marker sits INSIDE the highlight, and the marker's own separating space is the only
86
- // one before the name — so the band is the same width on every focused row and the name
87
- // does not shift a column when focus arrives.
88
- /* @__PURE__ */ jsxs(Text, { color: focusedRowLabelColor(row), backgroundColor: CLI_COLORS.LABEL_BG, children: [
89
- statusMarker,
90
- `${getSkillById(row.skillId).displayName} `
91
- ] })
92
- ) : /* @__PURE__ */ jsxs(Text, { color: rowLabelColor(row), dimColor: !!row.readOnly, children: [
93
- statusMarker,
94
- getSkillById(row.skillId).displayName
95
- ] }) }),
96
- /* @__PURE__ */ jsx(Box, { flexDirection: "row", flexWrap: "wrap", children: row.options.map((option, index) => /* @__PURE__ */ jsx(
97
- SourceTag,
98
- {
99
- option,
100
- isFocused: effectiveFocused && index === focusedOptionIndex,
101
- readOnly: row.readOnly,
102
- disabled: row.disabled
103
- },
104
- option.mode
105
- )) })
106
- ] });
107
- };
108
- var ScopeRowHeader = ({
109
- scope,
110
- isGroupStart
111
- }) => /* @__PURE__ */ jsx(Box, { width: SCOPE_COL_WIDTH, children: isGroupStart && /* @__PURE__ */ jsx(Text, { color: CLI_COLORS.WARNING, bold: true, children: SCOPE_ROW_HEADERS[scope] }) });
112
- function firstFocusableRowIndex(rows, startRow) {
113
- const cyclicRowOrder = rows.map((_, offset) => (startRow + offset) % rows.length);
114
- return cyclicRowOrder.find((row) => {
115
- const candidate = rows[row];
116
- return candidate && !isRowInert(candidate);
117
- }) ?? startRow;
118
- }
119
- function lastFocusableRowIndex(rows) {
120
- for (let i = rows.length - 1; i >= 0; i--) {
121
- const candidate = rows[i];
122
- if (candidate && !isRowInert(candidate)) return i;
123
- }
124
- return -1;
125
- }
126
- function groupRowsByScope(rows) {
127
- const indexed = rows.map((row, i) => ({ row, originalIndex: i }));
128
- const globalRows = indexed.filter(({ row }) => row.scope === "global");
129
- const projectRows = indexed.filter(({ row }) => row.scope !== "global");
130
- if (globalRows.length === 0 || projectRows.length === 0) return [];
131
- return [
132
- { scope: "global", rows: globalRows },
133
- { scope: "project", rows: projectRows }
134
- ];
135
- }
136
- var SourceGrid = ({
137
- rows,
138
- availableHeight = 0,
139
- onSelect,
140
- defaultFocusedRow = 0,
141
- defaultFocusedCol = 0,
142
- onFocusChange
143
- }) => {
144
- const getColCount = useCallback((row) => rows[row]?.options.length ?? 0, [rows]);
145
- const skipRow = useCallback(
146
- (row) => {
147
- const candidate = rows[row];
148
- return !!candidate && isRowInert(candidate);
149
- },
150
- [rows]
151
- );
152
- const effectiveDefaultRow = firstFocusableRowIndex(rows, defaultFocusedRow);
153
- const { focusedRow, focusedCol, currentFocus, moveFocus } = useFocusedListItem(
154
- rows.length,
155
- getColCount,
156
- {
157
- wrap: true,
158
- ...onFocusChange !== void 0 && { onChange: onFocusChange },
159
- initialRow: effectiveDefaultRow,
160
- initialCol: defaultFocusedCol,
161
- skipRow
162
- }
163
- );
164
- const {
165
- setSectionRef,
166
- setViewportRef,
167
- setContentRef,
168
- scrollEnabled,
169
- scrollTopPx,
170
- hiddenAbove,
171
- hiddenBelow,
172
- scrollBy
173
- } = useSectionScroll({
174
- sectionCount: rows.length,
175
- focusedIndex: focusedRow,
176
- availableHeight,
177
- minViewportRows: SOURCE_GRID_MIN_VIEWPORT_ROWS
178
- });
179
- const lastFocusableRow = useMemo(() => lastFocusableRowIndex(rows), [rows]);
180
- useInput(
181
- useCallback(
182
- (input, key) => {
183
- const focused = currentFocus();
184
- if (input === KEY_SPACE) {
185
- const currentRow = rows[focused.row];
186
- if (!currentRow || isRowInert(currentRow)) return;
187
- const currentOption = currentRow.options[focused.col];
188
- if (currentOption) {
189
- onSelect(currentRow.skillId, currentOption.mode);
190
- }
191
- return;
192
- }
193
- const noFocusableRow = lastFocusableRow === -1;
194
- const atLastFocusableRow = focused.row === lastFocusableRow;
195
- if (key.leftArrow) {
196
- moveFocus("left");
197
- } else if (key.rightArrow) {
198
- moveFocus("right");
199
- } else if (key.upArrow) {
200
- if (scrollEnabled && noFocusableRow && hiddenAbove > 0) {
201
- scrollBy(-1);
202
- } else {
203
- moveFocus("up");
204
- }
205
- } else if (key.downArrow) {
206
- if (scrollEnabled && (noFocusableRow || atLastFocusableRow) && hiddenBelow > 0) {
207
- scrollBy(1);
208
- } else {
209
- moveFocus("down");
210
- }
211
- }
212
- },
213
- [
214
- rows,
215
- currentFocus,
216
- onSelect,
217
- moveFocus,
218
- scrollEnabled,
219
- lastFocusableRow,
220
- hiddenAbove,
221
- hiddenBelow,
222
- scrollBy
223
- ]
224
- )
225
- );
226
- if (rows.length === 0) {
227
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No skills to display." }) });
228
- }
229
- const noShrink = scrollEnabled ? { flexShrink: 0 } : {};
230
- const scopeGroups = groupRowsByScope(rows);
231
- const sectionElements = scopeGroups.length > 0 ? scopeGroups.map((group) => /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, ...noShrink, children: group.rows.map(({ row, originalIndex }, rowIndexInGroup) => /* @__PURE__ */ jsxs(
232
- Box,
233
- {
234
- flexDirection: "row",
235
- ref: (el) => setSectionRef(originalIndex, el),
236
- ...noShrink,
237
- children: [
238
- /* @__PURE__ */ jsx(ScopeRowHeader, { scope: group.scope, isGroupStart: rowIndexInGroup === 0 }),
239
- /* @__PURE__ */ jsx(
240
- SourceSection,
241
- {
242
- row,
243
- isFocused: originalIndex === focusedRow,
244
- focusedOptionIndex: focusedCol
245
- }
246
- )
247
- ]
248
- },
249
- `${row.skillId}-${row.scope ?? "default"}`
250
- )) }, group.scope)) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx(
251
- Box,
252
- {
253
- ref: (el) => setSectionRef(rowIndex, el),
254
- ...noShrink,
255
- children: /* @__PURE__ */ jsx(
256
- SourceSection,
257
- {
258
- row,
259
- isFocused: rowIndex === focusedRow,
260
- focusedOptionIndex: focusedCol
261
- }
262
- )
263
- },
264
- `${row.skillId}-${row.scope ?? "default"}`
265
- ));
266
- return /* @__PURE__ */ jsxs(
267
- Box,
268
- {
269
- flexDirection: "column",
270
- ...scrollEnabled ? { height: availableHeight, overflow: "hidden" } : { flexGrow: 1 },
271
- children: [
272
- /* @__PURE__ */ jsx(Box, { ref: setViewportRef, flexDirection: "column", overflow: "hidden", flexGrow: 1, children: /* @__PURE__ */ jsx(
273
- Box,
274
- {
275
- ref: setContentRef,
276
- flexDirection: "column",
277
- marginTop: scrollTopPx > 0 ? -scrollTopPx : 0,
278
- ...noShrink,
279
- children: sectionElements
280
- }
281
- ) }),
282
- /* @__PURE__ */ jsx(ScrollAffordance, { hiddenAbove, hiddenBelow })
283
- ]
284
- }
285
- );
286
- };
287
-
288
- export {
289
- isRowInert,
290
- SourceGrid
291
- };
292
- //# sourceMappingURL=chunk-TZ2YJJKK.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/components/wizard/source-grid.tsx"],"sourcesContent":["import React, { useCallback, useMemo } from \"react\";\nimport { Box, Text, useInput, type Key } from \"ink\";\nimport type { InstallMode, SkillId, SkillScope } from \"../../types/index.js\";\nimport { CLI_COLORS, INSTALL_MODE_CELL_LABELS, UI_SYMBOLS } from \"../../consts.js\";\nimport { getSkillById } from \"../../lib/matrix/matrix-provider.js\";\nimport { useFocusedListItem } from \"../hooks/use-focused-list-item.js\";\nimport { useSectionScroll } from \"../hooks/use-section-scroll.js\";\nimport { KEY_SPACE } from \"./hotkeys.js\";\nimport { ScrollAffordance } from \"./scroll-affordance.js\";\n\n/**\n * Skill-name cell: the 24 columns the name itself gets, plus the fixed 2-column marker cell that\n * shares the box (see `rowStatusMarker`). Widening by exactly the marker width keeps every name\n * that fitted before the marker was reserved fitting still.\n */\nconst SKILL_NAME_WIDTH = 26;\nconst INSTALL_MODE_COL_WIDTH = 18;\n\n/**\n * Scope gutter: the column of `Global` / `Project` row headers down the left of a scope-grouped\n * grid. Present only in the grouped layout — a single-scope grid has nothing to head, and renders\n * flat with no gutter at all. The header row reserves the width but prints no caption in it: the\n * labels below already name the column, so a caption over them would only repeat them.\n */\nconst SCOPE_COL_WIDTH = 11;\n\n/** Row-header label each scope block is headed by. */\nconst SCOPE_ROW_HEADERS: Record<SkillScope, string> = {\n global: \"Global\",\n project: \"Project\",\n};\n\n/** Blank marker cell — the same two columns a `${glyph} ` marker costs, so names never shift. */\nconst ROW_MARKER_BLANK = \" \";\n\n/**\n * Sources always clips-and-signals rather than bleeding, even at the very short viewports the wizard\n * still renders at (the Sources step is squeezed by the tab bar, dropdown card and footer): a single\n * clipped row plus the overflow affordance beats a bled grid. Distinct from the grid views that fall\n * back to a flex layout below SCROLL_VIEWPORT.MIN_VIEWPORT_ROWS.\n */\nconst SOURCE_GRID_MIN_VIEWPORT_ROWS = 1;\n\n/**\n * One cell of a row's install-mode control. There is no `id`: the two cells ARE the choice, and\n * the `SkillConfig.origin` each one writes is the store's to resolve — which is what stops this\n * surface from writing an origin value nothing installs from.\n */\nexport type SourceOption = {\n mode: Exclude<InstallMode, \"mixed\">;\n selected: boolean;\n};\n\nexport type SourceRow = {\n skillId: SkillId;\n /** The row's two install-mode cells, in render order. */\n options: SourceOption[];\n scope?: SkillScope;\n readOnly?: boolean;\n /**\n * Saved skill deselected this session — still shown so the user can see what saving removes,\n * but inert. Distinct from `readOnly`, which means \"locked because it is installed globally\".\n */\n disabled?: boolean;\n /**\n * Skill selected this session but absent from the hydration snapshot. Renders inline in\n * its normal position with the info panel's added marker (`+`) in green, and stays a normal\n * selectable/editable row — unlike `disabled`/`readOnly`, an added row is NOT inert.\n */\n added?: boolean;\n};\n\n/** Rows the user cannot act on: globally locked (`readOnly`) or pending removal (`disabled`). */\nexport function isRowInert(row: SourceRow): boolean {\n return !!row.readOnly || !!row.disabled;\n}\n\n/**\n * Status marker before the skill name. Lock = \"installed globally, not yours to change here\".\n * The removal marker + red colour and the added marker + green colour mirror the info panel's\n * removed/added-diff treatment (DIFF_PREFIX/DIFF_COLOR in skill-agent-summary.tsx) so a\n * pending-removal or added row reads the same on both surfaces. The marker (not the colour) is\n * what carries the meaning in no-color terminals.\n *\n * ALWAYS two columns wide — glyph plus one separating space, or `ROW_MARKER_BLANK` on a row with no\n * status — so every skill name starts in the same column whether or not its row is marked. Same\n * contract as DIFF_PREFIX, which gives even `unchanged` a 2-char prefix. `UI_SYMBOLS.LOCK` is\n * double-width and still renders a locked row one column wider; known and accepted.\n */\nfunction rowStatusMarker(row: SourceRow): string {\n if (row.readOnly) return `${UI_SYMBOLS.LOCK} `;\n if (row.disabled) return `${UI_SYMBOLS.REMOVED} `;\n if (row.added) return `${UI_SYMBOLS.ADDED} `;\n return ROW_MARKER_BLANK;\n}\n\n/**\n * Diff colour for a row's skill name, matching the info panel's palette (DIFF_COLOR in\n * skill-agent-summary.tsx): red = pending removal, green = added. `undefined` means the row carries\n * no diff status, so each caller supplies its own default.\n */\nfunction rowDiffColor(row: SourceRow): string | undefined {\n if (row.disabled) return CLI_COLORS.ERROR;\n if (row.added) return CLI_COLORS.SUCCESS;\n return undefined;\n}\n\n/** Skill-name colour for an unfocused row: its diff colour, or the neutral default. */\nfunction rowLabelColor(row: SourceRow): string {\n return rowDiffColor(row) ?? CLI_COLORS.NEUTRAL;\n}\n\n/**\n * Skill-name colour for the FOCUSED row, which renders on the dark LABEL_BG highlight. A diff row\n * keeps its diff colour — focus is not a reason to lose the added/removed signal. An ordinary row\n * deliberately stays plain WHITE rather than falling through to `rowLabelColor`: NEUTRAL grey on\n * LABEL_BG is the low-contrast pairing, so reusing it here would dim every ordinary focused row to\n * fix rows that have no diff to show in the first place.\n */\nfunction focusedRowLabelColor(row: SourceRow): string {\n return rowDiffColor(row) ?? CLI_COLORS.WHITE;\n}\n\nexport type SourceGridProps = {\n rows: SourceRow[];\n /** Available height in terminal lines for the scrollable viewport. 0 = no constraint. */\n availableHeight?: number;\n onSelect: (skillId: SkillId, mode: Exclude<InstallMode, \"mixed\">) => void;\n /** Optional initial focus row (default: 0). Use with React `key` to reset. */\n defaultFocusedRow?: number;\n /** Optional initial focus col (default: 0). Use with React `key` to reset. */\n defaultFocusedCol?: number;\n /** Optional callback fired whenever the focused position changes */\n onFocusChange?: (row: number, col: number) => void;\n};\n\ntype SourceSectionProps = {\n row: SourceRow;\n isFocused: boolean;\n focusedOptionIndex: number;\n};\n\nconst SourceTag: React.FC<{\n option: SourceOption;\n isFocused: boolean;\n readOnly?: boolean | undefined;\n disabled?: boolean | undefined;\n}> = ({ option, isFocused, readOnly, disabled }) => {\n if (readOnly || disabled) {\n // Inert rows carry selection in the same vocabulary editable ones do — weight, plus brightness\n // on the locked row whose cells are otherwise all dimmed. Never a glyph: a check appears nowhere\n // else in the grid, and on a pending-removal row it would tick the source the row is losing. The\n // prefix slot stays a blank spacer so the source labels line up with every other row's.\n return (\n <Box width={INSTALL_MODE_COL_WIDTH}>\n <Text\n {...(disabled && { color: CLI_COLORS.ERROR })}\n bold={option.selected}\n dimColor={!disabled && !option.selected}\n >\n {`${UI_SYMBOLS.CHEVRON_SPACER} `}\n {INSTALL_MODE_CELL_LABELS[option.mode]}\n </Text>\n </Box>\n );\n }\n\n const textColor = option.selected ? CLI_COLORS.PRIMARY : CLI_COLORS.WHITE;\n const isBold = isFocused || option.selected;\n const prefix = isFocused ? `${UI_SYMBOLS.CHEVRON} ` : `${UI_SYMBOLS.CHEVRON_SPACER} `;\n\n return (\n <Box width={INSTALL_MODE_COL_WIDTH}>\n <Text color={textColor} bold={isBold} dimColor={!option.selected && !isFocused}>\n {prefix}\n {INSTALL_MODE_CELL_LABELS[option.mode]}\n </Text>\n </Box>\n );\n};\n\nconst SourceSection: React.FC<SourceSectionProps> = ({ row, isFocused, focusedOptionIndex }) => {\n const isInert = isRowInert(row);\n const effectiveFocused = isInert ? false : isFocused;\n // Unlike removal, an added row stays a normal editable row — only its marker/colour differ.\n const statusMarker = rowStatusMarker(row);\n\n return (\n <Box flexDirection=\"row\">\n <Box width={SKILL_NAME_WIDTH}>\n {effectiveFocused ? (\n // The marker sits INSIDE the highlight, and the marker's own separating space is the only\n // one before the name — so the band is the same width on every focused row and the name\n // does not shift a column when focus arrives.\n <Text color={focusedRowLabelColor(row)} backgroundColor={CLI_COLORS.LABEL_BG}>\n {statusMarker}\n {`${getSkillById(row.skillId).displayName} `}\n </Text>\n ) : (\n <Text color={rowLabelColor(row)} dimColor={!!row.readOnly}>\n {statusMarker}\n {getSkillById(row.skillId).displayName}\n </Text>\n )}\n </Box>\n\n <Box flexDirection=\"row\" flexWrap=\"wrap\">\n {row.options.map((option, index) => (\n <SourceTag\n key={option.mode}\n option={option}\n isFocused={effectiveFocused && index === focusedOptionIndex}\n readOnly={row.readOnly}\n disabled={row.disabled}\n />\n ))}\n </Box>\n </Box>\n );\n};\n\ntype ScopeGroup = {\n scope: SkillScope;\n rows: { row: SourceRow; originalIndex: number }[];\n};\n\n/**\n * One row's cell in the scope gutter. Only the first row of a block prints the block's label; every\n * other row reserves the same width empty, which is what indents the rest of the block under its\n * header.\n */\nconst ScopeRowHeader: React.FC<{ scope: SkillScope; isGroupStart: boolean }> = ({\n scope,\n isGroupStart,\n}) => (\n <Box width={SCOPE_COL_WIDTH}>\n {isGroupStart && (\n <Text color={CLI_COLORS.WARNING} bold>\n {SCOPE_ROW_HEADERS[scope]}\n </Text>\n )}\n </Box>\n);\n\n/** First focusable row index at or cyclically after `startRow`; `startRow` when every row is inert. */\nfunction firstFocusableRowIndex(rows: SourceRow[], startRow: number): number {\n const cyclicRowOrder = rows.map((_, offset) => (startRow + offset) % rows.length);\n return (\n cyclicRowOrder.find((row) => {\n const candidate = rows[row];\n return candidate && !isRowInert(candidate);\n }) ?? startRow\n );\n}\n\n/** Index of the last focusable (non-inert) row, or -1 when every row is inert. */\nfunction lastFocusableRowIndex(rows: SourceRow[]): number {\n for (let i = rows.length - 1; i >= 0; i--) {\n const candidate = rows[i];\n if (candidate && !isRowInert(candidate)) return i;\n }\n return -1;\n}\n\n/**\n * Groups rows by scope so the two blocks render separated by a blank line, each headed by its scope\n * in the gutter. Returns an empty array when all rows share the same scope: one block needs no\n * header to tell it from another, so that grid renders flat with no gutter at all.\n */\nfunction groupRowsByScope(rows: SourceRow[]): ScopeGroup[] {\n const indexed = rows.map((row, i) => ({ row, originalIndex: i }));\n const globalRows = indexed.filter(({ row }) => row.scope === \"global\");\n const projectRows = indexed.filter(({ row }) => row.scope !== \"global\");\n\n if (globalRows.length === 0 || projectRows.length === 0) return [];\n\n return [\n { scope: \"global\", rows: globalRows },\n { scope: \"project\", rows: projectRows },\n ];\n}\n\nexport const SourceGrid: React.FC<SourceGridProps> = ({\n rows,\n availableHeight = 0,\n onSelect,\n defaultFocusedRow = 0,\n defaultFocusedCol = 0,\n onFocusChange,\n}) => {\n const getColCount = useCallback((row: number): number => rows[row]?.options.length ?? 0, [rows]);\n\n const skipRow = useCallback(\n (row: number): boolean => {\n const candidate = rows[row];\n return !!candidate && isRowInert(candidate);\n },\n [rows],\n );\n\n const effectiveDefaultRow = firstFocusableRowIndex(rows, defaultFocusedRow);\n\n const { focusedRow, focusedCol, currentFocus, moveFocus } = useFocusedListItem(\n rows.length,\n getColCount,\n {\n wrap: true,\n ...(onFocusChange !== undefined && { onChange: onFocusChange }),\n initialRow: effectiveDefaultRow,\n initialCol: defaultFocusedCol,\n skipRow,\n },\n );\n\n const {\n setSectionRef,\n setViewportRef,\n setContentRef,\n scrollEnabled,\n scrollTopPx,\n hiddenAbove,\n hiddenBelow,\n scrollBy,\n } = useSectionScroll({\n sectionCount: rows.length,\n focusedIndex: focusedRow,\n availableHeight,\n minViewportRows: SOURCE_GRID_MIN_VIEWPORT_ROWS,\n });\n\n const lastFocusableRow = useMemo(() => lastFocusableRowIndex(rows), [rows]);\n\n useInput(\n useCallback(\n (input: string, key: Key) => {\n // The focus as it stands now rather than as the last render painted it: a key that\n // follows a move in the same chunk is handled before that move renders.\n const focused = currentFocus();\n\n if (input === KEY_SPACE) {\n const currentRow = rows[focused.row];\n if (!currentRow || isRowInert(currentRow)) return;\n const currentOption = currentRow.options[focused.col];\n if (currentOption) {\n onSelect(currentRow.skillId, currentOption.mode);\n }\n return;\n }\n\n // With no focusable row (every row inert — a run of locked-global rows plus the\n // pending-removal row) there is no focus to move or to anchor overscroll on, so\n // vertical keys drive the viewport directly. Otherwise, overscroll only kicks in\n // once focus sits on the last focusable row (below it are only inert rows).\n const noFocusableRow = lastFocusableRow === -1;\n const atLastFocusableRow = focused.row === lastFocusableRow;\n\n if (key.leftArrow) {\n moveFocus(\"left\");\n } else if (key.rightArrow) {\n moveFocus(\"right\");\n } else if (key.upArrow) {\n if (scrollEnabled && noFocusableRow && hiddenAbove > 0) {\n scrollBy(-1);\n } else {\n moveFocus(\"up\");\n }\n } else if (key.downArrow) {\n // Keep scrolling the viewport to reveal trailing inert rows (e.g. the\n // pending-removal row) instead of wrapping. Focus never lands on them, so the\n // viewport travels past the last focusable row (or scrolls on its own when\n // nothing is focusable).\n if (scrollEnabled && (noFocusableRow || atLastFocusableRow) && hiddenBelow > 0) {\n scrollBy(1);\n } else {\n moveFocus(\"down\");\n }\n }\n },\n [\n rows,\n currentFocus,\n onSelect,\n moveFocus,\n scrollEnabled,\n lastFocusableRow,\n hiddenAbove,\n hiddenBelow,\n scrollBy,\n ],\n ),\n );\n\n if (rows.length === 0) {\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>No skills to display.</Text>\n </Box>\n );\n }\n\n const noShrink = scrollEnabled ? { flexShrink: 0 } : {};\n\n const scopeGroups = groupRowsByScope(rows);\n\n const sectionElements =\n scopeGroups.length > 0\n ? scopeGroups.map((group) => (\n <Box key={group.scope} flexDirection=\"column\" marginTop={1} {...noShrink}>\n {group.rows.map(({ row, originalIndex }, rowIndexInGroup) => (\n <Box\n key={`${row.skillId}-${row.scope ?? \"default\"}`}\n flexDirection=\"row\"\n ref={(el) => setSectionRef(originalIndex, el)}\n {...noShrink}\n >\n <ScopeRowHeader scope={group.scope} isGroupStart={rowIndexInGroup === 0} />\n <SourceSection\n row={row}\n isFocused={originalIndex === focusedRow}\n focusedOptionIndex={focusedCol}\n />\n </Box>\n ))}\n </Box>\n ))\n : rows.map((row, rowIndex) => (\n <Box\n key={`${row.skillId}-${row.scope ?? \"default\"}`}\n ref={(el) => setSectionRef(rowIndex, el)}\n {...noShrink}\n >\n <SourceSection\n row={row}\n isFocused={rowIndex === focusedRow}\n focusedOptionIndex={focusedCol}\n />\n </Box>\n ));\n\n return (\n <Box\n flexDirection=\"column\"\n {...(scrollEnabled\n ? { height: availableHeight, overflow: \"hidden\" as const }\n : { flexGrow: 1 })}\n >\n <Box ref={setViewportRef} flexDirection=\"column\" overflow=\"hidden\" flexGrow={1}>\n <Box\n ref={setContentRef}\n flexDirection=\"column\"\n marginTop={scrollTopPx > 0 ? -scrollTopPx : 0}\n {...noShrink}\n >\n {sectionElements}\n </Box>\n </Box>\n <ScrollAffordance hiddenAbove={hiddenAbove} hiddenBelow={hiddenBelow} />\n </Box>\n );\n};\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/lib/seed/seed-apply.ts","../src/cli/utils/open-url.ts"],"sourcesContent":["import os from \"os\";\n\nimport { isDeepEqual, omit, partition, pick } from \"remeda\";\n\nimport { loadProjectConfigFromDir } from \"../configuration/project-config.js\";\nimport { isActiveAt } from \"../configuration/scope-predicates.js\";\nimport { providerInUse } from \"../installation/install-layout.js\";\nimport { getCategoryDomain, getSkillDisplayName, matrix } from \"../matrix/matrix-provider.js\";\nimport { orderDomains } from \"../wizard/domain-order.js\";\nimport { SHARED_CONFIG_APPLY, SHARED_CONFIG_ARRIVALS } from \"../../utils/messages.js\";\nimport { typedEntries, typedKeys, typedValues } from \"../../utils/typed-object.js\";\n\nimport type { WizardResultV2 } from \"../../components/wizard/wizard.js\";\nimport type { AgentName } from \"../../types/agents.js\";\nimport type {\n AgentScopeConfig,\n ProjectConfig,\n SkillConfig,\n SkillScope,\n} from \"../../types/config.js\";\nimport type { Category, Domain } from \"../../types/matrix.js\";\nimport type { SkillAssignment, SkillId } from \"../../types/skills.js\";\nimport type { StackAgentConfig } from \"../../types/stacks.js\";\nimport type { SeedPayload, SeedSkill } from \"@workspace/matrix/seed\";\n\n/**\n * What applying a shared configuration here is not allowed to take away, split by the reason —\n * because the reasons have different remedies and only the user knows which they meant.\n */\nexport type KeptFromRoundTrip = {\n /** Written here rather than installed, so no shared configuration ever carried them. */\n authoredSkillIds: SkillId[];\n /** Named by the configuration and unplaceable by this catalogue, so nothing here applied. */\n unplaceableSkillIds: SkillId[];\n};\n\n/**\n * A kept skill's installed row that the payload's own skill displaced: the category holds one\n * skill, and the payload fills it on that sub-agent. The skill stays installed; the row goes.\n */\nexport type UnassignedRow = { skillId: SkillId; agentName: AgentName; category: Category };\n\nexport type ReconciledSharedConfig = {\n /** The decoded payload with everything above put back into it. */\n result: WizardResultV2;\n kept: KeptFromRoundTrip;\n /** The kept skills' rows left out, each because the payload's own skill takes its slot. */\n unassigned: UnassignedRow[];\n};\n\nexport type ReconcileOptions = {\n /** The payload as `seedToWizardResult` decoded it. */\n decoded: WizardResultV2;\n /** This directory's installation — its own config, or the global one it inherits. */\n installed: ProjectConfig | null;\n /** Ids outside the round trip, from `skillsAuthoredHere` — one definition, both halves. */\n authoredHere: ReadonlySet<SkillId>;\n /**\n * Ids the payload NAMED that the decode could not place — `SeedMapping.skippedSkillIds`,\n * which is why they are ids off the wire rather than `SkillId`s of this catalogue.\n *\n * Stated by the caller rather than recomputed here: the decode already answered it, and a\n * second derivation could disagree with the skips the same run reported.\n */\n unplaceable: ReadonlySet<string>;\n};\n\nconst NOTHING_KEPT: KeptFromRoundTrip = {\n authoredSkillIds: [],\n unplaceableSkillIds: [],\n};\n\n/**\n * Puts back what this run may not remove, so the destructive apply removes only what it may.\n *\n * `edit --from` makes the project MATCH the payload: a skill the previous configuration\n * installed and this one omits is removed. Two kinds of installed entry are outside that\n * authority, and each is outside it for its own reason.\n *\n * A skill written here, because `forkedFrom` decides ownership and the producer drops a\n * directory carrying none: the payload never named it, so reading its absence as an instruction\n * to delete it would be this command inventing an instruction nobody gave. And an id the payload\n * DID name that this catalogue could not place, because there the instruction exists and the run\n * failed to carry it out — a destructive command removes on intent, never on its own inability.\n *\n * Scope is the third reason, and it is answered apart, by {@link restoreInstalledGlobal}: from a\n * project the global install is not this run's to remove from or change at all, so it is put back\n * whole rather than entry by entry, and nothing about it is a disclosure of something kept.\n *\n * Both are put back into the RESULT rather than excused at the writer, and that is the whole\n * point of this module. `authoritativeScope` decides whether the merger preserves a config row;\n * it does not protect the files, because the removal DIFF is what drives the plugin uninstall\n * and the `deleteLocalSkill` call. An entry left in the removal set is deleted from disk\n * whatever the merger later does with its row.\n *\n * What comes back with an entry matters as much as the entry. A kept skill with no stack row is\n * installed and loaded by no sub-agent; a kept skill whose domain fell off `selectedDomains` is\n * hidden from the next wizard, deselected by not being shown, and deleted by the run after this\n * one — a removal this one promised not to make.\n */\nexport function reconcileSharedConfig(options: ReconcileOptions): ReconciledSharedConfig {\n const { decoded, installed } = options;\n if (!installed) return nothingToReconcile(decoded);\n\n const keptSkills = skillsToKeep(options, installed);\n if (keptSkills.length === 0) return nothingToReconcile(decoded);\n\n const { result, unassigned } = withKeptEntries(decoded, installed, keptSkills);\n return { result, kept: describeKept(options, keptSkills), unassigned };\n}\n\nfunction nothingToReconcile(decoded: WizardResultV2): ReconciledSharedConfig {\n return { result: decoded, kept: NOTHING_KEPT, unassigned: [] };\n}\n\n/**\n * The installed skills this run has to leave alone: absent from what the payload placed here,\n * and immune to removal by authorship, or because this catalogue could not place the id the\n * payload named.\n *\n * A tombstone is neither. It is a statement about something that is NOT installed here, so\n * there are no files to protect and nothing to tell the user is staying.\n */\nfunction skillsToKeep(options: ReconcileOptions, installed: ProjectConfig): SkillConfig[] {\n const named = new Set(options.decoded.skills.map((skill) => skill.id));\n\n return installed.skills.filter(\n (skill) => !skill.excluded && !named.has(skill.id) && mayNotRemove(skill, options),\n );\n}\n\n/** Whether either reason covers this entry — the question the split below refines. */\nfunction mayNotRemove(skill: SkillConfig, options: ReconcileOptions): boolean {\n return options.authoredHere.has(skill.id) || options.unplaceable.has(skill.id);\n}\n\ntype KeptReason = \"authored\" | \"unplaceable\";\n\nfunction describeKept(options: ReconcileOptions, keptSkills: SkillConfig[]): KeptFromRoundTrip {\n const idsKeptFor = (reason: KeptReason): SkillId[] =>\n keptSkills.filter((skill) => reasonKept(skill, options) === reason).map((skill) => skill.id);\n\n return {\n authoredSkillIds: idsKeptFor(\"authored\"),\n unplaceableSkillIds: idsKeptFor(\"unplaceable\"),\n };\n}\n\n/**\n * Which of the two an entry is disclosed as, where both are true — judged from the more\n * permanent claim to the less, because each statement carries its own remedy and only the one\n * that is true of the whole entry is worth naming.\n *\n * A skill nobody installed cannot be removed by any shared configuration from anywhere. An id\n * this catalogue cannot place is inert only for as long as this installation reads this\n * catalogue, so `update` is a real way out of it and nothing is a way out of the other.\n */\nfunction reasonKept(skill: SkillConfig, options: ReconcileOptions): KeptReason {\n return options.authoredHere.has(skill.id) ? \"authored\" : \"unplaceable\";\n}\n\n/**\n * The payload, plus the entries it was never entitled to speak about — and the kept rows its own\n * skills left no room for.\n */\nfunction withKeptEntries(\n decoded: WizardResultV2,\n installed: ProjectConfig,\n keptSkills: SkillConfig[],\n): Pick<ReconciledSharedConfig, \"result\" | \"unassigned\"> {\n const withSkills: WizardResultV2 = {\n ...decoded,\n skills: [...decoded.skills, ...keptSkills],\n selectedDomains: withKeptDomains(decoded.selectedDomains, keptSkills),\n };\n if (decoded.assignedStack === undefined) return { result: withSkills, unassigned: [] };\n\n const { assignedStack, unassigned } = withKeptStackRows(decoded.assignedStack, installed.stack, {\n keptSkillIds: new Set(keptSkills.map((skill) => skill.id)),\n survivingAgents: new Set(decoded.selectedAgents),\n });\n return { result: { ...withSkills, assignedStack }, unassigned };\n}\n\n/** The domains the payload chose, plus the ones its kept entries still need a tab for. */\nfunction withKeptDomains(selected: Domain[], keptSkills: SkillConfig[]): Domain[] {\n const keptDomains = keptSkills.flatMap((skill) => domainOfSkill(skill.id) ?? []);\n const missing = keptDomains.filter((domain) => !selected.includes(domain));\n if (missing.length === 0) return selected;\n\n return orderDomains([...selected, ...new Set(missing)]);\n}\n\n/**\n * A kept skill's domain, where this catalogue knows the skill. Optional by nature rather than by\n * caution: a config entry the loaded source cannot resolve has no category to place, and\n * `edit`'s own unresolvable-entry path is what speaks about those.\n */\nfunction domainOfSkill(skillId: SkillId): Domain | undefined {\n const category = matrix.skills[skillId]?.category;\n return category === undefined ? undefined : getCategoryDomain(category);\n}\n\ntype KeptRowFilter = {\n keptSkillIds: ReadonlySet<SkillId>;\n survivingAgents: ReadonlySet<AgentName>;\n};\n\n/** The payload's stack with the kept rows folded in, and the kept rows it had no room for. */\ntype KeptRows = {\n assignedStack: Partial<Record<AgentName, StackAgentConfig>>;\n unassigned: UnassignedRow[];\n};\n\n/**\n * The installed stack rows a kept skill needs, folded into the payload's own.\n *\n * `assignedStack` REPLACES the ownership-derived stack rather than merging with it, so a row\n * this reconcile does not carry is a row nothing downstream re-derives. A kept skill is kept\n * because the payload never spoke about it, and a row naming it is the same silence one layer\n * down — so the row the installation already had is the only statement there is about it.\n *\n * A row under a sub-agent this configuration removes is dropped with it. That sub-agent IS this\n * run's to remove, and a stack row naming one no configuration installs is what `compile` warns\n * about and leaves out of the agents it writes.\n *\n * A kept row JOINS the payload's rows under its category rather than replacing them: the payload\n * may assign a skill of its own to the same category of the same sub-agent, and a replaced array\n * leaves that skill installed and loaded by no sub-agent. Except where the category holds one\n * skill and the payload already fills it: the two cannot both be written there, so the payload's\n * skill takes the slot — the same rule {@link arrivingRows} keeps for a global sub-agent — and the\n * kept row is handed back as unassigned for the plan to name.\n */\nfunction withKeptStackRows(\n assigned: Partial<Record<AgentName, StackAgentConfig>>,\n installedStack: Partial<Record<AgentName, StackAgentConfig>> | undefined,\n filter: KeptRowFilter,\n): KeptRows {\n const isKept = (_category: Category, row: SkillAssignment): boolean =>\n filter.keptSkillIds.has(row.id);\n\n return typedEntries<AgentName, StackAgentConfig>(installedStack ?? {})\n .filter(([agent]) => filter.survivingAgents.has(agent))\n .reduce<KeptRows>(\n (carried, [agent, agentStack]) =>\n withAgentKeptRows(carried, agent, rowsWhere(agentStack, isKept), assigned[agent]),\n { assignedStack: assigned, unassigned: [] },\n );\n}\n\n/**\n * One sub-agent's kept rows, folded in: each joins the payload's rows under its category, unless\n * that category holds one skill the payload already gives this sub-agent.\n */\nfunction withAgentKeptRows(\n carried: KeptRows,\n agent: AgentName,\n keptRows: [Category, SkillAssignment][],\n payloadRows: StackAgentConfig | undefined,\n): KeptRows {\n const filled = filledExclusiveCategories(payloadRows);\n const [displaced, joining] = partition(keptRows, ([category]) => filled.has(category));\n\n return {\n assignedStack: withAppendedRows(carried.assignedStack, agent, joining),\n unassigned: [\n ...carried.unassigned,\n ...displaced.map(([category, row]) => ({ skillId: row.id, agentName: agent, category })),\n ],\n };\n}\n\n/**\n * The decode, plus each installed sub-agent the payload assigns a skill this catalogue cannot place\n * and this installation still holds — each as installed.\n *\n * The decode carries a sub-agent in by the skills it places, so one whose only skill it skipped is\n * left out, and the destructive apply would remove it. That is the removal\n * {@link reconcileSharedConfig} refuses for the skill, for the same reason: the payload named this\n * sub-agent, so its absence is this catalogue's limit rather than an instruction. Put back before\n * the reconcile, it survives it, and the reconcile carries the kept skill's rows onto it.\n */\nexport function withUnplaceableAssignees(\n decoded: WizardResultV2,\n installed: ProjectConfig | null,\n payload: SeedPayload,\n unplaceable: ReadonlySet<string>,\n): WizardResultV2 {\n if (!installed) return decoded;\n\n const assigned = assigneesOfHeld(payload, installed, unplaceable);\n const selected = new Set(decoded.selectedAgents);\n const leftOutThoughAssigned = (agent: AgentScopeConfig): boolean =>\n !agent.excluded && assigned.has(agent.name) && !selected.has(agent.name);\n\n const assignees = installed.agents.filter(leftOutThoughAssigned);\n if (assignees.length === 0) return decoded;\n\n return {\n ...decoded,\n selectedAgents: [...decoded.selectedAgents, ...assignees.map((agent) => agent.name)],\n agentConfigs: [...decoded.agentConfigs, ...assignees],\n };\n}\n\n/**\n * The sub-agents the payload assigns an unplaceable skill the installation holds, less any it\n * switches off — whose rows the decode ignores even for a skill it places.\n */\nfunction assigneesOfHeld(\n payload: SeedPayload,\n installed: ProjectConfig,\n unplaceable: ReadonlySet<string>,\n): Set<string> {\n const held = new Set<string>(\n installed.skills.filter((skill) => !skill.excluded).map((skill) => skill.id),\n );\n const isHeldUnplaceable = ([id]: [string, SeedSkill]): boolean =>\n unplaceable.has(id) && held.has(id);\n const isNotSwitchedOff = (name: string): boolean => payload.agents[name]?.on !== false;\n\n return new Set(\n typedEntries<string, SeedSkill>(payload.skills)\n .filter(isHeldUnplaceable)\n .flatMap(([, skill]) => typedKeys<string>(skill.assignments))\n .filter(isNotSwitchedOff),\n );\n}\n\n/** The parts of the global installation's config a project run must leave as they are. */\nexport type InstalledGlobal = Pick<ProjectConfig, \"skills\" | \"agents\" | \"stack\">;\n\n/**\n * The global installation a run in `projectDir` inherits, as its own config states it — or `null`\n * when there is none.\n *\n * Read from the global config rather than from the rows a project's own file inlines: the global\n * config is what the gate writes and what every other registered project inherits, and an inlined\n * copy can be older than it. Through the project's own provider, because a project inherits only\n * from the global installation of its provider. Called only from a project — at the home directory\n * the run IS the global installation, and there is nothing to leave alone.\n */\nexport async function readInstalledGlobal(projectDir: string): Promise<InstalledGlobal | null> {\n const loaded = await loadProjectConfigFromDir(os.homedir(), providerInUse(projectDir));\n return loaded?.config ?? null;\n}\n\n/** Global entries a configuration names, by what a project run does with each. */\ntype GlobalEntries = { skillIds: SkillId[]; agentNames: AgentName[] };\n\n/** What a run from a project does with the global install above it: adds to it, and nothing else. */\nexport type HeldGlobal = {\n /** The configuration, with every global entry the global install holds stated as it holds it. */\n result: WizardResultV2;\n /**\n * The global entries the configuration names that the global install already holds. Its skills\n * are skipped — neither installed nor given a row — and listed as skipped.\n */\n installed: GlobalEntries;\n /**\n * Of those, the ones the configuration states otherwise — a skill's install mode, a sub-agent's\n * tuning, or rows other than the ones it ends up with — disclosed as kept as installed. From\n * {@link restoreInstalledGlobal}, also every global skill and sub-agent the configuration leaves\n * out.\n */\n keptAsInstalled: GlobalEntries;\n};\n\nconst NO_GLOBAL_ENTRIES: GlobalEntries = { skillIds: [], agentNames: [] };\n\n/**\n * Restates every global entry the configuration names that the global install already holds, as\n * the global install holds it: the skill's install mode, the sub-agent's tuning, and the rows\n * that sub-agent already has.\n *\n * From a project, `--from` only ADDS to the global install (owner ruling 2026-10-02). It is one\n * installation every project on the machine reads, and a run started in one of them is not a\n * decision about the rest — so an entry it already holds is not this run's to reinstall, retune or\n * reassign, however the configuration states it. Restated in the RESULT rather than excused at\n * the writer, for the reason {@link reconcileSharedConfig} gives: the result is what the diff, the\n * install steps and the writer all read, so an entry left as the payload stated it is one of them\n * acting on it.\n *\n * What the global install lacks arrives whole, and a skill's assignment is part of it: a new\n * global skill assigned to a global sub-agent the global install already holds adds its row to\n * that sub-agent, beside the rows it has (owner ruling, later on 2026-10-02). A skill the global\n * install already holds is skipped outright, so it brings no row with it either.\n *\n * `installed` is `null` where there is nothing to hold: at the home directory, where the run IS\n * the global install, and on a machine with none.\n */\nexport function holdInstalledGlobal(\n decoded: WizardResultV2,\n installed: InstalledGlobal | null,\n): HeldGlobal {\n if (!installed) {\n return { result: decoded, installed: NO_GLOBAL_ENTRIES, keptAsInstalled: NO_GLOBAL_ENTRIES };\n }\n\n const heldSkills = activeGlobalById(installed.skills, (skill) => skill.id);\n const heldAgents = activeGlobalById(installed.agents, (agent) => agent.name);\n const skills = pairWithHeld(decoded.skills, heldSkills, (skill) => skill.id);\n const agents = pairWithHeld(decoded.agentConfigs, heldAgents, (agent) => agent.name);\n const namedSkillIds = skills.map(({ named }) => named.id);\n const namedAgentNames = agents.map(({ named }) => named.name);\n const assignedStack =\n decoded.assignedStack &&\n withHeldRows(decoded.assignedStack, installed.stack, {\n agentNames: namedAgentNames,\n skippedSkillIds: new Set(namedSkillIds),\n });\n\n const restatesSkill = ({ named, held }: Held<SkillConfig>): boolean =>\n named.origin === held.origin;\n const restatesAgent = ({ named, held }: Held<AgentScopeConfig>): boolean =>\n named.model === held.model &&\n named.effort === held.effort &&\n sameAssignments(decoded.assignedStack?.[named.name], assignedStack?.[named.name]);\n\n return {\n result: {\n ...decoded,\n skills: decoded.skills.map(asHeld(skills)),\n agentConfigs: decoded.agentConfigs.map(asHeld(agents)),\n ...(assignedStack !== undefined && { assignedStack }),\n },\n installed: { skillIds: namedSkillIds, agentNames: namedAgentNames },\n keptAsInstalled: {\n skillIds: skills.filter((pair) => !restatesSkill(pair)).map(({ named }) => named.id),\n agentNames: agents.filter((pair) => !restatesAgent(pair)).map(({ named }) => named.name),\n },\n };\n}\n\n/**\n * {@link holdInstalledGlobal}, plus every global entry the configuration leaves out, put back as\n * installed — what `edit --from` needs from a project.\n *\n * That apply is destructive: an installed entry absent from the result is removed, files and\n * all. The project's own entries are its to remove; the global install's are not, so they are put\n * back whole — skills, sub-agents and those sub-agents' rows — and the removal plan never holds\n * one. A global skill or sub-agent put back is one the configuration leaves out, so it is named as\n * kept.\n */\nexport function restoreInstalledGlobal(\n decoded: WizardResultV2,\n installed: InstalledGlobal | null,\n): HeldGlobal {\n const held = holdInstalledGlobal(decoded, installed);\n if (!installed) return held;\n\n const omitted = omittedGlobal(held.result, installed);\n const { skillIds, agentNames } = held.keptAsInstalled;\n return {\n ...held,\n result: withOmittedGlobal(held.result, installed, omitted),\n keptAsInstalled: {\n skillIds: [...skillIds, ...omitted.skills.map((skill) => skill.id)],\n agentNames: [...agentNames, ...omitted.agents.map((agent) => agent.name)],\n },\n };\n}\n\n/** A config roster's active global entries, by the key the result names them with. */\nfunction activeGlobalById<Entry extends SkillConfig | AgentScopeConfig, Key extends string>(\n entries: Entry[],\n keyOf: (entry: Entry) => Key,\n): Map<Key, Entry> {\n return new Map(\n entries.filter((entry) => isActiveAt(entry, \"global\")).map((entry) => [keyOf(entry), entry]),\n );\n}\n\n/** A decoded global entry, beside the entry the global install already holds under its name. */\ntype Held<Entry> = { named: Entry; held: Entry };\n\n/** Each decoded entry as the global install holds it, where it names one; otherwise unchanged. */\nfunction asHeld<Entry>(pairs: Held<Entry>[]): (entry: Entry) => Entry {\n const heldFor = new Map(pairs.map(({ named, held }) => [named, held]));\n return (entry) => heldFor.get(entry) ?? entry;\n}\n\n/** The decoded entries that name, at global scope, one the global install already holds. */\nfunction pairWithHeld<Entry extends SkillConfig | AgentScopeConfig, Key extends string>(\n decoded: Entry[],\n heldByKey: ReadonlyMap<Key, Entry>,\n keyOf: (entry: Entry) => Key,\n): Held<Entry>[] {\n return decoded.flatMap((named) => {\n const held = heldByKey.get(keyOf(named));\n return held !== undefined && isActiveAt(named, \"global\") ? [{ named, held }] : [];\n });\n}\n\n/**\n * Whether two sub-agent stacks hold the same skills under the same categories at the same load\n * state — the order rows were written in is not part of what a sub-agent loads.\n */\nexport function sameAssignments(\n left: StackAgentConfig | undefined,\n right: StackAgentConfig | undefined,\n): boolean {\n return isDeepEqual(assignmentKeys(left), assignmentKeys(right));\n}\n\n/** One stack's rows as comparable strings, in a fixed order. */\nfunction assignmentKeys(agentStack: StackAgentConfig | undefined): string[] {\n return typedEntries<Category, SkillAssignment[]>(agentStack ?? {})\n .flatMap(([category, assignments]) =>\n assignments.map((row) => `${category}/${row.id}/${row.preloaded === true}`),\n )\n .sort();\n}\n\n/**\n * The payload's stack, with each named sub-agent's rows replaced by the ones it is installed\n * with — none, where the global install gives it none.\n */\nfunction withInstalledRows(\n assigned: Partial<Record<AgentName, StackAgentConfig>>,\n installedStack: InstalledGlobal[\"stack\"],\n agentNames: AgentName[],\n): Partial<Record<AgentName, StackAgentConfig>> {\n return { ...omit(assigned, agentNames), ...pick(installedStack ?? {}, agentNames) };\n}\n\n/** The held sub-agents, and the skills whose rows stay out because the skill is skipped. */\ntype HeldRows = { agentNames: AgentName[]; skippedSkillIds: ReadonlySet<SkillId> };\n\n/**\n * {@link withInstalledRows}, plus the configuration's rows on those sub-agents for the skills the\n * global install lacks — a skill arrives together with its assignment. A row the sub-agent\n * already has stays first and as it is, so what it loads now it still loads, in the same order.\n */\nfunction withHeldRows(\n assigned: Partial<Record<AgentName, StackAgentConfig>>,\n installedStack: InstalledGlobal[\"stack\"],\n held: HeldRows,\n): Partial<Record<AgentName, StackAgentConfig>> {\n const asInstalled = withInstalledRows(assigned, installedStack, held.agentNames);\n\n return held.agentNames.reduce(\n (stack, agentName) =>\n withAppendedRows(\n stack,\n agentName,\n arrivingRows(assigned[agentName], stack[agentName], held.skippedSkillIds),\n ),\n asInstalled,\n );\n}\n\n/**\n * The configuration's rows for one held sub-agent that bring something with them: a skill the\n * global install lacks, which that sub-agent does not already load, under a category with room for\n * it. A category that holds one skill, which the sub-agent already fills, keeps the skill it has —\n * the arriving one is installed without that row, rather than written as a second skill where the\n * config can hold only one.\n */\nfunction arrivingRows(\n stated: StackAgentConfig | undefined,\n installed: StackAgentConfig | undefined,\n skippedSkillIds: ReadonlySet<SkillId>,\n): [Category, SkillAssignment][] {\n const alreadyLoaded = new Set(rowIds(installed));\n const filled = filledExclusiveCategories(installed);\n const arrives = (category: Category, row: SkillAssignment): boolean =>\n !skippedSkillIds.has(row.id) && !alreadyLoaded.has(row.id) && !filled.has(category);\n\n return rowsWhere(stated, arrives);\n}\n\n/** One sub-agent's rows, as the skill ids they load. */\nfunction rowIds(agentStack: StackAgentConfig | undefined): SkillId[] {\n return typedValues<Category, SkillAssignment[]>(agentStack ?? {}).flatMap((rows) =>\n rows.map((row) => row.id),\n );\n}\n\n/**\n * The categories this sub-agent already fills that hold one skill at most — declared so by the\n * catalogue, because a category it does not declare is not exclusive (see `propagate.ts`).\n */\nfunction filledExclusiveCategories(agentStack: StackAgentConfig | undefined): Set<Category> {\n const isExclusive = (category: Category): boolean =>\n matrix.categories[category]?.exclusive === true;\n\n return new Set(\n typedEntries<Category, SkillAssignment[]>(agentStack ?? {})\n .filter(([category, rows]) => rows.length > 0 && isExclusive(category))\n .map(([category]) => category),\n );\n}\n\n/** The stack, with `added` appended to one sub-agent's rows — as it was where nothing is added. */\nfunction withAppendedRows(\n stack: Partial<Record<AgentName, StackAgentConfig>>,\n agentName: AgentName,\n added: [Category, SkillAssignment][],\n): Partial<Record<AgentName, StackAgentConfig>> {\n if (added.length === 0) return stack;\n return { ...stack, [agentName]: appendRows(stack[agentName] ?? {}, added) };\n}\n\n/** The rows of one sub-agent's stack that pass `keep`, each beside the category it sits under. */\nfunction rowsWhere(\n agentStack: StackAgentConfig | undefined,\n keep: (category: Category, row: SkillAssignment) => boolean,\n): [Category, SkillAssignment][] {\n return typedEntries<Category, SkillAssignment[]>(agentStack ?? {}).flatMap(([category, rows]) =>\n rows\n .filter((row) => keep(category, row))\n .map((row): [Category, SkillAssignment] => [category, row]),\n );\n}\n\n/** A sub-agent's stack with `added` appended, each row after those already under its category. */\nfunction appendRows(\n agentStack: StackAgentConfig,\n added: [Category, SkillAssignment][],\n): StackAgentConfig {\n return added.reduce<StackAgentConfig>(\n (rows, [category, row]) => ({ ...rows, [category]: [...(rows[category] ?? []), row] }),\n agentStack,\n );\n}\n\n/** The global install's active entries a result does not name. */\ntype OmittedGlobal = { skills: SkillConfig[]; agents: AgentScopeConfig[] };\n\nfunction omittedGlobal(result: WizardResultV2, installed: InstalledGlobal): OmittedGlobal {\n const namedSkillIds = new Set(result.skills.map((skill) => skill.id));\n const namedAgentNames = new Set(result.agentConfigs.map((agent) => agent.name));\n\n return {\n skills: [...activeGlobalById(installed.skills, (skill) => skill.id).values()].filter(\n (skill) => !namedSkillIds.has(skill.id),\n ),\n agents: [...activeGlobalById(installed.agents, (agent) => agent.name).values()].filter(\n (agent) => !namedAgentNames.has(agent.name),\n ),\n };\n}\n\n/** The result, plus every global entry it does not name, each as the global install holds it. */\nfunction withOmittedGlobal(\n result: WizardResultV2,\n installed: InstalledGlobal,\n omitted: OmittedGlobal,\n): WizardResultV2 {\n const omittedAgentNames = omitted.agents.map((agent) => agent.name);\n\n return {\n ...result,\n skills: [...result.skills, ...omitted.skills],\n agentConfigs: [...result.agentConfigs, ...omitted.agents],\n selectedAgents: [...new Set([...result.selectedAgents, ...omittedAgentNames])],\n selectedDomains: withKeptDomains(result.selectedDomains, omitted.skills),\n ...(result.assignedStack !== undefined && {\n assignedStack: withInstalledRows(result.assignedStack, installed.stack, omittedAgentNames),\n }),\n };\n}\n\n/**\n * The entries a run writes, less those the global install already holds: a carried skill's bytes\n * written over a global skill directory would change it as surely as a reinstall.\n */\nexport function notInstalledGlobally<Entry extends { id: SkillId }>(\n entries: Entry[],\n held: HeldGlobal,\n): Entry[] {\n const installed = new Set(held.installed.skillIds);\n return entries.filter((entry) => !installed.has(entry.id));\n}\n\n/** What a `--from` install puts into one scope, as its list names them. */\nexport type ArrivingEntries = { skillIds: SkillId[]; agentNames: AgentName[] };\n\n/** What a `--from` install puts where — the two lists it prints before it writes. */\nexport type Arrivals = Record<SkillScope, ArrivingEntries>;\n\n/** The active skills and sub-agents given, split by the scope each one lands at. */\nexport function arrivalsByScope(skills: SkillConfig[], agents: AgentScopeConfig[]): Arrivals {\n const landingAt = (scope: SkillScope): ArrivingEntries => ({\n skillIds: skills.filter((skill) => isActiveAt(skill, scope)).map((skill) => skill.id),\n agentNames: agents.filter((agent) => isActiveAt(agent, scope)).map((agent) => agent.name),\n });\n return { project: landingAt(\"project\"), global: landingAt(\"global\") };\n}\n\n/**\n * Everything `init --from` installs, by scope: the whole configuration, less what the global\n * install already holds — that is not arriving anywhere.\n */\nfunction installArrivals(held: HeldGlobal): Arrivals {\n const installedSkillIds = new Set(held.installed.skillIds);\n const installedAgentNames = new Set(held.installed.agentNames);\n return arrivalsByScope(\n held.result.skills.filter((skill) => !installedSkillIds.has(skill.id)),\n held.result.agentConfigs.filter((agent) => !installedAgentNames.has(agent.name)),\n );\n}\n\n/**\n * What `init --from` prints before it writes: the two lists of what it installs where, then the\n * global skills it skips because the global install above the project already holds them. A\n * skipped skill is named rather than left out, so a configuration's skill that does not arrive is\n * never one the user was not told about.\n */\nexport function installPlanLines(held: HeldGlobal): string[] {\n return [\n ...arrivalLines(installArrivals(held)),\n ...scopeLines(SHARED_CONFIG_ARRIVALS.SKIPPED_HEADING, {\n skillIds: held.installed.skillIds,\n agentNames: [],\n }),\n ];\n}\n\n/** How a skill reads in a shared-config plan: the name it was picked by, with the id behind it. */\nexport function skillLabel(skillId: SkillId): string {\n const displayName = getSkillDisplayName(skillId);\n return displayName === skillId ? skillId : `${displayName} (${skillId})`;\n}\n\n/**\n * The two lists, as printed: project first, then global, each omitted when nothing lands there,\n * and each split into skills and sub-agents under the headings the removal plan uses.\n */\nexport function arrivalLines(arrivals: Arrivals): string[] {\n return [\n ...scopeLines(SHARED_CONFIG_ARRIVALS.PROJECT_HEADING, arrivals.project),\n ...scopeLines(SHARED_CONFIG_ARRIVALS.GLOBAL_HEADING, arrivals.global),\n ];\n}\n\n/** One scope's list, or nothing when nothing lands there. */\nfunction scopeLines(heading: string, entries: ArrivingEntries): string[] {\n const groups = [\n { label: SHARED_CONFIG_APPLY.SKILLS_HEADING, items: entries.skillIds.map(skillLabel) },\n { label: SHARED_CONFIG_APPLY.AGENTS_HEADING, items: entries.agentNames },\n ].filter((group) => group.items.length > 0);\n if (groups.length === 0) return [];\n\n return [\n heading,\n ...groups.flatMap((group) => [` ${group.label}`, ...group.items.map((item) => ` ${item}`)]),\n ];\n}\n","import { execCommand } from \"./exec.js\";\nimport { getErrorMessage } from \"./errors.js\";\n\n/** The shell-out one platform answers \"open this with whatever handles it\" with. */\nexport type OpenerCommand = { command: string; args: string[] };\n\n/**\n * Never a throw. A machine with no browser, no desktop session or no opener at all is a\n * legitimate place to run this CLI, so failing to open a link is something to say rather than\n * something to fail on — the same posture the seed boundaries take, and for the same reason:\n * the link is already printed by the time this runs, so nothing is lost by it not working.\n */\nexport type OpenUrlResult = { ok: true } | { ok: false; error: string };\n\n/**\n * The command a platform opens a link with.\n *\n * There is no dependency for this. All three are one-line shell-outs to something the operating\n * system already ships, and a package for it would be a fourth thing to keep current for no\n * behaviour this does not have.\n *\n * Windows goes through `cmd` because `start` is a shell builtin rather than a program, and the\n * empty string after it is its TITLE argument: without one, `start` reads the first quoted\n * argument it is given as the window title and opens nothing.\n */\nexport function browserOpenerCommand(platform: NodeJS.Platform, url: string): OpenerCommand {\n if (platform === \"darwin\") return { command: \"open\", args: [url] };\n if (platform === \"win32\") return { command: \"cmd\", args: [\"/c\", \"start\", \"\", url] };\n return { command: \"xdg-open\", args: [url] };\n}\n\nfunction openerRefusedMessage(command: string, exitCode: number): string {\n return `Could not open your browser — '${command}' exited ${exitCode}.`;\n}\n\nfunction openerUnavailableMessage(command: string, reason: string): string {\n return `Could not open your browser — '${command}' could not be run: ${reason}`;\n}\n\n/**\n * Hands a link to whatever this platform opens links with.\n *\n * The URL travels as its own argument rather than inside a command line: `execCommand` spawns\n * without a shell, so the argument vector is the whole of the injection guard — there is no\n * string for a link to break out of.\n */\nexport async function openUrl(url: string): Promise<OpenUrlResult> {\n const { command, args } = browserOpenerCommand(process.platform, url);\n\n try {\n const result = await execCommand(command, args);\n if (result.exitCode !== 0) {\n return { ok: false, error: openerRefusedMessage(command, result.exitCode) };\n }\n return { ok: true };\n } catch (error) {\n return { ok: false, error: openerUnavailableMessage(command, getErrorMessage(error)) };\n }\n}\n"],"mappings":"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","names":[]}