mdpush 1.2.0 → 1.3.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/package.json +1 -1
- package/src/commands/skill-command.js +85 -9
- package/src/index.js +10 -5
package/package.json
CHANGED
|
@@ -86,9 +86,37 @@ export async function skillPushCommand(dir, options) {
|
|
|
86
86
|
if (failed) process.exit(1)
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/**
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Install published skills into ~/.claude/skills/<slug> (or --dest).
|
|
91
|
+
* Accepts several slugs (space or comma separated); each installs
|
|
92
|
+
* independently — one failure doesn't abort the rest (exit 1 at the end).
|
|
93
|
+
*/
|
|
94
|
+
export async function skillInstallCommand(slugs, options) {
|
|
95
|
+
const list = [...new Set(slugs.flatMap(s => s.split(',')).map(s => s.trim()).filter(Boolean))]
|
|
96
|
+
if (list.length > 1 && options.dest) {
|
|
97
|
+
console.log(chalk.red('--dest is ambiguous with multiple skills — install them one at a time.'))
|
|
98
|
+
process.exit(2)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const failedSlugs = []
|
|
102
|
+
for (const slug of list) {
|
|
103
|
+
try {
|
|
104
|
+
await installOneSkill(slug, options)
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.log(chalk.red(`✖ ${slug}: ${err.message}`))
|
|
107
|
+
failedSlugs.push(slug)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (list.length > 1) {
|
|
111
|
+
console.log(`\n${chalk.green(`${list.length - failedSlugs.length} skill(s) installed`)}${failedSlugs.length ? `, ${chalk.red(`${failedSlugs.length} failed (${failedSlugs.join(', ')})`)}` : ''}`)
|
|
112
|
+
}
|
|
113
|
+
if (failedSlugs.length) process.exit(1)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Install one skill. `?source=cli-install` marks a real install so the
|
|
117
|
+
* server bumps install_count (detail-page manifest fetches don't). */
|
|
118
|
+
async function installOneSkill(slug, options) {
|
|
119
|
+
const manifestRes = await apiRequest(`/api/skills/${slug}/manifest?source=cli-install`)
|
|
92
120
|
const manifest = await manifestRes.json()
|
|
93
121
|
|
|
94
122
|
const dest = path.resolve(options.dest || path.join(os.homedir(), '.claude', 'skills', slug))
|
|
@@ -115,15 +143,63 @@ export async function skillInstallCommand(slug, options) {
|
|
|
115
143
|
}
|
|
116
144
|
}
|
|
117
145
|
console.log(`\n${chalk.green(`${success} installed`)}${failed ? `, ${chalk.red(`${failed} failed`)}` : ''}`)
|
|
118
|
-
|
|
146
|
+
// Throw (not exit) so a batch install can continue with the next slug.
|
|
147
|
+
if (failed) throw new Error(`${failed} file(s) failed`)
|
|
119
148
|
}
|
|
120
149
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
150
|
+
/** Fetch + print the catalog table. Returns the skills array (or []). */
|
|
151
|
+
async function printCatalog({ numbered = false } = {}) {
|
|
123
152
|
const res = await apiRequest('/api/skills')
|
|
124
153
|
const skills = await res.json()
|
|
125
|
-
if (skills.length === 0)
|
|
126
|
-
|
|
127
|
-
|
|
154
|
+
if (skills.length === 0) {
|
|
155
|
+
console.log(chalk.yellow('No skills published.'))
|
|
156
|
+
return []
|
|
157
|
+
}
|
|
158
|
+
skills.forEach((s, i) => {
|
|
159
|
+
const idx = numbered ? chalk.gray(`${String(i + 1).padStart(3)}. `) : ''
|
|
160
|
+
const cat = (s.category || '—').padEnd(10)
|
|
161
|
+
const installs = String(s.installCount ?? 0).padStart(4)
|
|
162
|
+
const desc = (s.description || '(no description)').slice(0, 80)
|
|
163
|
+
console.log(`${idx}${chalk.cyan(s.slug.padEnd(24))} ${chalk.magenta(cat)} ${chalk.gray(`⤓${installs}`)} ${desc}`)
|
|
164
|
+
})
|
|
165
|
+
return skills
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** List published skills (slug · category · installs · description). */
|
|
169
|
+
export async function skillListCommand() {
|
|
170
|
+
await printCatalog()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Interactive browse (`mdpush skill` with no subcommand) — numbered catalog,
|
|
175
|
+
* pick by number/comma list, installs the selection. Zero-dependency prompt
|
|
176
|
+
* via node:readline; falls back to plain list when stdin isn't a TTY.
|
|
177
|
+
*/
|
|
178
|
+
export async function skillBrowseCommand() {
|
|
179
|
+
const skills = await printCatalog({ numbered: true })
|
|
180
|
+
if (skills.length === 0 || !process.stdin.isTTY) return
|
|
181
|
+
|
|
182
|
+
const readline = await import('node:readline/promises')
|
|
183
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
184
|
+
// Race against 'close' — an EOF on stdin (Ctrl-D, piped input running dry)
|
|
185
|
+
// would otherwise leave the question promise pending forever.
|
|
186
|
+
const answer = (await Promise.race([
|
|
187
|
+
rl.question(chalk.blue('\nInstall which? (numbers, comma-separated — empty to exit): ')),
|
|
188
|
+
new Promise(resolve => rl.once('close', () => resolve('')))
|
|
189
|
+
])).trim()
|
|
190
|
+
rl.close()
|
|
191
|
+
// A TTY stdin touched by readline keeps the event loop alive after close —
|
|
192
|
+
// exit explicitly on every path (install failures already exit(1) upstream).
|
|
193
|
+
if (!answer) process.exit(0)
|
|
194
|
+
|
|
195
|
+
const picks = [...new Set(answer.split(/[\s,]+/))]
|
|
196
|
+
.map(n => skills[parseInt(n, 10) - 1])
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
.map(s => s.slug)
|
|
199
|
+
if (picks.length === 0) {
|
|
200
|
+
console.log(chalk.yellow('No valid selection.'))
|
|
201
|
+
process.exit(2)
|
|
128
202
|
}
|
|
203
|
+
await skillInstallCommand(picks, {})
|
|
204
|
+
process.exit(0)
|
|
129
205
|
}
|
package/src/index.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { createRequire } from 'module'
|
|
1
2
|
import { program } from 'commander'
|
|
3
|
+
|
|
4
|
+
// Single-source the CLI version from package.json (was hardcoded and stale)
|
|
5
|
+
const pkg = createRequire(import.meta.url)('../package.json')
|
|
2
6
|
import { loginCommand } from './commands/login-command.js'
|
|
3
7
|
import { pushCommand } from './commands/push-command.js'
|
|
4
8
|
import { configCommand } from './commands/config-command.js'
|
|
5
9
|
import { logoutCommand } from './commands/logout-command.js'
|
|
6
|
-
import { skillPushCommand, skillInstallCommand, skillListCommand } from './commands/skill-command.js'
|
|
10
|
+
import { skillPushCommand, skillInstallCommand, skillListCommand, skillBrowseCommand } from './commands/skill-command.js'
|
|
7
11
|
|
|
8
12
|
program
|
|
9
13
|
.name('mdpush')
|
|
10
14
|
.description('Push markdown, text, and HTML artifact files to sielay')
|
|
11
|
-
.version(
|
|
15
|
+
.version(pkg.version)
|
|
12
16
|
|
|
13
17
|
program.command('login')
|
|
14
18
|
.description('Authenticate with server')
|
|
@@ -25,6 +29,7 @@ program.command('push')
|
|
|
25
29
|
|
|
26
30
|
const skill = program.command('skill')
|
|
27
31
|
.description('Publish and install Claude Code skill bundles (projects with kind=skill)')
|
|
32
|
+
.action(skillBrowseCommand) // bare `mdpush skill` → interactive browse
|
|
28
33
|
|
|
29
34
|
skill.command('push')
|
|
30
35
|
.description('Push a skill directory (must contain SKILL.md) to the server')
|
|
@@ -33,9 +38,9 @@ skill.command('push')
|
|
|
33
38
|
.action(skillPushCommand)
|
|
34
39
|
|
|
35
40
|
skill.command('install')
|
|
36
|
-
.description('Install
|
|
37
|
-
.argument('<
|
|
38
|
-
.option('-d, --dest <path>', 'Destination directory (
|
|
41
|
+
.description('Install published skills into ~/.claude/skills/<slug>')
|
|
42
|
+
.argument('<slugs...>', 'Skill slug(s) — space or comma separated (see: mdpush skill list)')
|
|
43
|
+
.option('-d, --dest <path>', 'Destination directory (single skill only)')
|
|
39
44
|
.action(skillInstallCommand)
|
|
40
45
|
|
|
41
46
|
skill.command('list')
|