@klhapp/skillmux 1.9.3 → 1.11.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.
Files changed (53) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +19 -19
  3. package/docs/README.md +4 -4
  4. package/docs/assets/architecture-dark.svg +39 -32
  5. package/docs/assets/architecture-light.svg +25 -18
  6. package/docs/cli.md +147 -36
  7. package/docs/concepts.md +11 -11
  8. package/docs/configuration.md +7 -5
  9. package/docs/deployment.md +10 -6
  10. package/docs/getting-started.md +18 -14
  11. package/docs/mcp-routing.md +1 -1
  12. package/docs/skill-management.md +17 -11
  13. package/docs/troubleshooting.md +4 -4
  14. package/package.json +1 -1
  15. package/src/adapters.ts +157 -11
  16. package/src/cli.ts +396 -1319
  17. package/src/commands/audit.ts +53 -56
  18. package/src/commands/config.ts +33 -26
  19. package/src/commands/context.ts +104 -0
  20. package/src/commands/core.ts +7 -3
  21. package/src/commands/doctor.ts +97 -0
  22. package/src/commands/eval.ts +22 -15
  23. package/src/commands/init.ts +672 -0
  24. package/src/commands/install.ts +132 -0
  25. package/src/commands/local-vault.ts +60 -0
  26. package/src/commands/models.ts +10 -0
  27. package/src/commands/outdated.ts +2 -1
  28. package/src/commands/project.ts +194 -51
  29. package/src/commands/report.ts +66 -0
  30. package/src/commands/scan.ts +61 -0
  31. package/src/commands/shared.ts +7 -14
  32. package/src/commands/skill.ts +33 -0
  33. package/src/commands/sync.ts +232 -0
  34. package/src/commands/target.ts +45 -15
  35. package/src/commands/update.ts +2 -1
  36. package/src/completions.ts +41 -15
  37. package/src/config-service.ts +4 -54
  38. package/src/context.ts +8 -3
  39. package/src/db-audit.ts +286 -0
  40. package/src/db-index.ts +238 -0
  41. package/src/db.ts +3 -521
  42. package/src/global-flags.ts +46 -0
  43. package/src/init-agents.ts +329 -0
  44. package/src/init-instructions.ts +47 -28
  45. package/src/logger.ts +26 -0
  46. package/src/mcp-registration.ts +89 -0
  47. package/src/output.ts +80 -18
  48. package/src/prompts.ts +75 -20
  49. package/src/router-core.ts +8 -27
  50. package/src/scan.ts +19 -19
  51. package/src/server.ts +161 -14
  52. package/src/toml-writer.ts +51 -0
  53. package/src/init-clients.ts +0 -220
@@ -0,0 +1,51 @@
1
+ export function stringifyToml(obj: Record<string, any>): string {
2
+ let out = "";
3
+ const topLevel: Record<string, any> = {};
4
+ const sections: Record<string, any> = {};
5
+
6
+ for (const [k, v] of Object.entries(obj)) {
7
+ if (typeof v === "object" && v !== null && !Array.isArray(v)) {
8
+ sections[k] = v;
9
+ } else {
10
+ topLevel[k] = v;
11
+ }
12
+ }
13
+
14
+ for (const [k, v] of Object.entries(topLevel)) {
15
+ out += `${k} = ${formatTomlVal(v)}\n`;
16
+ }
17
+ if (Object.keys(topLevel).length > 0) out += "\n";
18
+
19
+ for (const [secName, secObj] of Object.entries(sections)) {
20
+ out += stringifyTomlSection([secName], secObj);
21
+ }
22
+
23
+ return out;
24
+ }
25
+
26
+ export function stringifyTomlSection(path: string[], obj: Record<string, any>): string {
27
+ let out = `[${path.join(".")}]\n`;
28
+ const subSections: Record<string, any> = {};
29
+
30
+ for (const [k, v] of Object.entries(obj)) {
31
+ if (typeof v === "object" && v !== null && !Array.isArray(v)) {
32
+ subSections[k] = v;
33
+ } else {
34
+ out += `${k} = ${formatTomlVal(v)}\n`;
35
+ }
36
+ }
37
+ out += "\n";
38
+
39
+ for (const [subName, subObj] of Object.entries(subSections)) {
40
+ out += stringifyTomlSection([...path, subName], subObj);
41
+ }
42
+
43
+ return out;
44
+ }
45
+
46
+ export function formatTomlVal(v: unknown): string {
47
+ if (typeof v === "string") return JSON.stringify(v);
48
+ if (typeof v === "boolean" || typeof v === "number") return String(v);
49
+ if (Array.isArray(v)) return JSON.stringify(v);
50
+ return JSON.stringify(v);
51
+ }
@@ -1,220 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- export const SUPPORTED_CLIENT_IDS = [
6
- "claude-code",
7
- "codex",
8
- "gemini-cli",
9
- "opencode",
10
- "github-copilot",
11
- "windsurf",
12
- "antigravity",
13
- "goose",
14
- "hermes",
15
- "skillmux-mcp",
16
- ] as const;
17
-
18
- export type ClientId = (typeof SUPPORTED_CLIENT_IDS)[number];
19
- export type DeliveryMode = "managed-pins" | "full-vault" | "mcp";
20
-
21
- export interface DetectedClient {
22
- client: ClientId;
23
- evidence: string;
24
- }
25
-
26
- interface ClientDefinition {
27
- id: ClientId;
28
- surfaceId?: "agent-skills" | "claude-code" | "codex" | "antigravity";
29
- deliveryMode: DeliveryMode;
30
- }
31
-
32
- export interface PlannedClientSurface {
33
- id: string;
34
- targetName: string;
35
- path: string;
36
- deliveryMode: "managed-pins";
37
- clients: ClientId[];
38
- }
39
-
40
- export interface ClientSurfacePlan {
41
- clients: ClientDefinition[];
42
- surfaces: PlannedClientSurface[];
43
- }
44
-
45
- type ReadinessStatus = "ready" | "planned" | "manual" | "not-applicable";
46
-
47
- export interface ReadinessAxis {
48
- status: ReadinessStatus;
49
- detail: string;
50
- }
51
-
52
- export interface ClientReadiness {
53
- client: ClientId;
54
- skillSurface: ReadinessAxis;
55
- mcpRegistration: ReadinessAxis;
56
- instructionSetup: ReadinessAxis;
57
- }
58
-
59
- export interface ResolvedBuiltInTarget {
60
- targetName: string;
61
- path: string;
62
- warning?: string;
63
- }
64
-
65
- const CLIENTS: Record<ClientId, ClientDefinition> = {
66
- "claude-code": { id: "claude-code", surfaceId: "claude-code", deliveryMode: "managed-pins" },
67
- codex: { id: "codex", surfaceId: "codex", deliveryMode: "managed-pins" },
68
- "gemini-cli": { id: "gemini-cli", surfaceId: "agent-skills", deliveryMode: "managed-pins" },
69
- opencode: { id: "opencode", surfaceId: "agent-skills", deliveryMode: "managed-pins" },
70
- "github-copilot": { id: "github-copilot", surfaceId: "agent-skills", deliveryMode: "managed-pins" },
71
- windsurf: { id: "windsurf", surfaceId: "agent-skills", deliveryMode: "managed-pins" },
72
- antigravity: { id: "antigravity", surfaceId: "antigravity", deliveryMode: "managed-pins" },
73
- goose: { id: "goose", deliveryMode: "full-vault" },
74
- hermes: { id: "hermes", deliveryMode: "full-vault" },
75
- "skillmux-mcp": { id: "skillmux-mcp", deliveryMode: "mcp" },
76
- };
77
-
78
- export function detectInstalledClients(
79
- options: {
80
- home?: string;
81
- codexHome?: string;
82
- exists?: (path: string) => boolean;
83
- } = {},
84
- ): DetectedClient[] {
85
- const home = options.home ?? homedir();
86
- const codexHome = options.codexHome ?? join(home, ".codex");
87
- const exists = options.exists ?? existsSync;
88
- const candidates: Array<[ClientId, string]> = [
89
- ["claude-code", join(home, ".claude")],
90
- ["codex", codexHome],
91
- ["gemini-cli", join(home, ".gemini")],
92
- ["opencode", join(home, ".config", "opencode")],
93
- ["github-copilot", join(home, ".config", "github-copilot")],
94
- ["windsurf", join(home, ".codeium", "windsurf")],
95
- ["goose", join(home, ".config", "goose")],
96
- ["hermes", join(home, ".hermes")],
97
- ];
98
- return candidates
99
- .filter(([, evidence]) => exists(evidence))
100
- .map(([client, evidence]) => ({ client, evidence }));
101
- }
102
-
103
- function surfacePath(
104
- surfaceId: NonNullable<ClientDefinition["surfaceId"]>,
105
- options: { home: string; codexHome?: string },
106
- ): string {
107
- if (surfaceId === "agent-skills") return join(options.home, ".agents", "skills");
108
- if (surfaceId === "claude-code") return join(options.home, ".claude", "skills");
109
- if (surfaceId === "codex") return join(options.codexHome ?? join(options.home, ".codex"), "skills");
110
- return join(options.home, ".gemini", "config", "skills");
111
- }
112
-
113
- export function resolveBuiltInTarget(
114
- name: string,
115
- options: { home?: string; codexHome?: string; customPath?: string } = {},
116
- ): ResolvedBuiltInTarget {
117
- const home = options.home ?? homedir();
118
- if (name === "custom") {
119
- if (!options.customPath) throw new Error("--target custom requires --path <dir>");
120
- return { targetName: name, path: options.customPath };
121
- }
122
- if (name === "agent-skills" || name === "agents") {
123
- return {
124
- targetName: name,
125
- path: surfacePath("agent-skills", { home }),
126
- ...(name === "agents"
127
- ? { warning: "--target agents is deprecated; use --target agent-skills" }
128
- : {}),
129
- };
130
- }
131
- if (name === "claude-code" || name === "claude") {
132
- return {
133
- targetName: name,
134
- path: surfacePath("claude-code", { home }),
135
- ...(name === "claude"
136
- ? { warning: "--target claude is deprecated; use --target claude-code" }
137
- : {}),
138
- };
139
- }
140
- if (name === "codex") {
141
- return {
142
- targetName: name,
143
- path: surfacePath("codex", { home, codexHome: options.codexHome }),
144
- };
145
- }
146
- throw new Error(
147
- `unknown --target "${name}"; supported targets: agent-skills, claude-code, codex, custom`,
148
- );
149
- }
150
-
151
- export function planClientSurfaces(
152
- requestedClients: readonly string[],
153
- options: { home?: string; codexHome?: string } = {},
154
- ): ClientSurfacePlan {
155
- const clients = [...new Set(requestedClients)].map((id) => {
156
- if (!SUPPORTED_CLIENT_IDS.includes(id as ClientId)) {
157
- throw new Error(
158
- `unsupported client "${id}"; supported clients: ${SUPPORTED_CLIENT_IDS.join(", ")}`,
159
- );
160
- }
161
- return CLIENTS[id as ClientId];
162
- });
163
- const home = options.home ?? homedir();
164
- const surfaces = new Map<string, PlannedClientSurface>();
165
-
166
- for (const client of clients) {
167
- if (!client.surfaceId) continue;
168
- const path = surfacePath(client.surfaceId, { home, codexHome: options.codexHome });
169
- const existing = surfaces.get(path);
170
- if (existing) {
171
- if (!existing.clients.includes(client.id)) existing.clients.push(client.id);
172
- continue;
173
- }
174
- surfaces.set(path, {
175
- id: client.surfaceId,
176
- targetName: client.surfaceId,
177
- path,
178
- deliveryMode: "managed-pins",
179
- clients: [client.id],
180
- });
181
- }
182
-
183
- return { clients, surfaces: [...surfaces.values()] };
184
- }
185
-
186
- export function assessClientReadiness(
187
- plan: ClientSurfacePlan,
188
- instructionReadiness: Partial<Record<ClientId, ReadinessAxis>> = {},
189
- ): ClientReadiness[] {
190
- return plan.clients.map((client) => {
191
- const surface = plan.surfaces.find((candidate) => candidate.clients.includes(client.id));
192
- let skillSurface: ReadinessAxis;
193
- if (surface) {
194
- skillSurface = { status: "planned", detail: surface.path };
195
- } else if (client.id === "goose") {
196
- skillSurface = { status: "manual", detail: "configure the full vault in Goose" };
197
- } else if (client.id === "hermes") {
198
- skillSurface = { status: "manual", detail: "configure the full vault in Hermes external_dirs" };
199
- } else {
200
- skillSurface = {
201
- status: "not-applicable",
202
- detail: "skills resolve through Skillmux MCP",
203
- };
204
- }
205
-
206
- const mcpRegistration: ReadinessAxis = client.deliveryMode === "mcp"
207
- ? { status: "manual", detail: "register the Skillmux MCP server" }
208
- : { status: "not-applicable", detail: "native skill loading" };
209
-
210
- return {
211
- client: client.id,
212
- skillSurface,
213
- mcpRegistration,
214
- instructionSetup: instructionReadiness[client.id] ?? {
215
- status: "manual",
216
- detail: "instruction adapter not applied",
217
- },
218
- };
219
- });
220
- }