superacli 1.0.0
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/.env.example +14 -0
- package/README.md +173 -0
- package/cli/adapters/http.js +72 -0
- package/cli/adapters/mcp.js +193 -0
- package/cli/adapters/openapi.js +160 -0
- package/cli/ask.js +208 -0
- package/cli/config.js +133 -0
- package/cli/executor.js +117 -0
- package/cli/help-json.js +46 -0
- package/cli/mcp-local.js +72 -0
- package/cli/plan-runtime.js +32 -0
- package/cli/planner.js +67 -0
- package/cli/skills.js +240 -0
- package/cli/supercli.js +704 -0
- package/docs/features/adapters.md +25 -0
- package/docs/features/agent-friendly.md +28 -0
- package/docs/features/ask.md +32 -0
- package/docs/features/config-sync.md +22 -0
- package/docs/features/execution-plans.md +25 -0
- package/docs/features/observability.md +22 -0
- package/docs/features/skills.md +25 -0
- package/docs/features/storage.md +25 -0
- package/docs/features/workflows.md +33 -0
- package/docs/initial/AGENTS_FRIENDLY_TOOLS.md +553 -0
- package/docs/initial/agent-friendly.md +447 -0
- package/docs/initial/architecture.md +436 -0
- package/docs/initial/built-in-mcp-server.md +64 -0
- package/docs/initial/command-plan.md +532 -0
- package/docs/initial/core-features-2.md +428 -0
- package/docs/initial/core-features.md +366 -0
- package/docs/initial/dag.md +20 -0
- package/docs/initial/description.txt +9 -0
- package/docs/initial/idea.txt +564 -0
- package/docs/initial/initial-spec-details.md +726 -0
- package/docs/initial/initial-spec.md +731 -0
- package/docs/initial/mcp-local-mode.md +53 -0
- package/docs/initial/mcp-sse-mode.md +54 -0
- package/docs/initial/skills-support.md +246 -0
- package/docs/initial/storage-adapter-example.md +155 -0
- package/docs/initial/supercli-vs-gwc.md +109 -0
- package/examples/mcp-sse/install-demo.js +86 -0
- package/examples/mcp-sse/server.js +81 -0
- package/examples/mcp-stdio/install-demo.js +78 -0
- package/examples/mcp-stdio/server.js +50 -0
- package/package.json +21 -0
- package/server/app.js +59 -0
- package/server/public/app.js +18 -0
- package/server/routes/ask.js +92 -0
- package/server/routes/commands.js +126 -0
- package/server/routes/config.js +58 -0
- package/server/routes/jobs.js +122 -0
- package/server/routes/mcp.js +79 -0
- package/server/routes/plans.js +134 -0
- package/server/routes/specs.js +79 -0
- package/server/services/configService.js +88 -0
- package/server/storage/adapter.js +32 -0
- package/server/storage/file.js +64 -0
- package/server/storage/mongo.js +55 -0
- package/server/views/command-edit.ejs +110 -0
- package/server/views/commands.ejs +49 -0
- package/server/views/jobs.ejs +72 -0
- package/server/views/layout.ejs +42 -0
- package/server/views/mcp.ejs +80 -0
- package/server/views/partials/foot.ejs +5 -0
- package/server/views/partials/head.ejs +27 -0
- package/server/views/specs.ejs +91 -0
- package/tests/test-cli.js +367 -0
- package/tests/test-mcp.js +189 -0
- package/tests/test-openapi.js +101 -0
package/cli/skills.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
const { createPlan } = require("./planner")
|
|
2
|
+
|
|
3
|
+
function normalizeSkillId(input) {
|
|
4
|
+
if (!input || typeof input !== "string") return null
|
|
5
|
+
const parts = input.trim().split(".")
|
|
6
|
+
if (parts.length !== 3 || parts.some(p => !p)) return null
|
|
7
|
+
return {
|
|
8
|
+
id: parts.join("."),
|
|
9
|
+
namespace: parts[0],
|
|
10
|
+
resource: parts[1],
|
|
11
|
+
action: parts[2]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function escapeYamlString(value) {
|
|
16
|
+
const str = value == null ? "" : String(value)
|
|
17
|
+
return `"${str.replace(/\\/g, "\\\\").replace(/\"/g, "\\\"")}"`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function toDagNodes(cmd) {
|
|
21
|
+
const plan = createPlan(cmd, {})
|
|
22
|
+
return plan.steps.map(step => {
|
|
23
|
+
const node = {
|
|
24
|
+
id: step.step,
|
|
25
|
+
type: step.type
|
|
26
|
+
}
|
|
27
|
+
if (step.step > 1) node.depends_on = [step.step - 1]
|
|
28
|
+
if (step.adapter) node.adapter = step.adapter
|
|
29
|
+
if (step.method) node.method = step.method
|
|
30
|
+
if (step.url) node.url = step.url
|
|
31
|
+
if (step.operationId) node.operation_id = step.operationId
|
|
32
|
+
if (step.tool) node.tool = step.tool
|
|
33
|
+
return node
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderYamlObject(value, indent = 0) {
|
|
38
|
+
const pad = " ".repeat(indent)
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
if (value.length === 0) return `${pad}[]`
|
|
41
|
+
return value
|
|
42
|
+
.map(item => {
|
|
43
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
44
|
+
const entries = Object.entries(item)
|
|
45
|
+
if (entries.length === 0) return `${pad}- {}`
|
|
46
|
+
const [firstKey, firstValue] = entries[0]
|
|
47
|
+
let out = `${pad}- ${firstKey}: ${renderYamlScalar(firstValue)}`
|
|
48
|
+
for (let i = 1; i < entries.length; i++) {
|
|
49
|
+
out += `\n${pad} ${entries[i][0]}: ${renderYamlScalar(entries[i][1])}`
|
|
50
|
+
}
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
return `${pad}- ${renderYamlScalar(item)}`
|
|
54
|
+
})
|
|
55
|
+
.join("\n")
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!value || typeof value !== "object") {
|
|
59
|
+
return `${pad}${renderYamlScalar(value)}`
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const lines = []
|
|
63
|
+
for (const [key, val] of Object.entries(value)) {
|
|
64
|
+
if (Array.isArray(val) || (val && typeof val === "object")) {
|
|
65
|
+
lines.push(`${pad}${key}:`)
|
|
66
|
+
lines.push(renderYamlObject(val, indent + 2))
|
|
67
|
+
} else {
|
|
68
|
+
lines.push(`${pad}${key}: ${renderYamlScalar(val)}`)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return lines.join("\n")
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function renderYamlScalar(value) {
|
|
75
|
+
if (typeof value === "boolean") return value ? "true" : "false"
|
|
76
|
+
if (typeof value === "number") return String(value)
|
|
77
|
+
if (value == null) return "null"
|
|
78
|
+
return escapeYamlString(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildCommandSkillMarkdown(cmd, options = {}) {
|
|
82
|
+
const includeDag = !!options.showDag
|
|
83
|
+
const argLines = (cmd.args || []).map(arg => ({
|
|
84
|
+
name: arg.name,
|
|
85
|
+
type: arg.type || "string",
|
|
86
|
+
required: !!arg.required,
|
|
87
|
+
description: arg.description || ""
|
|
88
|
+
}))
|
|
89
|
+
|
|
90
|
+
const frontmatter = {
|
|
91
|
+
skill_name: `${cmd.namespace}_${cmd.resource}_${cmd.action}`,
|
|
92
|
+
description: cmd.description || `Execute ${cmd.namespace}.${cmd.resource}.${cmd.action}`,
|
|
93
|
+
command: `${cmd.namespace} ${cmd.resource} ${cmd.action}`,
|
|
94
|
+
arguments: argLines,
|
|
95
|
+
output_schema: cmd.output || { type: "object" },
|
|
96
|
+
metadata: {
|
|
97
|
+
side_effects: !!cmd.mutation,
|
|
98
|
+
risk_level: cmd.risk_level || "safe",
|
|
99
|
+
dag_supported: true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (includeDag) {
|
|
104
|
+
frontmatter.dag = toDagNodes(cmd)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const exampleArgs = (cmd.args || [])
|
|
108
|
+
.map(arg => {
|
|
109
|
+
if (arg.required) return `--${arg.name} <${arg.name}>`
|
|
110
|
+
return `--${arg.name} <${arg.name}>`
|
|
111
|
+
})
|
|
112
|
+
.join(" ")
|
|
113
|
+
.trim()
|
|
114
|
+
|
|
115
|
+
const examples = [
|
|
116
|
+
`supercli ${cmd.namespace} ${cmd.resource} ${cmd.action}${exampleArgs ? ` ${exampleArgs}` : ""} --json`
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
if (includeDag) {
|
|
120
|
+
examples.push(`supercli skills get ${cmd.namespace}.${cmd.resource}.${cmd.action} --show-dag`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return `---\n${renderYamlObject(frontmatter)}\n---\n\n# Examples\n\n\`\`\`bash\n${examples.join("\n")}\n\`\`\``
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildTeachSkillMarkdown(options = {}) {
|
|
127
|
+
const includeDag = !!options.showDag
|
|
128
|
+
|
|
129
|
+
const frontmatter = {
|
|
130
|
+
skill_name: "teach_skills_usage",
|
|
131
|
+
description: "Introduces LLMs to SuperCLI skills commands and explains how to request and execute skills.",
|
|
132
|
+
command: "skills teach",
|
|
133
|
+
arguments: [
|
|
134
|
+
{
|
|
135
|
+
name: "format",
|
|
136
|
+
type: "string",
|
|
137
|
+
required: false,
|
|
138
|
+
description: "Output format, default skill.md"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "show-dag",
|
|
142
|
+
type: "boolean",
|
|
143
|
+
required: false,
|
|
144
|
+
description: "Include internal DAG for agent reasoning"
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
output_schema: {
|
|
148
|
+
instruction: "string",
|
|
149
|
+
examples: "array"
|
|
150
|
+
},
|
|
151
|
+
metadata: {
|
|
152
|
+
side_effects: false,
|
|
153
|
+
risk_level: "safe",
|
|
154
|
+
dag_supported: true
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (includeDag) {
|
|
159
|
+
frontmatter.dag = [
|
|
160
|
+
{ id: 1, type: "resolve_skills_catalog" },
|
|
161
|
+
{ id: 2, type: "render_meta_skill", depends_on: [1] },
|
|
162
|
+
{ id: 3, type: "emit_skill_markdown", depends_on: [2] }
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return `---\n${renderYamlObject(frontmatter)}\n---\n\n# Instruction\n\nThis skill teaches LLMs how to discover and use SuperCLI skills:\n\n1. List available skills:\n\n\`\`\`bash\nsupercli skills list --json\n\`\`\`\n\n2. Fetch a specific skill:\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 command with validated arguments:\n\n\`\`\`bash\nsupercli <namespace> <resource> <action> --arg value --json\n\`\`\`\n\n# Examples\n\n\`\`\`bash\nsupercli skills teach --format skill.md\nsupercli skills teach --format skill.md --show-dag\n\`\`\``
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function listSkillsMetadata(config) {
|
|
170
|
+
return (config.commands || []).map(cmd => ({
|
|
171
|
+
name: `${cmd.namespace}.${cmd.resource}.${cmd.action}`,
|
|
172
|
+
description: cmd.description || ""
|
|
173
|
+
}))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function handleSkillsCommand(options) {
|
|
177
|
+
const { positional, flags, config, humanMode, output, outputHumanTable, outputError } = options
|
|
178
|
+
const subcommand = positional[1]
|
|
179
|
+
const format = flags.format || "skill.md"
|
|
180
|
+
|
|
181
|
+
if (subcommand === "list") {
|
|
182
|
+
const skills = listSkillsMetadata(config)
|
|
183
|
+
if (humanMode && !flags.json) {
|
|
184
|
+
console.log("\n ⚡ Skills\n")
|
|
185
|
+
outputHumanTable(skills, [
|
|
186
|
+
{ key: "name", label: "Name" },
|
|
187
|
+
{ key: "description", label: "Description" }
|
|
188
|
+
])
|
|
189
|
+
console.log("")
|
|
190
|
+
} else {
|
|
191
|
+
output({ skills })
|
|
192
|
+
}
|
|
193
|
+
return true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (subcommand === "teach") {
|
|
197
|
+
if (format !== "skill.md") {
|
|
198
|
+
outputError({ code: 85, type: "invalid_argument", message: "skills teach supports only --format skill.md", recoverable: false })
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
console.log(buildTeachSkillMarkdown({ showDag: !!flags["show-dag"] }))
|
|
202
|
+
return true
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (subcommand === "get") {
|
|
206
|
+
if (format !== "skill.md") {
|
|
207
|
+
outputError({ code: 85, type: "invalid_argument", message: "skills get supports only --format skill.md", recoverable: false })
|
|
208
|
+
return true
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const dottedId = positional[2] || (positional[3] && positional[4] ? `${positional[2]}.${positional[3]}.${positional[4]}` : "")
|
|
212
|
+
const parsed = normalizeSkillId(dottedId)
|
|
213
|
+
if (!parsed) {
|
|
214
|
+
outputError({ code: 85, type: "invalid_argument", message: "Usage: supercli skills get <namespace.resource.action> [--format skill.md]", recoverable: false })
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const cmd = config.commands.find(c =>
|
|
219
|
+
c.namespace === parsed.namespace && c.resource === parsed.resource && c.action === parsed.action
|
|
220
|
+
)
|
|
221
|
+
if (!cmd) {
|
|
222
|
+
outputError({ code: 92, type: "resource_not_found", message: `Skill ${parsed.id} not found`, suggestions: ["Run: supercli skills list --json"] })
|
|
223
|
+
return true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
console.log(buildCommandSkillMarkdown(cmd, { showDag: !!flags["show-dag"] }))
|
|
227
|
+
return true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
outputError({ code: 85, type: "invalid_argument", message: "Unknown skills subcommand. Use: list, get, teach", recoverable: false })
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
module.exports = {
|
|
235
|
+
normalizeSkillId,
|
|
236
|
+
buildCommandSkillMarkdown,
|
|
237
|
+
buildTeachSkillMarkdown,
|
|
238
|
+
listSkillsMetadata,
|
|
239
|
+
handleSkillsCommand
|
|
240
|
+
}
|