skillfish 1.0.17 → 1.0.18

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/README.md CHANGED
@@ -169,10 +169,52 @@ skillfish update --yes # Update all without prompting
169
169
  skillfish update --json # Check for updates (JSON output)
170
170
  ```
171
171
 
172
- <details>
173
- <summary>Exit Codes</summary>
172
+ ---
173
+
174
+ ## Non-Interactive Mode
175
+
176
+ All commands work without prompts for use in scripts, CI pipelines, and automation. Non-interactive mode activates when:
177
+
178
+ - The `--json` flag is passed, or
179
+ - stdin is not a TTY (piped input, CI runners, cron jobs)
180
+
181
+ In non-interactive mode, commands use default values where possible and error with guidance when required flags are missing.
182
+
183
+ ### Required flags
184
+
185
+ | Command | Required | Defaults |
186
+ |---------|----------|----------|
187
+ | `add` | `<owner/repo>` + skill name, `--path`, or `--all` if repo has multiple skills | Location: global (`~/`), Agents: all detected |
188
+ | `init` | `--name`, `--description` | Location: project (`./`), Agents: all detected |
189
+ | `list` | (none) | Both locations, all agents |
190
+ | `remove` | Skill name or `--all` | Both locations, all agents |
191
+ | `update` | `--yes` to apply updates | All tracked skills |
192
+
193
+ All commands accept `--project` or `--global` to override the default location.
194
+
195
+ ### Confirmation behavior
196
+
197
+ Confirmation prompts are skipped in non-interactive mode. Commands that modify skills (`add`, `init`, `remove`) proceed automatically. The `update` command is the exception: `--json` without `--yes` runs in **check-only mode**, reporting outdated skills without applying changes.
198
+
199
+ Use `--yes` to explicitly skip confirmations in interactive mode.
174
200
 
175
- Exit codes help agents and scripts understand command results without parsing error messages.
201
+ ### JSON output
202
+
203
+ Pass `--json` to get structured output on stdout. All commands return a common shape:
204
+
205
+ ```json
206
+ {
207
+ "success": true,
208
+ "exit_code": 0,
209
+ "errors": []
210
+ }
211
+ ```
212
+
213
+ Each command adds its own fields: `installed` and `skipped` (add), `created` and `skipped` (init), `removed` (remove), `outdated` and `updated` (update), `installed` and `agents_detected` (list).
214
+
215
+ ### Exit codes
216
+
217
+ Exit codes are consistent across all commands:
176
218
 
177
219
  | Code | Name | Meaning |
178
220
  |------|------|---------|
@@ -182,14 +224,21 @@ Exit codes help agents and scripts understand command results without parsing er
182
224
  | 3 | Network Error | Network failure (timeout, rate limit) |
183
225
  | 4 | Not Found | Requested resource not found (skill, agent, repo) |
184
226
 
185
- JSON output includes `exit_code` for programmatic access:
227
+ ### CI example
186
228
 
187
229
  ```bash
188
- skillfish add owner/repo --json
189
- # Output includes: "exit_code": 0 (or error code)
190
- ```
230
+ # Install skills in CI (non-interactive, JSON output)
231
+ skillfish add owner/repo --yes --json
191
232
 
192
- </details>
233
+ # Create a skill template in CI
234
+ skillfish init --name my-skill --description "My skill" --project --json
235
+
236
+ # Check for outdated skills without applying
237
+ skillfish update --json
238
+
239
+ # Apply updates
240
+ skillfish update --yes --json
241
+ ```
193
242
 
194
243
  ---
195
244
 
@@ -6,6 +6,7 @@ import { homedir } from 'os';
6
6
  import { dirname, basename } from 'path';
7
7
  import * as p from '@clack/prompts';
8
8
  import pc from 'picocolors';
9
+ import { printBanner } from '../lib/banner.js';
9
10
  import { trackInstall } from '../telemetry.js';
10
11
  import { isValidPath, parseFrontmatter, deriveSkillName, toTitleCase, truncate, batchMap, createJsonOutput, isInputTTY, isTTY, } from '../utils.js';
11
12
  import { getDetectedAgents, AGENT_CONFIGS } from '../lib/agents.js';
@@ -67,11 +68,7 @@ Examples:
67
68
  }
68
69
  // Show banner and intro (TTY only, not in JSON mode)
69
70
  if (isTTY() && !jsonMode) {
70
- console.log();
71
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
72
- console.log(` ${pc.cyan('><>')} ${pc.bold('SKILL FISH')} ${pc.cyan('><>')}`);
73
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
74
- console.log();
71
+ printBanner();
75
72
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
76
73
  }
77
74
  const force = options.force ?? false;
@@ -7,6 +7,7 @@ import { join } from 'path';
7
7
  import { existsSync, mkdirSync, writeFileSync } from 'fs';
8
8
  import * as p from '@clack/prompts';
9
9
  import pc from 'picocolors';
10
+ import { printBanner } from '../lib/banner.js';
10
11
  import { getDetectedAgents, getAgentSkillDir } from '../lib/agents.js';
11
12
  import { SKILL_FILENAME } from '../lib/github.js';
12
13
  import { EXIT_CODES } from '../lib/constants.js';
@@ -155,11 +156,7 @@ Examples:
155
156
  }
156
157
  // Show banner and intro (TTY only, not in JSON mode)
157
158
  if (isTTY() && !jsonMode) {
158
- console.log();
159
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
160
- console.log(` ${pc.cyan('><>')} ${pc.bold('SKILL FISH')} ${pc.cyan('><>')}`);
161
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
162
- console.log();
159
+ printBanner();
163
160
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)} ${pc.dim('· Create a skill')}`);
164
161
  }
165
162
  const skipPrompts = options.yes ?? false;
@@ -6,10 +6,11 @@ import { homedir } from 'os';
6
6
  import { join } from 'path';
7
7
  import * as p from '@clack/prompts';
8
8
  import pc from 'picocolors';
9
+ import { printBanner } from '../lib/banner.js';
9
10
  import { getDetectedAgents, getAgentSkillDir } from '../lib/agents.js';
10
11
  import { listInstalledSkillsInDir } from '../lib/installer.js';
11
12
  import { EXIT_CODES } from '../lib/constants.js';
12
- import { isInputTTY } from '../utils.js';
13
+ import { isTTY, isInputTTY } from '../utils.js';
13
14
  export const listCommand = new Command('list')
14
15
  .description('List installed skills across all detected agents')
15
16
  .option('--project', 'List project-level skills only (./.claude)')
@@ -134,8 +135,10 @@ Examples:
134
135
  agents_detected: detected.map((a) => a.name),
135
136
  });
136
137
  }
137
- // Display intro
138
- console.log();
138
+ // Display intro (TTY only, not in JSON mode)
139
+ if (isTTY() && !jsonMode) {
140
+ printBanner();
141
+ }
139
142
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`Skills for ${found[0].name}`)}`);
140
143
  if (globalSkills.length === 0 && projectSkills.length === 0) {
141
144
  p.outro(pc.dim('No skills installed'));
@@ -184,7 +187,9 @@ Examples:
184
187
  }
185
188
  // Interactive mode: show agent selector
186
189
  if (isInputTTY()) {
187
- console.log();
190
+ if (isTTY() && !jsonMode) {
191
+ printBanner();
192
+ }
188
193
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim('Installed skills')}`);
189
194
  // Step 1: Build options with skill counts in label (always visible)
190
195
  const agentOptions = detected.map((agent) => {
@@ -254,7 +259,9 @@ Examples:
254
259
  }
255
260
  // Non-interactive mode: display all agents with skills
256
261
  const { installed, globalSkills, projectSkills } = collectSkills(detected);
257
- console.log();
262
+ if (isTTY() && !jsonMode) {
263
+ printBanner();
264
+ }
258
265
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim('Installed skills')}`);
259
266
  console.log();
260
267
  console.log(pc.bold('Detected Agents'));
@@ -7,6 +7,7 @@ import { join } from 'path';
7
7
  import { existsSync, rmSync } from 'fs';
8
8
  import * as p from '@clack/prompts';
9
9
  import pc from 'picocolors';
10
+ import { printBanner } from '../lib/banner.js';
10
11
  import { getDetectedAgents, getAgentSkillDir } from '../lib/agents.js';
11
12
  import { listInstalledSkillsInDir } from '../lib/installer.js';
12
13
  import { isTTY, isInputTTY } from '../utils.js';
@@ -63,11 +64,7 @@ Examples:
63
64
  }
64
65
  // Show banner (TTY only, not in JSON mode)
65
66
  if (isTTY() && !jsonMode) {
66
- console.log();
67
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
68
- console.log(` ${pc.cyan('><>')} ${pc.bold('SKILL FISH')} ${pc.cyan('><>')}`);
69
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
70
- console.log();
67
+ printBanner();
71
68
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
72
69
  }
73
70
  const skipConfirm = options.yes ?? false;
@@ -6,6 +6,7 @@ import { homedir } from 'os';
6
6
  import { join } from 'path';
7
7
  import * as p from '@clack/prompts';
8
8
  import pc from 'picocolors';
9
+ import { printBanner } from '../lib/banner.js';
9
10
  import { getDetectedAgents, getAgentSkillDir } from '../lib/agents.js';
10
11
  import { listInstalledSkillsInDir, installSkill } from '../lib/installer.js';
11
12
  import { readManifest } from '../lib/manifest.js';
@@ -54,11 +55,7 @@ Examples:
54
55
  }
55
56
  // Show banner (TTY only, not in JSON mode)
56
57
  if (isTTY() && !jsonMode) {
57
- console.log();
58
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
59
- console.log(` ${pc.cyan('><>')} ${pc.bold('SKILL FISH')} ${pc.cyan('><>')}`);
60
- console.log(pc.cyan(' ≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋≋'));
61
- console.log();
58
+ printBanner();
62
59
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
63
60
  }
64
61
  // Detect agents
package/dist/index.js CHANGED
@@ -8,7 +8,9 @@ import { Command } from 'commander';
8
8
  import { readFileSync } from 'fs';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { dirname, join } from 'path';
11
+ import pc from 'picocolors';
11
12
  import updateNotifier from 'update-notifier';
13
+ import { getBannerText } from './lib/banner.js';
12
14
  import { addCommand } from './commands/add.js';
13
15
  import { initCommand } from './commands/init.js';
14
16
  import { listCommand } from './commands/list.js';
@@ -19,6 +21,16 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
19
21
  const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
20
22
  // Check for updates (runs in background, non-blocking)
21
23
  const notifier = updateNotifier({ pkg });
24
+ // Shared help styling for all commands
25
+ const helpStyles = {
26
+ sortSubcommands: true,
27
+ styleTitle: (str) => pc.bold(pc.underline(str)),
28
+ styleCommandText: (str) => pc.bold(pc.cyan(str)),
29
+ styleSubcommandText: (str) => pc.cyan(str),
30
+ styleOptionText: (str) => pc.yellow(str),
31
+ styleArgumentText: (str) => pc.dim(str),
32
+ styleDescriptionText: (str) => pc.dim(str),
33
+ };
22
34
  const program = new Command()
23
35
  .name('skillfish')
24
36
  .description('Install and manage AI agent skills from GitHub repositories')
@@ -31,18 +43,30 @@ const program = new Command()
31
43
  writeOut: (str) => process.stdout.write(str),
32
44
  writeErr: (str) => process.stderr.write(str),
33
45
  })
34
- .configureHelp({
35
- sortSubcommands: true,
36
- })
37
- .addHelpText('after', `
38
- Examples:
39
- $ skillfish add owner/repo Install skills from a repository
40
- $ skillfish add owner/repo/plugin/skill Install a specific skill
41
- $ skillfish init Create a new skill template
42
- $ skillfish list Show installed skills
43
- $ skillfish remove my-skill Remove a skill
44
-
45
- Documentation: https://skill.fish`);
46
+ .configureHelp(helpStyles)
47
+ .addHelpText('beforeAll', () => (process.stdout.isTTY ? getBannerText() : ''))
48
+ .addHelpText('after', () => {
49
+ const isTTY = process.stdout.isTTY;
50
+ const examples = [
51
+ ['skillfish add owner/repo', 'Install skills from a repository'],
52
+ ['skillfish add owner/repo/plugin/skill', 'Install a specific skill'],
53
+ ['skillfish init', 'Create a new skill template'],
54
+ ['skillfish list', 'Show installed skills'],
55
+ ['skillfish remove my-skill', 'Remove a skill'],
56
+ ];
57
+ const title = isTTY ? pc.bold(pc.underline('Examples:')) : 'Examples:';
58
+ const lines = examples.map(([cmd, desc]) => {
59
+ const prefix = isTTY ? pc.dim(' $ ') : ' $ ';
60
+ const command = isTTY ? pc.cyan(cmd) : cmd;
61
+ // Pad to align descriptions
62
+ const padding = ' '.repeat(Math.max(1, 42 - cmd.length));
63
+ const description = isTTY ? pc.dim(desc) : desc;
64
+ return `${prefix}${command}${padding}${description}`;
65
+ });
66
+ const docUrl = isTTY ? pc.bold(pc.cyan('https://skill.fish')) : 'https://skill.fish';
67
+ const docLabel = isTTY ? pc.dim('Documentation:') : 'Documentation:';
68
+ return `\n${title}\n${lines.join('\n')}\n\n${docLabel} ${docUrl}`;
69
+ });
46
70
  // Store version in options for commands to access
47
71
  program.hook('preAction', (thisCommand) => {
48
72
  thisCommand.setOptionValue('version', pkg.version);
@@ -53,6 +77,10 @@ program.addCommand(initCommand);
53
77
  program.addCommand(listCommand);
54
78
  program.addCommand(removeCommand);
55
79
  program.addCommand(updateCommand);
80
+ // Propagate help styling to all subcommands (must run after all addCommand() calls)
81
+ for (const cmd of program.commands) {
82
+ cmd.configureHelp(helpStyles);
83
+ }
56
84
  // Handle --json flag for help output
57
85
  program.on('option:json', () => {
58
86
  // JSON mode is handled by commands
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Shared ASCII art logo and banner display for skillfish CLI.
3
+ */
4
+ export declare const LOGO_LINES: readonly [" ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗██╗███████╗██╗ ██╗", " ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝██║██╔════╝██║ ██║", " ███████╗█████╔╝ ██║██║ ██║ █████╗ ██║███████╗███████║", " ╚════██║██╔═██╗ ██║██║ ██║ ██╔══╝ ██║╚════██║██╔══██║", " ███████║██║ ██╗██║███████╗███████╗██╗██║ ██║███████║██║ ██║", " ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝"];
5
+ /**
6
+ * Get the colored banner as a string.
7
+ * Returns the colored logo + tagline, or plain text when colors are disabled.
8
+ */
9
+ export declare function getBannerText(): string;
10
+ /**
11
+ * Print the colored ASCII art banner with tagline.
12
+ * Only outputs when stdout is a TTY.
13
+ */
14
+ export declare function printBanner(): void;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Shared ASCII art logo and banner display for skillfish CLI.
3
+ */
4
+ import pc from 'picocolors';
5
+ import { isTTY } from '../utils.js';
6
+ export const LOGO_LINES = [
7
+ ' ███████╗██╗ ██╗██╗██╗ ██╗ ███████╗██╗███████╗██╗ ██╗',
8
+ ' ██╔════╝██║ ██╔╝██║██║ ██║ ██╔════╝██║██╔════╝██║ ██║',
9
+ ' ███████╗█████╔╝ ██║██║ ██║ █████╗ ██║███████╗███████║',
10
+ ' ╚════██║██╔═██╗ ██║██║ ██║ ██╔══╝ ██║╚════██║██╔══██║',
11
+ ' ███████║██║ ██╗██║███████╗███████╗██╗██║ ██║███████║██║ ██║',
12
+ ' ╚══════╝╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝',
13
+ ];
14
+ // Ocean gradient colors (top to bottom): light cyan → deeper blue
15
+ // All colors kept bright enough to be readable on dark backgrounds
16
+ // Length must match LOGO_LINES (one color per line).
17
+ const GRADIENT_COLORS = [
18
+ [0, 230, 255],
19
+ [0, 210, 250],
20
+ [0, 190, 240],
21
+ [0, 170, 230],
22
+ [0, 155, 220],
23
+ [0, 140, 210],
24
+ ];
25
+ /** Check if colors are disabled via NO_COLOR convention (https://no-color.org). */
26
+ function isColorDisabled() {
27
+ return 'NO_COLOR' in process.env;
28
+ }
29
+ /**
30
+ * Check if the terminal supports truecolor (24-bit) via COLORTERM env var.
31
+ */
32
+ function supportsTruecolor() {
33
+ if (isColorDisabled())
34
+ return false;
35
+ const ct = process.env.COLORTERM;
36
+ return ct === 'truecolor' || ct === '24bit';
37
+ }
38
+ /** Apply truecolor gradient, fall back to picocolors cyan. */
39
+ function colorLine(line, index) {
40
+ if (supportsTruecolor()) {
41
+ const color = GRADIENT_COLORS[index % GRADIENT_COLORS.length];
42
+ const [r, g, b] = color;
43
+ return `\x1b[38;2;${r};${g};${b}m${line}\x1b[0m`;
44
+ }
45
+ return pc.cyan(line);
46
+ }
47
+ /**
48
+ * Get the colored banner as a string.
49
+ * Returns the colored logo + tagline, or plain text when colors are disabled.
50
+ */
51
+ export function getBannerText() {
52
+ const lines = [''];
53
+ for (let i = 0; i < LOGO_LINES.length; i++) {
54
+ lines.push(colorLine(LOGO_LINES[i], i));
55
+ }
56
+ lines.push(` ${pc.dim('The Skill Manager for AI Coding Agents')} — ${pc.bold(pc.cyan('https://skill.fish'))}`);
57
+ lines.push('');
58
+ return lines.join('\n');
59
+ }
60
+ /**
61
+ * Print the colored ASCII art banner with tagline.
62
+ * Only outputs when stdout is a TTY.
63
+ */
64
+ export function printBanner() {
65
+ if (!isTTY())
66
+ return;
67
+ console.log(getBannerText());
68
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillfish",
3
- "version": "1.0.17",
3
+ "version": "1.0.18",
4
4
  "description": "All in one Skill manager for AI coding agents. Install, update, and sync Skills across Claude Code, Cursor, Copilot + more.",
5
5
  "type": "module",
6
6
  "bin": {