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/README.md
CHANGED
|
@@ -32,9 +32,9 @@ No lock-in. No central gatekeeper. Your packages live in plain git repos, your c
|
|
|
32
32
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
|
-
> **Status: early (v0.
|
|
36
|
-
> sources, plan/sync/update/drift/uninstall, overlays, eject/remember,
|
|
37
|
-
> Expect sharp edges.
|
|
35
|
+
> **Status: early (v0.3).** The lifecycle core is real and tested — local/git/skillkit/vercel
|
|
36
|
+
> sources, optional registry discovery, plan/sync/update/drift/uninstall, overlays, eject/remember,
|
|
37
|
+
> profiles, rich JSON merge, and pluggable adapters. Expect sharp edges.
|
|
38
38
|
|
|
39
39
|
## What it does
|
|
40
40
|
|
|
@@ -133,7 +133,7 @@ agentwheel sync ./my-pack --adapter-config ./myco-internal.jsonc
|
|
|
133
133
|
```
|
|
134
134
|
|
|
135
135
|
Built-in adapters ship for common runtimes; declarative adapters need no code and stay private.
|
|
136
|
-
|
|
136
|
+
Programmatic adapters, for private runtime logic beyond file placement, require explicit `--allow-adapter-code`.
|
|
137
137
|
|
|
138
138
|
Copilot support is intentionally file-drop only: instructions, rules, and prompt/command files are
|
|
139
139
|
placed in GitHub-native locations, while raw `SKILL.md` directories stay disabled until there is a
|
|
@@ -143,7 +143,7 @@ clear conversion format.
|
|
|
143
143
|
|
|
144
144
|
- [x] **v0.1** — install spine: local sources; openclaw/claude/codex adapters; skills/rules/instructions; `plan` · `sync` · `--dry-run` · `uninstall`; manifest + drift + idempotency.
|
|
145
145
|
- [x] **v0.2** — git source driver; `update` (pinned & tracking); overlays/additive/override/eject; `init`; hermes + copilot adapters; commands/mcp/hooks artifacts; OpenClaw semantic plugin planning.
|
|
146
|
-
- [
|
|
146
|
+
- [x] **v0.3** — skillkit/vercel source drivers; optional registry & federation; programmatic adapters behind `--allow-adapter-code`; rich JSON merge for mcp/hooks/settings; profiles.
|
|
147
147
|
|
|
148
148
|
## Design docs
|
|
149
149
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/source/identify.ts
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
import { resolve } from "path";
|
|
6
|
+
|
|
7
|
+
// src/utils/fs.ts
|
|
8
|
+
import { createHash } from "crypto";
|
|
9
|
+
import {
|
|
10
|
+
copyFile,
|
|
11
|
+
cp,
|
|
12
|
+
mkdir,
|
|
13
|
+
readdir,
|
|
14
|
+
readFile,
|
|
15
|
+
rename,
|
|
16
|
+
rm,
|
|
17
|
+
stat,
|
|
18
|
+
writeFile
|
|
19
|
+
} from "fs/promises";
|
|
20
|
+
import { dirname, join, relative } from "path";
|
|
21
|
+
async function pathExists(path) {
|
|
22
|
+
try {
|
|
23
|
+
await stat(path);
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async function hashPath(path) {
|
|
30
|
+
const stats = await stat(path);
|
|
31
|
+
if (stats.isFile()) {
|
|
32
|
+
const content = await readFile(path);
|
|
33
|
+
return createHash("sha256").update("file\0").update(content).digest("hex");
|
|
34
|
+
}
|
|
35
|
+
if (!stats.isDirectory()) {
|
|
36
|
+
throw new Error(`Unsupported path kind: ${path}`);
|
|
37
|
+
}
|
|
38
|
+
const hash = createHash("sha256").update("dir\0");
|
|
39
|
+
const files = await listFiles(path);
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
hash.update(relative(path, file).replaceAll("\\", "/")).update("\0");
|
|
42
|
+
hash.update(await hashPath(file)).update("\0");
|
|
43
|
+
}
|
|
44
|
+
return hash.digest("hex");
|
|
45
|
+
}
|
|
46
|
+
async function listFiles(root) {
|
|
47
|
+
const out = [];
|
|
48
|
+
async function walk(dir) {
|
|
49
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
50
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
51
|
+
if (entry.name === ".git" || entry.name === "node_modules") continue;
|
|
52
|
+
const full = join(dir, entry.name);
|
|
53
|
+
if (entry.isDirectory()) {
|
|
54
|
+
await walk(full);
|
|
55
|
+
} else if (entry.isFile()) {
|
|
56
|
+
out.push(full);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
await walk(root);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
async function atomicCopy(source, dest, kind) {
|
|
64
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
65
|
+
const temp = `${dest}.agentwheel-tmp-${process.pid}-${Date.now()}`;
|
|
66
|
+
await rm(temp, { recursive: true, force: true });
|
|
67
|
+
if (kind === "file") {
|
|
68
|
+
await copyFile(source, temp);
|
|
69
|
+
} else {
|
|
70
|
+
await cp(source, temp, { recursive: true, dereference: true });
|
|
71
|
+
}
|
|
72
|
+
await rm(dest, { recursive: true, force: true });
|
|
73
|
+
await rename(temp, dest);
|
|
74
|
+
}
|
|
75
|
+
async function writeJsonAtomic(path, data) {
|
|
76
|
+
await mkdir(dirname(path), { recursive: true });
|
|
77
|
+
const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
78
|
+
await writeFile(temp, `${JSON.stringify(data, null, 2)}
|
|
79
|
+
`, "utf8");
|
|
80
|
+
await rename(temp, path);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/source/identify.ts
|
|
84
|
+
function inferSourceDriverName(source) {
|
|
85
|
+
if (source.startsWith("skillkit:")) return "skillkit";
|
|
86
|
+
if (source.startsWith("vercel:")) return "vercel-skills";
|
|
87
|
+
return source.startsWith("github:") || source.startsWith("git:") ? "git" : "local";
|
|
88
|
+
}
|
|
89
|
+
async function isExplicitSource(source) {
|
|
90
|
+
if (source.startsWith("github:") || source.startsWith("git:") || source.startsWith("skillkit:") || source.startsWith("vercel:")) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (source.startsWith("./") || source.startsWith("../") || source.startsWith("/") || source.startsWith("~/")) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return pathExists(resolveLocalPath(source));
|
|
97
|
+
}
|
|
98
|
+
function resolveLocalPath(source) {
|
|
99
|
+
if (source === "~") return homedir();
|
|
100
|
+
if (source.startsWith("~/")) return resolve(homedir(), source.slice(2));
|
|
101
|
+
return resolve(source);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export {
|
|
105
|
+
pathExists,
|
|
106
|
+
hashPath,
|
|
107
|
+
atomicCopy,
|
|
108
|
+
writeJsonAtomic,
|
|
109
|
+
inferSourceDriverName,
|
|
110
|
+
isExplicitSource
|
|
111
|
+
};
|