superacli 1.1.7 → 1.1.9
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/CONTRIBUTING.md +270 -0
- package/README.md +64 -1
- package/__tests__/adapter-schema.test.js +251 -86
- package/__tests__/resend-plugin.test.js +109 -82
- package/cli/adapters/process.js +10 -5
- package/cli/plugins-manager.js +3 -3
- package/cli/skills-catalog.js +169 -10
- package/cli/skills.js +46 -3
- package/cli/supercli.js +214 -78
- package/docs/index.html +183 -123
- package/index.html +384 -0
- package/package.json +2 -2
- package/plugins/openhands/plugin.json +6 -6
- package/plugins/openhands/skills/quickstart/SKILL.md +23 -0
- package/plugins/plugins.json +11 -0
- package/docs/docs.html +0 -224
package/cli/plugins-manager.js
CHANGED
|
@@ -357,8 +357,8 @@ function runStoredHook(pluginName, kind, storedHook) {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
-
function checkBinary(binary) {
|
|
361
|
-
const r = spawnSync(binary, ["--version"], { encoding: "utf-8", timeout: 5000 })
|
|
360
|
+
function checkBinary(binary, args) {
|
|
361
|
+
const r = spawnSync(binary, args || ["--version"], { encoding: "utf-8", timeout: 5000 })
|
|
362
362
|
if (r.error) {
|
|
363
363
|
return {
|
|
364
364
|
binary,
|
|
@@ -394,7 +394,7 @@ function doctorPlugin(name) {
|
|
|
394
394
|
const checks = []
|
|
395
395
|
for (const check of (plugin.checks || [])) {
|
|
396
396
|
if (check && check.type === "binary" && check.name) {
|
|
397
|
-
checks.push({ type: "binary", ...checkBinary(check.name) })
|
|
397
|
+
checks.push({ type: "binary", ...checkBinary(check.name, check.args) })
|
|
398
398
|
}
|
|
399
399
|
}
|
|
400
400
|
|
package/cli/skills-catalog.js
CHANGED
|
@@ -7,17 +7,27 @@ function dcliDir() {
|
|
|
7
7
|
return process.env.SUPERCLI_HOME || path.join(os.homedir(), ".dcli")
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
function supercliDir() {
|
|
11
|
+
return process.env.SUPERCLI_HOME || path.join(os.homedir(), ".supercli")
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
function providersFile() {
|
|
11
|
-
|
|
15
|
+
const supercliPath = path.join(supercliDir(), "skills-providers.json")
|
|
16
|
+
const dcliPath = path.join(dcliDir(), "skills-providers.json")
|
|
17
|
+
return fs.existsSync(supercliPath) ? supercliPath : dcliPath
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
function indexFile() {
|
|
15
|
-
|
|
21
|
+
const supercliPath = path.join(supercliDir(), "skills-index.json")
|
|
22
|
+
const dcliPath = path.join(dcliDir(), "skills-index.json")
|
|
23
|
+
return fs.existsSync(supercliPath) ? supercliPath : dcliPath
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
function ensureDir() {
|
|
19
27
|
const dir = dcliDir()
|
|
20
28
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true })
|
|
29
|
+
const supercliDirPath = supercliDir()
|
|
30
|
+
if (!fs.existsSync(supercliDirPath)) fs.mkdirSync(supercliDirPath, { recursive: true })
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
function defaultProviders() {
|
|
@@ -135,7 +145,7 @@ function syncCatalog() {
|
|
|
135
145
|
for (const entry of entries) {
|
|
136
146
|
if (!entry || !entry.id || !entry.source_url) continue
|
|
137
147
|
skills.push({
|
|
138
|
-
id:
|
|
148
|
+
id: provider.name + ":" + entry.id,
|
|
139
149
|
provider: provider.name,
|
|
140
150
|
name: entry.name || entry.id,
|
|
141
151
|
description: entry.description || "",
|
|
@@ -147,6 +157,34 @@ function syncCatalog() {
|
|
|
147
157
|
continue
|
|
148
158
|
}
|
|
149
159
|
|
|
160
|
+
if (provider.type === "plugin_fs") {
|
|
161
|
+
const pluginDir = provider.plugin_dir
|
|
162
|
+
if (!pluginDir || !fs.existsSync(pluginDir)) continue
|
|
163
|
+
const skillsDir = path.join(pluginDir, "skills")
|
|
164
|
+
if (fs.existsSync(skillsDir)) {
|
|
165
|
+
const files = walkDir(skillsDir)
|
|
166
|
+
for (const filePath of files) {
|
|
167
|
+
const markdown = fs.readFileSync(filePath, "utf-8")
|
|
168
|
+
const { frontmatter, body } = parseFrontmatter(markdown)
|
|
169
|
+
const baseId = baseSkillId(filePath, frontmatter, skillsDir)
|
|
170
|
+
const heading = body.split("\n").find(l => l.startsWith("# "))
|
|
171
|
+
const name = frontmatter.skill_name || (heading ? heading.slice(2).trim() : baseId)
|
|
172
|
+
const description = frontmatter.description || ""
|
|
173
|
+
const tags = typeof frontmatter.tags === "string" ? frontmatter.tags.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
174
|
+
skills.push({
|
|
175
|
+
id: provider.name + ":" + baseId,
|
|
176
|
+
provider: provider.name,
|
|
177
|
+
name,
|
|
178
|
+
description,
|
|
179
|
+
source_path: filePath,
|
|
180
|
+
tags,
|
|
181
|
+
updated_at: new Date().toISOString()
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
continue
|
|
186
|
+
}
|
|
187
|
+
|
|
150
188
|
const roots = Array.isArray(provider.roots) ? provider.roots : []
|
|
151
189
|
for (const root of roots) {
|
|
152
190
|
const files = walkDir(root)
|
|
@@ -158,9 +196,8 @@ function syncCatalog() {
|
|
|
158
196
|
const name = frontmatter.skill_name || (heading ? heading.slice(2).trim() : baseId)
|
|
159
197
|
const description = frontmatter.description || ""
|
|
160
198
|
const tags = typeof frontmatter.tags === "string" ? frontmatter.tags.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
161
|
-
|
|
162
199
|
skills.push({
|
|
163
|
-
id:
|
|
200
|
+
id: provider.name + ":" + baseId,
|
|
164
201
|
provider: provider.name,
|
|
165
202
|
name,
|
|
166
203
|
description,
|
|
@@ -206,7 +243,6 @@ function getCatalogSkill(providerId) {
|
|
|
206
243
|
const hit = (idx.skills || []).find(s => s.id === providerId)
|
|
207
244
|
if (!hit) return null
|
|
208
245
|
let markdown = null
|
|
209
|
-
|
|
210
246
|
if (hit.source_path) {
|
|
211
247
|
if (!fs.existsSync(hit.source_path)) return null
|
|
212
248
|
markdown = fs.readFileSync(hit.source_path, "utf-8")
|
|
@@ -215,15 +251,136 @@ function getCatalogSkill(providerId) {
|
|
|
215
251
|
if (res.error || res.status !== 0) return null
|
|
216
252
|
markdown = (res.stdout || "").trim()
|
|
217
253
|
}
|
|
218
|
-
|
|
219
254
|
if (!markdown) return null
|
|
255
|
+
return { ...hit, markdown }
|
|
256
|
+
}
|
|
220
257
|
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get catalog information with provider breakdown
|
|
262
|
+
*/
|
|
263
|
+
function getCatalogInfo() {
|
|
264
|
+
const idx = readIndex()
|
|
265
|
+
const providers = listProviders()
|
|
266
|
+
|
|
267
|
+
const providerStats = providers.map(provider => {
|
|
268
|
+
if (!provider.enabled) {
|
|
269
|
+
return { name: provider.name, type: provider.type, enabled: false, status: 'disabled' }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let skillsCount = 0
|
|
273
|
+
let status = 'active'
|
|
274
|
+
let details = {}
|
|
275
|
+
|
|
276
|
+
if (provider.type === 'plugin_fs') {
|
|
277
|
+
const pluginDir = provider.plugin_dir
|
|
278
|
+
if (!pluginDir || !fs.existsSync(pluginDir)) {
|
|
279
|
+
status = 'missing'
|
|
280
|
+
details.error = 'Plugin directory not found'
|
|
281
|
+
} else {
|
|
282
|
+
const skillsDir = path.join(pluginDir, 'skills')
|
|
283
|
+
if (fs.existsSync(skillsDir)) {
|
|
284
|
+
const files = walkDir(skillsDir)
|
|
285
|
+
skillsCount = files.length
|
|
286
|
+
details.plugin_dir = pluginDir
|
|
287
|
+
details.skills_dir = skillsDir
|
|
288
|
+
} else {
|
|
289
|
+
status = 'no_skills_dir'
|
|
290
|
+
details.skills_dir = skillsDir
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} else if (provider.type === 'local_fs' || provider.type === 'repo_fs') {
|
|
294
|
+
const roots = Array.isArray(provider.roots) ? provider.roots : []
|
|
295
|
+
for (const root of roots) {
|
|
296
|
+
if (fs.existsSync(root)) {
|
|
297
|
+
const files = walkDir(root)
|
|
298
|
+
skillsCount += files.length
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
details.roots = roots
|
|
302
|
+
} else if (provider.type === 'remote_static') {
|
|
303
|
+
skillsCount = Array.isArray(provider.entries) ? provider.entries.length : 0
|
|
304
|
+
details.entries_count = skillsCount
|
|
305
|
+
details.source_repo = provider.source_repo
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
name: provider.name,
|
|
310
|
+
type: provider.type,
|
|
311
|
+
enabled: provider.enabled,
|
|
312
|
+
status,
|
|
313
|
+
skills_count: skillsCount,
|
|
314
|
+
...details
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
|
|
221
318
|
return {
|
|
222
|
-
|
|
223
|
-
|
|
319
|
+
index: {
|
|
320
|
+
version: idx.version,
|
|
321
|
+
updated_at: idx.updated_at,
|
|
322
|
+
total_skills: idx.skills.length
|
|
323
|
+
},
|
|
324
|
+
providers: providerStats,
|
|
325
|
+
recent_skills: idx.skills.slice(-5).map(s => ({
|
|
326
|
+
id: s.id,
|
|
327
|
+
name: s.name,
|
|
328
|
+
provider: s.provider,
|
|
329
|
+
added_at: s.updated_at
|
|
330
|
+
}))
|
|
224
331
|
}
|
|
225
332
|
}
|
|
226
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Describe all provider types with examples
|
|
336
|
+
*/
|
|
337
|
+
function describeProviderTypes() {
|
|
338
|
+
return {
|
|
339
|
+
provider_types: [
|
|
340
|
+
{
|
|
341
|
+
name: 'local_fs',
|
|
342
|
+
description: 'Scans local directories for SKILL.md files',
|
|
343
|
+
example: {
|
|
344
|
+
name: 'my-skills',
|
|
345
|
+
type: 'local_fs',
|
|
346
|
+
roots: ['~/.config/opencode/skills'],
|
|
347
|
+
enabled: true
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'repo_fs',
|
|
352
|
+
description: 'Scans repository directories for SKILL.md files',
|
|
353
|
+
example: {
|
|
354
|
+
name: 'repo',
|
|
355
|
+
type: 'repo_fs',
|
|
356
|
+
roots: ['.agents/skills', 'docs/skills'],
|
|
357
|
+
enabled: true
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: 'remote_static',
|
|
362
|
+
description: 'Pre-indexed skills from remote repositories (GitHub)',
|
|
363
|
+
example: {
|
|
364
|
+
name: 'visual-explainer',
|
|
365
|
+
type: 'remote_static',
|
|
366
|
+
source_repo: 'https://github.com/user/repo',
|
|
367
|
+
entries: [{ id: 'skill-1', source_url: 'https://raw.githubusercontent.com/...' }]
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: 'plugin_fs',
|
|
372
|
+
description: 'Auto-discovers SKILL.md files from installed plugin skills/ directory',
|
|
373
|
+
example: {
|
|
374
|
+
name: 'superbackend',
|
|
375
|
+
type: 'plugin_fs',
|
|
376
|
+
plugin_dir: '/path/to/plugins/superbackend'
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
]
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
227
384
|
module.exports = {
|
|
228
385
|
listProviders,
|
|
229
386
|
addProvider,
|
|
@@ -233,5 +390,7 @@ module.exports = {
|
|
|
233
390
|
syncCatalog,
|
|
234
391
|
listCatalogSkills,
|
|
235
392
|
searchCatalog,
|
|
236
|
-
getCatalogSkill
|
|
393
|
+
getCatalogSkill,
|
|
394
|
+
getCatalogInfo,
|
|
395
|
+
describeProviderTypes
|
|
237
396
|
}
|
package/cli/skills.js
CHANGED
|
@@ -13,7 +13,9 @@ const {
|
|
|
13
13
|
syncCatalog,
|
|
14
14
|
listCatalogSkills,
|
|
15
15
|
searchCatalog,
|
|
16
|
-
getCatalogSkill
|
|
16
|
+
getCatalogSkill,
|
|
17
|
+
getCatalogInfo,
|
|
18
|
+
describeProviderTypes
|
|
17
19
|
} = require("./skills-catalog")
|
|
18
20
|
|
|
19
21
|
function normalizeSkillId(input) {
|
|
@@ -179,7 +181,7 @@ function buildTeachSkillMarkdown(options = {}) {
|
|
|
179
181
|
]
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
return `---\n${renderYamlObject(frontmatter)}\n---\n\n# Instruction\n\nThis skill teaches LLMs how to discover and use SuperCLI capabilities and skill documents:\n\n1. List available capability docs and catalog skills:\n\n\`\`\`bash\nsupercli skills list --json\n\`\`\`\n\n2. Fetch documentation for a specific capability:\n\n\`\`\`bash\nsupercli skills get <namespace.resource.action> --format skill.md\n\`\`\`\n\n3. Parse YAML frontmatter to understand command, arguments, output schema, and metadata.\n\n4. Execute the capability with validated arguments:\n\n\`\`\`bash\nsupercli <namespace> <resource> <action> --arg value --json\n\`\`\`\n\n5. For plugin discovery and remote plugin installs, use:\n\n\`\`\`bash\nsupercli skills get ${PLUGINS_USAGE_SKILL_ID} --format skill.md\n\`\`\`\n\n6. To index skill documents from a local directory (e.g., a project with docs/skills):\n\n\`\`\`bash\n# List current providers\nsupercli skills providers list --json\n\n# Add a local provider for a project\nsupercli skills providers add --name myproject --type local_fs --roots ./myproject/docs/skills\n\n# Sync the catalog to index new skill documents\nsupercli skills sync\n\n# Search skill documents from the new provider\nsupercli skills search <query> --provider myproject\n\n# Remove a provider if needed\nsupercli skills providers remove --name myproject\n\`\`\`\n\n# Examples\n\n\`\`\`bash\nsupercli skills teach --format skill.md\nsupercli skills teach --format skill.md --show-dag\nsupercli skills providers add --name myproject --type local_fs --roots ./myproject/docs/skills\nsupercli skills sync\nsupercli skills search btc --provider myproject\n\`\`\``
|
|
184
|
+
return `---\n${renderYamlObject(frontmatter)}\n---\n\n# Instruction\n\nThis skill teaches LLMs how to discover and use SuperCLI capabilities and skill documents:\n\n1. List available capability docs and catalog skills:\n\n\`\`\`bash\nsupercli skills list --json\n\`\`\`\n\n2. Fetch documentation for a specific capability:\n\n\`\`\`bash\nsupercli skills get <namespace.resource.action> --format skill.md\n\`\`\`\n\n3. Parse YAML frontmatter to understand command, arguments, output schema, and metadata.\n\n4. Execute the capability with validated arguments:\n\n\`\`\`bash\nsupercli <namespace> <resource> <action> --arg value --json\n\`\`\`\n\n5. For plugin discovery and remote plugin installs, use:\n\n\`\`\`bash\nsupercli skills get ${PLUGINS_USAGE_SKILL_ID} --format skill.md\n\`\`\`\n\n6. To index skill documents from a local directory (e.g., a project with docs/skills):\n\n\`\`\`bash\n# List current providers\nsupercli skills providers list --json\n\n# Add a local provider for a project\nsupercli skills providers add --name myproject --type local_fs --roots ./myproject/docs/skills\n\n# Sync the catalog to index new skill documents\nsupercli skills sync\n\n# Search skill documents from the new provider\nsupercli skills search <query> --provider myproject\n\n# Remove a provider if needed\nsupercli skills providers remove --name myproject\n\`\`\`\n\n7. **Using Positional Arguments** (for commands that require positional values):\n\n\`\`\`bash\n# Some commands require positional arguments instead of named flags\nsupercli <namespace> <resource> <action> <positional1> <positional2> --json\n\n# Example: Delete by ID (positional argument)\nsupercli superbackend waiting-list delete --id <objectId> --quiet\n\n# Example: Passthrough to plugin CLI\nsupercli superbackend _ _ <resource> <command> <args...> --quiet\n\`\`\`\n\n# Examples\n\n\`\`\`bash\nsupercli skills teach --format skill.md\nsupercli skills teach --format skill.md --show-dag\nsupercli skills providers add --name myproject --type local_fs --roots ./myproject/docs/skills\nsupercli skills sync\nsupercli skills search btc --provider myproject\nsupercli superbackend waiting-list delete --id <id> --quiet\nsupercli superbackend _ _ users list --value 10 --quiet\n\`\`\``
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
function buildPluginsUsageSkillMarkdown(options = {}) {
|
|
@@ -339,7 +341,48 @@ function handleSkillsCommand(options) {
|
|
|
339
341
|
return true
|
|
340
342
|
}
|
|
341
343
|
|
|
342
|
-
|
|
344
|
+
if (action === "describe") {
|
|
345
|
+
const types = describeProviderTypes()
|
|
346
|
+
if (humanMode && !flags.json) {
|
|
347
|
+
console.log("\n ⚡ Skill Provider Types\n")
|
|
348
|
+
for (const t of types.provider_types) {
|
|
349
|
+
console.log(" " + t.name + ":")
|
|
350
|
+
console.log(" " + t.description)
|
|
351
|
+
console.log(" Example:")
|
|
352
|
+
console.log(" " + JSON.stringify(t.example, null, 6))
|
|
353
|
+
console.log("")
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
output(types)
|
|
357
|
+
}
|
|
358
|
+
return true
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
outputError({ code: 85, type: "invalid_argument", message: "Unknown providers subcommand. Use: list, add, remove, show, describe", recoverable: false })
|
|
362
|
+
return true
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (subcommand === "catalog") {
|
|
366
|
+
const action = positional[2]
|
|
367
|
+
if (action === "info") {
|
|
368
|
+
const info = getCatalogInfo()
|
|
369
|
+
if (humanMode && !flags.json) {
|
|
370
|
+
console.log("\n ⚡ Skills Catalog Info\n")
|
|
371
|
+
console.log(" Index:")
|
|
372
|
+
console.log(" Version:", info.index.version)
|
|
373
|
+
console.log(" Updated:", info.index.updated_at)
|
|
374
|
+
console.log(" Total Skills:", info.index.total_skills)
|
|
375
|
+
console.log("\n Providers:")
|
|
376
|
+
for (const p of info.providers) {
|
|
377
|
+
console.log(" - " + p.name + " (" + p.type + "): " + p.skills_count + " skills [" + p.status + "]")
|
|
378
|
+
}
|
|
379
|
+
console.log("")
|
|
380
|
+
} else {
|
|
381
|
+
output({ catalog: info })
|
|
382
|
+
}
|
|
383
|
+
return true
|
|
384
|
+
}
|
|
385
|
+
outputError({ code: 85, type: "invalid_argument", message: "Usage: supercli skills catalog info [--json]", recoverable: false })
|
|
343
386
|
return true
|
|
344
387
|
}
|
|
345
388
|
|