@llmtune/cli 0.1.5 → 0.1.7
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/dist/marketplace/client.js +18 -6
- package/package.json +1 -1
- package/docs/SKILL_AUTHORING.md +0 -175
- package/scripts/smoke-test.js +0 -142
|
@@ -7,6 +7,10 @@ exports.publishSkill = publishSkill;
|
|
|
7
7
|
function getApiBase(config) {
|
|
8
8
|
return String(config.apiBase ?? "https://api.llmtune.io/api/agent/v1").replace(/\/$/, "");
|
|
9
9
|
}
|
|
10
|
+
/** Skills API lives at /api/agent/skills (not under /v1). */
|
|
11
|
+
function getAgentRoot(config) {
|
|
12
|
+
return getApiBase(config).replace(/\/v1$/, "");
|
|
13
|
+
}
|
|
10
14
|
function getHeaders(config) {
|
|
11
15
|
return {
|
|
12
16
|
"Content-Type": "application/json",
|
|
@@ -17,7 +21,7 @@ function getHeaders(config) {
|
|
|
17
21
|
* List all skills in the marketplace.
|
|
18
22
|
*/
|
|
19
23
|
async function listSkills(config, options) {
|
|
20
|
-
const base =
|
|
24
|
+
const base = getAgentRoot(config);
|
|
21
25
|
const params = new URLSearchParams();
|
|
22
26
|
if (options?.search)
|
|
23
27
|
params.set("search", options.search);
|
|
@@ -32,13 +36,21 @@ async function listSkills(config, options) {
|
|
|
32
36
|
if (!response.ok) {
|
|
33
37
|
throw new Error(`Failed to list skills: HTTP ${response.status}`);
|
|
34
38
|
}
|
|
35
|
-
|
|
39
|
+
const json = (await response.json());
|
|
40
|
+
if (Array.isArray(json.data)) {
|
|
41
|
+
return {
|
|
42
|
+
skills: json.data,
|
|
43
|
+
total: json.total ?? json.data.length,
|
|
44
|
+
page: options?.page ?? 1,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return json;
|
|
36
48
|
}
|
|
37
49
|
/**
|
|
38
50
|
* Get details for a specific skill.
|
|
39
51
|
*/
|
|
40
52
|
async function getSkillDetails(config, name) {
|
|
41
|
-
const base =
|
|
53
|
+
const base = getAgentRoot(config);
|
|
42
54
|
const url = `${base}/skills/${encodeURIComponent(name)}`;
|
|
43
55
|
const response = await fetch(url, { headers: getHeaders(config) });
|
|
44
56
|
if (!response.ok) {
|
|
@@ -52,12 +64,12 @@ async function getSkillDetails(config, name) {
|
|
|
52
64
|
* Install a skill from the marketplace to the local skills directory.
|
|
53
65
|
*/
|
|
54
66
|
async function installSkill(config, name) {
|
|
55
|
-
const base =
|
|
67
|
+
const base = getAgentRoot(config);
|
|
56
68
|
const url = `${base}/skills/install`;
|
|
57
69
|
const response = await fetch(url, {
|
|
58
70
|
method: "POST",
|
|
59
71
|
headers: getHeaders(config),
|
|
60
|
-
body: JSON.stringify({
|
|
72
|
+
body: JSON.stringify({ name, content: "" }),
|
|
61
73
|
});
|
|
62
74
|
if (!response.ok) {
|
|
63
75
|
const body = await response.text();
|
|
@@ -70,7 +82,7 @@ async function installSkill(config, name) {
|
|
|
70
82
|
* Publish a local skill to the marketplace.
|
|
71
83
|
*/
|
|
72
84
|
async function publishSkill(config, skill) {
|
|
73
|
-
const base =
|
|
85
|
+
const base = getAgentRoot(config);
|
|
74
86
|
const url = `${base}/skills`;
|
|
75
87
|
const response = await fetch(url, {
|
|
76
88
|
method: "POST",
|
package/package.json
CHANGED
package/docs/SKILL_AUTHORING.md
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
# Skill Authoring Guide
|
|
2
|
-
|
|
3
|
-
Skills are reusable AI workflows that extend the LLMTune CLI. Each skill is defined by a `SKILL.md` file inside a named directory.
|
|
4
|
-
|
|
5
|
-
## Quick Start
|
|
6
|
-
|
|
7
|
-
Create a skill in `~/.llmtune/skills/` or `.llmtune/skills/` in your project:
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
~/.llmtune/skills/
|
|
11
|
-
my-skill/
|
|
12
|
-
SKILL.md
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## SKILL.md Format
|
|
16
|
-
|
|
17
|
-
A skill file has two parts: **YAML frontmatter** (optional) and **Markdown body** (the prompt template).
|
|
18
|
-
|
|
19
|
-
```markdown
|
|
20
|
-
---
|
|
21
|
-
description: "Fix ESLint errors in a file"
|
|
22
|
-
user-invocable: true
|
|
23
|
-
allowed-tools: [read, edit, glob, grep]
|
|
24
|
-
arguments: file_path
|
|
25
|
-
trust: local
|
|
26
|
-
---
|
|
27
|
-
|
|
28
|
-
You are a linting expert. Fix all ESLint errors in the file at `{{file_path}}`.
|
|
29
|
-
|
|
30
|
-
1. Read the file
|
|
31
|
-
2. Identify lint errors
|
|
32
|
-
3. Fix each error while preserving the original intent
|
|
33
|
-
4. Report what you changed
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Frontmatter Fields
|
|
37
|
-
|
|
38
|
-
| Field | Type | Default | Description |
|
|
39
|
-
|-------|------|---------|-------------|
|
|
40
|
-
| `description` | string | (required) | Short description shown in `/skills` list |
|
|
41
|
-
| `user-invocable` | boolean | `true` | Whether users can invoke with `/<skill-name>` |
|
|
42
|
-
| `allowed-tools` | string[] | all tools | Restrict which tools the skill can use |
|
|
43
|
-
| `arguments` | string or string[] | none | Argument names for substitution |
|
|
44
|
-
| `trust` | string | `local` | Trust level: `local`, `community`, `verified`, `signed` |
|
|
45
|
-
| `when-to-use` | string | none | Hint for when the agent should auto-invoke this skill |
|
|
46
|
-
|
|
47
|
-
## Argument Substitution
|
|
48
|
-
|
|
49
|
-
Arguments are referenced in the body using `{{arg_name}}` syntax:
|
|
50
|
-
|
|
51
|
-
```markdown
|
|
52
|
-
---
|
|
53
|
-
arguments: file_path, style
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
Refactor the file at `{{file_path}}` to use `{{style}}` coding style.
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
When invoked: `/my-skill src/auth.ts functional`
|
|
60
|
-
|
|
61
|
-
- `{{file_path}}` becomes `src/auth.ts`
|
|
62
|
-
- `{{style}}` becomes `functional`
|
|
63
|
-
|
|
64
|
-
## Trust Levels
|
|
65
|
-
|
|
66
|
-
Skills have four trust levels that determine what tools they can access:
|
|
67
|
-
|
|
68
|
-
| Level | Tools Allowed | Use Case |
|
|
69
|
-
|-------|--------------|----------|
|
|
70
|
-
| **local** | All | Skills you create yourself in `~/.llmtune/skills/` |
|
|
71
|
-
| **community** | read, glob, grep only | Skills installed from marketplace |
|
|
72
|
-
| **verified** | All | Skills reviewed by the LLMTune team |
|
|
73
|
-
| **signed** | All | Cryptographically signed skills with verified authors |
|
|
74
|
-
|
|
75
|
-
## Invoking Skills
|
|
76
|
-
|
|
77
|
-
### From the CLI REPL
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
> /explain-code src/auth.ts
|
|
81
|
-
> /fix-lint src/utils/helpers.ts
|
|
82
|
-
> /generate-test src/services/user.ts
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### From Commander (non-interactive)
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
llmtune skills run explain-code --args "src/auth.ts"
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Built-in Skills
|
|
92
|
-
|
|
93
|
-
The LLMTune CLI includes these built-in skills:
|
|
94
|
-
|
|
95
|
-
| Skill | Description | Arguments |
|
|
96
|
-
|-------|-------------|-----------|
|
|
97
|
-
| `explain-code` | Explain code with a clear breakdown | `file_path` |
|
|
98
|
-
| `fix-lint` | Auto-fix linting errors | `file_path` |
|
|
99
|
-
| `generate-test` | Generate unit tests for a file | `file_path` |
|
|
100
|
-
| `security-review` | Review code for security issues | `file_path` |
|
|
101
|
-
|
|
102
|
-
## Publishing to the Marketplace
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
# Sign your skill
|
|
106
|
-
llmtune skills sign my-skill
|
|
107
|
-
|
|
108
|
-
# Publish to marketplace
|
|
109
|
-
llmtune skills publish my-skill
|
|
110
|
-
|
|
111
|
-
# Install from marketplace
|
|
112
|
-
llmtune skills install security-review
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Examples
|
|
116
|
-
|
|
117
|
-
### Code Review Skill
|
|
118
|
-
|
|
119
|
-
```markdown
|
|
120
|
-
---
|
|
121
|
-
description: "Review code for best practices and potential issues"
|
|
122
|
-
allowed-tools: [read, glob, grep]
|
|
123
|
-
arguments: file_path
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
Review the code at `{{file_path}}` for:
|
|
127
|
-
|
|
128
|
-
1. **Security vulnerabilities** - XSS, injection, auth issues
|
|
129
|
-
2. **Performance problems** - N+1 queries, unnecessary re-renders, memory leaks
|
|
130
|
-
3. **Code quality** - Naming, structure, DRY violations
|
|
131
|
-
4. **Error handling** - Missing error cases, swallowed errors
|
|
132
|
-
5. **Type safety** - Any `any` types, missing null checks
|
|
133
|
-
|
|
134
|
-
Provide specific, actionable feedback with line numbers.
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Database Migration Skill
|
|
138
|
-
|
|
139
|
-
```markdown
|
|
140
|
-
---
|
|
141
|
-
description: "Generate a Prisma migration for schema changes"
|
|
142
|
-
allowed-tools: [read, write, edit, bash]
|
|
143
|
-
arguments: description
|
|
144
|
-
---
|
|
145
|
-
|
|
146
|
-
The user wants to make this database change: {{description}}
|
|
147
|
-
|
|
148
|
-
1. Read the current prisma/schema.prisma
|
|
149
|
-
2. Make the necessary schema changes
|
|
150
|
-
3. Generate the migration using `npx prisma migrate dev --name <descriptive-name>`
|
|
151
|
-
4. Report what changed
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
### API Endpoint Skill
|
|
155
|
-
|
|
156
|
-
```markdown
|
|
157
|
-
---
|
|
158
|
-
description: "Scaffold a new REST API endpoint"
|
|
159
|
-
allowed-tools: [read, write, edit, glob, grep]
|
|
160
|
-
arguments: method, path
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
Create a new API endpoint:
|
|
164
|
-
|
|
165
|
-
- Method: `{{method}}`
|
|
166
|
-
- Path: `{{path}}`
|
|
167
|
-
|
|
168
|
-
1. Find existing route files to understand the pattern
|
|
169
|
-
2. Create the route handler
|
|
170
|
-
3. Add input validation
|
|
171
|
-
4. Add error handling
|
|
172
|
-
5. Register the route
|
|
173
|
-
|
|
174
|
-
Follow the existing code patterns in the project.
|
|
175
|
-
```
|
package/scripts/smoke-test.js
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* CLI smoke tests — run: npm test
|
|
4
|
-
* Set LLMTUNE_API_KEY or configure ~/.llmtune/config.json for live API tests.
|
|
5
|
-
*/
|
|
6
|
-
const fs = require("fs")
|
|
7
|
-
const path = require("path")
|
|
8
|
-
const os = require("os")
|
|
9
|
-
|
|
10
|
-
let passed = 0
|
|
11
|
-
let failed = 0
|
|
12
|
-
let skipped = 0
|
|
13
|
-
|
|
14
|
-
function ok(name) {
|
|
15
|
-
passed++
|
|
16
|
-
console.log(` ✓ ${name}`)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function fail(name, err) {
|
|
20
|
-
failed++
|
|
21
|
-
console.log(` ✗ ${name}: ${err}`)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function skip(name, reason) {
|
|
25
|
-
skipped++
|
|
26
|
-
console.log(` ⊘ ${name} (${reason})`)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function test(name, fn) {
|
|
30
|
-
try {
|
|
31
|
-
fn()
|
|
32
|
-
ok(name)
|
|
33
|
-
} catch (e) {
|
|
34
|
-
fail(name, e.message)
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async function testAsync(name, fn) {
|
|
39
|
-
try {
|
|
40
|
-
await fn()
|
|
41
|
-
ok(name)
|
|
42
|
-
} catch (e) {
|
|
43
|
-
fail(name, e.message)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function main() {
|
|
48
|
-
console.log("=== LLMTune CLI Smoke Tests ===\n")
|
|
49
|
-
|
|
50
|
-
test("dist/index.js exists", () => {
|
|
51
|
-
const p = path.join(__dirname, "..", "dist", "index.js")
|
|
52
|
-
if (!fs.existsSync(p)) throw new Error("run npm run build first")
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test("agent identity module", () => {
|
|
56
|
-
const { buildAgentIdentitySection } = require("../dist/context/agent-identity")
|
|
57
|
-
const text = buildAgentIdentitySection("z-ai/GLM-5.1")
|
|
58
|
-
if (!text.includes("LLMTune Agent")) throw new Error("missing identity")
|
|
59
|
-
if (text.toLowerCase().includes("you are claude")) throw new Error("should not identify as Claude")
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test("git context (Windows-safe)", () => {
|
|
63
|
-
const { collectGitContext } = require("../dist/context/git-context")
|
|
64
|
-
collectGitContext(process.cwd())
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test("microcompact", () => {
|
|
68
|
-
const { microcompactMessages } = require("../dist/compact/microcompact")
|
|
69
|
-
const { compacted } = microcompactMessages([
|
|
70
|
-
{ role: "tool", content: "x".repeat(5000) },
|
|
71
|
-
])
|
|
72
|
-
if (compacted[0].content.length >= 5000) throw new Error("not compressed")
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
test("context analyzer", () => {
|
|
76
|
-
const { analyzeContextUsage } = require("../dist/context/analyzer")
|
|
77
|
-
const a = analyzeContextUsage({
|
|
78
|
-
systemPrompt: "test",
|
|
79
|
-
toolSpecs: [],
|
|
80
|
-
messages: [{ role: "user", content: "hi" }],
|
|
81
|
-
model: "z-ai/GLM-5.1",
|
|
82
|
-
})
|
|
83
|
-
if (a.totalTokens <= 0) throw new Error("expected tokens > 0")
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
test("permission manager trust", () => {
|
|
87
|
-
const { PermissionManager } = require("../dist/tools/permissions")
|
|
88
|
-
const pm = new PermissionManager()
|
|
89
|
-
pm.trustTool("bash")
|
|
90
|
-
if (!pm.isTrusted("bash")) throw new Error("trust failed")
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
await testAsync("registry dispatchAsync", async () => {
|
|
94
|
-
const { ToolRegistry } = require("../dist/tools/registry")
|
|
95
|
-
const { bashTool } = require("../dist/tools/tools/bash")
|
|
96
|
-
const reg = new ToolRegistry()
|
|
97
|
-
reg.register(bashTool)
|
|
98
|
-
await reg.dispatchAsync("bash", { command: "echo SMOKE_OK" }, {
|
|
99
|
-
workspaceRoot: process.cwd(),
|
|
100
|
-
cwd: process.cwd(),
|
|
101
|
-
})
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const configPath = path.join(os.homedir(), ".llmtune", "config.json")
|
|
105
|
-
const hasApi = Boolean(process.env.LLMTUNE_API_KEY) || fs.existsSync(configPath)
|
|
106
|
-
|
|
107
|
-
if (hasApi) {
|
|
108
|
-
await testAsync("API agent loop + bash tool", async () => {
|
|
109
|
-
const { createClient } = require("../dist/auth/client")
|
|
110
|
-
const { Conversation } = require("../dist/agent/conversation")
|
|
111
|
-
const { ToolRegistry } = require("../dist/tools/registry")
|
|
112
|
-
const { bashTool } = require("../dist/tools/tools/bash")
|
|
113
|
-
const { runAgentLoop } = require("../dist/agent/loop")
|
|
114
|
-
|
|
115
|
-
const client = createClient()
|
|
116
|
-
const registry = new ToolRegistry()
|
|
117
|
-
registry.register(bashTool)
|
|
118
|
-
const conversation = new Conversation("z-ai/GLM-5.1")
|
|
119
|
-
const cwd = process.cwd()
|
|
120
|
-
|
|
121
|
-
const result = await runAgentLoop(
|
|
122
|
-
client,
|
|
123
|
-
conversation,
|
|
124
|
-
registry,
|
|
125
|
-
"Run bash command: echo AGENT_SMOKE_OK. Use bash tool only.",
|
|
126
|
-
{ model: "z-ai/GLM-5.1", maxTurns: 3, stream: false, cwd, workspaceRoot: cwd },
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
if (result.totalToolCalls === 0) throw new Error("expected at least one tool call")
|
|
130
|
-
})
|
|
131
|
-
} else {
|
|
132
|
-
skip("API agent loop + bash tool", "no API key")
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
console.log(`\n=== Results: ${passed} passed, ${failed} failed, ${skipped} skipped ===`)
|
|
136
|
-
process.exit(failed > 0 ? 1 : 0)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
main().catch((err) => {
|
|
140
|
-
console.error(err)
|
|
141
|
-
process.exit(1)
|
|
142
|
-
})
|