agentwheel 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -1,10 +1,80 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ atomicCopy,
4
+ hashPath,
5
+ inferSourceDriverName,
6
+ pathExists,
7
+ writeJsonAtomic
8
+ } from "./chunk-N2LZY7LO.js";
2
9
 
3
10
  // src/cli/index.ts
4
- import { mkdir as mkdir6, rm as rm6, writeFile as writeFile3 } from "fs/promises";
5
- import { join as join12 } from "path";
11
+ import { mkdir as mkdir7, rm as rm8, writeFile as writeFile4 } from "fs/promises";
12
+ import { join as join17 } from "path";
6
13
  import { Command } from "commander";
7
14
 
15
+ // src/adapters/resolve.ts
16
+ import { resolve as resolve2 } from "path";
17
+
18
+ // src/model/adapter.ts
19
+ import { readFile } from "fs/promises";
20
+ import { parse, printParseErrorCode } from "jsonc-parser";
21
+ import { z as z2 } from "zod";
22
+
23
+ // src/model/artifact.ts
24
+ import { z } from "zod";
25
+ var artifactTypeSchema = z.enum([
26
+ "instructions",
27
+ "rules",
28
+ "skills",
29
+ "commands",
30
+ "subagents",
31
+ "mcp",
32
+ "hooks",
33
+ "settings",
34
+ "plugins"
35
+ ]);
36
+ var fileKindSchema = z.enum(["file", "dir"]);
37
+ var artifactSchema = z.object({
38
+ type: artifactTypeSchema,
39
+ name: z.string().min(1),
40
+ sourcePath: z.string().min(1),
41
+ stagedPath: z.string().min(1).optional(),
42
+ relativePath: z.string().min(1),
43
+ kind: fileKindSchema,
44
+ hash: z.string().min(16),
45
+ packageName: z.string().min(1).optional(),
46
+ channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed")
47
+ });
48
+
49
+ // src/model/adapter.ts
50
+ var targetMappingSchema = z2.object({
51
+ dest: z2.string().min(1),
52
+ enabled: z2.boolean().default(true),
53
+ semantic: z2.enum(["openclaw-plugin"]).optional(),
54
+ merge: z2.enum(["json-deep"]).optional()
55
+ });
56
+ var adapterSchema = z2.object({
57
+ name: z2.string().min(1),
58
+ displayName: z2.string().min(1).optional(),
59
+ targets: z2.partialRecord(
60
+ artifactTypeSchema,
61
+ targetMappingSchema
62
+ ).default({})
63
+ });
64
+ async function loadAdapterConfig(path) {
65
+ const content = await readFile(path, "utf8");
66
+ const errors = [];
67
+ const parsed = parse(content, errors, {
68
+ allowTrailingComma: true,
69
+ disallowComments: false
70
+ });
71
+ if (errors.length > 0) {
72
+ const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
73
+ throw new Error(`Invalid adapter config ${path}: ${details}`);
74
+ }
75
+ return adapterSchema.parse(parsed);
76
+ }
77
+
8
78
  // src/adapters/claude.ts
9
79
  var claudeAdapter = {
10
80
  name: "claude",
@@ -42,8 +112,9 @@ var hermesAdapter = {
42
112
  rules: { enabled: true, dest: ".hermes/rules" },
43
113
  skills: { enabled: true, dest: ".hermes/skills" },
44
114
  commands: { enabled: true, dest: ".hermes/commands" },
45
- mcp: { enabled: true, dest: ".hermes/mcp" },
46
- hooks: { enabled: true, dest: ".hermes/hooks" }
115
+ mcp: { enabled: true, dest: ".hermes/mcp", merge: "json-deep" },
116
+ hooks: { enabled: true, dest: ".hermes/hooks", merge: "json-deep" },
117
+ settings: { enabled: true, dest: ".hermes/settings.json", merge: "json-deep" }
47
118
  }
48
119
  };
49
120
 
@@ -56,8 +127,9 @@ var openClawAdapter = {
56
127
  rules: { enabled: true, dest: ".openclaw/rules" },
57
128
  skills: { enabled: true, dest: ".openclaw/skills" },
58
129
  commands: { enabled: true, dest: ".openclaw/commands" },
59
- mcp: { enabled: true, dest: ".openclaw/mcp" },
60
- hooks: { enabled: true, dest: ".openclaw/hooks" },
130
+ mcp: { enabled: true, dest: ".openclaw/mcp", merge: "json-deep" },
131
+ hooks: { enabled: true, dest: ".openclaw/hooks", merge: "json-deep" },
132
+ settings: { enabled: true, dest: ".openclaw/settings.json", merge: "json-deep" },
61
133
  plugins: { enabled: true, dest: ".openclaw/plugins", semantic: "openclaw-plugin" }
62
134
  }
63
135
  };
@@ -72,148 +144,121 @@ function getAdapter(name) {
72
144
  return adapter;
73
145
  }
74
146
 
75
- // src/model/adapter.ts
76
- import { readFile } from "fs/promises";
77
- import { parse, printParseErrorCode } from "jsonc-parser";
78
- import { z as z2 } from "zod";
79
-
80
- // src/model/artifact.ts
81
- import { z } from "zod";
82
- var artifactTypeSchema = z.enum([
83
- "instructions",
84
- "rules",
85
- "skills",
86
- "commands",
87
- "subagents",
88
- "mcp",
89
- "hooks",
90
- "plugins"
91
- ]);
92
- var fileKindSchema = z.enum(["file", "dir"]);
93
- var artifactSchema = z.object({
94
- type: artifactTypeSchema,
95
- name: z.string().min(1),
96
- sourcePath: z.string().min(1),
97
- stagedPath: z.string().min(1).optional(),
98
- relativePath: z.string().min(1),
99
- kind: fileKindSchema,
100
- hash: z.string().min(16),
101
- packageName: z.string().min(1).optional(),
102
- channel: z.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed")
103
- });
104
-
105
- // src/model/adapter.ts
106
- var targetMappingSchema = z2.object({
107
- dest: z2.string().min(1),
108
- enabled: z2.boolean().default(true),
109
- semantic: z2.enum(["openclaw-plugin"]).optional()
110
- });
111
- var adapterSchema = z2.object({
112
- name: z2.string().min(1),
113
- displayName: z2.string().min(1).optional(),
114
- targets: z2.partialRecord(
115
- artifactTypeSchema,
116
- targetMappingSchema
117
- ).default({})
118
- });
119
- async function loadAdapterConfig(path) {
120
- const content = await readFile(path, "utf8");
121
- const errors = [];
122
- const parsed = parse(content, errors, {
123
- allowTrailingComma: true,
124
- disallowComments: false
147
+ // src/adapters/programmatic.ts
148
+ import { stat, readFile as readFile2, writeFile } from "fs/promises";
149
+ import { tmpdir } from "os";
150
+ import { pathToFileURL } from "url";
151
+ import { extname, join, resolve } from "path";
152
+ import ts from "typescript";
153
+ async function loadProgrammaticAdapter(modulePath, options) {
154
+ if (!options.allowCode) {
155
+ throw new Error("Refusing to load adapter code without --allow-adapter-code");
156
+ }
157
+ if (modulePath.startsWith("http://") || modulePath.startsWith("https://") || modulePath.startsWith("github:") || modulePath.startsWith("git:")) {
158
+ throw new Error("Programmatic adapters must be loaded from an explicit local path");
159
+ }
160
+ const resolvedPath = resolve(modulePath);
161
+ const stats = await stat(resolvedPath);
162
+ if (!stats.isFile()) {
163
+ throw new Error(`Programmatic adapter module is not a file: ${resolvedPath}`);
164
+ }
165
+ const hash = await hashPath(resolvedPath);
166
+ const importPath = extname(resolvedPath) === ".ts" ? await transpileTypeScriptAdapter(resolvedPath, hash) : resolvedPath;
167
+ const imported = await import(`${pathToFileURL(importPath).href}?agentwheel=${Date.now()}`);
168
+ const candidate = imported.adapter;
169
+ if (!candidate || typeof candidate !== "object") {
170
+ throw new Error(`Programmatic adapter module must export "adapter": ${resolvedPath}`);
171
+ }
172
+ const parsed = adapterSchema.parse(candidate);
173
+ const runtime = {
174
+ modulePath: resolvedPath,
175
+ hash,
176
+ capabilities: Array.isArray(candidate.capabilities) ? candidate.capabilities.filter((item) => typeof item === "string") : [],
177
+ plan: typeof candidate.plan === "function" ? candidate.plan.bind(candidate) : void 0,
178
+ apply: typeof candidate.apply === "function" ? candidate.apply.bind(candidate) : void 0,
179
+ uninstall: typeof candidate.uninstall === "function" ? candidate.uninstall.bind(candidate) : void 0
180
+ };
181
+ return { ...parsed, programmatic: runtime };
182
+ }
183
+ async function transpileTypeScriptAdapter(modulePath, hash) {
184
+ const source = await readFile2(modulePath, "utf8");
185
+ const output = ts.transpileModule(source, {
186
+ compilerOptions: {
187
+ module: ts.ModuleKind.ES2022,
188
+ target: ts.ScriptTarget.ES2022,
189
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
190
+ esModuleInterop: true,
191
+ sourceMap: false
192
+ },
193
+ fileName: modulePath
125
194
  });
126
- if (errors.length > 0) {
127
- const details = errors.map((error) => `${printParseErrorCode(error.error)} at offset ${error.offset}`).join(", ");
128
- throw new Error(`Invalid adapter config ${path}: ${details}`);
195
+ const outPath = join(tmpdir(), `agentwheel-adapter-${hash}.mjs`);
196
+ await writeFile(outPath, output.outputText, "utf8");
197
+ return outPath;
198
+ }
199
+
200
+ // src/adapters/resolve.ts
201
+ async function resolveAdapter(options) {
202
+ if (options.adapterModule) {
203
+ const modulePath = resolve2(options.baseDir ?? process.cwd(), options.adapterModule);
204
+ const adapter = await loadProgrammaticAdapter(modulePath, { allowCode: options.allowAdapterCode === true });
205
+ options.warn?.(`WARNING: loaded local adapter code ${adapter.programmatic?.modulePath} (${adapter.programmatic?.hash})`);
206
+ return adapter;
129
207
  }
130
- return adapterSchema.parse(parsed);
208
+ if (options.adapterConfig) {
209
+ return loadAdapterConfig(resolve2(options.baseDir ?? process.cwd(), options.adapterConfig));
210
+ }
211
+ return getAdapter(options.adapter ?? "openclaw");
131
212
  }
132
213
 
133
214
  // src/install/apply.ts
134
215
  import { execFile } from "child_process";
135
- import { rm as rm3 } from "fs/promises";
216
+ import { rm as rm2 } from "fs/promises";
136
217
  import { promisify } from "util";
137
218
 
138
- // src/utils/fs.ts
139
- import { createHash } from "crypto";
140
- import {
141
- copyFile,
142
- cp,
143
- mkdir,
144
- readdir,
145
- readFile as readFile2,
146
- rename,
147
- rm,
148
- stat,
149
- writeFile
150
- } from "fs/promises";
151
- import { dirname, join, relative } from "path";
152
- async function pathExists(path) {
153
- try {
154
- await stat(path);
155
- return true;
156
- } catch {
157
- return false;
158
- }
219
+ // src/install/json-merge.ts
220
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
221
+ import { dirname } from "path";
222
+ import { mkdir } from "fs/promises";
223
+ async function mergeJsonFile(sourcePath, destPath) {
224
+ const source = JSON.parse(await readFile3(sourcePath, "utf8"));
225
+ const current = await pathExists(destPath) ? JSON.parse(await readFile3(destPath, "utf8")) : {};
226
+ const merged = deepMerge(current, source);
227
+ await mkdir(dirname(destPath), { recursive: true });
228
+ await writeFile2(destPath, `${JSON.stringify(merged, null, 2)}
229
+ `, "utf8");
159
230
  }
160
- async function hashPath(path) {
161
- const stats = await stat(path);
162
- if (stats.isFile()) {
163
- const content = await readFile2(path);
164
- return createHash("sha256").update("file\0").update(content).digest("hex");
165
- }
166
- if (!stats.isDirectory()) {
167
- throw new Error(`Unsupported path kind: ${path}`);
231
+ function deepMerge(base, incoming) {
232
+ if (Array.isArray(base) && Array.isArray(incoming)) {
233
+ return dedupeArray([...base, ...incoming]);
168
234
  }
169
- const hash = createHash("sha256").update("dir\0");
170
- const files = await listFiles(path);
171
- for (const file of files) {
172
- hash.update(relative(path, file).replaceAll("\\", "/")).update("\0");
173
- hash.update(await hashPath(file)).update("\0");
235
+ if (isRecord(base) && isRecord(incoming)) {
236
+ const out = { ...base };
237
+ for (const [key, value] of Object.entries(incoming)) {
238
+ out[key] = key in out ? deepMerge(out[key], value) : value;
239
+ }
240
+ return out;
174
241
  }
175
- return hash.digest("hex");
242
+ return incoming;
243
+ }
244
+ function isRecord(value) {
245
+ return typeof value === "object" && value !== null && !Array.isArray(value);
176
246
  }
177
- async function listFiles(root) {
247
+ function dedupeArray(values) {
248
+ const seen = /* @__PURE__ */ new Set();
178
249
  const out = [];
179
- async function walk(dir) {
180
- const entries = await readdir(dir, { withFileTypes: true });
181
- for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
182
- if (entry.name === ".git" || entry.name === "node_modules") continue;
183
- const full = join(dir, entry.name);
184
- if (entry.isDirectory()) {
185
- await walk(full);
186
- } else if (entry.isFile()) {
187
- out.push(full);
188
- }
189
- }
250
+ for (const value of values) {
251
+ const key = JSON.stringify(value);
252
+ if (seen.has(key)) continue;
253
+ seen.add(key);
254
+ out.push(value);
190
255
  }
191
- await walk(root);
192
256
  return out;
193
257
  }
194
- async function atomicCopy(source, dest, kind) {
195
- await mkdir(dirname(dest), { recursive: true });
196
- const temp = `${dest}.agentwheel-tmp-${process.pid}-${Date.now()}`;
197
- await rm(temp, { recursive: true, force: true });
198
- if (kind === "file") {
199
- await copyFile(source, temp);
200
- } else {
201
- await cp(source, temp, { recursive: true, dereference: true });
202
- }
203
- await rm(dest, { recursive: true, force: true });
204
- await rename(temp, dest);
205
- }
206
- async function writeJsonAtomic(path, data) {
207
- await mkdir(dirname(path), { recursive: true });
208
- const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
209
- await writeFile(temp, `${JSON.stringify(data, null, 2)}
210
- `, "utf8");
211
- await rename(temp, path);
212
- }
213
258
 
214
259
  // src/install/manifest.ts
215
- import { readFile as readFile3, rm as rm2 } from "fs/promises";
216
- import { resolve } from "path";
260
+ import { readFile as readFile4, rm } from "fs/promises";
261
+ import { resolve as resolve3 } from "path";
217
262
 
218
263
  // src/model/manifest.ts
219
264
  import { z as z3 } from "zod";
@@ -228,13 +273,18 @@ var manifestEntrySchema = z3.object({
228
273
  channel: z3.enum(["managed", "overlay", "addition", "override", "ejected"]).default("managed"),
229
274
  packageName: z3.string().min(1).optional(),
230
275
  semanticCommand: z3.array(z3.string()).optional(),
231
- executed: z3.boolean().optional()
276
+ executed: z3.boolean().optional(),
277
+ mergeStrategy: z3.enum(["json-deep"]).optional()
232
278
  });
233
279
  var installManifestSchema = z3.object({
234
280
  version: z3.literal(1),
235
281
  adapter: z3.string().min(1),
236
282
  targetRoot: z3.string().min(1),
237
283
  generatedAt: z3.string().datetime(),
284
+ adapterCode: z3.object({
285
+ modulePath: z3.string().min(1),
286
+ hash: z3.string().min(16)
287
+ }).optional(),
238
288
  entries: z3.array(manifestEntrySchema)
239
289
  });
240
290
  var sourceLockSchema = z3.object({
@@ -276,7 +326,7 @@ function sourceLockPath(targetRoot, adapter) {
276
326
  async function readInstallManifest(targetRoot, adapter) {
277
327
  const path = installManifestPath(targetRoot, adapter);
278
328
  if (!await pathExists(path)) return void 0;
279
- return installManifestSchema.parse(JSON.parse(await readFile3(path, "utf8")));
329
+ return installManifestSchema.parse(JSON.parse(await readFile4(path, "utf8")));
280
330
  }
281
331
  async function writeInstallManifest(manifest) {
282
332
  await writeJsonAtomic(installManifestPath(manifest.targetRoot, manifest.adapter), manifest);
@@ -287,14 +337,14 @@ async function writeSourceLock(targetRoot, adapter, lock) {
287
337
  async function readSourceLock(targetRoot, adapter) {
288
338
  const path = sourceLockPath(targetRoot, adapter);
289
339
  if (!await pathExists(path)) return void 0;
290
- return sourceLockSchema.parse(JSON.parse(await readFile3(path, "utf8")));
340
+ return sourceLockSchema.parse(JSON.parse(await readFile4(path, "utf8")));
291
341
  }
292
342
  async function removeStateFiles(targetRoot, adapter) {
293
- await rm2(installManifestPath(targetRoot, adapter), { force: true });
294
- await rm2(sourceLockPath(targetRoot, adapter), { force: true });
343
+ await rm(installManifestPath(targetRoot, adapter), { force: true });
344
+ await rm(sourceLockPath(targetRoot, adapter), { force: true });
295
345
  }
296
346
  function normalizeTargetRoot(path) {
297
- return resolve(path);
347
+ return resolve3(path);
298
348
  }
299
349
 
300
350
  // src/install/apply.ts
@@ -335,11 +385,36 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
335
385
  semanticCommand: operation.semanticCommand,
336
386
  executed: options.executePlugins === true
337
387
  });
388
+ } else if (operation.action === "program") {
389
+ if (!plan.adapterCode || !operation.desiredHash || !operation.programmaticOperation) {
390
+ throw new Error(`Invalid programmatic operation: ${operation.relativeDestPath}`);
391
+ }
392
+ if (operation.programmaticApply) {
393
+ await operation.programmaticApply(operation.programmaticOperation, {
394
+ targetRoot: plan.targetRoot,
395
+ adapterName: plan.adapter
396
+ });
397
+ }
398
+ entries.push({
399
+ path: operation.relativeDestPath,
400
+ artifactType: operation.artifactType,
401
+ artifactName: operation.artifactName,
402
+ kind: operation.kind,
403
+ hash: operation.desiredHash,
404
+ sourceHash: operation.desiredHash,
405
+ updatedAt: now,
406
+ channel: operation.channel,
407
+ packageName: operation.packageName
408
+ });
338
409
  } else if (operation.action === "create" || operation.action === "update") {
339
410
  if (!operation.sourcePath || !operation.desiredHash) {
340
411
  throw new Error(`Invalid operation missing source/hash: ${operation.relativeDestPath}`);
341
412
  }
342
- await atomicCopy(operation.sourcePath, operation.destPath, operation.kind);
413
+ if (operation.mergeStrategy === "json-deep") {
414
+ await mergeJsonFile(operation.sourcePath, operation.destPath);
415
+ } else {
416
+ await atomicCopy(operation.sourcePath, operation.destPath, operation.kind);
417
+ }
343
418
  entries.push({
344
419
  path: operation.relativeDestPath,
345
420
  artifactType: operation.artifactType,
@@ -350,7 +425,8 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
350
425
  updatedAt: now,
351
426
  channel: operation.channel,
352
427
  packageName: operation.packageName,
353
- semanticCommand: operation.semanticCommand
428
+ semanticCommand: operation.semanticCommand,
429
+ mergeStrategy: operation.mergeStrategy
354
430
  });
355
431
  } else if (operation.action === "skip") {
356
432
  if (!operation.desiredHash) {
@@ -361,15 +437,16 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
361
437
  artifactType: operation.artifactType,
362
438
  artifactName: operation.artifactName,
363
439
  kind: operation.kind,
364
- hash: operation.desiredHash,
440
+ hash: operation.mergeStrategy === "json-deep" && operation.currentHash ? operation.currentHash : operation.desiredHash,
365
441
  sourceHash: operation.desiredHash,
366
442
  updatedAt: now,
367
443
  channel: operation.channel,
368
444
  packageName: operation.packageName,
369
- semanticCommand: operation.semanticCommand
445
+ semanticCommand: operation.semanticCommand,
446
+ mergeStrategy: operation.mergeStrategy
370
447
  });
371
448
  } else if (operation.action === "remove") {
372
- await rm3(operation.destPath, { recursive: true, force: true });
449
+ await rm2(operation.destPath, { recursive: true, force: true });
373
450
  }
374
451
  }
375
452
  const manifest = {
@@ -377,6 +454,7 @@ async function applyInstallPlan(plan, sourceLock, options = {}) {
377
454
  adapter: plan.adapter,
378
455
  targetRoot: plan.targetRoot,
379
456
  generatedAt: now,
457
+ adapterCode: plan.adapterCode,
380
458
  entries: entries.sort((a, b) => a.path.localeCompare(b.path))
381
459
  };
382
460
  await writeInstallManifest(manifest);
@@ -390,13 +468,13 @@ async function uninstall(plan, dryRun) {
390
468
  }
391
469
  if (dryRun) return;
392
470
  for (const operation of plan.operations) {
393
- await rm3(operation.destPath, { recursive: true, force: true });
471
+ await rm2(operation.destPath, { recursive: true, force: true });
394
472
  }
395
473
  await removeStateFiles(plan.targetRoot, plan.adapter);
396
474
  }
397
475
 
398
476
  // src/install/plan.ts
399
- import { join as join3, relative as relative2 } from "path";
477
+ import { join as join3, relative } from "path";
400
478
 
401
479
  // src/targets/plugins/openclaw.ts
402
480
  function openClawPluginInstallCommand(request) {
@@ -412,6 +490,23 @@ async function createInstallPlan(bundle, adapter, targetRoot, manifest) {
412
490
  desired.set(op.relativeDestPath, op);
413
491
  }
414
492
  }
493
+ if (adapter.programmatic?.plan) {
494
+ for (const op of await adapter.programmatic.plan({ targetRoot, adapterName: adapter.name })) {
495
+ desired.set(`programmatic/${op.name}`, {
496
+ action: "program",
497
+ artifactType: "settings",
498
+ artifactName: op.name,
499
+ kind: "file",
500
+ destPath: targetRoot,
501
+ relativeDestPath: `programmatic/${op.name}`,
502
+ desiredHash: adapter.programmatic.hash,
503
+ reason: op.reason ?? "programmatic adapter operation planned",
504
+ channel: "managed",
505
+ programmaticOperation: op,
506
+ programmaticApply: adapter.programmatic.apply
507
+ });
508
+ }
509
+ }
415
510
  const manifestByPath = new Map((manifest?.entries ?? []).map((entry) => [entry.path, entry]));
416
511
  const operations = [];
417
512
  for (const op of desired.values()) {
@@ -424,6 +519,30 @@ async function createInstallPlan(bundle, adapter, targetRoot, manifest) {
424
519
  }
425
520
  continue;
426
521
  }
522
+ if (op.action === "program") {
523
+ const existing2 = manifestByPath.get(op.relativeDestPath);
524
+ if (existing2 && existing2.hash === op.desiredHash) {
525
+ operations.push({ ...op, action: "skip", manifestHash: existing2.hash, reason: "programmatic operation already applied" });
526
+ } else {
527
+ operations.push(op);
528
+ }
529
+ continue;
530
+ }
531
+ if (op.mergeStrategy === "json-deep") {
532
+ const existing2 = manifestByPath.get(op.relativeDestPath);
533
+ const exists2 = await pathExists(op.destPath);
534
+ if (!exists2) {
535
+ operations.push({ ...op, action: "create", reason: "merge destination missing" });
536
+ continue;
537
+ }
538
+ const currentHash2 = await hashPath(op.destPath);
539
+ if (existing2 && existing2.sourceHash === op.desiredHash) {
540
+ operations.push({ ...op, action: "skip", currentHash: currentHash2, manifestHash: existing2.hash, reason: "merged source already up to date" });
541
+ } else {
542
+ operations.push({ ...op, action: "update", currentHash: currentHash2, manifestHash: existing2?.hash, reason: existing2 ? "merge source changed" : "merge into existing JSON" });
543
+ }
544
+ continue;
545
+ }
427
546
  const existing = manifestByPath.get(op.relativeDestPath);
428
547
  const exists = await pathExists(op.destPath);
429
548
  if (!exists) {
@@ -491,7 +610,10 @@ async function createInstallPlan(bundle, adapter, targetRoot, manifest) {
491
610
  adapter: adapter.name,
492
611
  targetRoot,
493
612
  operations,
494
- hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict")
613
+ hasBlockingChanges: operations.some((op) => op.action === "drift" || op.action === "conflict"),
614
+ adapterCode: adapter.programmatic ? { modulePath: adapter.programmatic.modulePath, hash: adapter.programmatic.hash } : void 0,
615
+ programmaticApply: adapter.programmatic?.apply,
616
+ programmaticUninstall: adapter.programmatic?.uninstall
495
617
  };
496
618
  }
497
619
  function operationForArtifact(artifact, adapter, targetRoot) {
@@ -514,7 +636,7 @@ function operationForArtifact(artifact, adapter, targetRoot) {
514
636
  semanticCommand: openClawPluginInstallCommand({ path: sourcePath, dryRun: true })
515
637
  };
516
638
  }
517
- const destPath = artifact.type === "instructions" ? join3(targetRoot, target.dest) : join3(targetRoot, target.dest, artifact.name);
639
+ const destPath = artifact.type === "instructions" || artifact.type === "settings" ? join3(targetRoot, target.dest) : join3(targetRoot, target.dest, artifact.name);
518
640
  return {
519
641
  action: "create",
520
642
  artifactType: artifact.type,
@@ -522,11 +644,12 @@ function operationForArtifact(artifact, adapter, targetRoot) {
522
644
  kind: artifact.kind,
523
645
  sourcePath: artifact.stagedPath ?? artifact.sourcePath,
524
646
  destPath,
525
- relativeDestPath: relative2(targetRoot, destPath).replaceAll("\\", "/"),
647
+ relativeDestPath: relative(targetRoot, destPath).replaceAll("\\", "/"),
526
648
  desiredHash: artifact.hash,
527
649
  reason: "destination missing",
528
650
  channel: artifact.channel ?? "managed",
529
- packageName: artifact.packageName
651
+ packageName: artifact.packageName,
652
+ mergeStrategy: target.merge
530
653
  };
531
654
  }
532
655
  function summarizePlan(plan) {
@@ -537,7 +660,8 @@ function summarizePlan(plan) {
537
660
  remove: 0,
538
661
  drift: 0,
539
662
  conflict: 0,
540
- plugin: 0
663
+ plugin: 0,
664
+ program: 0
541
665
  };
542
666
  for (const operation of plan.operations) {
543
667
  summary[operation.action]++;
@@ -600,7 +724,8 @@ var labels = {
600
724
  remove: "REMOVE",
601
725
  drift: "DRIFT",
602
726
  conflict: "CONFLICT",
603
- plugin: "PLUGIN"
727
+ plugin: "PLUGIN",
728
+ program: "PROGRAM"
604
729
  };
605
730
  var channelLabels = {
606
731
  managed: "MANAGED",
@@ -625,13 +750,13 @@ function formatPlan(plan) {
625
750
 
626
751
  // src/source/git.ts
627
752
  import { execFile as execFile2 } from "child_process";
628
- import { mkdir as mkdir2, rm as rm4 } from "fs/promises";
753
+ import { mkdir as mkdir2, rm as rm3 } from "fs/promises";
629
754
  import { homedir } from "os";
630
- import { basename as basename2, join as join7, resolve as resolve3 } from "path";
755
+ import { basename as basename2, join as join7, resolve as resolve5 } from "path";
631
756
  import { promisify as promisify2 } from "util";
632
757
 
633
758
  // src/model/package.ts
634
- import { readFile as readFile4 } from "fs/promises";
759
+ import { readFile as readFile5 } from "fs/promises";
635
760
  import { join as join5 } from "path";
636
761
  import { parse as parse2, printParseErrorCode as printParseErrorCode2 } from "jsonc-parser";
637
762
  import { z as z4 } from "zod";
@@ -655,7 +780,7 @@ async function findPackageManifestPath(root) {
655
780
  async function readPackageManifest(root) {
656
781
  const path = await findPackageManifestPath(root);
657
782
  if (!path) return void 0;
658
- const content = await readFile4(path, "utf8");
783
+ const content = await readFile5(path, "utf8");
659
784
  const errors = [];
660
785
  const parsed = parse2(content, errors, { allowTrailingComma: true, disallowComments: false });
661
786
  if (errors.length > 0) {
@@ -666,12 +791,12 @@ async function readPackageManifest(root) {
666
791
  }
667
792
 
668
793
  // src/source/local.ts
669
- import { readdir as readdir2, stat as stat2 } from "fs/promises";
670
- import { basename, join as join6, resolve as resolve2 } from "path";
794
+ import { readdir, stat as stat2 } from "fs/promises";
795
+ import { basename, join as join6, resolve as resolve4 } from "path";
671
796
  var LocalSourceDriver = class {
672
797
  name = "local";
673
798
  async resolve(source) {
674
- const resolvedPath = resolve2(source);
799
+ const resolvedPath = resolve4(source);
675
800
  if (!await pathExists(resolvedPath)) {
676
801
  throw new Error(`Local source not found: ${resolvedPath}`);
677
802
  }
@@ -757,7 +882,7 @@ var LocalSourceDriver = class {
757
882
  }
758
883
  }
759
884
  }
760
- for (const type of ["commands", "mcp", "hooks", "plugins"]) {
885
+ for (const type of ["commands", "mcp", "hooks", "settings", "plugins"]) {
761
886
  const dir = join6(root, type);
762
887
  if (!await pathExists(dir)) continue;
763
888
  artifacts.push(...await listGenericArtifacts(type, dir, type, resolved.packageName));
@@ -794,7 +919,7 @@ async function firstExisting(paths) {
794
919
  return void 0;
795
920
  }
796
921
  async function sortedDirEntries(path) {
797
- return (await readdir2(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
922
+ return (await readdir(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
798
923
  }
799
924
  async function listFromManifest(root, packageName) {
800
925
  const manifest = await readPackageManifest(root);
@@ -884,9 +1009,9 @@ var GitSourceDriver = class {
884
1009
  }
885
1010
  async fetch(resolved) {
886
1011
  const parsed = parseGitSource(resolved.source);
887
- await mkdir2(resolve3(resolved.resolvedPath, ".."), { recursive: true });
1012
+ await mkdir2(resolve5(resolved.resolvedPath, ".."), { recursive: true });
888
1013
  if (!await pathExists(join7(resolved.resolvedPath, ".git"))) {
889
- await rm4(resolved.resolvedPath, { recursive: true, force: true });
1014
+ await rm3(resolved.resolvedPath, { recursive: true, force: true });
890
1015
  await git(["clone", "--no-tags", parsed.url, resolved.resolvedPath]);
891
1016
  } else {
892
1017
  await git(["-C", resolved.resolvedPath, "fetch", "--prune", "origin"]);
@@ -946,16 +1071,347 @@ function parseGitSource(source) {
946
1071
  throw new Error(`Invalid git source: ${source}`);
947
1072
  }
948
1073
  function cachePathFor(url, cacheRoot) {
949
- const root = cacheRoot ? resolve3(cacheRoot) : join7(homedir(), ".agentwheel", "cache");
950
- const slug = url.replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
951
- return join7(root, slug || basename2(url));
1074
+ const root = cacheRoot ? resolve5(cacheRoot) : join7(homedir(), ".agentwheel", "cache");
1075
+ const slug2 = url.replace(/^[a-z]+:\/\//i, "").replace(/\.git$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "");
1076
+ return join7(root, slug2 || basename2(url));
952
1077
  }
953
1078
  async function git(args) {
954
1079
  return execFileAsync2("git", args, { maxBuffer: 1024 * 1024 * 10 });
955
1080
  }
956
1081
 
1082
+ // src/source/skillkit.ts
1083
+ import { cp, mkdir as mkdir3, readFile as readFile6, rm as rm4 } from "fs/promises";
1084
+ import { homedir as homedir2 } from "os";
1085
+ import { basename as basename4, dirname as dirname3, join as join9, resolve as resolve6 } from "path";
1086
+ import * as defaultSkillKit from "@skillkit/core";
1087
+
1088
+ // src/source/skill-artifacts.ts
1089
+ import { readdir as readdir2, stat as stat3 } from "fs/promises";
1090
+ import { basename as basename3, dirname as dirname2, extname as extname2, join as join8 } from "path";
1091
+ async function artifactsFromSkillPaths(paths, packageName) {
1092
+ const artifacts = [];
1093
+ const seen = /* @__PURE__ */ new Set();
1094
+ for (const item of paths) {
1095
+ const artifact = await artifactFromSkillPath(item, packageName);
1096
+ if (!artifact) continue;
1097
+ const key = `${artifact.name}:${artifact.sourcePath}`;
1098
+ if (seen.has(key)) continue;
1099
+ seen.add(key);
1100
+ artifacts.push(artifact);
1101
+ }
1102
+ return artifacts.sort((a, b) => a.name.localeCompare(b.name));
1103
+ }
1104
+ async function discoverSkillPaths(root) {
1105
+ const paths = [];
1106
+ await walk(root, paths);
1107
+ return paths;
1108
+ }
1109
+ async function artifactFromSkillPath(item, packageName) {
1110
+ const stats = await stat3(item.path);
1111
+ if (stats.isDirectory()) {
1112
+ const skillMd = join8(item.path, "SKILL.md");
1113
+ if (!await pathExists(skillMd)) return void 0;
1114
+ const name = sanitizeSkillName(item.name ?? basename3(item.path));
1115
+ return {
1116
+ type: "skills",
1117
+ name,
1118
+ sourcePath: item.path,
1119
+ relativePath: join8("skills", name),
1120
+ kind: "dir",
1121
+ hash: await hashPath(item.path),
1122
+ packageName,
1123
+ channel: "managed"
1124
+ };
1125
+ }
1126
+ if (stats.isFile() && basename3(item.path).toLowerCase() === "skill.md") {
1127
+ const dir = dirname2(item.path);
1128
+ const name = sanitizeSkillName(item.name ?? basename3(dir));
1129
+ return {
1130
+ type: "skills",
1131
+ name,
1132
+ sourcePath: dir,
1133
+ relativePath: join8("skills", name),
1134
+ kind: "dir",
1135
+ hash: await hashPath(dir),
1136
+ packageName,
1137
+ channel: "managed"
1138
+ };
1139
+ }
1140
+ if (stats.isFile() && extname2(item.path).toLowerCase() === ".md") {
1141
+ const name = sanitizeSkillName(item.name ?? basename3(item.path, ".md"));
1142
+ return {
1143
+ type: "skills",
1144
+ name,
1145
+ sourcePath: item.path,
1146
+ relativePath: join8("skills", `${name}.md`),
1147
+ kind: "file",
1148
+ hash: await hashPath(item.path),
1149
+ packageName,
1150
+ channel: "managed"
1151
+ };
1152
+ }
1153
+ return void 0;
1154
+ }
1155
+ async function walk(dir, paths) {
1156
+ if (!await pathExists(dir)) return;
1157
+ const entries = await readdir2(dir, { withFileTypes: true });
1158
+ if (entries.some((entry) => entry.isFile() && entry.name === "SKILL.md")) {
1159
+ paths.push({ path: dir });
1160
+ return;
1161
+ }
1162
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
1163
+ if (!entry.isDirectory() || entry.name === ".git" || entry.name === "node_modules") continue;
1164
+ await walk(join8(dir, entry.name), paths);
1165
+ }
1166
+ }
1167
+ function sanitizeSkillName(name) {
1168
+ return name.trim().replace(/\.md$/i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "skill";
1169
+ }
1170
+
1171
+ // src/source/skillkit.ts
1172
+ var SkillKitSourceDriver = class {
1173
+ constructor(core = defaultSkillKit) {
1174
+ this.core = core;
1175
+ }
1176
+ core;
1177
+ name = "skillkit";
1178
+ async resolve(source, options = {}) {
1179
+ const spec = parseSkillKitSource(source);
1180
+ if (await pathExists(spec)) {
1181
+ const resolvedPath = resolve6(spec);
1182
+ return {
1183
+ driver: this.name,
1184
+ source,
1185
+ resolvedPath,
1186
+ packageName: `skillkit/${basename4(resolvedPath)}`,
1187
+ mode: options.mode ?? "pinned",
1188
+ sourceHash: await hashPath(resolvedPath)
1189
+ };
1190
+ }
1191
+ return {
1192
+ driver: this.name,
1193
+ source,
1194
+ resolvedPath: cachePathFor2(spec, options.cacheRoot),
1195
+ packageName: `skillkit/${packageSlug(spec)}`,
1196
+ mode: options.mode ?? "tracking",
1197
+ requestedRef: options.ref
1198
+ };
1199
+ }
1200
+ async fetch(resolved) {
1201
+ const spec = parseSkillKitSource(resolved.source);
1202
+ if (await pathExists(spec)) {
1203
+ return resolved;
1204
+ }
1205
+ const providerSpec = normalizeProviderSource(spec);
1206
+ const provider = this.core.detectProvider?.(providerSpec);
1207
+ if (!provider?.clone) {
1208
+ throw new Error("SkillKit provider API unavailable or cannot resolve source. Expected @skillkit/core detectProvider().clone().");
1209
+ }
1210
+ await mkdir3(dirname3(resolved.resolvedPath), { recursive: true });
1211
+ const result = await provider.clone(providerSpec, resolved.resolvedPath, {});
1212
+ if (!result.success || !result.path) {
1213
+ throw new Error(`SkillKit provider failed to fetch ${spec}: ${result.error ?? "unknown error"}`);
1214
+ }
1215
+ if (resolve6(result.path) !== resolve6(resolved.resolvedPath)) {
1216
+ await rm4(resolved.resolvedPath, { recursive: true, force: true });
1217
+ await cp(result.path, resolved.resolvedPath, { recursive: true, dereference: true });
1218
+ }
1219
+ if (result.tempRoot) {
1220
+ await rm4(result.tempRoot, { recursive: true, force: true });
1221
+ }
1222
+ return {
1223
+ ...resolved,
1224
+ sourceHash: await hashPath(resolved.resolvedPath)
1225
+ };
1226
+ }
1227
+ async list(resolved) {
1228
+ const skills = this.discover(resolved.resolvedPath);
1229
+ return artifactsFromSkillPaths(skills, resolved.packageName);
1230
+ }
1231
+ async scan(resolved) {
1232
+ if (!this.core.SkillScanner) {
1233
+ return { ok: false, findings: [{ level: "error", message: "SkillKit SkillScanner API unavailable" }] };
1234
+ }
1235
+ const scanner = new this.core.SkillScanner();
1236
+ const findings = [];
1237
+ for (const skill of this.discover(resolved.resolvedPath)) {
1238
+ const scan = await scanner.scan(skill.path);
1239
+ for (const finding of scan.findings ?? []) {
1240
+ findings.push({
1241
+ level: mapSeverity(finding.severity),
1242
+ message: finding.title ?? finding.description ?? "SkillKit scanner finding",
1243
+ path: finding.filePath
1244
+ });
1245
+ }
1246
+ }
1247
+ return { ok: !findings.some((finding) => finding.level === "error"), findings };
1248
+ }
1249
+ async translate(resolved) {
1250
+ if (!this.core.translateSkill) {
1251
+ throw new Error("SkillKit translateSkill API unavailable");
1252
+ }
1253
+ for (const skill of this.discover(resolved.resolvedPath)) {
1254
+ const skillMd = join9(skill.path, "SKILL.md");
1255
+ if (await pathExists(skillMd)) {
1256
+ this.core.translateSkill(await readFile6(skillMd, "utf8"), "openclaw", { sourceFilename: "SKILL.md" });
1257
+ }
1258
+ }
1259
+ return resolved;
1260
+ }
1261
+ async export(resolved) {
1262
+ return resolved;
1263
+ }
1264
+ discover(root) {
1265
+ if (!this.core.discoverSkills) {
1266
+ throw new Error("SkillKit discoverSkills API unavailable");
1267
+ }
1268
+ return this.core.discoverSkills(root).filter((skill) => typeof skill.path === "string").map((skill) => ({ name: skill.name, path: skill.path }));
1269
+ }
1270
+ };
1271
+ function parseSkillKitSource(source) {
1272
+ if (!source.startsWith("skillkit:")) {
1273
+ throw new Error(`Invalid SkillKit source: ${source}`);
1274
+ }
1275
+ const spec = source.slice("skillkit:".length);
1276
+ if (!spec) throw new Error(`Invalid SkillKit source: ${source}`);
1277
+ return spec;
1278
+ }
1279
+ function normalizeProviderSource(spec) {
1280
+ if (spec.startsWith("github:")) return spec.slice("github:".length);
1281
+ if (spec.startsWith("git:https://github.com/")) return spec.slice("git:".length);
1282
+ return spec;
1283
+ }
1284
+ function cachePathFor2(spec, cacheRoot) {
1285
+ const root = cacheRoot ? resolve6(cacheRoot) : join9(homedir2(), ".agentwheel", "cache");
1286
+ return join9(root, "skillkit", packageSlug(spec));
1287
+ }
1288
+ function packageSlug(spec) {
1289
+ return spec.replace(/^[a-z]+:\/\//i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "source";
1290
+ }
1291
+ function mapSeverity(severity) {
1292
+ if (severity === "critical" || severity === "high") return "error";
1293
+ if (severity === "medium" || severity === "low") return "warning";
1294
+ return "info";
1295
+ }
1296
+
1297
+ // src/source/vercel-skills.ts
1298
+ import { stat as stat4 } from "fs/promises";
1299
+ import { basename as basename5, join as join10, resolve as resolve7 } from "path";
1300
+ var VercelSkillsSourceDriver = class {
1301
+ name = "vercel-skills";
1302
+ git = new GitSourceDriver();
1303
+ async resolve(source, options = {}) {
1304
+ const parsed = parseVercelSource(source);
1305
+ if (parsed.kind === "local") {
1306
+ const resolvedPath = resolve7(parsed.path);
1307
+ if (!await pathExists(resolvedPath) || !(await stat4(resolvedPath)).isDirectory()) {
1308
+ throw new Error(`Vercel skills local source not found: ${resolvedPath}`);
1309
+ }
1310
+ return {
1311
+ driver: this.name,
1312
+ source,
1313
+ resolvedPath,
1314
+ packageName: `vercel/${basename5(resolvedPath)}`,
1315
+ mode: options.mode ?? "pinned",
1316
+ sourceHash: await hashPath(resolvedPath)
1317
+ };
1318
+ }
1319
+ const gitResolved = await this.git.resolve(parsed.gitSource, options);
1320
+ return {
1321
+ ...gitResolved,
1322
+ driver: this.name,
1323
+ source,
1324
+ packageName: parsed.packageName
1325
+ };
1326
+ }
1327
+ async fetch(resolved) {
1328
+ const parsed = parseVercelSource(resolved.source);
1329
+ if (parsed.kind === "local") return resolved;
1330
+ const fetched = await this.git.fetch({
1331
+ ...resolved,
1332
+ driver: "git",
1333
+ source: parsed.gitSource
1334
+ });
1335
+ const resolvedPath = parsed.subpath ? join10(fetched.resolvedPath, parsed.subpath) : fetched.resolvedPath;
1336
+ if (!await pathExists(resolvedPath)) {
1337
+ throw new Error(`Vercel skills subpath not found: ${parsed.subpath}`);
1338
+ }
1339
+ return {
1340
+ ...resolved,
1341
+ resolvedPath,
1342
+ resolvedCommit: fetched.resolvedCommit,
1343
+ sourceHash: await hashPath(resolvedPath)
1344
+ };
1345
+ }
1346
+ async list(resolved) {
1347
+ return artifactsFromSkillPaths(await discoverSkillPaths(resolved.resolvedPath), resolved.packageName);
1348
+ }
1349
+ async scan(resolved) {
1350
+ const artifacts = await this.list(resolved);
1351
+ return {
1352
+ ok: artifacts.length > 0,
1353
+ findings: artifacts.length > 0 ? [] : [{ level: "warning", message: "No SKILL.md files found", path: resolved.resolvedPath }]
1354
+ };
1355
+ }
1356
+ async translate(resolved) {
1357
+ return resolved;
1358
+ }
1359
+ async export(resolved) {
1360
+ return resolved;
1361
+ }
1362
+ };
1363
+ function parseVercelSource(source) {
1364
+ if (!source.startsWith("vercel:")) {
1365
+ throw new Error(`Invalid Vercel skills source: ${source}`);
1366
+ }
1367
+ const spec = source.slice("vercel:".length);
1368
+ if (!spec) throw new Error(`Invalid Vercel skills source: ${source}`);
1369
+ if (spec.startsWith(".") || spec.startsWith("/")) return { kind: "local", path: spec };
1370
+ if (spec.startsWith("git:")) {
1371
+ return { kind: "git", gitSource: spec, packageName: `vercel/${slug(spec)}` };
1372
+ }
1373
+ if (spec.startsWith("github:")) {
1374
+ const name = spec.slice("github:".length).split("#", 1)[0];
1375
+ return { kind: "git", gitSource: spec, packageName: `vercel/${name}` };
1376
+ }
1377
+ if (spec.startsWith("skills.sh/") || spec.startsWith("https://skills.sh/")) {
1378
+ const parsed = parseSkillsSh(spec);
1379
+ return {
1380
+ kind: "git",
1381
+ gitSource: `github:${parsed.owner}/${parsed.repo}${parsed.ref ? `#${parsed.ref}` : ""}`,
1382
+ packageName: `vercel/${parsed.owner}/${parsed.repo}`,
1383
+ subpath: parsed.skillName
1384
+ };
1385
+ }
1386
+ if (/^[^/]+\/[^/]+/.test(spec)) {
1387
+ const [repo, ref] = spec.split("#", 2);
1388
+ const [owner, name] = repo.split("/", 2);
1389
+ return {
1390
+ kind: "git",
1391
+ gitSource: `github:${owner}/${name}${ref ? `#${ref}` : ""}`,
1392
+ packageName: `vercel/${owner}/${name}`
1393
+ };
1394
+ }
1395
+ throw new Error(`Invalid Vercel skills source: ${source}`);
1396
+ }
1397
+ function parseSkillsSh(spec) {
1398
+ const clean = spec.replace(/^https?:\/\//, "").replace(/^skills\.sh\//, "");
1399
+ const [path, ref] = clean.split("#", 2);
1400
+ const [owner, repo, ...rest] = path.split("/").filter(Boolean);
1401
+ if (!owner || !repo) throw new Error(`Invalid skills.sh source: ${spec}`);
1402
+ return { owner, repo, skillName: rest.length > 0 ? rest.join("/") : void 0, ref };
1403
+ }
1404
+ function slug(value) {
1405
+ return value.replace(/^[a-z]+:\/\//i, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "") || "source";
1406
+ }
1407
+
957
1408
  // src/source/index.ts
958
- var drivers = [new LocalSourceDriver(), new GitSourceDriver()];
1409
+ var drivers = [
1410
+ new LocalSourceDriver(),
1411
+ new GitSourceDriver(),
1412
+ new SkillKitSourceDriver(),
1413
+ new VercelSkillsSourceDriver()
1414
+ ];
959
1415
  function getSourceDriver(name = "local") {
960
1416
  const driver = drivers.find((candidate) => candidate.name === name);
961
1417
  if (!driver) {
@@ -965,13 +1421,13 @@ function getSourceDriver(name = "local") {
965
1421
  }
966
1422
 
967
1423
  // src/staging/staging.ts
968
- import { cp as cp3, mkdir as mkdir4, mkdtemp } from "fs/promises";
969
- import { dirname as dirname3, join as join9 } from "path";
970
- import { tmpdir } from "os";
1424
+ import { cp as cp3, mkdir as mkdir5, mkdtemp } from "fs/promises";
1425
+ import { dirname as dirname5, join as join12 } from "path";
1426
+ import { tmpdir as tmpdir2 } from "os";
971
1427
 
972
1428
  // src/staging/customize.ts
973
- import { cp as cp2, mkdir as mkdir3, readdir as readdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
974
- import { dirname as dirname2, join as join8 } from "path";
1429
+ import { cp as cp2, mkdir as mkdir4, readdir as readdir3, readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
1430
+ import { dirname as dirname4, join as join11 } from "path";
975
1431
  async function applyCustomizations(artifacts, options) {
976
1432
  let next = [...artifacts];
977
1433
  next = await applyReplacements(next, options, "override");
@@ -981,16 +1437,16 @@ async function applyCustomizations(artifacts, options) {
981
1437
  return next.sort((a, b) => `${a.type}:${a.name}:${a.channel}`.localeCompare(`${b.type}:${b.name}:${b.channel}`));
982
1438
  }
983
1439
  async function applyInstructionOverlay(artifacts, options) {
984
- const overlayPath = join8(options.workspaceRoot, ".agentwheel", "overlays", options.adapter.name, "instructions.local.md");
1440
+ const overlayPath = join11(options.workspaceRoot, ".agentwheel", "overlays", options.adapter.name, "instructions.local.md");
985
1441
  if (!await pathExists(overlayPath)) return artifacts;
986
1442
  const index = artifacts.findIndex((artifact2) => artifact2.type === "instructions");
987
1443
  if (index < 0) return artifacts;
988
1444
  const artifact = artifacts[index];
989
- const managed = await readFile5(artifact.stagedPath ?? artifact.sourcePath, "utf8");
990
- const local = await readFile5(overlayPath, "utf8");
991
- const composedPath = join8(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
992
- await mkdir3(dirname2(composedPath), { recursive: true });
993
- await writeFile2(
1445
+ const managed = await readFile7(artifact.stagedPath ?? artifact.sourcePath, "utf8");
1446
+ const local = await readFile7(overlayPath, "utf8");
1447
+ const composedPath = join11(options.stageRoot, ".agentwheel-composed", "instructions", "AGENTS.md");
1448
+ await mkdir4(dirname4(composedPath), { recursive: true });
1449
+ await writeFile3(
994
1450
  composedPath,
995
1451
  [
996
1452
  "<!-- BEGIN agentwheel managed: upstream -->",
@@ -1016,19 +1472,19 @@ async function applyInstructionOverlay(artifacts, options) {
1016
1472
  return [...artifacts.slice(0, index), updated, ...artifacts.slice(index + 1)];
1017
1473
  }
1018
1474
  async function applyAdditions(artifacts, options) {
1019
- const additionsRoot = join8(options.workspaceRoot, ".agentwheel", "additions");
1020
- const rulesRoot = join8(additionsRoot, "rules");
1475
+ const additionsRoot = join11(options.workspaceRoot, ".agentwheel", "additions");
1476
+ const rulesRoot = join11(additionsRoot, "rules");
1021
1477
  if (!await pathExists(rulesRoot)) return artifacts;
1022
1478
  const additions = [];
1023
1479
  for (const entry of await sortedDirEntries2(rulesRoot)) {
1024
- const full = join8(rulesRoot, entry.name);
1480
+ const full = join11(rulesRoot, entry.name);
1025
1481
  if (!entry.isFile()) continue;
1026
1482
  additions.push({
1027
1483
  type: "rules",
1028
1484
  name: entry.name,
1029
1485
  sourcePath: full,
1030
1486
  stagedPath: full,
1031
- relativePath: join8("additions", "rules", entry.name),
1487
+ relativePath: join11("additions", "rules", entry.name),
1032
1488
  kind: "file",
1033
1489
  hash: await hashPath(full),
1034
1490
  packageName: options.packageName,
@@ -1040,25 +1496,25 @@ async function applyAdditions(artifacts, options) {
1040
1496
  async function applyReplacements(artifacts, options, channel) {
1041
1497
  const packageName = options.packageName;
1042
1498
  if (!packageName) return artifacts;
1043
- const root = join8(options.workspaceRoot, ".agentwheel", channel === "override" ? "overrides" : "ejected", ...packageName.split("/"));
1499
+ const root = join11(options.workspaceRoot, ".agentwheel", channel === "override" ? "overrides" : "ejected", ...packageName.split("/"));
1044
1500
  if (!await pathExists(root)) return artifacts;
1045
1501
  const byKey = new Map(artifacts.map((artifact) => [artifactKey(artifact), artifact]));
1046
- for (const type of ["instructions", "rules", "skills", "commands", "subagents", "mcp", "hooks", "plugins"]) {
1047
- const typeRoot = join8(root, type);
1502
+ for (const type of ["instructions", "rules", "skills", "commands", "subagents", "mcp", "hooks", "settings", "plugins"]) {
1503
+ const typeRoot = join11(root, type);
1048
1504
  if (!await pathExists(typeRoot)) continue;
1049
1505
  for (const entry of await sortedDirEntries2(typeRoot)) {
1050
- const full = join8(typeRoot, entry.name);
1506
+ const full = join11(typeRoot, entry.name);
1051
1507
  const kind = entry.isDirectory() ? "dir" : "file";
1052
1508
  const existing = byKey.get(`${type}:${entry.name}`);
1053
- const stagedPath = join8(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
1054
- await mkdir3(dirname2(stagedPath), { recursive: true });
1509
+ const stagedPath = join11(options.stageRoot, ".agentwheel-composed", channel, type, entry.name);
1510
+ await mkdir4(dirname4(stagedPath), { recursive: true });
1055
1511
  await cp2(full, stagedPath, { recursive: kind === "dir", dereference: true });
1056
1512
  byKey.set(`${type}:${entry.name}`, {
1057
1513
  type,
1058
1514
  name: entry.name,
1059
1515
  sourcePath: full,
1060
1516
  stagedPath,
1061
- relativePath: existing?.relativePath ?? join8(type, entry.name),
1517
+ relativePath: existing?.relativePath ?? join11(type, entry.name),
1062
1518
  kind,
1063
1519
  hash: await hashPath(stagedPath),
1064
1520
  packageName,
@@ -1079,11 +1535,11 @@ async function sortedDirEntries2(path) {
1079
1535
  async function stageSource(driver, source, options = {}) {
1080
1536
  const resolved = await driver.export(await driver.translate(await driver.fetch(await driver.resolve(source, options))));
1081
1537
  const artifacts = await driver.list(resolved);
1082
- const root = await mkdtemp(join9(tmpdir(), "agentwheel-stage-"));
1538
+ const root = await mkdtemp(join12(tmpdir2(), "agentwheel-stage-"));
1083
1539
  const stagedArtifacts = [];
1084
1540
  for (const artifact of artifacts) {
1085
- const stagedPath = join9(root, artifact.relativePath);
1086
- await mkdir4(dirname3(stagedPath), { recursive: true });
1541
+ const stagedPath = join12(root, artifact.relativePath);
1542
+ await mkdir5(dirname5(stagedPath), { recursive: true });
1087
1543
  await cp3(artifact.sourcePath, stagedPath, { recursive: artifact.kind === "dir", dereference: true });
1088
1544
  stagedArtifacts.push({
1089
1545
  ...artifact,
@@ -1127,29 +1583,47 @@ async function stageSource(driver, source, options = {}) {
1127
1583
  }
1128
1584
 
1129
1585
  // src/model/workspace.ts
1130
- import { readFile as readFile6 } from "fs/promises";
1131
- import { join as join10 } from "path";
1586
+ import { readFile as readFile8 } from "fs/promises";
1587
+ import { join as join13 } from "path";
1132
1588
  import { z as z5 } from "zod";
1133
1589
  var workspacePackageSchema = z5.object({
1134
1590
  name: z5.string().min(1),
1135
1591
  source: z5.string().min(1),
1136
- driver: z5.enum(["local", "git"]).default("local"),
1592
+ driver: z5.enum(["local", "git", "skillkit", "vercel-skills"]).default("local"),
1137
1593
  adapter: z5.string().min(1).default("openclaw"),
1138
1594
  adapterConfig: z5.string().min(1).optional(),
1595
+ adapterModule: z5.string().min(1).optional(),
1596
+ adapterCodeHash: z5.string().min(16).optional(),
1139
1597
  mode: z5.enum(["pinned", "tracking"]).default("pinned"),
1140
1598
  requestedRef: z5.string().min(1).optional()
1141
1599
  });
1600
+ var workspaceProfileRuntimeSchema = z5.object({
1601
+ adapter: z5.string().min(1).default("openclaw"),
1602
+ adapterConfig: z5.string().min(1).optional(),
1603
+ adapterModule: z5.string().min(1).optional(),
1604
+ targetRoot: z5.string().min(1).optional(),
1605
+ executePlugins: z5.boolean().optional()
1606
+ });
1607
+ var workspaceProfileSchema = z5.object({
1608
+ runtimes: z5.array(workspaceProfileRuntimeSchema).min(1)
1609
+ });
1610
+ var workspaceRegistrySchema = z5.object({
1611
+ sources: z5.array(z5.string().min(1)).optional(),
1612
+ ttlSeconds: z5.number().int().positive().optional()
1613
+ }).default({});
1142
1614
  var workspaceConfigSchema = z5.object({
1143
1615
  schemaVersion: z5.literal(1),
1144
- packages: z5.array(workspacePackageSchema).default([])
1616
+ packages: z5.array(workspacePackageSchema).default([]),
1617
+ registry: workspaceRegistrySchema,
1618
+ profiles: z5.record(z5.string(), workspaceProfileSchema).default({})
1145
1619
  });
1146
1620
  function workspaceConfigPath(workspaceRoot) {
1147
- return join10(workspaceRoot, ".agentwheel", "config.json");
1621
+ return join13(workspaceRoot, ".agentwheel", "config.json");
1148
1622
  }
1149
1623
  async function readWorkspaceConfig(workspaceRoot) {
1150
1624
  const path = workspaceConfigPath(workspaceRoot);
1151
- if (!await pathExists(path)) return { schemaVersion: 1, packages: [] };
1152
- return workspaceConfigSchema.parse(JSON.parse(await readFile6(path, "utf8")));
1625
+ if (!await pathExists(path)) return { schemaVersion: 1, packages: [], registry: {}, profiles: {} };
1626
+ return workspaceConfigSchema.parse(JSON.parse(await readFile8(path, "utf8")));
1153
1627
  }
1154
1628
  async function writeWorkspaceConfig(workspaceRoot, config) {
1155
1629
  await writeJsonAtomic(workspaceConfigPath(workspaceRoot), workspaceConfigSchema.parse(config));
@@ -1158,15 +1632,15 @@ function upsertPackage(config, entry) {
1158
1632
  const packages = config.packages.filter((candidate) => candidate.name !== entry.name);
1159
1633
  packages.push(entry);
1160
1634
  packages.sort((a, b) => a.name.localeCompare(b.name));
1161
- return { schemaVersion: 1, packages };
1635
+ return { schemaVersion: 1, packages, registry: config.registry ?? {}, profiles: config.profiles ?? {} };
1162
1636
  }
1163
1637
 
1164
1638
  // src/lifecycle/customization.ts
1165
- import { appendFile, cp as cp4, mkdir as mkdir5, rm as rm5 } from "fs/promises";
1166
- import { dirname as dirname4, join as join11 } from "path";
1639
+ import { appendFile, cp as cp4, mkdir as mkdir6, rm as rm5 } from "fs/promises";
1640
+ import { dirname as dirname6, join as join14 } from "path";
1167
1641
  async function remember(workspaceRoot, runtime, text) {
1168
- const overlayPath = join11(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
1169
- await mkdir5(dirname4(overlayPath), { recursive: true });
1642
+ const overlayPath = join14(workspaceRoot, ".agentwheel", "overlays", runtime, "instructions.local.md");
1643
+ await mkdir6(dirname6(overlayPath), { recursive: true });
1170
1644
  await appendFile(overlayPath, `${text.trim()}
1171
1645
  `, "utf8");
1172
1646
  return { overlayPath };
@@ -1182,7 +1656,7 @@ async function ejectArtifact(workspaceRoot, item) {
1182
1656
  const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
1183
1657
  const bundle = await stageSource(driver, pkg.source, {
1184
1658
  adapter,
1185
- cacheRoot: join11(workspaceRoot, ".agentwheel", "cache"),
1659
+ cacheRoot: join14(workspaceRoot, ".agentwheel", "cache"),
1186
1660
  mode: pkg.mode
1187
1661
  });
1188
1662
  try {
@@ -1190,8 +1664,8 @@ async function ejectArtifact(workspaceRoot, item) {
1190
1664
  if (!artifact) {
1191
1665
  throw new Error(`Artifact not found: ${item}`);
1192
1666
  }
1193
- const ejectedPath = join11(workspaceRoot, ".agentwheel", "ejected", ...parsed.packageName.split("/"), parsed.type, parsed.name);
1194
- await mkdir5(dirname4(ejectedPath), { recursive: true });
1667
+ const ejectedPath = join14(workspaceRoot, ".agentwheel", "ejected", ...parsed.packageName.split("/"), parsed.type, parsed.name);
1668
+ await mkdir6(dirname6(ejectedPath), { recursive: true });
1195
1669
  await rm5(ejectedPath, { recursive: true, force: true });
1196
1670
  await cp4(artifact.stagedPath ?? artifact.sourcePath, ejectedPath, { recursive: artifact.kind === "dir", dereference: true });
1197
1671
  return { ...parsed, ejectedPath };
@@ -1210,6 +1684,204 @@ function parseEjectItem(item) {
1210
1684
  return { packageName, type, name };
1211
1685
  }
1212
1686
 
1687
+ // src/lifecycle/profile.ts
1688
+ import { rm as rm7 } from "fs/promises";
1689
+ import { join as join16 } from "path";
1690
+
1691
+ // src/registry/client.ts
1692
+ import { readFile as readFile9, rm as rm6, stat as stat6 } from "fs/promises";
1693
+ import { homedir as homedir3 } from "os";
1694
+ import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
1695
+ import { fileURLToPath } from "url";
1696
+
1697
+ // src/model/registry.ts
1698
+ import { z as z6 } from "zod";
1699
+ var registryEntrySchema = z6.object({
1700
+ name: z6.string().min(1),
1701
+ source: z6.string().min(1),
1702
+ type: z6.enum(["package", "skill", "plugin", "mcp", "adapter"]).default("package"),
1703
+ description: z6.string().default(""),
1704
+ tags: z6.array(z6.string().min(1)).default([])
1705
+ });
1706
+ var registryIndexSchema = z6.union([
1707
+ z6.array(registryEntrySchema),
1708
+ z6.object({
1709
+ schemaVersion: z6.literal(1).optional(),
1710
+ entries: z6.array(registryEntrySchema)
1711
+ })
1712
+ ]).transform((value) => Array.isArray(value) ? value : value.entries);
1713
+ var registryCacheSchema = z6.object({
1714
+ version: z6.literal(1),
1715
+ fetchedAt: z6.string().datetime(),
1716
+ sources: z6.array(z6.string().min(1)),
1717
+ entries: z6.array(registryEntrySchema)
1718
+ });
1719
+
1720
+ // src/registry/client.ts
1721
+ var DEFAULT_REGISTRY_SOURCE = "github:NestDevLab/agentwheel-registry";
1722
+ var DEFAULT_REGISTRY_TTL_MS = 24 * 60 * 60 * 1e3;
1723
+ var RegistryClient = class {
1724
+ constructor(options = {}) {
1725
+ this.options = options;
1726
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
1727
+ this.cachePath = options.cachePath ?? defaultRegistryCachePath();
1728
+ }
1729
+ options;
1730
+ git = new GitSourceDriver();
1731
+ now;
1732
+ cachePath;
1733
+ async getIndex(options = {}) {
1734
+ const sources = await this.getSources();
1735
+ const ttlMs = await this.getTtlMs();
1736
+ const cached = await this.readCache();
1737
+ if (!options.refresh && cached && sameSources(cached.sources, sources) && !this.isExpired(cached, ttlMs)) {
1738
+ return { entries: cached.entries, sources: cached.sources, fetchedAt: cached.fetchedAt, fromCache: true };
1739
+ }
1740
+ const entries = mergeIndexes(await Promise.all(sources.map((source) => this.fetchSource(source))));
1741
+ const fetchedAt = this.now().toISOString();
1742
+ await writeJsonAtomic(this.cachePath, { version: 1, fetchedAt, sources, entries });
1743
+ return { entries, sources, fetchedAt, fromCache: false };
1744
+ }
1745
+ async resolve(name, options = {}) {
1746
+ const index = await this.getIndex(options);
1747
+ return index.entries.find((entry) => entry.name === name);
1748
+ }
1749
+ async search(query, options = {}) {
1750
+ const q = query.toLowerCase();
1751
+ const index = await this.getIndex(options);
1752
+ return index.entries.filter(
1753
+ (entry) => entry.name.toLowerCase().includes(q) || entry.description.toLowerCase().includes(q) || entry.tags.some((tag) => tag.toLowerCase().includes(q))
1754
+ );
1755
+ }
1756
+ async clearCache() {
1757
+ await rm6(this.cachePath, { force: true });
1758
+ }
1759
+ async getSources() {
1760
+ if (this.options.sources?.length) return this.options.sources;
1761
+ if (process.env.AGENTWHEEL_REGISTRY) {
1762
+ return process.env.AGENTWHEEL_REGISTRY.split(",").map((source) => source.trim()).filter(Boolean);
1763
+ }
1764
+ if (this.options.workspaceRoot) {
1765
+ const config = await readWorkspaceConfig(this.options.workspaceRoot);
1766
+ if (config.registry.sources?.length) return config.registry.sources;
1767
+ }
1768
+ return [DEFAULT_REGISTRY_SOURCE];
1769
+ }
1770
+ async getTtlMs() {
1771
+ if (this.options.ttlMs !== void 0) return this.options.ttlMs;
1772
+ if (this.options.workspaceRoot) {
1773
+ const config = await readWorkspaceConfig(this.options.workspaceRoot);
1774
+ if (config.registry.ttlSeconds !== void 0) return config.registry.ttlSeconds * 1e3;
1775
+ }
1776
+ return DEFAULT_REGISTRY_TTL_MS;
1777
+ }
1778
+ async readCache() {
1779
+ if (!await pathExists(this.cachePath)) return void 0;
1780
+ return registryCacheSchema.parse(JSON.parse(await readFile9(this.cachePath, "utf8")));
1781
+ }
1782
+ isExpired(cache, ttlMs) {
1783
+ return this.now().getTime() - new Date(cache.fetchedAt).getTime() > ttlMs;
1784
+ }
1785
+ async fetchSource(source) {
1786
+ const raw = await this.readSourceIndex(source);
1787
+ return registryIndexSchema.parse(JSON.parse(raw));
1788
+ }
1789
+ async readSourceIndex(source) {
1790
+ if (source.startsWith("http://") || source.startsWith("https://")) {
1791
+ const response = await fetch(source);
1792
+ if (!response.ok) throw new Error(`Registry source failed (${response.status}): ${source}`);
1793
+ return response.text();
1794
+ }
1795
+ const filePath = source.startsWith("file:") ? fileURLToPath(source) : source;
1796
+ if (await pathExists(filePath)) {
1797
+ const fullPath = resolve8(filePath);
1798
+ const stats = await stat6(fullPath);
1799
+ return readFile9(stats.isDirectory() ? join15(fullPath, "index.json") : fullPath, "utf8");
1800
+ }
1801
+ const resolved = await this.git.fetch(await this.git.resolve(source, { cacheRoot: join15(dirname7(this.cachePath), "registry-repos") }));
1802
+ return readFile9(join15(resolved.resolvedPath, "index.json"), "utf8");
1803
+ }
1804
+ };
1805
+ async function resolvePackageSource(source, workspaceRoot) {
1806
+ const { isExplicitSource } = await import("./identify-7SEBWCNQ.js");
1807
+ if (await isExplicitSource(source)) return { source };
1808
+ const entry = await new RegistryClient({ workspaceRoot }).resolve(source);
1809
+ if (!entry) {
1810
+ throw new Error(`Registry entry not found: ${source}. Use an explicit path/git/skillkit/vercel source to bypass the registry.`);
1811
+ }
1812
+ return { source: entry.source, registryEntry: entry };
1813
+ }
1814
+ function mergeIndexes(indexes) {
1815
+ const merged = /* @__PURE__ */ new Map();
1816
+ for (const index of indexes) {
1817
+ for (const entry of index) {
1818
+ if (!merged.has(entry.name)) merged.set(entry.name, entry);
1819
+ }
1820
+ }
1821
+ return [...merged.values()].sort((a, b) => a.name.localeCompare(b.name));
1822
+ }
1823
+ function defaultRegistryCachePath() {
1824
+ return join15(homedir3(), ".agentwheel", "registry-cache.json");
1825
+ }
1826
+ function sameSources(a, b) {
1827
+ return a.length === b.length && a.every((source, index) => source === b[index]);
1828
+ }
1829
+
1830
+ // src/lifecycle/profile.ts
1831
+ async function syncProfile(options) {
1832
+ const config = await readWorkspaceConfig(options.workspaceRoot);
1833
+ const profile = config.profiles[options.profile];
1834
+ if (!profile) {
1835
+ throw new Error(`Unknown profile: ${options.profile}`);
1836
+ }
1837
+ const packages = options.source ? [await packageFromSource(options.source, options)] : config.packages;
1838
+ if (packages.length === 0) {
1839
+ throw new Error("Profile sync needs a source argument or configured packages.");
1840
+ }
1841
+ const results = [];
1842
+ for (const pkg of packages) {
1843
+ for (const runtime of profile.runtimes) {
1844
+ const targetRoot = runtime.targetRoot ?? options.workspaceRoot;
1845
+ const adapter = await resolveAdapter({
1846
+ adapter: runtime.adapter,
1847
+ adapterConfig: runtime.adapterConfig,
1848
+ adapterModule: runtime.adapterModule,
1849
+ allowAdapterCode: options.allowAdapterCode,
1850
+ baseDir: options.workspaceRoot,
1851
+ warn: options.warn
1852
+ });
1853
+ const driver = getSourceDriver(pkg.driver);
1854
+ const bundle = await stageSource(driver, pkg.source, {
1855
+ workspaceRoot: options.workspaceRoot,
1856
+ adapter,
1857
+ cacheRoot: join16(options.workspaceRoot, ".agentwheel", "cache"),
1858
+ mode: options.mode ?? pkg.mode
1859
+ });
1860
+ try {
1861
+ const plan = await createInstallPlan(bundle, adapter, targetRoot, await readInstallManifest(targetRoot, adapter.name));
1862
+ results.push({ runtime: adapter.name, packageName: pkg.name, plan });
1863
+ if (!options.dryRun) {
1864
+ await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: runtime.executePlugins ?? options.executePlugins });
1865
+ }
1866
+ } finally {
1867
+ await rm7(bundle.root, { recursive: true, force: true });
1868
+ }
1869
+ }
1870
+ }
1871
+ return results;
1872
+ }
1873
+ async function packageFromSource(source, options) {
1874
+ const resolved = await resolvePackageSource(source, options.workspaceRoot);
1875
+ const driver = options.driver ?? inferSourceDriverName(resolved.source);
1876
+ return {
1877
+ name: resolved.registryEntry?.name ?? source,
1878
+ source: resolved.source,
1879
+ driver,
1880
+ adapter: "openclaw",
1881
+ mode: options.mode ?? "pinned"
1882
+ };
1883
+ }
1884
+
1213
1885
  // src/lifecycle/update.ts
1214
1886
  function shouldUpdatePackage(pkg, lock) {
1215
1887
  if (!lock) {
@@ -1243,41 +1915,52 @@ program.command("init").argument("[kind]", "workspace or package", "workspace").
1243
1915
  await writeWorkspaceConfig(root, await readWorkspaceConfig(root));
1244
1916
  console.log("Initialized .agentwheel/config.json.");
1245
1917
  });
1246
- program.command("add").argument("<source>", "package source").option("--driver <driver>", "source driver (local or git)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").action(async (source, options) => {
1918
+ program.command("add").argument("<source>", "package source").option("--driver <driver>", "source driver (local, git, skillkit, or vercel-skills)").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "workspace root", process.cwd()).option("--mode <mode>", "pinned or tracking", "pinned").option("--name <name>", "package alias").action(async (source, options) => {
1247
1919
  const targetRoot = normalizeTargetRoot(options.targetRoot);
1248
- const driverName = options.driver ?? inferDriver(source);
1920
+ const resolvedInput = await resolvePackageSource(source, targetRoot);
1921
+ const resolvedSource = resolvedInput.source;
1922
+ const driverName = options.driver ?? inferSourceDriverName(resolvedSource);
1249
1923
  const driver = getSourceDriver(driverName);
1250
- const adapter = options.adapterConfig ? await loadAdapterConfig(options.adapterConfig) : getAdapter(options.adapter);
1251
- const bundle = await stageSource(driver, source, {
1924
+ const adapter = await resolveAdapter({
1925
+ adapter: options.adapter,
1926
+ adapterConfig: options.adapterConfig,
1927
+ adapterModule: options.adapterModule,
1928
+ allowAdapterCode: options.allowAdapterCode,
1929
+ baseDir: targetRoot,
1930
+ warn: (message) => console.warn(message)
1931
+ });
1932
+ const bundle = await stageSource(driver, resolvedSource, {
1252
1933
  workspaceRoot: targetRoot,
1253
1934
  adapter,
1254
- cacheRoot: join12(targetRoot, ".agentwheel", "cache"),
1935
+ cacheRoot: join17(targetRoot, ".agentwheel", "cache"),
1255
1936
  mode: options.mode
1256
1937
  });
1257
- const name = options.name ?? bundle.source.packageName ?? source;
1938
+ const name = options.name ?? resolvedInput.registryEntry?.name ?? bundle.source.packageName ?? source;
1258
1939
  const entry = {
1259
1940
  name,
1260
- source,
1941
+ source: resolvedSource,
1261
1942
  driver: driverName,
1262
1943
  adapter: adapter.name,
1263
1944
  adapterConfig: options.adapterConfig,
1945
+ adapterModule: options.adapterModule,
1946
+ adapterCodeHash: adapter.programmatic?.hash,
1264
1947
  mode: options.mode,
1265
1948
  requestedRef: bundle.source.requestedRef
1266
1949
  };
1267
1950
  await writeWorkspaceConfig(targetRoot, upsertPackage(await readWorkspaceConfig(targetRoot), entry));
1268
- await rm6(bundle.root, { recursive: true, force: true });
1951
+ await rm8(bundle.root, { recursive: true, force: true });
1269
1952
  console.log(`Added ${name}.`);
1270
1953
  });
1271
- program.command("list").argument("<source>", "local source directory").option("--driver <driver>", "source driver", "local").action(async (source, options) => {
1272
- const driver = getSourceDriver(options.driver);
1954
+ program.command("list").argument("<source>", "local source directory").option("--driver <driver>", "source driver").action(async (source, options) => {
1955
+ const driver = getSourceDriver(options.driver ?? inferSourceDriverName(source));
1273
1956
  const resolved = await driver.resolve(source);
1274
1957
  const artifacts = await driver.list(resolved);
1275
1958
  for (const artifact of artifacts) {
1276
1959
  console.log(`${artifact.type} ${artifact.name} ${artifact.relativePath}`);
1277
1960
  }
1278
1961
  });
1279
- program.command("scan").argument("<source>", "local source directory").option("--driver <driver>", "source driver", "local").action(async (source, options) => {
1280
- const driver = getSourceDriver(options.driver);
1962
+ program.command("scan").argument("<source>", "local source directory").option("--driver <driver>", "source driver").action(async (source, options) => {
1963
+ const driver = getSourceDriver(options.driver ?? inferSourceDriverName(source));
1281
1964
  const resolved = await driver.resolve(source);
1282
1965
  const result = await driver.scan(resolved);
1283
1966
  if (result.findings.length === 0) {
@@ -1289,23 +1972,47 @@ program.command("scan").argument("<source>", "local source directory").option("-
1289
1972
  }
1290
1973
  if (!result.ok) process.exitCode = 1;
1291
1974
  });
1292
- program.command("plan").argument("<source>", "source directory").option("--driver <driver>", "source driver", "local").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--target-root <path>", "runtime/project root", process.cwd()).option("--mode <mode>", "pinned or tracking").action(async (source, options) => {
1975
+ program.command("plan").argument("<source>", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root", process.cwd()).option("--mode <mode>", "pinned or tracking").action(async (source, options) => {
1293
1976
  const { plan, bundle } = await buildPlan(source, options);
1294
1977
  console.log(formatPlan(plan));
1295
- await rm6(bundle.root, { recursive: true, force: true });
1978
+ await rm8(bundle.root, { recursive: true, force: true });
1296
1979
  if (plan.hasBlockingChanges) process.exitCode = 1;
1297
1980
  });
1298
- program.command("sync").argument("<source>", "source directory").option("--driver <driver>", "source driver", "local").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--target-root <path>", "runtime/project root", process.cwd()).option("--mode <mode>", "pinned or tracking").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).action(async (source, options) => {
1981
+ program.command("sync").argument("[source]", "source directory").option("--driver <driver>", "source driver").option("--adapter <adapter>", "built-in adapter", "openclaw").option("--adapter-config <path>", "adapter JSON/JSONC file").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root", process.cwd()).option("--mode <mode>", "pinned or tracking").option("--profile <name>", "workspace runtime profile").option("--dry-run", "show plan without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).action(async (source, options) => {
1982
+ if (options.profile) {
1983
+ const targetRoot = normalizeTargetRoot(options.targetRoot);
1984
+ const results = await syncProfile({
1985
+ workspaceRoot: targetRoot,
1986
+ profile: options.profile,
1987
+ source,
1988
+ driver: options.driver,
1989
+ mode: options.mode,
1990
+ dryRun: options.dryRun,
1991
+ executePlugins: options.executePlugins,
1992
+ allowAdapterCode: options.allowAdapterCode,
1993
+ warn: (message) => console.warn(message)
1994
+ });
1995
+ for (const result of results) {
1996
+ console.log(`Profile ${options.profile} / ${result.runtime} / ${result.packageName}:`);
1997
+ console.log(formatPlan(result.plan));
1998
+ if (result.plan.hasBlockingChanges) process.exitCode = 1;
1999
+ }
2000
+ if (!options.dryRun) console.log("Applied.");
2001
+ return;
2002
+ }
2003
+ if (!source) {
2004
+ throw new Error("sync requires a source unless --profile is used");
2005
+ }
1299
2006
  const { plan, bundle } = await buildPlan(source, options);
1300
2007
  console.log(formatPlan(plan));
1301
2008
  if (!options.dryRun) {
1302
2009
  await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
1303
2010
  console.log("Applied.");
1304
2011
  }
1305
- await rm6(bundle.root, { recursive: true, force: true });
2012
+ await rm8(bundle.root, { recursive: true, force: true });
1306
2013
  if (plan.hasBlockingChanges) process.exitCode = 1;
1307
2014
  });
1308
- program.command("update").option("--target-root <path>", "workspace root", process.cwd()).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).action(async (options) => {
2015
+ program.command("update").option("--target-root <path>", "workspace root", process.cwd()).option("--dry-run", "show plans without writing", false).option("--execute-plugins", "execute semantic plugin installs", false).option("--allow-adapter-code", "allow loading local adapter code from configured packages", false).action(async (options) => {
1309
2016
  const targetRoot = normalizeTargetRoot(options.targetRoot);
1310
2017
  const config = await readWorkspaceConfig(targetRoot);
1311
2018
  if (config.packages.length === 0) {
@@ -1313,7 +2020,14 @@ program.command("update").option("--target-root <path>", "workspace root", proce
1313
2020
  return;
1314
2021
  }
1315
2022
  for (const pkg of config.packages) {
1316
- const adapter = pkg.adapterConfig ? await loadAdapterConfig(pkg.adapterConfig) : getAdapter(pkg.adapter);
2023
+ const adapter = await resolveAdapter({
2024
+ adapter: pkg.adapter,
2025
+ adapterConfig: pkg.adapterConfig,
2026
+ adapterModule: pkg.adapterModule,
2027
+ allowAdapterCode: options.allowAdapterCode,
2028
+ baseDir: targetRoot,
2029
+ warn: (message) => console.warn(message)
2030
+ });
1317
2031
  const lock = await readSourceLock(targetRoot, adapter.name);
1318
2032
  const decision = shouldUpdatePackage(pkg, lock);
1319
2033
  if (!decision.shouldUpdate) {
@@ -1324,6 +2038,8 @@ program.command("update").option("--target-root <path>", "workspace root", proce
1324
2038
  driver: pkg.driver,
1325
2039
  adapter: pkg.adapter,
1326
2040
  adapterConfig: pkg.adapterConfig,
2041
+ adapterModule: pkg.adapterModule,
2042
+ allowAdapterCode: options.allowAdapterCode,
1327
2043
  targetRoot,
1328
2044
  mode: pkg.mode
1329
2045
  });
@@ -1333,10 +2049,27 @@ program.command("update").option("--target-root <path>", "workspace root", proce
1333
2049
  await applyInstallPlan(plan, bundle.sourceLock, { executePlugins: options.executePlugins });
1334
2050
  console.log(`Applied ${pkg.name}.`);
1335
2051
  }
1336
- await rm6(bundle.root, { recursive: true, force: true });
2052
+ await rm8(bundle.root, { recursive: true, force: true });
1337
2053
  if (plan.hasBlockingChanges) process.exitCode = 1;
1338
2054
  }
1339
2055
  });
2056
+ program.command("registry").description("manage optional registry indexes").addCommand(
2057
+ new Command("update").description("refresh the local registry cache").option("--target-root <path>", "workspace root", process.cwd()).action(async (options) => {
2058
+ const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot) });
2059
+ const index = await client.getIndex({ refresh: true });
2060
+ console.log(`Registry refreshed: ${index.entries.length} entries from ${index.sources.join(", ")}`);
2061
+ })
2062
+ ).addCommand(
2063
+ new Command("list").description("list available registry entries").option("--target-root <path>", "workspace root", process.cwd()).action(async (options) => {
2064
+ const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot) });
2065
+ printRegistryEntries((await client.getIndex()).entries);
2066
+ })
2067
+ ).addCommand(
2068
+ new Command("search").description("search registry entries").argument("<query>", "search query").option("--target-root <path>", "workspace root", process.cwd()).action(async (query, options) => {
2069
+ const client = new RegistryClient({ workspaceRoot: normalizeTargetRoot(options.targetRoot) });
2070
+ printRegistryEntries(await client.search(query));
2071
+ })
2072
+ );
1340
2073
  program.command("remember").requiredOption("--runtime <runtime>", "runtime/adapter name").option("--target-root <path>", "workspace root", process.cwd()).argument("<text>", "text to append to the local instructions overlay").action(async (text, options) => {
1341
2074
  const targetRoot = normalizeTargetRoot(options.targetRoot);
1342
2075
  const result = await remember(targetRoot, options.runtime, text);
@@ -1347,27 +2080,45 @@ program.command("eject").argument("<item>", "package/type/name").option("--targe
1347
2080
  const result = await ejectArtifact(targetRoot, item);
1348
2081
  console.log(`Ejected ${item} to ${result.ejectedPath}.`);
1349
2082
  });
1350
- program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--target-root <path>", "runtime/project root", process.cwd()).option("--dry-run", "show removals without writing", false).action(async (options) => {
2083
+ program.command("uninstall").option("--adapter <adapter>", "adapter", "openclaw").option("--adapter-module <path>", "local programmatic adapter module").option("--allow-adapter-code", "allow loading local adapter code", false).option("--target-root <path>", "runtime/project root", process.cwd()).option("--dry-run", "show removals without writing", false).action(async (options) => {
1351
2084
  const targetRoot = normalizeTargetRoot(options.targetRoot);
1352
- const manifest = await readInstallManifest(targetRoot, options.adapter);
2085
+ const programmaticAdapter = options.adapterModule ? await resolveAdapter({
2086
+ adapter: options.adapter,
2087
+ adapterModule: options.adapterModule,
2088
+ allowAdapterCode: options.allowAdapterCode,
2089
+ baseDir: targetRoot,
2090
+ warn: (message) => console.warn(message)
2091
+ }) : void 0;
2092
+ const adapterName = programmaticAdapter?.name ?? options.adapter;
2093
+ const manifest = await readInstallManifest(targetRoot, adapterName);
1353
2094
  if (!manifest) {
1354
- console.log(`No install manifest for ${options.adapter} at ${targetRoot}`);
2095
+ console.log(`No install manifest for ${adapterName} at ${targetRoot}`);
1355
2096
  return;
1356
2097
  }
1357
2098
  const plan = await createUninstallPlan(manifest);
1358
2099
  console.log(formatPlan(plan));
1359
2100
  await uninstall(plan, options.dryRun);
2101
+ if (!options.dryRun && programmaticAdapter) {
2102
+ await programmaticAdapter.programmatic?.uninstall?.({ targetRoot, adapterName: programmaticAdapter.name });
2103
+ }
1360
2104
  if (!options.dryRun) console.log("Uninstalled.");
1361
2105
  if (plan.hasBlockingChanges) process.exitCode = 1;
1362
2106
  });
1363
2107
  async function buildPlan(source, options) {
1364
- const driver = getSourceDriver(options.driver ?? inferDriver(source));
1365
- const adapter = options.adapterConfig ? await loadAdapterConfig(options.adapterConfig) : getAdapter(options.adapter);
2108
+ const driver = getSourceDriver(options.driver ?? inferSourceDriverName(source));
1366
2109
  const targetRoot = normalizeTargetRoot(options.targetRoot);
2110
+ const adapter = await resolveAdapter({
2111
+ adapter: options.adapter,
2112
+ adapterConfig: options.adapterConfig,
2113
+ adapterModule: options.adapterModule,
2114
+ allowAdapterCode: options.allowAdapterCode,
2115
+ baseDir: targetRoot,
2116
+ warn: (message) => console.warn(message)
2117
+ });
1367
2118
  const bundle = await stageSource(driver, source, {
1368
2119
  workspaceRoot: targetRoot,
1369
2120
  adapter,
1370
- cacheRoot: join12(targetRoot, ".agentwheel", "cache"),
2121
+ cacheRoot: join17(targetRoot, ".agentwheel", "cache"),
1371
2122
  mode: options.mode
1372
2123
  });
1373
2124
  const manifest = await readInstallManifest(targetRoot, adapter.name);
@@ -1375,10 +2126,10 @@ async function buildPlan(source, options) {
1375
2126
  return { plan, bundle };
1376
2127
  }
1377
2128
  async function initPackage(root) {
1378
- await mkdir6(join12(root, "instructions"), { recursive: true });
1379
- await mkdir6(join12(root, "rules"), { recursive: true });
1380
- await mkdir6(join12(root, "skills"), { recursive: true });
1381
- const manifestPath = join12(root, "agentwheel.json");
2129
+ await mkdir7(join17(root, "instructions"), { recursive: true });
2130
+ await mkdir7(join17(root, "rules"), { recursive: true });
2131
+ await mkdir7(join17(root, "skills"), { recursive: true });
2132
+ const manifestPath = join17(root, "agentwheel.json");
1382
2133
  const manifest = {
1383
2134
  schemaVersion: 1,
1384
2135
  name: "example/agentwheel-package",
@@ -1389,12 +2140,15 @@ async function initPackage(root) {
1389
2140
  { type: "skills", path: "skills" }
1390
2141
  ]
1391
2142
  };
1392
- await writeFile3(manifestPath, `${JSON.stringify(manifest, null, 2)}
2143
+ await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
1393
2144
  `, "utf8");
1394
- await writeFile3(join12(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
2145
+ await writeFile4(join17(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
1395
2146
  }
1396
- function inferDriver(source) {
1397
- return source.startsWith("github:") || source.startsWith("git:") ? "git" : "local";
2147
+ function printRegistryEntries(entries) {
2148
+ for (const entry of entries) {
2149
+ const tags = entry.tags?.length ? ` [${entry.tags.join(",")}]` : "";
2150
+ console.log(`${entry.name} ${entry.type} ${entry.source} ${entry.description}${tags}`);
2151
+ }
1398
2152
  }
1399
2153
  program.parseAsync().catch((error) => {
1400
2154
  console.error(error instanceof Error ? error.message : String(error));