@skilly-hand/skilly-hand 0.23.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,26 @@ All notable changes to this project are documented in this file.
16
16
  ### Removed
17
17
  - _None._
18
18
 
19
+ ## [0.23.1] - 2026-04-13
20
+ [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.23.1)
21
+
22
+ ### Added
23
+ - Added an Inquirer-powered guided home launcher with searchable command discovery and a built-in Command Guide.
24
+ - Added shared CLI command metadata in `packages/cli/src/command-registry.js` and wired help/interactive flows to consume it.
25
+ - Added `scripts/review-rangers-check.mjs` plus `npm run review:rangers`, and integrated it into `verify:publish`.
26
+
27
+ ### Changed
28
+ - Updated CLI help output to include a dedicated Commands section sourced from the shared command registry.
29
+ - Updated docs to describe the new interactive launcher and mark `--classic` as a deprecated compatibility flag.
30
+ - Updated dependency policy and script contract tests for the new release gates and runtime dependency set.
31
+
32
+ ### Fixed
33
+ - Added guard tests to prevent reintroducing `react`/`ink` runtime dependencies after the interactive stack migration.
34
+
35
+ ### Removed
36
+ - Removed the legacy Ink full-screen UI implementation (`packages/cli/src/ink-ui.js`) and related tests.
37
+ - Removed runtime dependencies on `ink` and `react` in favor of `inquirer`.
38
+
19
39
  ## [0.23.0] - 2026-04-12
20
40
  [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.23.0)
21
41
 
package/README.md CHANGED
@@ -36,7 +36,8 @@
36
36
  npx skilly-hand
37
37
  ```
38
38
 
39
- `npx skilly-hand` opens a full-screen skilly-hand terminal UI when running in a TTY.
39
+ `npx skilly-hand` opens an interactive prompt workflow when running in a TTY.
40
+ The guided home supports type-to-filter command discovery and includes a built-in Command Guide.
40
41
 
41
42
  ---
42
43
 
@@ -55,7 +56,7 @@ npx skilly-hand
55
56
  | Flag | Description |
56
57
  | ---- | ----------- |
57
58
  | `--json` | Emit machine-readable output and disable interactive prompts |
58
- | `--classic` | Force plain text command mode and skip full-screen TUI |
59
+ | `--classic` | Deprecated compatibility flag that keeps plain command mode (launcher disabled) |
59
60
  | `--yes`, `-y` | Skip confirmation prompts for mutating commands (`install`, `uninstall`) |
60
61
  | `--dry-run` | Preview install plan without writing files |
61
62
  | `--agent`, `-a <name>` | Target a specific assistant (repeatable; e.g. `--agent claude --agent cursor`) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/skilly-hand",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -35,7 +35,8 @@
35
35
  "security:check": "node ./scripts/security-check.mjs --strict-deps",
36
36
  "verify:packlist": "node ./scripts/verify-packlist.mjs",
37
37
  "verify:versions": "node ./scripts/verify-versions.mjs",
38
- "verify:publish": "npm run verify:versions && npm run deps:policy:check && npm run security:check && npm run catalog:check && npm test && npm run verify:packlist",
38
+ "review:rangers": "node ./scripts/review-rangers-check.mjs",
39
+ "verify:publish": "npm run verify:versions && npm run deps:policy:check && npm run security:check && npm run catalog:check && npm test && npm run review:rangers && npm run verify:packlist",
39
40
  "publish:prepare": "npm run verify:publish && npm pack --dry-run --json",
40
41
  "publish:otp": "node ./scripts/publish-with-otp.mjs",
41
42
  "publish:next": "node ./scripts/publish-with-otp.mjs --tag next",
@@ -49,7 +50,6 @@
49
50
  "doctor": "node ./packages/cli/src/bin.js doctor"
50
51
  },
51
52
  "dependencies": {
52
- "ink": "7.0.0",
53
- "react": "19.2.5"
53
+ "inquirer": "13.4.1"
54
54
  }
55
55
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/catalog",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/cli",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,8 @@ import {
14
14
  } from "../../core/src/index.js";
15
15
  import { createTerminalRenderer } from "../../core/src/terminal.js";
16
16
  import { detectProject } from "../../detectors/src/index.js";
17
- import { confirmWithInk, launchInkApp } from "./ink-ui.js";
17
+ import { createInquirerInteractiveUi } from "./inquirer-ui.js";
18
+ import { formatHelpUsageLines, getCliCommands, getInteractiveCommands } from "./command-registry.js";
18
19
  import {
19
20
  createResultDoc,
20
21
  kvBlock,
@@ -90,20 +91,17 @@ export function parseArgs(argv) {
90
91
  }
91
92
 
92
93
  function buildHelpText(renderer, appVersion) {
93
- const usage = renderer.section("Usage", renderer.list([
94
- "npx skilly-hand # interactive launcher when running in a TTY",
95
- "npx skilly-hand install",
96
- "npx skilly-hand native setup",
97
- "npx skilly-hand detect",
98
- "npx skilly-hand list",
99
- "npx skilly-hand doctor",
100
- "npx skilly-hand uninstall"
101
- ], { bullet: "-" }));
94
+ const usage = renderer.section("Usage", renderer.list(formatHelpUsageLines(), { bullet: "-" }));
95
+
96
+ const commands = renderer.section("Commands", renderer.list(
97
+ getCliCommands().map((command) => `${command.usage} # ${command.description}`),
98
+ { bullet: "-" }
99
+ ));
102
100
 
103
101
  const flags = renderer.section("Flags", renderer.list([
104
102
  "--dry-run Show install plan without writing files",
105
103
  "--json Emit stable JSON output for automation",
106
- "--classic Force plain text command mode (skip full-screen TUI)",
104
+ "--classic Deprecated alias for plain command mode (launcher disabled)",
107
105
  "--yes, -y Skip install/uninstall confirmations",
108
106
  "--verbose, -v Reserved for future debug detail",
109
107
  "--agent, -a <name> standard|codex|claude|cursor|gemini|copilot|antigravity|windsurf|trae (repeatable)",
@@ -125,6 +123,7 @@ function buildHelpText(renderer, appVersion) {
125
123
  return renderer.joinBlocks([
126
124
  renderer.banner(appVersion),
127
125
  usage,
126
+ commands,
128
127
  flags,
129
128
  examples
130
129
  ]);
@@ -474,8 +473,11 @@ async function runInteractiveSession({
474
473
  appVersion,
475
474
  interactiveUi
476
475
  }) {
476
+ const interactiveCommands = getInteractiveCommands();
477
+
477
478
  await interactiveUi.launch({
478
479
  appVersion,
480
+ commands: interactiveCommands,
479
481
  actions: {
480
482
  async runCommandBundle(command) {
481
483
  if (command === "native-setup") {
@@ -738,10 +740,7 @@ export async function runCli({
738
740
  env = process.env,
739
741
  platform = process.platform,
740
742
  services: providedServices = {},
741
- interactiveUi = {
742
- launch: ({ appVersion, actions }) => launchInkApp({ appVersion, actions }),
743
- confirm: ({ message, defaultValue }) => confirmWithInk({ message, defaultValue })
744
- },
743
+ interactiveUi = createInquirerInteractiveUi(),
745
744
  appVersion = version,
746
745
  cwdResolver = process.cwd
747
746
  } = {}) {
@@ -754,15 +753,7 @@ export async function runCli({
754
753
  renderer.writeJson({
755
754
  command: command || "install",
756
755
  help: true,
757
- usage: [
758
- "npx skilly-hand",
759
- "npx skilly-hand install",
760
- "npx skilly-hand native setup",
761
- "npx skilly-hand detect",
762
- "npx skilly-hand list",
763
- "npx skilly-hand doctor",
764
- "npx skilly-hand uninstall"
765
- ]
756
+ usage: formatHelpUsageLines().map((line) => line.split("#")[0].trim())
766
757
  });
767
758
  return;
768
759
  }
@@ -773,6 +764,10 @@ export async function runCli({
773
764
 
774
765
  const cwd = path.resolve(flags.cwd || cwdResolver());
775
766
 
767
+ if (flags.classic && !flags.json) {
768
+ renderer.write(renderer.status("info", "`--classic` is deprecated and kept only for backwards compatibility."));
769
+ }
770
+
776
771
  if (isInteractiveLauncherMode({ command, flags, stdout, stdin })) {
777
772
  try {
778
773
  await runInteractiveSession({
@@ -0,0 +1,108 @@
1
+ const CLI_COMMANDS = [
2
+ {
3
+ value: "install",
4
+ label: "Install",
5
+ description: "Install portable skills into the current project.",
6
+ bestFor: "First-time setup and skill refresh",
7
+ aliases: ["setup", "init", "apply"],
8
+ usage: "npx skilly-hand install"
9
+ },
10
+ {
11
+ value: "native-setup",
12
+ label: "Native Setup",
13
+ description: "Sync native assistant adapters and instruction files.",
14
+ bestFor: "After install or when assistant targets change",
15
+ aliases: ["native", "adapters"],
16
+ usage: "npx skilly-hand native setup"
17
+ },
18
+ {
19
+ value: "detect",
20
+ label: "Detect",
21
+ description: "Scan project technologies and show detected signals.",
22
+ bestFor: "Preview auto-detection before installation",
23
+ aliases: ["scan", "inspect"],
24
+ usage: "npx skilly-hand detect"
25
+ },
26
+ {
27
+ value: "list",
28
+ label: "List",
29
+ description: "List all skills available in the catalog.",
30
+ bestFor: "Browse skills and tags",
31
+ aliases: ["catalog", "skills"],
32
+ usage: "npx skilly-hand list"
33
+ },
34
+ {
35
+ value: "doctor",
36
+ label: "Doctor",
37
+ description: "Diagnose install health, probes, and native coverage.",
38
+ bestFor: "Troubleshooting and pre-release checks",
39
+ aliases: ["health", "check"],
40
+ usage: "npx skilly-hand doctor"
41
+ },
42
+ {
43
+ value: "uninstall",
44
+ label: "Uninstall",
45
+ description: "Remove managed skilly-hand installation files.",
46
+ bestFor: "Restore project to pre-install state",
47
+ aliases: ["remove", "cleanup"],
48
+ usage: "npx skilly-hand uninstall"
49
+ }
50
+ ];
51
+
52
+ const INTERACTIVE_ONLY_COMMANDS = [
53
+ {
54
+ value: "command-guide",
55
+ label: "Command Guide",
56
+ description: "Show command explanations, aliases, and examples.",
57
+ bestFor: "Learning what to run",
58
+ aliases: ["help", "guide"],
59
+ usage: "npx skilly-hand --help"
60
+ },
61
+ {
62
+ value: "exit",
63
+ label: "Exit",
64
+ description: "Close the guided launcher.",
65
+ bestFor: "Leave interactive mode",
66
+ aliases: ["quit"],
67
+ usage: "npx skilly-hand"
68
+ }
69
+ ];
70
+
71
+ function normalizeText(value) {
72
+ return String(value || "").trim().toLowerCase();
73
+ }
74
+
75
+ function buildSearchText(command) {
76
+ return [
77
+ command.value,
78
+ command.label,
79
+ command.description,
80
+ command.bestFor,
81
+ ...(command.aliases || [])
82
+ ]
83
+ .map(normalizeText)
84
+ .join(" ");
85
+ }
86
+
87
+ export function getCliCommands() {
88
+ return CLI_COMMANDS.map((command) => ({ ...command, aliases: [...command.aliases] }));
89
+ }
90
+
91
+ export function getInteractiveCommands() {
92
+ const merged = [...CLI_COMMANDS, ...INTERACTIVE_ONLY_COMMANDS];
93
+ return merged.map((command) => ({ ...command, aliases: [...command.aliases] }));
94
+ }
95
+
96
+ export function filterCommands(commands, term) {
97
+ const normalizedTerm = normalizeText(term);
98
+ if (!normalizedTerm) return [...commands];
99
+ return commands.filter((command) => buildSearchText(command).includes(normalizedTerm));
100
+ }
101
+
102
+ export function formatHelpUsageLines() {
103
+ return [
104
+ "npx skilly-hand # interactive launcher when running in a TTY",
105
+ ...CLI_COMMANDS.map((command) => command.usage)
106
+ ];
107
+ }
108
+
@@ -0,0 +1,201 @@
1
+ import inquirer from "inquirer";
2
+ import { filterCommands, getInteractiveCommands } from "./command-registry.js";
3
+
4
+ function writeBlock(write, title, body) {
5
+ const lines = [];
6
+ if (title) {
7
+ lines.push(`\n${title}`);
8
+ lines.push("=".repeat(Math.max(12, title.length)));
9
+ }
10
+ if (body) {
11
+ lines.push(String(body).trimEnd());
12
+ }
13
+ lines.push("");
14
+ write(`${lines.join("\n")}\n`);
15
+ }
16
+
17
+ function formatSkillChoiceLabel(skill) {
18
+ const tags = (skill.tags || []).join(", ") || "none";
19
+ const agents = (skill.agentSupport || []).join(", ") || "none";
20
+ return `${skill.id} | ${skill.title} | tags: ${tags} | agents: ${agents}`;
21
+ }
22
+
23
+ function toCheckboxChoices(items, valueKey, labelFn) {
24
+ return items.map((item) => ({
25
+ name: labelFn(item),
26
+ value: item[valueKey],
27
+ checked: Boolean(item.checked)
28
+ }));
29
+ }
30
+
31
+ function commandChoiceName(command) {
32
+ return `${command.label} - ${command.description}`;
33
+ }
34
+
35
+ function buildCommandGuide(commands) {
36
+ const rows = commands.filter((command) => command.value !== "exit").map((command) => {
37
+ const aliases = (command.aliases || []).join(", ") || "none";
38
+ return [
39
+ `${command.label} (${command.value})`,
40
+ `Best for: ${command.bestFor}`,
41
+ `Aliases: ${aliases}`,
42
+ `Run: ${command.usage}`
43
+ ].join("\n");
44
+ });
45
+ return rows.join("\n\n");
46
+ }
47
+
48
+ function buildGuidedHomeIntro() {
49
+ return [
50
+ "Type to filter commands, then press Enter to run.",
51
+ "Tips: arrow keys to navigate, Esc to clear search, Ctrl+C to exit."
52
+ ].join("\n");
53
+ }
54
+
55
+ function installTipsBlock() {
56
+ return [
57
+ "Recommended skills are preselected using stack detection.",
58
+ "Use space to toggle, a to toggle all, i to invert selection."
59
+ ].join("\n");
60
+ }
61
+
62
+ function agentTipsBlock() {
63
+ return [
64
+ "Pick assistant targets that should receive installed skills.",
65
+ "Use space to toggle and Enter to continue."
66
+ ].join("\n");
67
+ }
68
+
69
+ const COMMAND_HINTS = {
70
+ "native-setup": "Tip: run `npx skilly-hand doctor` next to verify native coverage.",
71
+ detect: "Tip: run `npx skilly-hand install` to apply detected recommendations.",
72
+ list: "Tip: use `npx skilly-hand install --include <tag>` to narrow installation.",
73
+ doctor: "Tip: resolve any warnings, then rerun doctor before release.",
74
+ uninstall: "Tip: run `npx skilly-hand install` anytime to restore managed files."
75
+ };
76
+
77
+ export function createInquirerInteractiveUi({
78
+ prompt = (questions) => inquirer.prompt(questions),
79
+ write = (value) => process.stdout.write(value)
80
+ } = {}) {
81
+ async function confirm({ message, defaultValue = false }) {
82
+ try {
83
+ const response = await prompt([
84
+ {
85
+ type: "confirm",
86
+ name: "confirmed",
87
+ message: String(message),
88
+ default: defaultValue
89
+ }
90
+ ]);
91
+ return Boolean(response.confirmed);
92
+ } catch (error) {
93
+ if (error?.name === "ExitPromptError") return false;
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ async function launch({ appVersion, actions, commands = getInteractiveCommands() }) {
99
+ const header = appVersion ? `skilly-hand v${appVersion}` : "skilly-hand";
100
+ writeBlock(write, "Guided Home", buildGuidedHomeIntro());
101
+
102
+ while (true) {
103
+ const { command } = await prompt([
104
+ {
105
+ type: "search",
106
+ name: "command",
107
+ message: `${header}: choose a command`,
108
+ source: async (term) =>
109
+ filterCommands(commands, term).map((item) => ({
110
+ name: commandChoiceName(item),
111
+ value: item.value,
112
+ description: `${item.bestFor} | aliases: ${(item.aliases || []).join(", ") || "none"}`
113
+ })),
114
+ default: "install",
115
+ pageSize: 10
116
+ }
117
+ ]);
118
+
119
+ if (command === "exit") return;
120
+ if (command === "command-guide") {
121
+ writeBlock(write, "Command Guide", buildCommandGuide(commands));
122
+ continue;
123
+ }
124
+
125
+ if (command === "install") {
126
+ const context = await actions.prepareInstall();
127
+ const skillChoices = toCheckboxChoices(context.skills || [], "id", formatSkillChoiceLabel);
128
+ const agentChoices = toCheckboxChoices(context.agents || [], "value", (agent) => agent.value);
129
+
130
+ writeBlock(write, "Install Tips", installTipsBlock());
131
+
132
+ const { selectedSkillIds } = await prompt([
133
+ {
134
+ type: "checkbox",
135
+ name: "selectedSkillIds",
136
+ message: "Select skills to install",
137
+ choices: skillChoices,
138
+ pageSize: 16
139
+ }
140
+ ]);
141
+
142
+ writeBlock(write, "Assistant Target Tips", agentTipsBlock());
143
+
144
+ const { selectedAgents } = await prompt([
145
+ {
146
+ type: "checkbox",
147
+ name: "selectedAgents",
148
+ message: "Select assistant targets",
149
+ choices: agentChoices,
150
+ pageSize: 12
151
+ }
152
+ ]);
153
+
154
+ const previewBundle = await actions.previewInstallBundle({
155
+ selectedSkillIds,
156
+ selectedAgents
157
+ });
158
+ writeBlock(write, "Install Preview", previewBundle?.text || "");
159
+
160
+ const { installDecision } = await prompt([
161
+ {
162
+ type: "list",
163
+ name: "installDecision",
164
+ message: "Next action",
165
+ choices: [
166
+ { name: "Apply installation", value: "apply" },
167
+ { name: "Back to command menu", value: "menu" }
168
+ ]
169
+ }
170
+ ]);
171
+
172
+ if (installDecision === "apply") {
173
+ const applyBundle = await actions.applyInstallBundle({
174
+ selectedSkillIds,
175
+ selectedAgents
176
+ });
177
+ writeBlock(write, "Install Result", applyBundle?.text || "");
178
+ writeBlock(write, "Next Hint", "Run `npx skilly-hand doctor` to verify installation health.");
179
+ }
180
+ continue;
181
+ }
182
+
183
+ if (command === "uninstall") {
184
+ const accepted = await confirm({
185
+ message: "Remove the skilly-hand installation from this project?",
186
+ defaultValue: false
187
+ });
188
+ if (!accepted) {
189
+ writeBlock(write, "Uninstall", "Uninstall cancelled.");
190
+ continue;
191
+ }
192
+ }
193
+
194
+ const bundle = await actions.runCommandBundle(command);
195
+ writeBlock(write, "Command Result", bundle?.text || "");
196
+ if (COMMAND_HINTS[command]) writeBlock(write, "Next Hint", COMMAND_HINTS[command]);
197
+ }
198
+ }
199
+
200
+ return { launch, confirm };
201
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/core",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/detectors",
3
- "version": "0.23.0",
3
+ "version": "0.23.1",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }