agentwheel 0.2.0 → 0.3.1
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 +33 -5
- package/dist/chunk-N2LZY7LO.js +111 -0
- package/dist/identify-7SEBWCNQ.js +9 -0
- package/dist/index.js +1018 -245
- 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
|
|
|
@@ -102,6 +102,34 @@ A package is a git repo (or folder) with a JSON manifest and a canonical layout:
|
|
|
102
102
|
Publish by pushing to any git host. A registry exists only for short names and discovery — it's
|
|
103
103
|
optional, and `agentwheel add <url|path>` always works without it.
|
|
104
104
|
|
|
105
|
+
## How to add a package
|
|
106
|
+
|
|
107
|
+
The public package registry lives at
|
|
108
|
+
[`NestDevLab/agentwheel-registry`](https://github.com/NestDevLab/agentwheel-registry).
|
|
109
|
+
|
|
110
|
+
1. Create a public repo with `agentwheel.json` and a standard layout such as `instructions/`, `rules/`, `skills/`, `commands/`, `mcp/`, or `hooks/`.
|
|
111
|
+
2. Open a pull request to `agentwheel-registry` that adds an entry to `index.json`.
|
|
112
|
+
3. Users install by short name:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
agentwheel registry update
|
|
116
|
+
agentwheel add your-package-name --adapter openclaw
|
|
117
|
+
agentwheel update --dry-run
|
|
118
|
+
agentwheel update
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Example registry entry:
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"name": "your-package-name",
|
|
126
|
+
"source": "github:your-org/your-agent-package",
|
|
127
|
+
"type": "package",
|
|
128
|
+
"description": "Reusable skills, rules, and instructions for agentwheel.",
|
|
129
|
+
"tags": ["skills", "rules", "instructions"]
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
105
133
|
## Customizing without getting overwritten
|
|
106
134
|
|
|
107
135
|
Drift detection blocks *accidental* edits to generated files. *Intentional* changes have four channels,
|
|
@@ -133,7 +161,7 @@ agentwheel sync ./my-pack --adapter-config ./myco-internal.jsonc
|
|
|
133
161
|
```
|
|
134
162
|
|
|
135
163
|
Built-in adapters ship for common runtimes; declarative adapters need no code and stay private.
|
|
136
|
-
|
|
164
|
+
Programmatic adapters, for private runtime logic beyond file placement, require explicit `--allow-adapter-code`.
|
|
137
165
|
|
|
138
166
|
Copilot support is intentionally file-drop only: instructions, rules, and prompt/command files are
|
|
139
167
|
placed in GitHub-native locations, while raw `SKILL.md` directories stay disabled until there is a
|
|
@@ -143,7 +171,7 @@ clear conversion format.
|
|
|
143
171
|
|
|
144
172
|
- [x] **v0.1** — install spine: local sources; openclaw/claude/codex adapters; skills/rules/instructions; `plan` · `sync` · `--dry-run` · `uninstall`; manifest + drift + idempotency.
|
|
145
173
|
- [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
|
-
- [
|
|
174
|
+
- [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
175
|
|
|
148
176
|
## Design docs
|
|
149
177
|
|
|
@@ -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
|
+
};
|