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/README.md +5 -5
- package/dist/chunk-N2LZY7LO.js +111 -0
- package/dist/identify-7SEBWCNQ.js +9 -0
- package/dist/index.js +991 -237
- package/package.json +2 -2
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
|
|
5
|
-
import { join as
|
|
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/
|
|
76
|
-
import { readFile } from "fs/promises";
|
|
77
|
-
import {
|
|
78
|
-
import {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
216
|
+
import { rm as rm2 } from "fs/promises";
|
|
136
217
|
import { promisify } from "util";
|
|
137
218
|
|
|
138
|
-
// src/
|
|
139
|
-
import {
|
|
140
|
-
import {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
|
242
|
+
return incoming;
|
|
243
|
+
}
|
|
244
|
+
function isRecord(value) {
|
|
245
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
176
246
|
}
|
|
177
|
-
|
|
247
|
+
function dedupeArray(values) {
|
|
248
|
+
const seen = /* @__PURE__ */ new Set();
|
|
178
249
|
const out = [];
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
|
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
|
|
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
|
|
340
|
+
return sourceLockSchema.parse(JSON.parse(await readFile4(path, "utf8")));
|
|
291
341
|
}
|
|
292
342
|
async function removeStateFiles(targetRoot, adapter) {
|
|
293
|
-
await
|
|
294
|
-
await
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
670
|
-
import { basename, join as join6, resolve as
|
|
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 =
|
|
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
|
|
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(
|
|
1012
|
+
await mkdir2(resolve5(resolved.resolvedPath, ".."), { recursive: true });
|
|
888
1013
|
if (!await pathExists(join7(resolved.resolvedPath, ".git"))) {
|
|
889
|
-
await
|
|
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 ?
|
|
950
|
-
const
|
|
951
|
-
return join7(root,
|
|
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 = [
|
|
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
|
|
969
|
-
import { dirname as
|
|
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
|
|
974
|
-
import { dirname as
|
|
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 =
|
|
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
|
|
990
|
-
const local = await
|
|
991
|
-
const composedPath =
|
|
992
|
-
await
|
|
993
|
-
await
|
|
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 =
|
|
1020
|
-
const rulesRoot =
|
|
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 =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
1054
|
-
await
|
|
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 ??
|
|
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(
|
|
1538
|
+
const root = await mkdtemp(join12(tmpdir2(), "agentwheel-stage-"));
|
|
1083
1539
|
const stagedArtifacts = [];
|
|
1084
1540
|
for (const artifact of artifacts) {
|
|
1085
|
-
const stagedPath =
|
|
1086
|
-
await
|
|
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
|
|
1131
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
1166
|
-
import { dirname as
|
|
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 =
|
|
1169
|
-
await
|
|
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:
|
|
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 =
|
|
1194
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
1251
|
-
|
|
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:
|
|
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
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
|
1978
|
+
await rm8(bundle.root, { recursive: true, force: true });
|
|
1296
1979
|
if (plan.hasBlockingChanges) process.exitCode = 1;
|
|
1297
1980
|
});
|
|
1298
|
-
program.command("sync").argument("
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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 ${
|
|
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 ??
|
|
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:
|
|
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
|
|
1379
|
-
await
|
|
1380
|
-
await
|
|
1381
|
-
const manifestPath =
|
|
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
|
|
2143
|
+
await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
1393
2144
|
`, "utf8");
|
|
1394
|
-
await
|
|
2145
|
+
await writeFile4(join17(root, "instructions", "AGENTS.md"), "# Agent Instructions\n", "utf8");
|
|
1395
2146
|
}
|
|
1396
|
-
function
|
|
1397
|
-
|
|
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));
|