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 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.2).** The install spine and lifecycle core are real and tested — local/git
36
- > sources, plan/sync/update/drift/uninstall, overlays, eject/remember, and pluggable adapters.
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
- (Programmatic adapters, for logic beyond file placement, are planned behind an explicit opt-in.)
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
- - [ ] **v0.3** — registry & federation (skill ecosystems, MCP); profiles; programmatic adapters.
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
+ };
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ inferSourceDriverName,
4
+ isExplicitSource
5
+ } from "./chunk-N2LZY7LO.js";
6
+ export {
7
+ inferSourceDriverName,
8
+ isExplicitSource
9
+ };