@klhapp/skillmux 1.2.0 → 1.3.1
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/CHANGELOG.md +19 -0
- package/README.md +162 -521
- package/config.example.toml +5 -4
- package/config.remote.example.toml +5 -2
- package/docs/README.md +52 -0
- package/docs/assets/architecture.svg +156 -0
- package/docs/assets/logo.png +0 -0
- package/docs/calibration.md +6 -1
- package/docs/cli.md +350 -0
- package/docs/concepts.md +165 -0
- package/docs/configuration.md +34 -10
- package/docs/deployment.md +250 -0
- package/docs/getting-started.md +239 -0
- package/docs/mcp-routing.md +172 -0
- package/docs/releasing.md +4 -3
- package/docs/skill-management.md +209 -0
- package/docs/troubleshooting.md +199 -0
- package/package.json +3 -6
- package/src/cli.ts +40 -3
- package/src/config-watcher.ts +5 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Start with:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
skillmux doctor
|
|
7
|
+
skillmux config show
|
|
8
|
+
skillmux config validate
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Add `--json` when you need machine-readable diagnostics.
|
|
12
|
+
|
|
13
|
+
## Vault failures
|
|
14
|
+
|
|
15
|
+
### Vault path does not exist
|
|
16
|
+
|
|
17
|
+
Check the effective path and its source:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
skillmux config show
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Create the directory or initialize config against a populated vault:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
skillmux config init --vault ~/skills --yes
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`config init` requires at least one valid `SKILL.md`.
|
|
30
|
+
|
|
31
|
+
### A skill does not appear
|
|
32
|
+
|
|
33
|
+
Each skill must sit one directory below the vault:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
~/skills/<skill-id>/SKILL.md
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
skillmux scan ~/skills/<skill-id>
|
|
43
|
+
skillmux index
|
|
44
|
+
skillmux skill which <skill-id>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The scanner reports invalid frontmatter and unreadable content. `skill which`
|
|
48
|
+
also reveals local-overlay shadowing.
|
|
49
|
+
|
|
50
|
+
## Sync failures
|
|
51
|
+
|
|
52
|
+
### Target has no ownership marker
|
|
53
|
+
|
|
54
|
+
Skillmux will not change an existing unmarked directory. Adopt it first:
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
skillmux init --client claude-code --dry-run
|
|
58
|
+
skillmux init --client claude-code --yes
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Target points to the full vault
|
|
62
|
+
|
|
63
|
+
Review the smaller pinned set before converting:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
skillmux init --client claude-code \
|
|
67
|
+
--migrate-full-vault \
|
|
68
|
+
--core code-context \
|
|
69
|
+
--dry-run
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Apply the same command with `--yes` after checking the plan.
|
|
73
|
+
|
|
74
|
+
### Unmanaged file collides with a pin
|
|
75
|
+
|
|
76
|
+
Skillmux preserves unmanaged target content. Rename or remove the conflicting
|
|
77
|
+
entry yourself, then rerun `skillmux sync`.
|
|
78
|
+
|
|
79
|
+
### Target belongs to another host
|
|
80
|
+
|
|
81
|
+
New targets include the current hostname. `sync` skips a target when its
|
|
82
|
+
manifest `host` differs. Run `skillmux target show <name>` and add a separate
|
|
83
|
+
target for the current machine instead of reusing the other machine's path.
|
|
84
|
+
|
|
85
|
+
### A local-overlay skill cannot be pinned
|
|
86
|
+
|
|
87
|
+
Core and project pins must exist in the canonical `vault_path`. Copy or commit
|
|
88
|
+
the skill there before pinning it. Routed lookup can still serve the overlay.
|
|
89
|
+
|
|
90
|
+
## Retrieval failures
|
|
91
|
+
|
|
92
|
+
### `resolve_skill` returns `ambiguous`
|
|
93
|
+
|
|
94
|
+
Ambiguity is the expected result without calibrated reranker thresholds. The
|
|
95
|
+
calling model should select a candidate and call `fetch_skill`.
|
|
96
|
+
|
|
97
|
+
Improve a weak shortlist by:
|
|
98
|
+
|
|
99
|
+
- writing a concrete skill description with task vocabulary;
|
|
100
|
+
- enabling embeddings;
|
|
101
|
+
- increasing recall depth when the relevant skill falls outside the fused
|
|
102
|
+
candidate set.
|
|
103
|
+
|
|
104
|
+
Use a labelled dataset and [Policy calibration](calibration.md) before enabling
|
|
105
|
+
automatic matches.
|
|
106
|
+
|
|
107
|
+
### Server reports lexical mode
|
|
108
|
+
|
|
109
|
+
Lexical mode means Skillmux can query FTS5 but cannot use embeddings. The next
|
|
110
|
+
step depends on the installation:
|
|
111
|
+
|
|
112
|
+
| Installation | Expected action |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| Skillmux CLI with local inference | Download the local model and rebuild the index |
|
|
115
|
+
| Full Docker image | Confirm the running tag is the full image and inspect `doctor` output |
|
|
116
|
+
| Slim Docker image | Configure remote embeddings or keep lexical fallback |
|
|
117
|
+
|
|
118
|
+
For a Skillmux CLI installation, run:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
skillmux doctor
|
|
122
|
+
skillmux models download
|
|
123
|
+
skillmux index
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For remote inference, verify the endpoint, model, dimension, API-key
|
|
127
|
+
environment variable, and network path. The slim image does not contain
|
|
128
|
+
GTE-small, so `models download` is not its recovery path.
|
|
129
|
+
|
|
130
|
+
### Reranker is unavailable
|
|
131
|
+
|
|
132
|
+
Skillmux keeps the hybrid shortlist when a reranker probe or request fails.
|
|
133
|
+
Check `inference.reranker.endpoint`, `adapter`, `model`, and the environment
|
|
134
|
+
variable named by `api_key_env`.
|
|
135
|
+
|
|
136
|
+
Use the complete request URL. Skillmux does not append `/rerank` or infer an
|
|
137
|
+
adapter from the endpoint.
|
|
138
|
+
|
|
139
|
+
### Delivered content changed after indexing
|
|
140
|
+
|
|
141
|
+
Skillmux checks the file before delivery and refreshes stale metadata. If a
|
|
142
|
+
skill cannot be parsed during a live edit, the index keeps the previous good
|
|
143
|
+
metadata but does not serve stale body bytes. Finish the write with valid
|
|
144
|
+
frontmatter and retry.
|
|
145
|
+
|
|
146
|
+
## HTTP failures
|
|
147
|
+
|
|
148
|
+
### Another machine cannot connect
|
|
149
|
+
|
|
150
|
+
The Skillmux CLI binds HTTP to `127.0.0.1`. Set `server.hostname` or
|
|
151
|
+
`HTTP_HOSTNAME` to a reachable interface, then restart the server. Docker
|
|
152
|
+
binds `0.0.0.0` inside the container, but the host still needs a published
|
|
153
|
+
port. Enable authentication before exposing either deployment.
|
|
154
|
+
|
|
155
|
+
### Browser receives `403`
|
|
156
|
+
|
|
157
|
+
Add the browser origin to `server.allowed_origins`. The value must match the
|
|
158
|
+
request's `Origin` header. Curl and server-to-server clients omit this header
|
|
159
|
+
and do not use the CORS list.
|
|
160
|
+
|
|
161
|
+
### Client receives `401`
|
|
162
|
+
|
|
163
|
+
Confirm `server.auth_enabled = true`, export the environment variable named by
|
|
164
|
+
`auth_token_env`, and send `Authorization: Bearer <token>`.
|
|
165
|
+
|
|
166
|
+
An enabled server with an empty token environment variable returns a server
|
|
167
|
+
configuration error rather than accepting an empty token.
|
|
168
|
+
|
|
169
|
+
### Client receives `429`
|
|
170
|
+
|
|
171
|
+
The rate limiter rejected the request. Read `Retry-After` and the
|
|
172
|
+
`X-RateLimit-*` response headers. Increase `requests_per_minute` only after
|
|
173
|
+
checking for a retry loop or shared-token traffic.
|
|
174
|
+
|
|
175
|
+
## Configuration migration errors
|
|
176
|
+
|
|
177
|
+
Skillmux rejects removed fields with migration guidance:
|
|
178
|
+
|
|
179
|
+
- replace `[targets.<name>].project` with `project_groups = [...]`;
|
|
180
|
+
- rename `[project.<group>].repos` to `paths`;
|
|
181
|
+
- use `skillmux core pin|unpin` instead of removed `manifest pin|unpin`;
|
|
182
|
+
- replace legacy reranker base-URL variables with
|
|
183
|
+
`SKILLMUX_RERANK_ENDPOINT` and `SKILLMUX_RERANK_ADAPTER`.
|
|
184
|
+
|
|
185
|
+
Run `skillmux config validate` after editing TOML.
|
|
186
|
+
|
|
187
|
+
## Collect diagnostics
|
|
188
|
+
|
|
189
|
+
For a bug report, include:
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
skillmux --help
|
|
193
|
+
skillmux config show
|
|
194
|
+
skillmux doctor --json
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Remove tokens, private endpoint credentials, local usernames, and raw audit
|
|
198
|
+
queries before posting output. Open an issue at
|
|
199
|
+
<https://github.com/klhq/skillmux/issues>.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klhapp/skillmux",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.3.1",
|
|
4
|
+
"description": "Skill management and retrieval for AI agents: sync native skills across clients and route the long tail over MCP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"author": "Lance Hsu <lance@klh.app>",
|
|
@@ -22,10 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
25
|
-
"docs
|
|
26
|
-
"docs/configuration.md",
|
|
27
|
-
"docs/calibration.md",
|
|
28
|
-
"docs/releasing.md",
|
|
25
|
+
"docs",
|
|
29
26
|
"README.md",
|
|
30
27
|
"LICENSE",
|
|
31
28
|
"CHANGELOG.md",
|
package/src/cli.ts
CHANGED
|
@@ -132,6 +132,31 @@ const KNOWN_COMMANDS = [
|
|
|
132
132
|
"local-vault",
|
|
133
133
|
];
|
|
134
134
|
|
|
135
|
+
const DOCKER_HOST_MANAGEMENT_GUIDANCE =
|
|
136
|
+
"This command manages local Skillmux or agent directories and is not supported inside the Docker image. Install the Skillmux CLI on the host using the Bun package or standalone Linux executable.";
|
|
137
|
+
|
|
138
|
+
function isDockerHostManagementCommand(command: string, subCommand: string): boolean {
|
|
139
|
+
if (
|
|
140
|
+
[
|
|
141
|
+
"init",
|
|
142
|
+
"sync",
|
|
143
|
+
"install",
|
|
144
|
+
"project",
|
|
145
|
+
"target",
|
|
146
|
+
"core",
|
|
147
|
+
"local-vault",
|
|
148
|
+
"models",
|
|
149
|
+
"context",
|
|
150
|
+
"calibrate",
|
|
151
|
+
"eval",
|
|
152
|
+
].includes(command)
|
|
153
|
+
) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return command === "config" && ["init", "set"].includes(subCommand);
|
|
158
|
+
}
|
|
159
|
+
|
|
135
160
|
async function main() {
|
|
136
161
|
const rawArgv = Bun.argv.slice(2);
|
|
137
162
|
|
|
@@ -141,6 +166,8 @@ async function main() {
|
|
|
141
166
|
let flagContext: string | undefined;
|
|
142
167
|
let flagServer: string | undefined;
|
|
143
168
|
let isDryRun = false;
|
|
169
|
+
const subCommand = rawArgv[1] ?? "";
|
|
170
|
+
const commandArgs = rawArgv.slice(2);
|
|
144
171
|
|
|
145
172
|
const command = rawArgv[0];
|
|
146
173
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -161,6 +188,18 @@ async function main() {
|
|
|
161
188
|
|
|
162
189
|
let resolvedTarget: ResolvedTarget = { type: "local", name: "local" };
|
|
163
190
|
|
|
191
|
+
if (
|
|
192
|
+
process.env.RUNNING_IN_DOCKER === "true" &&
|
|
193
|
+
isDockerHostManagementCommand(command, subCommand)
|
|
194
|
+
) {
|
|
195
|
+
handleError(new Error(DOCKER_HOST_MANAGEMENT_GUIDANCE), {
|
|
196
|
+
target: resolvedTarget,
|
|
197
|
+
isJson,
|
|
198
|
+
isVerbose,
|
|
199
|
+
});
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
164
203
|
// Only resolve target if command is target-aware or context/config/calibrate
|
|
165
204
|
const isLocalConfigInit = command === "config" && rawArgv[1] === "init";
|
|
166
205
|
if (
|
|
@@ -181,8 +220,6 @@ async function main() {
|
|
|
181
220
|
}
|
|
182
221
|
|
|
183
222
|
const adapter = createTargetAdapter(resolvedTarget, { allowInsecure });
|
|
184
|
-
const subCommand = rawArgv[1] ?? "";
|
|
185
|
-
const commandArgs = rawArgv.slice(2);
|
|
186
223
|
|
|
187
224
|
try {
|
|
188
225
|
switch (command) {
|
|
@@ -409,7 +446,7 @@ async function handleCalibrateCommand(
|
|
|
409
446
|
if (!Number.isFinite(value)) throw new Error(`${flag} must be a number`);
|
|
410
447
|
return value;
|
|
411
448
|
};
|
|
412
|
-
for (let i =
|
|
449
|
+
for (let i = 1; i < args.length; i++) {
|
|
413
450
|
const option = args[i];
|
|
414
451
|
if (option === "--dataset") {
|
|
415
452
|
datasetPath = args[++i];
|
package/src/config-watcher.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { watch } from "node:fs";
|
|
1
|
+
import { mkdirSync, watch } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { loadConfig } from "./config";
|
|
4
4
|
import type { Config } from "./types";
|
|
@@ -133,6 +133,10 @@ export class ConfigWatcher {
|
|
|
133
133
|
const dir = dirname(tomlPath);
|
|
134
134
|
const filename = tomlPath.split(/[/\\]/).pop()!;
|
|
135
135
|
|
|
136
|
+
// A config file is optional. Ensure its parent exists so zero-config
|
|
137
|
+
// startup is safe and later config writes are still observed.
|
|
138
|
+
mkdirSync(dir, { recursive: true });
|
|
139
|
+
|
|
136
140
|
this.watcher = watch(dir, { recursive: false }, (_event, changedName) => {
|
|
137
141
|
if (this.stopped) return;
|
|
138
142
|
// Fire for: the config file itself, or any .tmp variant of it (handles
|