agents-inc 0.163.0 → 0.165.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 (193) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +8 -9
  3. package/dist/{chunk-SMSB6YZW.js → chunk-42TNV2TK.js} +3 -3
  4. package/dist/chunk-4AAOOAIN.js +49 -0
  5. package/dist/chunk-4AAOOAIN.js.map +1 -0
  6. package/dist/{chunk-NQXO4LR3.js → chunk-4C7GL4F7.js} +16 -11
  7. package/dist/chunk-4C7GL4F7.js.map +1 -0
  8. package/dist/{chunk-Q4IRRVXK.js → chunk-57KU6SPH.js} +3 -3
  9. package/dist/{chunk-S63LXWMC.js → chunk-5M3PQNXI.js} +2 -2
  10. package/dist/{chunk-3WVH2CTK.js → chunk-5P26XBZO.js} +15 -8
  11. package/dist/chunk-5P26XBZO.js.map +1 -0
  12. package/dist/{chunk-3CSK5CQN.js → chunk-6AIZPSEI.js} +337 -140
  13. package/dist/chunk-6AIZPSEI.js.map +1 -0
  14. package/dist/{chunk-NKGYKOB7.js → chunk-6CY6PW5X.js} +7 -8
  15. package/dist/chunk-6CY6PW5X.js.map +1 -0
  16. package/dist/{chunk-XHPWTL4I.js → chunk-6SW6SB7H.js} +3 -3
  17. package/dist/chunk-AAWZED5M.js +980 -0
  18. package/dist/chunk-AAWZED5M.js.map +1 -0
  19. package/dist/{chunk-UITEKY2R.js → chunk-AQXKAPCD.js} +30 -4
  20. package/dist/chunk-AQXKAPCD.js.map +1 -0
  21. package/dist/{chunk-X4SSJZLY.js → chunk-B5UUHBAM.js} +12 -261
  22. package/dist/chunk-B5UUHBAM.js.map +1 -0
  23. package/dist/{chunk-ARYNOWKF.js → chunk-BGU25XAY.js} +8 -8
  24. package/dist/{chunk-QGUP3QJF.js → chunk-CBMAO6OP.js} +22 -18
  25. package/dist/{chunk-QGUP3QJF.js.map → chunk-CBMAO6OP.js.map} +1 -1
  26. package/dist/{chunk-YRS3QZP2.js → chunk-CEBDGZID.js} +3 -3
  27. package/dist/{chunk-ABAZMMG4.js → chunk-CK5RM3DJ.js} +2 -2
  28. package/dist/{chunk-PHCPUQVR.js → chunk-CTUJO23Z.js} +2 -2
  29. package/dist/{chunk-QZX7SYKU.js → chunk-E6CHHXGY.js} +2 -2
  30. package/dist/{chunk-ZVXGW2IC.js → chunk-F2ZWFWTP.js} +4 -6
  31. package/dist/chunk-F2ZWFWTP.js.map +1 -0
  32. package/dist/{chunk-AVK2HEKB.js → chunk-GKPW33FP.js} +2 -6
  33. package/dist/{chunk-AVK2HEKB.js.map → chunk-GKPW33FP.js.map} +1 -1
  34. package/dist/{chunk-ECURHW3T.js → chunk-HR4YDSJK.js} +13 -12
  35. package/dist/chunk-HR4YDSJK.js.map +1 -0
  36. package/dist/{chunk-XPDVAJDH.js → chunk-IM45LLQX.js} +3599 -1558
  37. package/dist/chunk-IM45LLQX.js.map +1 -0
  38. package/dist/chunk-IVNKU4DM.js +39 -0
  39. package/dist/chunk-IVNKU4DM.js.map +1 -0
  40. package/dist/{chunk-XVAQG2CV.js → chunk-JOEA52NR.js} +10 -8
  41. package/dist/chunk-JOEA52NR.js.map +1 -0
  42. package/dist/{chunk-RGYDFBCT.js → chunk-JV4TKXEN.js} +5 -5
  43. package/dist/{chunk-4VL3CEOS.js → chunk-LXXGZAD3.js} +3 -3
  44. package/dist/chunk-MBUGQI2M.js +73 -0
  45. package/dist/chunk-MBUGQI2M.js.map +1 -0
  46. package/dist/{chunk-URXIQE7I.js → chunk-MWDY76MT.js} +2 -2
  47. package/dist/{chunk-V5GLL2JB.js → chunk-P3KJCYSR.js} +7 -7
  48. package/dist/{chunk-6MV4JOCL.js → chunk-PSX7CT6V.js} +2 -2
  49. package/dist/{chunk-B3Y3QMWI.js → chunk-TRTVOVHR.js} +4 -4
  50. package/dist/{chunk-AOTCIUGX.js → chunk-TV5Y53R2.js} +2 -2
  51. package/dist/{chunk-K5ITEA3I.js → chunk-UBSSIIWD.js} +34 -17
  52. package/dist/chunk-UBSSIIWD.js.map +1 -0
  53. package/dist/chunk-UCORXS4D.js +250 -0
  54. package/dist/chunk-UCORXS4D.js.map +1 -0
  55. package/dist/chunk-UR3RRTPO.js +159 -0
  56. package/dist/chunk-UR3RRTPO.js.map +1 -0
  57. package/dist/chunk-V5CWACR5.js +375 -0
  58. package/dist/chunk-V5CWACR5.js.map +1 -0
  59. package/dist/{chunk-LT5GXEB2.js → chunk-VPJUCVU7.js} +8 -7
  60. package/dist/chunk-VPJUCVU7.js.map +1 -0
  61. package/dist/chunk-VRXX4D6K.js +31 -0
  62. package/dist/chunk-VRXX4D6K.js.map +1 -0
  63. package/dist/{chunk-IAVEMTN4.js → chunk-WIFIAQZU.js} +63 -42
  64. package/dist/chunk-WIFIAQZU.js.map +1 -0
  65. package/dist/chunk-WSGX4IWC.js +356 -0
  66. package/dist/chunk-WSGX4IWC.js.map +1 -0
  67. package/dist/{chunk-PM3374E7.js → chunk-WTANOSXK.js} +436 -739
  68. package/dist/chunk-WTANOSXK.js.map +1 -0
  69. package/dist/{chunk-XATLUKXK.js → chunk-X2KUI7V3.js} +6 -6
  70. package/dist/{chunk-NTF4UXGK.js → chunk-XATIKM3P.js} +73 -30
  71. package/dist/chunk-XATIKM3P.js.map +1 -0
  72. package/dist/{chunk-J27W6TXS.js → chunk-XQHZPINL.js} +8 -4
  73. package/dist/chunk-XQHZPINL.js.map +1 -0
  74. package/dist/{chunk-DSL66I4N.js → chunk-Y5SNGMB7.js} +6 -6
  75. package/dist/{chunk-7REKOTKG.js → chunk-YNOOZFIB.js} +3 -3
  76. package/dist/commands/build/marketplace.js +17 -17
  77. package/dist/commands/build/marketplace.js.map +1 -1
  78. package/dist/commands/build/plugins.js +11 -9
  79. package/dist/commands/build/plugins.js.map +1 -1
  80. package/dist/commands/compile.js +106 -27
  81. package/dist/commands/compile.js.map +1 -1
  82. package/dist/commands/doctor.js +427 -192
  83. package/dist/commands/doctor.js.map +1 -1
  84. package/dist/commands/edit.js +504 -314
  85. package/dist/commands/edit.js.map +1 -1
  86. package/dist/commands/eject.js +41 -23
  87. package/dist/commands/eject.js.map +1 -1
  88. package/dist/commands/init.js +44 -40
  89. package/dist/commands/list.js +44 -10
  90. package/dist/commands/list.js.map +1 -1
  91. package/dist/commands/new/marketplace.js +13 -10
  92. package/dist/commands/new/marketplace.js.map +1 -1
  93. package/dist/commands/search.js +12 -10
  94. package/dist/commands/search.js.map +1 -1
  95. package/dist/commands/share.js +103 -15
  96. package/dist/commands/share.js.map +1 -1
  97. package/dist/commands/uninstall.js +264 -106
  98. package/dist/commands/uninstall.js.map +1 -1
  99. package/dist/commands/update.js +50 -20
  100. package/dist/commands/update.js.map +1 -1
  101. package/dist/components/common/prompt-confirm.js +1 -1
  102. package/dist/components/common/removal-plan-confirm.js +2 -2
  103. package/dist/components/common/select-list.js +3 -3
  104. package/dist/components/common/spinner.js +6 -3
  105. package/dist/components/wizard/category-grid.js +5 -5
  106. package/dist/components/wizard/checkbox-grid.js +4 -4
  107. package/dist/components/wizard/domain-selection.js +9 -9
  108. package/dist/components/wizard/run-wizard-session.js +29 -29
  109. package/dist/components/wizard/scroll-affordance.js +2 -2
  110. package/dist/components/wizard/skill-agent-summary.js +5 -5
  111. package/dist/components/wizard/source-grid.js +6 -6
  112. package/dist/components/wizard/stack-selection.js +8 -8
  113. package/dist/components/wizard/step-agents.js +8 -8
  114. package/dist/components/wizard/step-build.js +8 -8
  115. package/dist/components/wizard/step-confirm.js +9 -9
  116. package/dist/components/wizard/step-sources.js +9 -9
  117. package/dist/components/wizard/step-stack.js +9 -9
  118. package/dist/components/wizard/summary-panel.js +8 -8
  119. package/dist/components/wizard/toast.js +2 -2
  120. package/dist/components/wizard/wizard-layout.js +12 -12
  121. package/dist/components/wizard/wizard-tabs.js +2 -2
  122. package/dist/components/wizard/wizard.js +27 -27
  123. package/dist/hooks/init.js +59 -43
  124. package/dist/hooks/init.js.map +1 -1
  125. package/dist/load-agent-defs-JHLE6MAE.js +14 -0
  126. package/dist/recompile-project-agents-ZPP5NOLZ.js +18 -0
  127. package/dist/{source-loader-GRBVNRAW.js → source-loader-RWZAYJ7E.js} +6 -4
  128. package/dist/src/agents/_templates/agent-body.liquid +55 -0
  129. package/dist/src/agents/_templates/agent.liquid +1 -55
  130. package/dist/src/agents/meta/agent-summoner/output.md +1 -1
  131. package/dist/src/agents/meta/agent-summoner/playbook.md +15 -7
  132. package/dist/src/agents/meta/codex-keeper/playbook.md +20 -5
  133. package/dist/stores/wizard-store.js +4 -4
  134. package/package.json +10 -9
  135. package/src/agents/_templates/agent-body.liquid +55 -0
  136. package/src/agents/_templates/agent.liquid +1 -55
  137. package/src/agents/meta/agent-summoner/output.md +1 -1
  138. package/src/agents/meta/agent-summoner/playbook.md +15 -7
  139. package/src/agents/meta/codex-keeper/playbook.md +20 -5
  140. package/src/schemas/project-config.schema.json +1 -1
  141. package/src/schemas/project-source-config.schema.json +1 -1
  142. package/dist/chunk-3CSK5CQN.js.map +0 -1
  143. package/dist/chunk-3WVH2CTK.js.map +0 -1
  144. package/dist/chunk-7ANJQLPJ.js +0 -35
  145. package/dist/chunk-7ANJQLPJ.js.map +0 -1
  146. package/dist/chunk-ECURHW3T.js.map +0 -1
  147. package/dist/chunk-FLW25ORT.js +0 -130
  148. package/dist/chunk-FLW25ORT.js.map +0 -1
  149. package/dist/chunk-IAVEMTN4.js.map +0 -1
  150. package/dist/chunk-J27W6TXS.js.map +0 -1
  151. package/dist/chunk-JCPANDNR.js +0 -35
  152. package/dist/chunk-JCPANDNR.js.map +0 -1
  153. package/dist/chunk-JIERYE6B.js +0 -480
  154. package/dist/chunk-JIERYE6B.js.map +0 -1
  155. package/dist/chunk-K5ITEA3I.js.map +0 -1
  156. package/dist/chunk-LT5GXEB2.js.map +0 -1
  157. package/dist/chunk-NKGYKOB7.js.map +0 -1
  158. package/dist/chunk-NQXO4LR3.js.map +0 -1
  159. package/dist/chunk-NTF4UXGK.js.map +0 -1
  160. package/dist/chunk-O664ITBD.js +0 -670
  161. package/dist/chunk-O664ITBD.js.map +0 -1
  162. package/dist/chunk-PM3374E7.js.map +0 -1
  163. package/dist/chunk-UITEKY2R.js.map +0 -1
  164. package/dist/chunk-X4SSJZLY.js.map +0 -1
  165. package/dist/chunk-XPDVAJDH.js.map +0 -1
  166. package/dist/chunk-XVAQG2CV.js.map +0 -1
  167. package/dist/chunk-ZBPEFQ4G.js +0 -12
  168. package/dist/chunk-ZBPEFQ4G.js.map +0 -1
  169. package/dist/chunk-ZVXGW2IC.js.map +0 -1
  170. package/dist/load-agent-defs-5J2MZLAA.js +0 -13
  171. package/dist/recompile-project-agents-FCJLHAE3.js +0 -17
  172. /package/dist/{chunk-SMSB6YZW.js.map → chunk-42TNV2TK.js.map} +0 -0
  173. /package/dist/{chunk-Q4IRRVXK.js.map → chunk-57KU6SPH.js.map} +0 -0
  174. /package/dist/{chunk-S63LXWMC.js.map → chunk-5M3PQNXI.js.map} +0 -0
  175. /package/dist/{chunk-XHPWTL4I.js.map → chunk-6SW6SB7H.js.map} +0 -0
  176. /package/dist/{chunk-ARYNOWKF.js.map → chunk-BGU25XAY.js.map} +0 -0
  177. /package/dist/{chunk-YRS3QZP2.js.map → chunk-CEBDGZID.js.map} +0 -0
  178. /package/dist/{chunk-ABAZMMG4.js.map → chunk-CK5RM3DJ.js.map} +0 -0
  179. /package/dist/{chunk-PHCPUQVR.js.map → chunk-CTUJO23Z.js.map} +0 -0
  180. /package/dist/{chunk-QZX7SYKU.js.map → chunk-E6CHHXGY.js.map} +0 -0
  181. /package/dist/{chunk-RGYDFBCT.js.map → chunk-JV4TKXEN.js.map} +0 -0
  182. /package/dist/{chunk-4VL3CEOS.js.map → chunk-LXXGZAD3.js.map} +0 -0
  183. /package/dist/{chunk-URXIQE7I.js.map → chunk-MWDY76MT.js.map} +0 -0
  184. /package/dist/{chunk-V5GLL2JB.js.map → chunk-P3KJCYSR.js.map} +0 -0
  185. /package/dist/{chunk-6MV4JOCL.js.map → chunk-PSX7CT6V.js.map} +0 -0
  186. /package/dist/{chunk-B3Y3QMWI.js.map → chunk-TRTVOVHR.js.map} +0 -0
  187. /package/dist/{chunk-AOTCIUGX.js.map → chunk-TV5Y53R2.js.map} +0 -0
  188. /package/dist/{chunk-XATLUKXK.js.map → chunk-X2KUI7V3.js.map} +0 -0
  189. /package/dist/{chunk-DSL66I4N.js.map → chunk-Y5SNGMB7.js.map} +0 -0
  190. /package/dist/{chunk-7REKOTKG.js.map → chunk-YNOOZFIB.js.map} +0 -0
  191. /package/dist/{load-agent-defs-5J2MZLAA.js.map → load-agent-defs-JHLE6MAE.js.map} +0 -0
  192. /package/dist/{recompile-project-agents-FCJLHAE3.js.map → recompile-project-agents-ZPP5NOLZ.js.map} +0 -0
  193. /package/dist/{source-loader-GRBVNRAW.js.map → source-loader-RWZAYJ7E.js.map} +0 -0
package/CHANGELOG.md CHANGED
@@ -7,6 +7,67 @@ Each release has detailed notes in its own file under [`changelogs/`](./changelo
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
8
8
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
9
9
 
10
+ ## [0.165.0] - 2026-10-01
11
+
12
+ **A new installation's source folder is `.agents-inc/<provider>/`, a configuration can be installed onto OpenAI Codex, and `edit --from` applies a sub-agent's model and effort**
13
+
14
+ **The source folder.** No command moves an installation that already exists.
15
+
16
+ - **New installs write `.agents-inc/claude/`** instead of `.claude-src/`, at both scopes: `<project>/.agents-inc/claude/` and `~/.agents-inc/claude/`. The folder is named for the product and grouped by provider, so a Codex installation is `.agents-inc/codex/` with the same shape. One installation is exactly one provider and the folder is what says which; nothing inside `config.ts` records it.
17
+ - **Nothing in the CLI moves an existing installation.** An installation on `.claude-src/` is read and written where it is, by every command, indefinitely. There is no dual-write, no copy on first run, and no deprecation warning that changes where bytes land. Every message naming the config file names the folder the scope is actually on.
18
+ - **Moving a folder is a manual step, and there is no command for it.** The procedure is in `.ai-docs/reference/concepts/source-folder-layout.md`, along with its two gotchas: `config-types.ts`'s relative import gains a level, and a `.gitignore` with a bare `.agents-inc/` rule hides the moved config, so the next commit records only the deletion. Running `npx agents-inc compile` in the moved scope repairs the first.
19
+ - **`doctor` gains a `Layout` row per scope.** It reports which folder the scope is on, whether both folders are on disk and which one is being read, and any compiled sub-agent whose prompt names the folder this installation does not use. At `$HOME` it also lists the registered projects still on the old name, labelled as the registry rather than an inventory of the machine.
20
+ - **`init`, `edit`, `compile`, `update` and `eject` refuse outright while a scope holds both folders**, because the folder the resolver prefers can be the stale one. The refusal names the folder being read and asks you to merge by hand. `doctor` and `list` keep working in that state. A scope on the old name alone is a supported state, not a fault: no write command prints a line about it, and `doctor`'s `Layout` row reports it as a warning naming the move by hand.
21
+ - **Installed output keeps every path it had.** Claude still installs into `.claude/`, skills into `.claude/skills/` and compiled sub-agents into `.claude/agents/`. A shared payload names no source folder, so the rename reaches no share id.
22
+ - **A marketplace source repository gains no provider segment.** A repo declares itself in `.agents-inc/config.ts`, and one still declaring `.claude-src/config.ts` is read there with no sunset.
23
+ - **`uninstall` removes the `.agents-inc/` parent** once the last provider folder under it is gone, and only when the parent is otherwise empty. If you keep your own files under it, the run says it kept the parent rather than leaving it behind silently.
24
+ - **A compiled `agent-summoner` names the source folder its own installation uses**, substituted at compile time, rather than a literal that is wrong on half of all installs.
25
+
26
+ **OpenAI Codex as a second provider.** You pick the provider in the web app, and the folder on disk is the only record of which one it is.
27
+
28
+ - **The provider is chosen in the editor, not in the terminal.** A `provider` row in the roster footer picks it, and the command the Install dialog hands you becomes `npx agents-inc init --from <id> --provider codex`. The terminal wizard gains no step. `--provider` without `--from` exits `INVALID_ARGS`, naming `--from` and the editor.
29
+ - **Where one scope holds an installation of each provider, `--provider` is the disambiguator.** `edit`, `uninstall`, `share`, `eject`, `compile` and `update` refuse rather than guess, naming both. `list --provider` picks one. `doctor` reports the state instead of refusing.
30
+ - **Codex offers three of the four placements**: plugin + global, eject + global, and eject + project. Eject + project lands at `<repo>/.agents/skills/<id>/`, a committed directory that reaches the model with no plugin, no marketplace and no trust entry. `plugin + project` is refused by name before anything is written, never quietly ejected instead. The refusal also fires when a config is read, so a `.agents-inc/claude/` copied to `.agents-inc/codex/` is caught.
31
+ - **Sixteen of the eighteen sub-agents compile, as Codex agent role definitions in TOML.** `agent-summoner` and `skill-summoner` are left out for v1, and every Codex install says so once. A role file carries no `model`, since every model a sub-agent can name is Claude's and Codex refuses to start a role naming one; each sub-agent runs on your Codex session's model. Six settings have no Codex expression, and each compile says so once.
32
+ - **`doctor` gains a `Placements Offered` row.** It reports a placement no host can fill and a scope holding two installations, the states other commands stop on, and never refuses.
33
+ - **`uninstall` counts the plugins it observed being removed**, on either host, rather than the plugins it asked about.
34
+ - **What Codex cannot do**: install a plugin for one project; restrict a sub-agent's tools (`tools` is a configuration struct, not an allowlist, so the read-only sub-agents can still edit files); preload a named list of skills; or carry a sub-agent's `disallowedTools`, which is dropped without a mention (no shipped sub-agent declares one).
35
+ - CLI-893: a Codex project's role files need that project trusted in your global Codex config, so the install writes that entry and prints exactly what it wrote. Any later `compile` does the same. A `trust_level` you already set, `"untrusted"` included, is left as you set it and reported. Each of the four ways that trust fails is silent: no entry, `untrusted`, a trailing slash on the path, or the entry written inside the project.
36
+ - CLI-895: an ejected skill's copy is deleted from the folder Codex reads. Switching it to plugin or deselecting it in `edit` used to leave the copy, so Codex read the skill twice.
37
+ - CLI-896: `uninstall` on Codex removes the role files it compiled (`.codex/agents/*.toml`) and leaves a role you wrote yourself, as it does `.claude/agents/*.md` on Claude. `compile` prunes a deselected Codex role the same way.
38
+
39
+ **Fixes on either host.**
40
+
41
+ - CLI-898: the editor's output preview lists each skill with the description the install writes, its `SKILL.md` description, rather than the wizard's short label. `build marketplace` puts it in `catalog.json` as `activationDescription`; a catalogue built before this release still previews the label.
42
+ - CLI-899: the loading spinner's last frame no longer survives into the `init` and `edit` wizards. A frame held by Ink's render throttle was painted after the clear, so the full-height wizard lost its top line.
43
+ - `compile` no longer refuses an installation with sub-agents and no skills, which the editor makes and `init --from` installs. A config that declares skills, none of which are on disk, is still refused.
44
+ - `edit --from` applies a configuration whose only difference is a sub-agent's `model` or `effort`, and reports it as its own line. It used to print `No changes made.` and write nothing. Re-selecting a sub-agent in the wizard no longer reverts its model and effort.
45
+ - A local marketplace directory whose path contains a space, such as `/Users/My Name/…`, now reaches `claude` and `codex` instead of being refused by a pattern written for `owner/repo` references.
46
+
47
+ **On Claude Code, a project sub-agent's completion gate runs only in a folder you have trusted.**
48
+
49
+ - Claude Code drops the gate otherwise, without a word anywhere you would look. So `init` and `compile` now say so in one line naming the folder. They print it when a compiled sub-agent in `<project>/.claude/agents/` has at least one hook under `Stop` or `SubagentStop`, and `~/.claude.json` has no record of you accepting that folder's trust dialog. A run from the home directory never prints it. The CLI never accepts the dialog for you.
50
+
51
+ **Withdrawn before release.**
52
+
53
+ - **`npx agents-inc migrate`.** An earlier draft of this release carried a command that performed the move, with `--adopt`, `--dry-run`, a forwarding stub at the old path, a registered-project rewrite and a git-visibility probe. It was never requested and is deleted whole; nothing in the CLI hands out the invocation.
54
+ - A per-agent lint and check gate, including the `check:` map and a Codex gate plugin with its hook trust, was reverted before release on the owner's 2026-10-01 ruling, and 0.163.0's typecheck `Stop` hook is unchanged.
55
+
56
+ See [changelogs/0.165.0.md](./changelogs/0.165.0.md) for full details.
57
+
58
+ ## [0.164.0] - 2026-09-06
59
+
60
+ **Every shipped sub-agent moves to the 2026-09-03 doctrine, the web skills say when to reach for them, and the editor takes the 2026-09-05 design refresh**
61
+
62
+ - All 18 shipped sub-agents now read in the doctrine's voice: 14,236 markdown lines down to 9,026 (−37%), 228 `**(You MUST …)**` down to 1 — the one that names the retired form in order to forbid it — and 26 `CRITICAL:` headings and 15 "Failure to follow…" closers to zero
63
+ - A `web-*` skill's `usageGuidance` names the situation it is for rather than restating its title. A third pass over the marketplace closed two surfaces the first two never reached, found by byte-comparing every file against the baseline rather than trusting an estimate: 38 files and 10,190 lines had never been opened, against an estimate of ~4,000
64
+ - The editor takes the third in-place design refresh — `selected` onto the skills hinge stating its own value, equal full-width domain cells at one type size, the first stack cell as the reset, a full-row assignment pulse, three action cells over Install, fullscreen and a splitter on the output preview, one icon set, receding disabled cells, a warmer column and an amber field on the active nav item (EDITOR-09)
65
+ - EDITOR-75: `pb-30` under a `sticky bottom-0` dock was not clearance — a sticky box is confined to its containing block's content box, so the padding lay entirely below anywhere the dock could rest, parking it 132px above the viewport floor with 132px of grid off-screen
66
+ - EDITOR-76: every bordered text field is now the whole of its own border. All five take the caret from every corner, and the caret lands where the pointer went rather than at the end — the half a `<label>` wrapper satisfies by accident
67
+ - EDITOR-79: the domain strip navigates rather than filters. Clicking `API` no longer hides `web`, `?domain=` is an anchor rather than a filter, and a tab reports `aria-current` instead of a pressed state it could never turn off
68
+
69
+ See [changelogs/0.164.0.md](./changelogs/0.164.0.md) for full details.
70
+
10
71
  ## [0.163.0] - 2026-09-04
11
72
 
12
73
  **Every compiled sub-agent gets a slimmer, cacheable prompt and a deterministic completion gate, and a skill's own trigger sentence finally reaches the agent that loads it**
package/README.md CHANGED
@@ -61,7 +61,6 @@ npx agents-inc init
61
61
  | [Editing your config](https://github.com/agents-inc/agents-inc/blob/main/apps/www/src/content/docs/docs/guides/editing-config.md) | Skill mappings, preloaded vs dynamic loading, and config structure |
62
62
  | [Customizing subagents](https://github.com/agents-inc/agents-inc/blob/main/apps/www/src/content/docs/docs/guides/customizing-subagents.md) | Eject and modify partials, templates, and skills |
63
63
  | [Writing custom skills and subagents](https://github.com/agents-inc/agents-inc/blob/main/apps/www/src/content/docs/docs/guides/writing-custom-skills.md) | Author skills and subagents from scratch or iterate on existing ones |
64
- | [Importing third-party skills](https://github.com/agents-inc/agents-inc/blob/main/apps/www/src/content/docs/docs/guides/importing-skills.md) | Install skills from external repositories |
65
64
  | [Creating a marketplace](https://github.com/agents-inc/agents-inc/blob/main/apps/www/src/content/docs/docs/guides/creating-a-marketplace.md) | Build a personal or org-level marketplace with curated skills |
66
65
  | [Documenting your codebase](https://github.com/agents-inc/agents-inc/blob/main/apps/www/src/content/docs/docs/guides/documenting-your-codebase.md) | Generate and maintain AI-focused reference documentation |
67
66
 
@@ -73,13 +72,13 @@ Over 200 skills across 9 domains:
73
72
  | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
74
73
  | Web | React, Vue, Angular, Svelte, SolidJS, Next.js, Remix, Nuxt, SvelteKit, Astro, Qwik, Tailwind, SCSS Modules, Zustand, Redux, Pinia, Vitest, Playwright, Storybook, and more |
75
74
  | API | Hono, Express, Fastify, NestJS, Elysia, Drizzle, Prisma, PostgreSQL, MongoDB, Redis, Stripe, and more |
76
- | AI | Anthropic SDK, OpenAI SDK, Vercel AI SDK, LangChain, LlamaIndex, Pinecone, ChromaDB, and more |
77
- | Mobile | React Native, Expo |
78
- | Desktop | Tauri, Electron, Electron Forge, electron-updater |
75
+ | AI | Anthropic SDK, OpenAI SDK, Vercel AI SDK, LangChain, LlamaIndex, and more |
76
+ | Mobile | React Native, Expo, and more |
77
+ | Desktop | Tauri, Electron, Electron Forge, electron-updater, and more |
79
78
  | CLI | oclif + Ink, Commander, Clack |
80
- | Infra | Docker, GitHub Actions, Cloudflare Workers |
81
- | Shared | Turborepo, ESLint + Prettier, Code Reviewing, Auth Security |
82
- | Meta | Research Methodology, CLI Reviewing |
79
+ | Infra | Docker, GitHub Actions, Cloudflare Workers, and more |
80
+ | Shared | Turborepo, ESLint + Prettier, Auth Security, and more |
81
+ | Meta | Code Reviewing, CLI Reviewing, Research Methodology, and more |
83
82
 
84
83
  Browse the full catalog on the [Plugin Marketplace](https://github.com/agents-inc/skills).
85
84
 
@@ -107,7 +106,7 @@ Each subagent is composed from modular partials (role, workflow, output format)
107
106
  | `edit` | Modify skill selection via the interactive wizard |
108
107
  | `compile` | Recompile subagents after changes |
109
108
  | `update` | Refresh the marketplaces this installation uses (ejected skills are yours) |
110
- | `search` | Search skills across all sources |
109
+ | `search` | Search this installation's marketplace and your local skills |
111
110
 
112
111
  ### Customization
113
112
 
@@ -119,7 +118,7 @@ Each subagent is composed from modular partials (role, workflow, output format)
119
118
 
120
119
  | Command | Description |
121
120
  | ------------------- | ---------------------------------------------- |
122
- | `build marketplace` | Generate `marketplace.json` from source skills |
121
+ | `build marketplace` | Generate `marketplace.json` from built plugins |
123
122
  | `build plugins` | Build skill and agent plugins for distribution |
124
123
 
125
124
  ### Diagnostics
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getAgentDefinitions
4
- } from "./chunk-O664ITBD.js";
4
+ } from "./chunk-UR3RRTPO.js";
5
5
  import {
6
6
  loadMergedAgents
7
- } from "./chunk-XPDVAJDH.js";
7
+ } from "./chunk-IM45LLQX.js";
8
8
 
9
9
  // src/cli/lib/operations/project/load-agent-defs.ts
10
10
  async function loadAgentDefs() {
@@ -20,4 +20,4 @@ async function loadAgentDefs() {
20
20
  export {
21
21
  loadAgentDefs
22
22
  };
23
- //# sourceMappingURL=chunk-SMSB6YZW.js.map
23
+ //# sourceMappingURL=chunk-42TNV2TK.js.map
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ render
4
+ } from "./chunk-JFLVEFFP.js";
5
+
6
+ // src/cli/components/common/prompt-confirm.tsx
7
+ import "react";
8
+ async function promptValue(build, options) {
9
+ const answer = { settle: () => {
10
+ } };
11
+ const chosen = new Promise((resolve) => {
12
+ answer.settle = resolve;
13
+ });
14
+ const releaseInput = holdInputRaw(process.stdin);
15
+ try {
16
+ const instance = render(build((value2) => answer.settle(value2)));
17
+ instance.waitUntilExit().then(
18
+ () => answer.settle(options.onExit),
19
+ () => answer.settle(options.onExit)
20
+ );
21
+ const value = await chosen;
22
+ if (options.clearOnResolve) instance.clear();
23
+ instance.unmount();
24
+ return value;
25
+ } finally {
26
+ releaseInput();
27
+ }
28
+ }
29
+ function holdInputRaw(input) {
30
+ if (!input.isTTY) return () => {
31
+ };
32
+ const wasRaw = input.isRaw;
33
+ input.setRawMode(true);
34
+ return () => {
35
+ input.setRawMode(wasRaw);
36
+ };
37
+ }
38
+ async function promptConfirm(build) {
39
+ return promptValue(
40
+ (resolve) => build({ onConfirm: () => resolve("confirmed"), onCancel: () => resolve("cancelled") }),
41
+ { onExit: "cancelled" }
42
+ );
43
+ }
44
+
45
+ export {
46
+ promptValue,
47
+ promptConfirm
48
+ };
49
+ //# sourceMappingURL=chunk-4AAOOAIN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/components/common/prompt-confirm.tsx"],"sourcesContent":["import React from \"react\";\nimport { render } from \"../render.js\";\n\nexport type ConfirmHandlers = {\n onConfirm: () => void;\n onCancel: () => void;\n};\n\nexport type PromptValueOptions<T> = {\n /** Value resolved when the Ink app exits before any callback fires (Ctrl+C / render failure). */\n onExit: T;\n /** Call instance.clear() before unmount — repaints a clean terminal (used by the dashboard). */\n clearOnResolve?: boolean;\n};\n\n/**\n * Renders an Ink prompt and resolves with the value produced by its callbacks.\n * First-wins: the first resolve (a callback or the app-exit fallback) settles the\n * promise; later resolves are ignored. The element is unmounted at the resolution\n * site (optionally cleared first). Callers own exit policy (exit codes, logging).\n *\n * A prompt may resolve before `render()` has handed its instance back: Ink flushes\n * mount effects synchronously, so a component that settles on mount reaches the\n * callback first. The teardown is then run the moment the instance exists — one\n * statement later, on the same tick — so callers that resolve from a keypress see\n * exactly the order they always did.\n *\n * The terminal is raw before the first frame is painted, through {@link holdInputRaw}.\n */\nexport async function promptValue<T>(\n build: (resolve: (value: T) => void) => React.ReactElement,\n options: PromptValueOptions<T>,\n): Promise<T> {\n // The promise's own `resolve`, lifted out so the render can be a plain `const` below\n // rather than a binding the element's callbacks would reach while `render()` is still\n // producing it. The seed is never called: a Promise executor runs synchronously, so the\n // next statement has already replaced it by the time anything can answer.\n const answer: { settle: (value: T) => void } = { settle: () => {} };\n const chosen = new Promise<T>((resolve) => {\n answer.settle = resolve;\n });\n\n const releaseInput = holdInputRaw(process.stdin);\n try {\n const instance = render(build((value) => answer.settle(value)));\n\n // App exit without a callback (e.g. Ctrl+C) or a render failure counts as the\n // fallback; the promise is first-wins, so a prior callback value is unaffected.\n instance.waitUntilExit().then(\n () => answer.settle(options.onExit),\n () => answer.settle(options.onExit),\n );\n\n const value = await chosen;\n\n // Taken down here rather than at each resolve site: it then happens exactly once,\n // whichever of the three endings settled the promise, and always with an instance in\n // hand — including the ending that arrives before `render()` has returned one.\n if (options.clearOnResolve) instance.clear();\n instance.unmount();\n\n return value;\n } finally {\n releaseInput();\n }\n}\n\n/**\n * Puts a terminal's input into raw mode for as long as a prompt is up, and returns what puts it\n * back the way it was. Input that is not a terminal has no mode to set, and is left alone.\n *\n * Ink takes raw mode only from `useInput`'s effect, which runs after the frame it belongs to has\n * been written. A key pressed between the two reaches a terminal still in cooked mode: the kernel\n * echoes an Enter as a line break, which scrolls a full-height frame up by a line that is never\n * repainted, and turns it into `\\n`, which Ink reads as `enter` — and every prompt here waits for\n * `return`. Taken here, before the render, the key is held as typed until Ink starts reading.\n *\n * Ink's own handling is unchanged: `useInput` still sets raw mode and Ink still turns it off at\n * unmount. This closes the gap before the effect, and restores the mode for a prompt that never\n * took input or failed to render, where Ink has nothing to turn off.\n *\n * Prompts only, never every render: raw mode turns Ctrl+C into a byte for whoever reads input,\n * and the spinner reads none, so a load under it could no longer be interrupted.\n */\nfunction holdInputRaw(input: NodeJS.ReadStream): () => void {\n if (!input.isTTY) return () => {};\n const wasRaw = input.isRaw;\n input.setRawMode(true);\n return () => {\n input.setRawMode(wasRaw);\n };\n}\n\n/**\n * Renders a confirm UI and resolves with the user's choice. The element is\n * unmounted as soon as either handler fires; a clean exit or render failure\n * without a choice resolves as \"cancelled\". Callers own exit policy.\n */\nexport async function promptConfirm(\n build: (handlers: ConfirmHandlers) => React.ReactElement,\n): Promise<\"confirmed\" | \"cancelled\"> {\n return promptValue<\"confirmed\" | \"cancelled\">(\n (resolve) =>\n build({ onConfirm: () => resolve(\"confirmed\"), onCancel: () => resolve(\"cancelled\") }),\n { onExit: \"cancelled\" },\n );\n}\n"],"mappings":";;;;;;AAAA,OAAkB;AA6BlB,eAAsB,YACpB,OACA,SACY;AAKZ,QAAM,SAAyC,EAAE,QAAQ,MAAM;AAAA,EAAC,EAAE;AAClE,QAAM,SAAS,IAAI,QAAW,CAAC,YAAY;AACzC,WAAO,SAAS;AAAA,EAClB,CAAC;AAED,QAAM,eAAe,aAAa,QAAQ,KAAK;AAC/C,MAAI;AACF,UAAM,WAAW,OAAO,MAAM,CAACA,WAAU,OAAO,OAAOA,MAAK,CAAC,CAAC;AAI9D,aAAS,cAAc,EAAE;AAAA,MACvB,MAAM,OAAO,OAAO,QAAQ,MAAM;AAAA,MAClC,MAAM,OAAO,OAAO,QAAQ,MAAM;AAAA,IACpC;AAEA,UAAM,QAAQ,MAAM;AAKpB,QAAI,QAAQ,eAAgB,UAAS,MAAM;AAC3C,aAAS,QAAQ;AAEjB,WAAO;AAAA,EACT,UAAE;AACA,iBAAa;AAAA,EACf;AACF;AAmBA,SAAS,aAAa,OAAsC;AAC1D,MAAI,CAAC,MAAM,MAAO,QAAO,MAAM;AAAA,EAAC;AAChC,QAAM,SAAS,MAAM;AACrB,QAAM,WAAW,IAAI;AACrB,SAAO,MAAM;AACX,UAAM,WAAW,MAAM;AAAA,EACzB;AACF;AAOA,eAAsB,cACpB,OACoC;AACpC,SAAO;AAAA,IACL,CAAC,YACC,MAAM,EAAE,WAAW,MAAM,QAAQ,WAAW,GAAG,UAAU,MAAM,QAAQ,WAAW,EAAE,CAAC;AAAA,IACvF,EAAE,QAAQ,YAAY;AAAA,EACxB;AACF;","names":["value"]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useFocusedListItem
4
- } from "./chunk-J27W6TXS.js";
4
+ } from "./chunk-XQHZPINL.js";
5
5
  import {
6
6
  HOTKEY_TOGGLE_LABELS,
7
7
  KEY_SPACE,
@@ -9,13 +9,13 @@ import {
9
9
  } from "./chunk-WTJ3AY33.js";
10
10
  import {
11
11
  useSectionScroll
12
- } from "./chunk-ABAZMMG4.js";
12
+ } from "./chunk-CK5RM3DJ.js";
13
13
  import {
14
14
  getSkillById
15
- } from "./chunk-AVK2HEKB.js";
15
+ } from "./chunk-GKPW33FP.js";
16
16
  import {
17
17
  CLI_COLORS
18
- } from "./chunk-UITEKY2R.js";
18
+ } from "./chunk-AQXKAPCD.js";
19
19
 
20
20
  // src/cli/components/wizard/category-grid.tsx
21
21
  import { useCallback as useCallback2, useEffect as useEffect2 } from "react";
@@ -33,6 +33,7 @@ function useCategoryGridInput({
33
33
  categories,
34
34
  focusedRow,
35
35
  focusedCol,
36
+ currentFocus,
36
37
  setFocused,
37
38
  moveFocus,
38
39
  onToggle,
@@ -55,8 +56,9 @@ function useCategoryGridInput({
55
56
  return;
56
57
  }
57
58
  if (key.tab && !key.shift) {
58
- const nextSection = findNextIndex(categories, focusedRow);
59
- if (nextSection !== focusedRow) {
59
+ const { row } = currentFocus();
60
+ const nextSection = findNextIndex(categories, row);
61
+ if (nextSection !== row) {
60
62
  setFocused(nextSection, 0);
61
63
  }
62
64
  return;
@@ -66,9 +68,11 @@ function useCategoryGridInput({
66
68
  return;
67
69
  }
68
70
  if (input === KEY_SPACE) {
69
- const currentOption = currentOptions[focusedCol];
70
- if (currentRow && currentOption) {
71
- onToggle(currentRow.id, currentOption.id);
71
+ const { row, col } = currentFocus();
72
+ const category = categories[row];
73
+ const option = category?.options[col];
74
+ if (category && option) {
75
+ onToggle(category.id, option.id);
72
76
  }
73
77
  return;
74
78
  }
@@ -204,7 +208,7 @@ var CategoryGrid = ({
204
208
  },
205
209
  [showLabels, onToggleLabels, onFocusChange]
206
210
  );
207
- const { focusedRow, focusedCol, setFocused, moveFocus } = useFocusedListItem(
211
+ const { focusedRow, focusedCol, currentFocus, setFocused, moveFocus } = useFocusedListItem(
208
212
  categories.length,
209
213
  getColCount,
210
214
  {
@@ -219,6 +223,7 @@ var CategoryGrid = ({
219
223
  categories,
220
224
  focusedRow,
221
225
  focusedCol,
226
+ currentFocus,
222
227
  setFocused,
223
228
  moveFocus,
224
229
  onToggle,
@@ -260,4 +265,4 @@ var CategoryGrid = ({
260
265
  export {
261
266
  CategoryGrid
262
267
  };
263
- //# sourceMappingURL=chunk-NQXO4LR3.js.map
268
+ //# sourceMappingURL=chunk-4C7GL4F7.js.map
@@ -0,0 +1 @@
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"]}
@@ -4,11 +4,11 @@ import {
4
4
  } from "./chunk-TSIU7DME.js";
5
5
  import {
6
6
  CategoryGrid
7
- } from "./chunk-NQXO4LR3.js";
7
+ } from "./chunk-4C7GL4F7.js";
8
8
  import {
9
9
  buildCategoriesForDomain,
10
10
  useWizardStore
11
- } from "./chunk-K5ITEA3I.js";
11
+ } from "./chunk-UBSSIIWD.js";
12
12
 
13
13
  // src/cli/components/wizard/step-build.tsx
14
14
  import { Box, useInput } from "ink";
@@ -89,4 +89,4 @@ var StepBuild = ({
89
89
  export {
90
90
  StepBuild
91
91
  };
92
- //# sourceMappingURL=chunk-Q4IRRVXK.js.map
92
+ //# sourceMappingURL=chunk-57KU6SPH.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CLI_COLORS
4
- } from "./chunk-UITEKY2R.js";
4
+ } from "./chunk-AQXKAPCD.js";
5
5
 
6
6
  // src/cli/components/wizard/wizard-tabs.tsx
7
7
  import { Box, Text } from "ink";
@@ -165,4 +165,4 @@ export {
165
165
  wizardTabsFor,
166
166
  WizardTabs
167
167
  };
168
- //# sourceMappingURL=chunk-S63LXWMC.js.map
168
+ //# sourceMappingURL=chunk-5M3PQNXI.js.map
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  useRowScroll
4
- } from "./chunk-YRS3QZP2.js";
4
+ } from "./chunk-CEBDGZID.js";
5
5
  import {
6
6
  KEY_SPACE
7
7
  } from "./chunk-WTJ3AY33.js";
8
8
  import {
9
9
  CLI_COLORS,
10
10
  UI_SYMBOLS
11
- } from "./chunk-UITEKY2R.js";
11
+ } from "./chunk-AQXKAPCD.js";
12
12
 
13
13
  // src/cli/components/wizard/checkbox-grid.tsx
14
14
  import { Box, Text, useInput } from "ink";
15
- import { useState } from "react";
15
+ import { useRef, useState } from "react";
16
16
  import { jsx, jsxs } from "react/jsx-runtime";
17
17
  var CheckboxGrid = ({
18
18
  subtitle,
@@ -25,18 +25,25 @@ var CheckboxGrid = ({
25
25
  emptyMessage = "Please select at least one item"
26
26
  }) => {
27
27
  const totalItems = items.length + 1;
28
- const [focusedIndex, setFocusedIndex] = useState(0);
28
+ const [focusedIndex, setRenderedIndex] = useState(0);
29
+ const focusedIndexRef = useRef(focusedIndex);
30
+ const focus = (index) => {
31
+ focusedIndexRef.current = index;
32
+ setRenderedIndex(index);
33
+ };
29
34
  useInput((input, key) => {
30
35
  if (key.escape) {
31
36
  onBack();
32
37
  return;
33
38
  }
34
39
  if (key.upArrow || input === "k") {
35
- setFocusedIndex((prev) => prev <= 0 ? totalItems - 1 : prev - 1);
40
+ const prev = focusedIndexRef.current;
41
+ focus(prev <= 0 ? totalItems - 1 : prev - 1);
36
42
  return;
37
43
  }
38
44
  if (key.downArrow || input === "j") {
39
- setFocusedIndex((prev) => prev >= totalItems - 1 ? 0 : prev + 1);
45
+ const prev = focusedIndexRef.current;
46
+ focus(prev >= totalItems - 1 ? 0 : prev + 1);
40
47
  return;
41
48
  }
42
49
  if (key.return) {
@@ -44,7 +51,7 @@ var CheckboxGrid = ({
44
51
  return;
45
52
  }
46
53
  if (input === KEY_SPACE) {
47
- const item = items[focusedIndex];
54
+ const item = items[focusedIndexRef.current];
48
55
  if (item) {
49
56
  onToggle(item.id);
50
57
  }
@@ -108,4 +115,4 @@ var CheckboxGrid = ({
108
115
  export {
109
116
  CheckboxGrid
110
117
  };
111
- //# sourceMappingURL=chunk-3WVH2CTK.js.map
118
+ //# sourceMappingURL=chunk-5P26XBZO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/components/wizard/checkbox-grid.tsx"],"sourcesContent":["import { Box, Text, useInput } from \"ink\";\nimport React, { useRef, useState } from \"react\";\nimport { CLI_COLORS, UI_SYMBOLS } from \"../../consts.js\";\nimport { useRowScroll } from \"../hooks/use-row-scroll.js\";\nimport { KEY_SPACE } from \"./hotkeys.js\";\n\nexport type CheckboxItem<T extends string = string> = {\n id: T;\n label: string;\n description: string;\n};\n\nexport type CheckboxGridProps<T extends string = string> = {\n subtitle?: string;\n items: CheckboxItem<T>[];\n selectedIds: T[];\n /** Available height in terminal lines for the scrollable viewport. 0 = no constraint. */\n availableHeight?: number;\n onToggle: (id: T) => void;\n onContinue: () => void;\n onBack: () => void;\n /** Message shown when nothing is selected */\n emptyMessage?: string;\n};\n\nexport const CheckboxGrid = <T extends string = string>({\n subtitle,\n items,\n selectedIds,\n availableHeight = 0,\n onToggle,\n onContinue,\n onBack,\n emptyMessage = \"Please select at least one item\",\n}: CheckboxGridProps<T>): React.ReactElement => {\n // Items + continue option at the end\n const totalItems = items.length + 1;\n const [focusedIndex, setRenderedIndex] = useState(0);\n // The focus Space reads, written the moment it moves rather than once the move renders: a Space\n // that follows a move in the same chunk is handled before that move renders.\n const focusedIndexRef = useRef(focusedIndex);\n\n const focus = (index: number): void => {\n focusedIndexRef.current = index;\n setRenderedIndex(index);\n };\n\n useInput((input, key) => {\n if (key.escape) {\n onBack();\n return;\n }\n\n if (key.upArrow || input === \"k\") {\n const prev = focusedIndexRef.current;\n focus(prev <= 0 ? totalItems - 1 : prev - 1);\n return;\n }\n\n if (key.downArrow || input === \"j\") {\n const prev = focusedIndexRef.current;\n focus(prev >= totalItems - 1 ? 0 : prev + 1);\n return;\n }\n\n if (key.return) {\n onContinue();\n return;\n }\n\n if (input === KEY_SPACE) {\n const item = items[focusedIndexRef.current];\n if (item) {\n onToggle(item.id);\n }\n }\n });\n\n // The Domains grid clips silently: `useRowScroll` exposes no hidden-line\n // counts and this view renders no `ScrollAffordance`, by owner decision — see\n // the hook's doc comment.\n // When focus is on continue (past items), scroll to show last items\n const effectiveRow = focusedIndex >= items.length ? items.length - 1 : focusedIndex;\n const { scrollEnabled, scrollTop } = useRowScroll({\n focusedIndex: effectiveRow,\n itemCount: items.length,\n availableHeight,\n });\n\n const itemElements = items.map((item, index) => {\n const isFocused = index === focusedIndex;\n const isSelected = selectedIds.includes(item.id);\n const checkbox = isSelected ? \"[\\u2713]\" : \"[ ]\";\n const pointer = isFocused ? UI_SYMBOLS.CHEVRON : UI_SYMBOLS.CHEVRON_SPACER;\n\n return (\n <Text key={item.id}>\n <Text {...(isFocused && { color: CLI_COLORS.PRIMARY })}>{pointer}</Text>\n <Text {...((isSelected || isFocused) && { color: CLI_COLORS.PRIMARY })} bold={isFocused}>\n {\" \"}\n {checkbox} {item.label}\n </Text>\n {item.description && (\n <Text dimColor>\n {\" \"}\n {item.description}\n </Text>\n )}\n </Text>\n );\n });\n\n const footerElement =\n selectedIds.length > 0 ? (\n <Text>\n {\"\\n\"}Selected: <Text color={CLI_COLORS.WARNING}>{selectedIds.join(\", \")}</Text>\n </Text>\n ) : emptyMessage ? (\n <Text dimColor>\n {\"\\n\"}\n {emptyMessage}\n </Text>\n ) : null;\n\n return (\n <Box flexDirection=\"column\">\n {subtitle && <Text dimColor>{subtitle}</Text>}\n <Box\n flexDirection=\"column\"\n {...(scrollEnabled && { height: availableHeight, overflow: \"hidden\" as const })}\n >\n <Box\n flexDirection=\"column\"\n marginTop={scrollTop > 0 ? -scrollTop : 0}\n {...(scrollEnabled && { flexShrink: 0 })}\n >\n {itemElements}\n </Box>\n </Box>\n {footerElement}\n </Box>\n );\n};\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,gBAAgB;AACpC,SAAgB,QAAQ,gBAAgB;AAgGhC,cACA,YADA;AAxED,IAAM,eAAe,CAA4B;AAAA,EACtD;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AACjB,MAAgD;AAE9C,QAAM,aAAa,MAAM,SAAS;AAClC,QAAM,CAAC,cAAc,gBAAgB,IAAI,SAAS,CAAC;AAGnD,QAAM,kBAAkB,OAAO,YAAY;AAE3C,QAAM,QAAQ,CAAC,UAAwB;AACrC,oBAAgB,UAAU;AAC1B,qBAAiB,KAAK;AAAA,EACxB;AAEA,WAAS,CAAC,OAAO,QAAQ;AACvB,QAAI,IAAI,QAAQ;AACd,aAAO;AACP;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,UAAU,KAAK;AAChC,YAAM,OAAO,gBAAgB;AAC7B,YAAM,QAAQ,IAAI,aAAa,IAAI,OAAO,CAAC;AAC3C;AAAA,IACF;AAEA,QAAI,IAAI,aAAa,UAAU,KAAK;AAClC,YAAM,OAAO,gBAAgB;AAC7B,YAAM,QAAQ,aAAa,IAAI,IAAI,OAAO,CAAC;AAC3C;AAAA,IACF;AAEA,QAAI,IAAI,QAAQ;AACd,iBAAW;AACX;AAAA,IACF;AAEA,QAAI,UAAU,WAAW;AACvB,YAAM,OAAO,MAAM,gBAAgB,OAAO;AAC1C,UAAI,MAAM;AACR,iBAAS,KAAK,EAAE;AAAA,MAClB;AAAA,IACF;AAAA,EACF,CAAC;AAMD,QAAM,eAAe,gBAAgB,MAAM,SAAS,MAAM,SAAS,IAAI;AACvE,QAAM,EAAE,eAAe,UAAU,IAAI,aAAa;AAAA,IAChD,cAAc;AAAA,IACd,WAAW,MAAM;AAAA,IACjB;AAAA,EACF,CAAC;AAED,QAAM,eAAe,MAAM,IAAI,CAAC,MAAM,UAAU;AAC9C,UAAM,YAAY,UAAU;AAC5B,UAAM,aAAa,YAAY,SAAS,KAAK,EAAE;AAC/C,UAAM,WAAW,aAAa,aAAa;AAC3C,UAAM,UAAU,YAAY,WAAW,UAAU,WAAW;AAE5D,WACE,qBAAC,QACC;AAAA,0BAAC,QAAM,GAAI,aAAa,EAAE,OAAO,WAAW,QAAQ,GAAK,mBAAQ;AAAA,MACjE,qBAAC,QAAM,IAAK,cAAc,cAAc,EAAE,OAAO,WAAW,QAAQ,GAAI,MAAM,WAC3E;AAAA;AAAA,QACA;AAAA,QAAS;AAAA,QAAE,KAAK;AAAA,SACnB;AAAA,MACC,KAAK,eACJ,qBAAC,QAAK,UAAQ,MACX;AAAA;AAAA,QACA,KAAK;AAAA,SACR;AAAA,SAVO,KAAK,EAYhB;AAAA,EAEJ,CAAC;AAED,QAAM,gBACJ,YAAY,SAAS,IACnB,qBAAC,QACE;AAAA;AAAA,IAAK;AAAA,IAAU,oBAAC,QAAK,OAAO,WAAW,SAAU,sBAAY,KAAK,IAAI,GAAE;AAAA,KAC3E,IACE,eACF,qBAAC,QAAK,UAAQ,MACX;AAAA;AAAA,IACA;AAAA,KACH,IACE;AAEN,SACE,qBAAC,OAAI,eAAc,UAChB;AAAA,gBAAY,oBAAC,QAAK,UAAQ,MAAE,oBAAS;AAAA,IACtC;AAAA,MAAC;AAAA;AAAA,QACC,eAAc;AAAA,QACb,GAAI,iBAAiB,EAAE,QAAQ,iBAAiB,UAAU,SAAkB;AAAA,QAE7E;AAAA,UAAC;AAAA;AAAA,YACC,eAAc;AAAA,YACd,WAAW,YAAY,IAAI,CAAC,YAAY;AAAA,YACvC,GAAI,iBAAiB,EAAE,YAAY,EAAE;AAAA,YAErC;AAAA;AAAA,QACH;AAAA;AAAA,IACF;AAAA,IACC;AAAA,KACH;AAEJ;","names":[]}