superacli 1.1.8 → 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.
@@ -171,16 +171,25 @@ async function execute(cmd, flags, context = {}) {
171
171
  }
172
172
 
173
173
  if (passthroughMode) {
174
+ // Passthrough mode: use __rawArgs from flags (collected from remaining positional args)
174
175
  const passthroughArgs = Array.isArray(flags.__rawArgs) ? flags.__rawArgs : []
175
176
  args.push(...passthroughArgs)
176
177
  } else {
178
+ // Normal mode: collect positional values from flags OR from __positionalArgs
177
179
  const positionalValues = []
180
+
181
+ // First, try to get positional values from flags (defined args)
178
182
  for (const name of positionalNames) {
179
183
  if (remainingFlags[name] !== undefined) {
180
184
  positionalValues.push(String(remainingFlags[name]))
181
185
  delete remainingFlags[name]
182
186
  }
183
187
  }
188
+
189
+ // Also support __positionalArgs array for commands that need raw positional values
190
+ if (Array.isArray(flags.__positionalArgs)) {
191
+ positionalValues.push(...flags.__positionalArgs)
192
+ }
184
193
 
185
194
  const flagArgs = []
186
195
  if (includeJsonFlag) flagArgs.push(includeJsonFlag)
@@ -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
 
package/cli/supercli.js CHANGED
@@ -226,7 +226,7 @@ function renderTopLevelHelp(config) {
226
226
  " MCP: supercli mcp list | supercli mcp add <name> --url <url> | supercli mcp tools --mcp-server <name> | supercli mcp call --mcp-server <name> --tool <tool> | supercli mcp bind --mcp-server <name> --tool <tool> --as <ns.res.act> | supercli mcp doctor --mcp-server <name> | supercli mcp remove <name>",
227
227
  );
228
228
  console.log(
229
- " Skill Docs: supercli skills list | supercli skills get <id> | supercli skills search --query <q> | supercli skills sync",
229
+ " Skill Docs: supercli skills list | supercli skills get <id> | supercli skills catalog info | supercli skills providers describe | supercli skills search --query <q> | supercli skills sync",
230
230
  );
231
231
  if (config.features?.ask || process.env.OPENAI_BASE_URL) {
232
232
  console.log(' AI: supercli ask "<your natural language query>"');
@@ -859,6 +859,26 @@ async function main() {
859
859
  return;
860
860
  }
861
861
 
862
+ // Handle passthrough positional arguments
863
+ const passthroughArgs = [];
864
+ if (cmd.adapterConfig && cmd.adapterConfig.passthrough === true) {
865
+ // For passthrough commands, collect remaining positional args after namespace.resource.action
866
+ const cmdPositionalIndex = positional.findIndex((p, i) =>
867
+ i >= 0 &&
868
+ positional[i] === namespace &&
869
+ positional[i + 1] === resource &&
870
+ positional[i + 2] === action
871
+ );
872
+ if (cmdPositionalIndex >= 0) {
873
+ // Collect all args after namespace resource action
874
+ for (let i = cmdPositionalIndex + 3; i < positional.length; i++) {
875
+ passthroughArgs.push(positional[i]);
876
+ }
877
+ }
878
+ // Pass to adapter via __rawArgs
879
+ uFlags.__rawArgs = passthroughArgs;
880
+ }
881
+
862
882
  const start = Date.now();
863
883
  const result = await execute(cmd, uFlags, {
864
884
  server: SERVER || "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superacli",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "Config-driven, AI-friendly dynamic CLI",
5
5
  "license": "MIT",
6
6
  "main": "server/app.js",
@@ -43,10 +43,10 @@
43
43
  "adapter": "process",
44
44
  "adapterConfig": {
45
45
  "command": "openhands",
46
- "baseArgs": ["--headless", "-t"],
46
+ "baseArgs": ["--headless", "--always-approve", "--override-with-envs", "-t"],
47
47
  "positionalArgs": ["task"],
48
48
  "parseJson": false,
49
- "timeout_ms": 15000,
49
+ "timeout_ms": 180000,
50
50
  "missingDependencyHelp": "Please install OpenHands CLI to use this command."
51
51
  },
52
52
  "args": [
@@ -64,10 +64,10 @@
64
64
  "adapter": "process",
65
65
  "adapterConfig": {
66
66
  "command": "openhands",
67
- "baseArgs": ["--headless", "-f"],
67
+ "baseArgs": ["--headless", "--always-approve", "--override-with-envs", "-f"],
68
68
  "positionalArgs": ["file"],
69
69
  "parseJson": false,
70
- "timeout_ms": 15000,
70
+ "timeout_ms": 180000,
71
71
  "missingDependencyHelp": "Please install OpenHands CLI to use this command."
72
72
  },
73
73
  "args": [
@@ -85,11 +85,11 @@
85
85
  "adapter": "process",
86
86
  "adapterConfig": {
87
87
  "command": "openhands",
88
- "baseArgs": ["--headless", "--json", "-t"],
88
+ "baseArgs": ["--headless", "--always-approve", "--override-with-envs", "--json", "-t"],
89
89
  "positionalArgs": ["task"],
90
90
  "stream": "jsonl",
91
91
  "parseJson": false,
92
- "timeout_ms": 15000,
92
+ "timeout_ms": 180000,
93
93
  "missingDependencyHelp": "Please install OpenHands CLI to use this command."
94
94
  },
95
95
  "args": [
@@ -8,6 +8,16 @@ Use this plugin to run OpenHands in headless automation mode.
8
8
  - Verify CLI: `openhands --version`
9
9
  - Install plugin: `supercli plugins install openhands`
10
10
 
11
+ ## Environment Setup
12
+
13
+ Set these environment variables before running headless tasks:
14
+
15
+ ```bash
16
+ export LLM_API_KEY="your-api-key"
17
+ export LLM_MODEL="openai/gpt-4o-mini" # or another model
18
+ export LLM_BASE_URL="https://openrouter.ai/api/v1" # optional
19
+ ```
20
+
11
21
  ## Common Commands
12
22
 
13
23
  - Version: `supercli openhands self version`
@@ -15,6 +25,19 @@ Use this plugin to run OpenHands in headless automation mode.
15
25
  - Headless task from file: `supercli openhands task file --file ./task.txt`
16
26
  - JSONL stream mode: `supercli openhands task json --task "Add tests"`
17
27
 
28
+ ## Smoke Test Example
29
+
30
+ ```bash
31
+ # Set environment variables
32
+ export LLM_API_KEY="your-openrouter-api-key"
33
+ export LLM_MODEL="openai/gpt-4o-mini"
34
+ export LLM_BASE_URL="https://openrouter.ai/api/v1"
35
+ export PATH="/path/to/venv/bin:$PATH"
36
+
37
+ # Run a simple task
38
+ supercli openhands task run --task "Create a file called test.txt with 'hello world'"
39
+ ```
40
+
18
41
  ## Passthrough
19
42
 
20
43
  For any unsupported subcommand, use passthrough: