@ryanreh99/skills-sync 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/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/assets/contracts/build/bundle.schema.json +76 -0
- package/dist/assets/contracts/inputs/config.schema.json +13 -0
- package/dist/assets/contracts/inputs/mcp-servers.schema.json +56 -0
- package/dist/assets/contracts/inputs/pack-manifest.schema.json +33 -0
- package/dist/assets/contracts/inputs/pack-sources.schema.json +47 -0
- package/dist/assets/contracts/inputs/profile.schema.json +21 -0
- package/dist/assets/contracts/inputs/upstreams.schema.json +45 -0
- package/dist/assets/contracts/runtime/targets.schema.json +120 -0
- package/dist/assets/contracts/state/upstreams-lock.schema.json +38 -0
- package/dist/assets/manifests/targets.linux.json +27 -0
- package/dist/assets/manifests/targets.macos.json +27 -0
- package/dist/assets/manifests/targets.windows.json +27 -0
- package/dist/assets/seed/config.json +3 -0
- package/dist/assets/seed/packs/personal/mcp/servers.json +20 -0
- package/dist/assets/seed/packs/personal/pack.json +7 -0
- package/dist/assets/seed/packs/personal/sources.json +31 -0
- package/dist/assets/seed/profiles/personal.json +4 -0
- package/dist/assets/seed/upstreams.json +23 -0
- package/dist/cli.js +532 -0
- package/dist/index.js +27 -0
- package/dist/lib/adapters/claude.js +49 -0
- package/dist/lib/adapters/codex.js +239 -0
- package/dist/lib/adapters/common.js +114 -0
- package/dist/lib/adapters/copilot.js +53 -0
- package/dist/lib/adapters/cursor.js +53 -0
- package/dist/lib/adapters/gemini.js +52 -0
- package/dist/lib/agents.js +888 -0
- package/dist/lib/bindings.js +510 -0
- package/dist/lib/build.js +190 -0
- package/dist/lib/bundle.js +165 -0
- package/dist/lib/config.js +324 -0
- package/dist/lib/core.js +447 -0
- package/dist/lib/detect.js +56 -0
- package/dist/lib/doctor.js +504 -0
- package/dist/lib/init.js +292 -0
- package/dist/lib/inventory.js +235 -0
- package/dist/lib/manage.js +463 -0
- package/dist/lib/mcp-config.js +264 -0
- package/dist/lib/profile-transfer.js +221 -0
- package/dist/lib/upstreams.js +782 -0
- package/docs/agent-storage-map.md +153 -0
- package/docs/architecture.md +117 -0
- package/docs/changelog.md +12 -0
- package/docs/commands.md +94 -0
- package/docs/contracts.md +112 -0
- package/docs/homebrew.md +46 -0
- package/docs/quickstart.md +14 -0
- package/docs/roadmap.md +5 -0
- package/docs/security.md +32 -0
- package/docs/user-guide.md +257 -0
- package/package.json +61 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"imports": [
|
|
3
|
+
{
|
|
4
|
+
"upstream": "anthropic",
|
|
5
|
+
"ref": "main",
|
|
6
|
+
"paths": [
|
|
7
|
+
"skills/frontend-design",
|
|
8
|
+
"skills/skill-creator"
|
|
9
|
+
],
|
|
10
|
+
"destPrefix": "anthropic"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"upstream": "openai",
|
|
14
|
+
"ref": "main",
|
|
15
|
+
"paths": [
|
|
16
|
+
"skills/.system/skill-creator",
|
|
17
|
+
"skills/.curated/spreadsheet"
|
|
18
|
+
],
|
|
19
|
+
"destPrefix": "openai"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"upstream": "awesome-copilot",
|
|
23
|
+
"ref": "main",
|
|
24
|
+
"paths": [
|
|
25
|
+
"skills/copilot-cli-quickstart",
|
|
26
|
+
"skills/suggest-awesome-github-copilot-skills"
|
|
27
|
+
],
|
|
28
|
+
"destPrefix": "awesome-copilot"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"upstreams": [
|
|
3
|
+
{
|
|
4
|
+
"id": "anthropic",
|
|
5
|
+
"type": "git",
|
|
6
|
+
"repo": "https://github.com/anthropics/skills.git",
|
|
7
|
+
"defaultRef": "main"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "openai",
|
|
11
|
+
"type": "git",
|
|
12
|
+
"repo": "https://github.com/openai/skills.git",
|
|
13
|
+
"defaultRef": "main"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "awesome-copilot",
|
|
17
|
+
"type": "git",
|
|
18
|
+
"repo": "https://github.com/github/awesome-copilot.git",
|
|
19
|
+
"defaultRef": "main"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { buildProfile } from "./lib/build.js";
|
|
6
|
+
import { cmdApply, cmdUnlink } from "./lib/bindings.js";
|
|
7
|
+
import { cmdAgentDrift, cmdAgentInventory } from "./lib/agents.js";
|
|
8
|
+
import { cmdDetect } from "./lib/detect.js";
|
|
9
|
+
import { cmdDoctor } from "./lib/doctor.js";
|
|
10
|
+
import { cmdInit } from "./lib/init.js";
|
|
11
|
+
import {
|
|
12
|
+
cmdCurrentProfile,
|
|
13
|
+
cmdListProfiles,
|
|
14
|
+
cmdNewProfile,
|
|
15
|
+
cmdRemoveProfile,
|
|
16
|
+
readDefaultProfile,
|
|
17
|
+
writeDefaultProfile
|
|
18
|
+
} from "./lib/config.js";
|
|
19
|
+
import { cmdListEverything, cmdShowProfileInventory } from "./lib/inventory.js";
|
|
20
|
+
import { cmdProfileExport, cmdProfileImport } from "./lib/profile-transfer.js";
|
|
21
|
+
import {
|
|
22
|
+
cmdProfileAddMcp,
|
|
23
|
+
cmdProfileAddSkill,
|
|
24
|
+
cmdProfileRemoveMcp,
|
|
25
|
+
cmdProfileRemoveSkill,
|
|
26
|
+
cmdUpstreamAdd,
|
|
27
|
+
cmdUpstreamRemove
|
|
28
|
+
} from "./lib/manage.js";
|
|
29
|
+
import { cmdListSkills, cmdListUpstreamContent, cmdListUpstreams, cmdSearchSkills } from "./lib/upstreams.js";
|
|
30
|
+
|
|
31
|
+
const VALID_BUILD_LOCK_MODES = new Set(["read", "write", "refresh"]);
|
|
32
|
+
const KNOWN_ROOT_COMMANDS = new Set([
|
|
33
|
+
"init",
|
|
34
|
+
"build",
|
|
35
|
+
"apply",
|
|
36
|
+
"doctor",
|
|
37
|
+
"unlink",
|
|
38
|
+
"list",
|
|
39
|
+
"search",
|
|
40
|
+
"use",
|
|
41
|
+
"current",
|
|
42
|
+
"ls",
|
|
43
|
+
"new",
|
|
44
|
+
"remove",
|
|
45
|
+
"profile",
|
|
46
|
+
"agent",
|
|
47
|
+
"upstream",
|
|
48
|
+
"detect",
|
|
49
|
+
"help"
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
const UNKNOWN_COMMAND_CODE = "skills-sync.unknownCommand";
|
|
53
|
+
const COMMANDER_HELP_CODE = "commander.helpDisplayed";
|
|
54
|
+
const COMMANDER_HELP_CODE_ALT = "commander.help";
|
|
55
|
+
const COMMANDER_VERSION_CODE = "commander.version";
|
|
56
|
+
|
|
57
|
+
function redactPathDetails(message) {
|
|
58
|
+
return String(message ?? "")
|
|
59
|
+
.replace(/[A-Za-z]:\\[^\s'"]+/g, "<path>")
|
|
60
|
+
.replace(/~\/[^\s'"]+/g, "<path>")
|
|
61
|
+
.replace(/\/(?:[^/\s]+\/)+[^/\s]+/g, "<path>")
|
|
62
|
+
.replace(/\b[\w.-]+\.(json|toml|md)\b/g, "<file>");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readPackageVersion() {
|
|
66
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
67
|
+
const packageRoot = path.resolve(path.dirname(__filename), "..");
|
|
68
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
69
|
+
try {
|
|
70
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
71
|
+
if (typeof packageJson.version === "string" && packageJson.version.trim().length > 0) {
|
|
72
|
+
return packageJson.version.trim();
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
// Fallback version keeps --version functional even in atypical environments.
|
|
76
|
+
}
|
|
77
|
+
return "0.0.0";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createUnknownCommandError() {
|
|
81
|
+
const error = new Error("Unknown command");
|
|
82
|
+
error.code = UNKNOWN_COMMAND_CODE;
|
|
83
|
+
return error;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function writeUnknownCommandError() {
|
|
87
|
+
process.stderr.write("Unknown command. See: skills-sync help\n");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function preflightUnknownCommandCheck(argv) {
|
|
91
|
+
if (argv.length === 0) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const [root, child] = argv;
|
|
96
|
+
const listChildren = new Set(["skills", "upstreams", "profiles", "everything", "upstream-content"]);
|
|
97
|
+
const searchChildren = new Set(["skills"]);
|
|
98
|
+
const profileChildren = new Set(["show", "add-skill", "remove-skill", "add-mcp", "remove-mcp", "export", "import"]);
|
|
99
|
+
const agentChildren = new Set(["inventory", "drift"]);
|
|
100
|
+
const upstreamChildren = new Set(["add", "remove"]);
|
|
101
|
+
|
|
102
|
+
if (root.startsWith("-")) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (!KNOWN_ROOT_COMMANDS.has(root)) {
|
|
106
|
+
throw createUnknownCommandError();
|
|
107
|
+
}
|
|
108
|
+
if (root === "list" && child && !child.startsWith("-") && !listChildren.has(child)) {
|
|
109
|
+
throw createUnknownCommandError();
|
|
110
|
+
}
|
|
111
|
+
if (root === "search" && child && !child.startsWith("-") && !searchChildren.has(child)) {
|
|
112
|
+
throw createUnknownCommandError();
|
|
113
|
+
}
|
|
114
|
+
if (root === "profile" && child && !child.startsWith("-") && !profileChildren.has(child)) {
|
|
115
|
+
throw createUnknownCommandError();
|
|
116
|
+
}
|
|
117
|
+
if (root === "agent" && child && !child.startsWith("-") && !agentChildren.has(child)) {
|
|
118
|
+
throw createUnknownCommandError();
|
|
119
|
+
}
|
|
120
|
+
if (root === "upstream" && child && !child.startsWith("-") && !upstreamChildren.has(child)) {
|
|
121
|
+
throw createUnknownCommandError();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseFormatOption(rawFormat) {
|
|
126
|
+
const format = (rawFormat || "text").toLowerCase();
|
|
127
|
+
if (format !== "text" && format !== "json") {
|
|
128
|
+
throw new Error("Invalid --format value. Use text or json.");
|
|
129
|
+
}
|
|
130
|
+
return format;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function collectOptionValues(value, previous) {
|
|
134
|
+
if (Array.isArray(previous)) {
|
|
135
|
+
return [...previous, value];
|
|
136
|
+
}
|
|
137
|
+
return [value];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function resolveMcpArgsOption({ args, arg }) {
|
|
141
|
+
const variadicArgs = Array.isArray(args) ? args : [];
|
|
142
|
+
const repeatedArgs = Array.isArray(arg) ? arg : [];
|
|
143
|
+
if (variadicArgs.length > 0 && repeatedArgs.length > 0) {
|
|
144
|
+
throw new Error("Use either --args or repeated --arg, not both.");
|
|
145
|
+
}
|
|
146
|
+
if (repeatedArgs.length > 0) {
|
|
147
|
+
return repeatedArgs;
|
|
148
|
+
}
|
|
149
|
+
return variadicArgs;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function cmdBuild(profileName, lockModeRaw) {
|
|
153
|
+
const lockMode = (lockModeRaw || "write").toLowerCase();
|
|
154
|
+
if (!VALID_BUILD_LOCK_MODES.has(lockMode)) {
|
|
155
|
+
throw new Error("Invalid --lock value. Use read, write, or refresh.");
|
|
156
|
+
}
|
|
157
|
+
const resolved = profileName ?? await readDefaultProfile();
|
|
158
|
+
if (!resolved) {
|
|
159
|
+
throw new Error("--profile is required (or set a default profile with 'skills-sync use <name>').");
|
|
160
|
+
}
|
|
161
|
+
await buildProfile(resolved, { lockMode });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function cmdApplyWithOptionalBuild(profileName, shouldBuild, options = {}) {
|
|
165
|
+
const normalizedProfile = typeof profileName === "string" && profileName.trim().length > 0 ? profileName.trim() : null;
|
|
166
|
+
const resolvedProfile = normalizedProfile ?? await readDefaultProfile();
|
|
167
|
+
if (shouldBuild) {
|
|
168
|
+
if (!resolvedProfile) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
"apply --build requires --profile <name> or a default profile."
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
await buildProfile(resolvedProfile, { lockMode: "write" });
|
|
174
|
+
}
|
|
175
|
+
await cmdApply(resolvedProfile, { dryRun: options.dryRun === true });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function createProgram() {
|
|
179
|
+
const program = new Command();
|
|
180
|
+
program
|
|
181
|
+
.name("skills-sync")
|
|
182
|
+
.description("Profile-scoped skills + MCP sync for multiple AI agents.")
|
|
183
|
+
.version(readPackageVersion())
|
|
184
|
+
.exitOverride()
|
|
185
|
+
.configureOutput({
|
|
186
|
+
writeOut: (str) => process.stdout.write(str),
|
|
187
|
+
writeErr: (str) => process.stderr.write(str),
|
|
188
|
+
outputError: () => {}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
program
|
|
192
|
+
.command("init")
|
|
193
|
+
.description("Initialize local workspace state (non-destructive). Use --seed to replace with seed content.")
|
|
194
|
+
.option("--seed", "replace local workspace with bundled seed content")
|
|
195
|
+
.option("--dry-run", "show planned init changes without mutating filesystem")
|
|
196
|
+
.option("--profile <name>", "profile name to scaffold and set as default (default: personal)")
|
|
197
|
+
.action((options) =>
|
|
198
|
+
cmdInit({
|
|
199
|
+
seed: options.seed === true,
|
|
200
|
+
dryRun: options.dryRun === true,
|
|
201
|
+
profile: options.profile
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
program
|
|
205
|
+
.command("build")
|
|
206
|
+
.description("Build deterministic runtime artifacts from a profile.")
|
|
207
|
+
.option("--profile <name>", "profile name (falls back to default profile)")
|
|
208
|
+
.option("--lock <mode>", "lock mode: read|write|refresh", "write")
|
|
209
|
+
.action((options) => cmdBuild(options.profile, options.lock));
|
|
210
|
+
|
|
211
|
+
program
|
|
212
|
+
.command("apply")
|
|
213
|
+
.description("Bind prebuilt runtime artifacts to tool target paths.")
|
|
214
|
+
.option("--profile <name>", "profile name (optional; defaults to runtime bundle profile)")
|
|
215
|
+
.option("--build", "run build before applying")
|
|
216
|
+
.option("--dry-run", "show planned changes without mutating filesystem")
|
|
217
|
+
.action((options) => cmdApplyWithOptionalBuild(options.profile, options.build === true, { dryRun: options.dryRun === true }));
|
|
218
|
+
|
|
219
|
+
program
|
|
220
|
+
.command("unlink")
|
|
221
|
+
.description("Remove bindings created by apply using state file.")
|
|
222
|
+
.option("--dry-run", "show what unlink would remove without mutating filesystem")
|
|
223
|
+
.action((options) => cmdUnlink({ dryRun: options.dryRun === true }));
|
|
224
|
+
|
|
225
|
+
program
|
|
226
|
+
.command("doctor")
|
|
227
|
+
.description("Validate manifests, state, upstream pins, and materialized runtime artifacts.")
|
|
228
|
+
.option(
|
|
229
|
+
"--profile <name>",
|
|
230
|
+
"profile name for source/upstream validation (falls back to default profile)"
|
|
231
|
+
)
|
|
232
|
+
.action(async (options) => cmdDoctor(options.profile ?? await readDefaultProfile()));
|
|
233
|
+
|
|
234
|
+
program
|
|
235
|
+
.command("detect")
|
|
236
|
+
.description("Detect agent support and installation status.")
|
|
237
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
238
|
+
.option("--agents <agents>", "optional comma-separated agent filter, e.g. codex,claude")
|
|
239
|
+
.action((options) => {
|
|
240
|
+
const format = (options.format || "text").toLowerCase();
|
|
241
|
+
if (format !== "text" && format !== "json") {
|
|
242
|
+
throw new Error("Invalid --format value. Use text or json.");
|
|
243
|
+
}
|
|
244
|
+
return cmdDetect({ format, agents: options.agents });
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const listCommand = program.command("list").description("List available resources.");
|
|
248
|
+
listCommand
|
|
249
|
+
.command("skills")
|
|
250
|
+
.description("List discovered upstream skills (directories under skills/** containing SKILL.md).")
|
|
251
|
+
.option("--upstream <id>", "upstream id (optional; defaults to all configured upstreams)")
|
|
252
|
+
.option("--ref <ref>", "ref/branch/tag")
|
|
253
|
+
.option("--profile <name>", "profile name to infer upstream/ref defaults")
|
|
254
|
+
.option("--verbose", "include skill titles (slower; reads SKILL.md contents)")
|
|
255
|
+
.option("--versbose", "alias for --verbose")
|
|
256
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
257
|
+
.action((options) => {
|
|
258
|
+
const format = (options.format || "text").toLowerCase();
|
|
259
|
+
if (format !== "text" && format !== "json") {
|
|
260
|
+
throw new Error("Invalid --format value. Use text or json.");
|
|
261
|
+
}
|
|
262
|
+
return cmdListSkills({
|
|
263
|
+
upstream: options.upstream,
|
|
264
|
+
ref: options.ref,
|
|
265
|
+
profile: options.profile,
|
|
266
|
+
format,
|
|
267
|
+
verbose: options.verbose === true || options.versbose === true
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
listCommand
|
|
271
|
+
.command("upstreams")
|
|
272
|
+
.description("List configured upstream repositories.")
|
|
273
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
274
|
+
.action((options) => {
|
|
275
|
+
const format = parseFormatOption(options.format);
|
|
276
|
+
return cmdListUpstreams({ format });
|
|
277
|
+
});
|
|
278
|
+
listCommand
|
|
279
|
+
.command("profiles")
|
|
280
|
+
.description("List all profiles discovered in local workspace and seed.")
|
|
281
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
282
|
+
.action((options) => {
|
|
283
|
+
const format = parseFormatOption(options.format);
|
|
284
|
+
return cmdListProfiles({ format });
|
|
285
|
+
});
|
|
286
|
+
listCommand
|
|
287
|
+
.command("everything")
|
|
288
|
+
.description("List all profiles with their effective skills and MCP servers.")
|
|
289
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
290
|
+
.action((options) => {
|
|
291
|
+
const format = parseFormatOption(options.format);
|
|
292
|
+
return cmdListEverything({ format });
|
|
293
|
+
});
|
|
294
|
+
listCommand
|
|
295
|
+
.command("upstream-content")
|
|
296
|
+
.description("List skills and discoverable MCP server manifests available in upstream repository refs.")
|
|
297
|
+
.option("--upstream <id>", "upstream id (optional; defaults to all configured upstreams)")
|
|
298
|
+
.option("--ref <ref>", "ref/branch/tag")
|
|
299
|
+
.option("--profile <name>", "profile name to infer upstream/ref defaults")
|
|
300
|
+
.option("--verbose", "include skill titles (slower; reads SKILL.md contents)")
|
|
301
|
+
.option("--versbose", "alias for --verbose")
|
|
302
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
303
|
+
.action((options) => {
|
|
304
|
+
const format = parseFormatOption(options.format);
|
|
305
|
+
return cmdListUpstreamContent({
|
|
306
|
+
upstream: options.upstream,
|
|
307
|
+
ref: options.ref,
|
|
308
|
+
profile: options.profile,
|
|
309
|
+
format,
|
|
310
|
+
verbose: options.verbose === true || options.versbose === true
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
program
|
|
315
|
+
.command("use <name>")
|
|
316
|
+
.description("Set the default profile; auto-scaffolds an empty local profile if missing.")
|
|
317
|
+
.action((name) => writeDefaultProfile(name));
|
|
318
|
+
|
|
319
|
+
program
|
|
320
|
+
.command("current")
|
|
321
|
+
.description("Print the current default profile name.")
|
|
322
|
+
.action(() => cmdCurrentProfile());
|
|
323
|
+
|
|
324
|
+
program
|
|
325
|
+
.command("ls")
|
|
326
|
+
.description("List all available profiles, marking the current default with ->.")
|
|
327
|
+
.action(() => cmdListProfiles());
|
|
328
|
+
|
|
329
|
+
const profileCommand = program.command("profile").description("Inspect and modify profile-level skill/MCP configuration.");
|
|
330
|
+
profileCommand
|
|
331
|
+
.command("show [name]")
|
|
332
|
+
.description("Show skills and MCP servers for a profile (defaults to current profile).")
|
|
333
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
334
|
+
.action((name, options) => {
|
|
335
|
+
const format = parseFormatOption(options.format);
|
|
336
|
+
return cmdShowProfileInventory({
|
|
337
|
+
profile: name,
|
|
338
|
+
format
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
profileCommand
|
|
342
|
+
.command("add-skill <name>")
|
|
343
|
+
.description("Add an upstream skill import to a profile.")
|
|
344
|
+
.requiredOption("--upstream <id>", "upstream id")
|
|
345
|
+
.requiredOption("--path <repoPath>", "upstream repository path, e.g. skills/my-skill")
|
|
346
|
+
.option("--ref <ref>", "ref/branch/tag (defaults to upstream defaultRef)")
|
|
347
|
+
.option("--dest-prefix <prefix>", "destination prefix in bundled skills tree")
|
|
348
|
+
.action((name, options) =>
|
|
349
|
+
cmdProfileAddSkill({
|
|
350
|
+
profile: name,
|
|
351
|
+
upstream: options.upstream,
|
|
352
|
+
skillPath: options.path,
|
|
353
|
+
ref: options.ref,
|
|
354
|
+
destPrefix: options.destPrefix
|
|
355
|
+
}));
|
|
356
|
+
profileCommand
|
|
357
|
+
.command("remove-skill <name>")
|
|
358
|
+
.description("Remove skill import path(s) from a profile.")
|
|
359
|
+
.requiredOption("--upstream <id>", "upstream id")
|
|
360
|
+
.requiredOption("--path <repoPath>", "upstream repository path, e.g. skills/my-skill")
|
|
361
|
+
.option("--ref <ref>", "optional ref filter")
|
|
362
|
+
.option("--dest-prefix <prefix>", "optional destination prefix filter")
|
|
363
|
+
.action((name, options) =>
|
|
364
|
+
cmdProfileRemoveSkill({
|
|
365
|
+
profile: name,
|
|
366
|
+
upstream: options.upstream,
|
|
367
|
+
skillPath: options.path,
|
|
368
|
+
ref: options.ref,
|
|
369
|
+
destPrefix: options.destPrefix
|
|
370
|
+
}));
|
|
371
|
+
profileCommand
|
|
372
|
+
.command("add-mcp <name> <server>")
|
|
373
|
+
.description("Add or update an MCP server in a profile.")
|
|
374
|
+
.option("--command <command>", "server command executable (stdio transport)")
|
|
375
|
+
.option("--url <url>", "server URL (HTTP transport)")
|
|
376
|
+
.option("--args <values...>", "optional command args")
|
|
377
|
+
.option(
|
|
378
|
+
"--arg <value>",
|
|
379
|
+
"single command arg (repeat to include values that start with '-')",
|
|
380
|
+
collectOptionValues,
|
|
381
|
+
[]
|
|
382
|
+
)
|
|
383
|
+
.option("--env <entries...>", "optional env vars as KEY=VALUE entries")
|
|
384
|
+
.action((name, server, options) =>
|
|
385
|
+
cmdProfileAddMcp({
|
|
386
|
+
profile: name,
|
|
387
|
+
name: server,
|
|
388
|
+
command: options.command,
|
|
389
|
+
url: options.url,
|
|
390
|
+
args: resolveMcpArgsOption({ args: options.args, arg: options.arg }),
|
|
391
|
+
env: options.env
|
|
392
|
+
}));
|
|
393
|
+
profileCommand
|
|
394
|
+
.command("remove-mcp <name> <server>")
|
|
395
|
+
.description("Remove an MCP server from a profile.")
|
|
396
|
+
.action((name, server) =>
|
|
397
|
+
cmdProfileRemoveMcp({
|
|
398
|
+
profile: name,
|
|
399
|
+
name: server
|
|
400
|
+
}));
|
|
401
|
+
profileCommand
|
|
402
|
+
.command("export [name]")
|
|
403
|
+
.description("Export a profile's config (pack manifest, sources, MCP, and local skills) to JSON.")
|
|
404
|
+
.option("--output <path>", "optional output path (defaults to stdout)")
|
|
405
|
+
.action((name, options) =>
|
|
406
|
+
cmdProfileExport({
|
|
407
|
+
profile: name,
|
|
408
|
+
output: options.output
|
|
409
|
+
}));
|
|
410
|
+
profileCommand
|
|
411
|
+
.command("import <name>")
|
|
412
|
+
.description("Import profile config JSON.")
|
|
413
|
+
.requiredOption("--input <path>", "path to exported profile JSON")
|
|
414
|
+
.option("--replace", "overwrite existing local profile files if present")
|
|
415
|
+
.action((name, options) =>
|
|
416
|
+
cmdProfileImport({
|
|
417
|
+
profile: name,
|
|
418
|
+
input: options.input,
|
|
419
|
+
replace: options.replace === true
|
|
420
|
+
}));
|
|
421
|
+
|
|
422
|
+
const agentCommand = program.command("agent").description("Inspect detected agent installs and profile drift.");
|
|
423
|
+
agentCommand
|
|
424
|
+
.command("inventory")
|
|
425
|
+
.description("Inspect installed skills and MCP servers per detected agent.")
|
|
426
|
+
.option("--agents <agents>", "optional comma-separated agent filter, e.g. codex,claude")
|
|
427
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
428
|
+
.action((options) => {
|
|
429
|
+
const format = parseFormatOption(options.format);
|
|
430
|
+
return cmdAgentInventory({
|
|
431
|
+
format,
|
|
432
|
+
agents: options.agents
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
agentCommand
|
|
436
|
+
.command("drift")
|
|
437
|
+
.description("Check or reconcile drift between profile-expected config and detected agent installs.")
|
|
438
|
+
.option("--profile <name>", "profile name (falls back to default profile)")
|
|
439
|
+
.option("--agents <agents>", "optional comma-separated agent filter, e.g. codex,claude")
|
|
440
|
+
.option("--dry-run", "report drift only without mutating files")
|
|
441
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
442
|
+
.action((options) => {
|
|
443
|
+
const format = parseFormatOption(options.format);
|
|
444
|
+
return cmdAgentDrift({
|
|
445
|
+
profile: options.profile,
|
|
446
|
+
dryRun: options.dryRun === true,
|
|
447
|
+
format,
|
|
448
|
+
agents: options.agents
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const upstreamCommand = program.command("upstream").description("Manage configured upstream repositories.");
|
|
453
|
+
upstreamCommand
|
|
454
|
+
.command("add <id>")
|
|
455
|
+
.description("Add an upstream repository.")
|
|
456
|
+
.requiredOption("--repo <url>", "git repository URL")
|
|
457
|
+
.option("--default-ref <ref>", "default ref/branch/tag", "main")
|
|
458
|
+
.option("--type <type>", "upstream type (currently only git)", "git")
|
|
459
|
+
.action((id, options) =>
|
|
460
|
+
cmdUpstreamAdd({
|
|
461
|
+
id,
|
|
462
|
+
repo: options.repo,
|
|
463
|
+
defaultRef: options.defaultRef,
|
|
464
|
+
type: options.type
|
|
465
|
+
}));
|
|
466
|
+
upstreamCommand
|
|
467
|
+
.command("remove <id>")
|
|
468
|
+
.description("Remove an upstream repository.")
|
|
469
|
+
.action((id) => cmdUpstreamRemove({ id }));
|
|
470
|
+
|
|
471
|
+
program
|
|
472
|
+
.command("new <name>")
|
|
473
|
+
.description("Scaffold a new profile and pack.")
|
|
474
|
+
.action((name) => cmdNewProfile(name));
|
|
475
|
+
|
|
476
|
+
program
|
|
477
|
+
.command("remove <name>")
|
|
478
|
+
.description("Delete a profile definition (pack is preserved).")
|
|
479
|
+
.action((name) => cmdRemoveProfile(name));
|
|
480
|
+
|
|
481
|
+
const searchCommand = program.command("search").description("Search available resources.");
|
|
482
|
+
searchCommand
|
|
483
|
+
.command("skills")
|
|
484
|
+
.description("Search upstream skills by keyword (path by default; path+title with --verbose).")
|
|
485
|
+
.option("--query <text>", "search term")
|
|
486
|
+
.option("--upstream <id>", "upstream id (optional; defaults to all configured upstreams)")
|
|
487
|
+
.option("--ref <ref>", "ref/branch/tag")
|
|
488
|
+
.option("--profile <name>", "profile name to infer upstream/ref defaults")
|
|
489
|
+
.option("--interactive", "interactive prompt mode")
|
|
490
|
+
.option("--verbose", "include title metadata and title matching (slower)")
|
|
491
|
+
.option("--versbose", "alias for --verbose")
|
|
492
|
+
.option("--format <format>", "output format: text|json", "text")
|
|
493
|
+
.action((options) => {
|
|
494
|
+
const format = parseFormatOption(options.format);
|
|
495
|
+
return cmdSearchSkills({
|
|
496
|
+
upstream: options.upstream,
|
|
497
|
+
ref: options.ref,
|
|
498
|
+
profile: options.profile,
|
|
499
|
+
query: options.query,
|
|
500
|
+
format,
|
|
501
|
+
interactive: options.interactive === true,
|
|
502
|
+
verbose: options.verbose === true || options.versbose === true
|
|
503
|
+
});
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
program.command("help").description("Show help.").action(() => program.outputHelp());
|
|
507
|
+
return program;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
511
|
+
try {
|
|
512
|
+
preflightUnknownCommandCheck(argv);
|
|
513
|
+
const program = createProgram();
|
|
514
|
+
await program.parseAsync(["node", "skills-sync", ...argv]);
|
|
515
|
+
return 0;
|
|
516
|
+
} catch (error) {
|
|
517
|
+
if (
|
|
518
|
+
error?.code === COMMANDER_HELP_CODE ||
|
|
519
|
+
error?.code === COMMANDER_HELP_CODE_ALT ||
|
|
520
|
+
error?.message === "(outputHelp)" ||
|
|
521
|
+
error?.code === COMMANDER_VERSION_CODE
|
|
522
|
+
) {
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
if (error?.code === UNKNOWN_COMMAND_CODE || error?.code === "commander.unknownOption" || error?.code === "commander.unknownCommand") {
|
|
526
|
+
writeUnknownCommandError();
|
|
527
|
+
return 2;
|
|
528
|
+
}
|
|
529
|
+
process.stderr.write(`[skills-sync] ERROR: ${redactPathDetails(error.message)}\n`);
|
|
530
|
+
return 1;
|
|
531
|
+
}
|
|
532
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { runCli } from "./cli.js";
|
|
6
|
+
|
|
7
|
+
export { runCli };
|
|
8
|
+
|
|
9
|
+
function isMainModule() {
|
|
10
|
+
if (!process.argv[1]) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const invokedFile = path.resolve(process.argv[1]);
|
|
14
|
+
try {
|
|
15
|
+
return import.meta.url === pathToFileURL(fs.realpathSync(invokedFile)).href;
|
|
16
|
+
} catch {
|
|
17
|
+
return import.meta.url === pathToFileURL(invokedFile).href;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (isMainModule()) {
|
|
22
|
+
runCli().then((exitCode) => {
|
|
23
|
+
if (exitCode !== 0) {
|
|
24
|
+
process.exit(exitCode);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { logWarn, readJsonFile } from "../core.js";
|
|
4
|
+
import { linkDirectoryProjection } from "./common.js";
|
|
5
|
+
|
|
6
|
+
export async function projectClaudeFromBundle(options) {
|
|
7
|
+
const {
|
|
8
|
+
runtimeInternalRoot,
|
|
9
|
+
bundleSkillsPath,
|
|
10
|
+
bundleMcpPath,
|
|
11
|
+
packRoot,
|
|
12
|
+
localConfigPath = null,
|
|
13
|
+
canOverride = false
|
|
14
|
+
} = options;
|
|
15
|
+
const runtimeRoot = path.join(runtimeInternalRoot, ".claude");
|
|
16
|
+
await fs.ensureDir(runtimeRoot);
|
|
17
|
+
|
|
18
|
+
const skillsMethod = await linkDirectoryProjection(bundleSkillsPath, path.join(runtimeRoot, "skills"));
|
|
19
|
+
await linkDirectoryProjection(bundleSkillsPath, path.join(runtimeRoot, "vendor_imports", "skills"));
|
|
20
|
+
|
|
21
|
+
const canonicalMcp = await readJsonFile(bundleMcpPath);
|
|
22
|
+
let projected = canonicalMcp;
|
|
23
|
+
|
|
24
|
+
if (!canOverride && localConfigPath && (await fs.pathExists(localConfigPath))) {
|
|
25
|
+
try {
|
|
26
|
+
const existing = await readJsonFile(localConfigPath);
|
|
27
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
28
|
+
throw new Error("expected JSON object root");
|
|
29
|
+
}
|
|
30
|
+
projected = {
|
|
31
|
+
...existing,
|
|
32
|
+
mcpServers: canonicalMcp?.mcpServers ?? {}
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logWarn(`Failed to seed Claude runtime config from local settings: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const runtimeConfigPath = path.join(runtimeRoot, "mcp.json");
|
|
40
|
+
await fs.writeFile(runtimeConfigPath, `${JSON.stringify(projected, null, 2)}\n`, "utf8");
|
|
41
|
+
const mcpMethod = "generated";
|
|
42
|
+
|
|
43
|
+
const overrideSource = path.join(packRoot, "tool-overrides", "claude");
|
|
44
|
+
if (await fs.pathExists(overrideSource)) {
|
|
45
|
+
await fs.copy(overrideSource, path.join(runtimeRoot, "tool-overrides"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { skillsMethod, mcpMethod };
|
|
49
|
+
}
|