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.
@@ -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
 
@@ -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
- return path.join(dcliDir(), "skills-providers.json")
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
- return path.join(dcliDir(), "skills-index.json")
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: `${provider.name}:${entry.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: `${provider.name}:${baseId}`,
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
- ...hit,
223
- markdown
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
- outputError({ code: 85, type: "invalid_argument", message: "Unknown providers subcommand. Use: list, add, remove, show", recoverable: false })
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