agentwheel 0.11.0 → 0.14.3
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 +66 -33
- package/dist/{chunk-N2LZY7LO.js → chunk-B3FMBTWC.js} +8 -2
- package/dist/{identify-7SEBWCNQ.js → identify-TXIDGMNL.js} +1 -1
- package/dist/index.js +1664 -605
- package/openpack.json +2 -2
- package/package.json +2 -1
- package/skills/agentwheel/SKILL.md +39 -16
- package/skills/agentwheel-smoke/SKILL.md +63 -0
- package/skills/agentwheel-smoke/scripts/smoke.mjs +397 -0
package/openpack.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentwheel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"description": "Weave skills, rules, and instructions across every AI agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "tsup && chmod +x dist/index.js",
|
|
42
42
|
"prepack": "pnpm build",
|
|
43
|
+
"sync-site-version": "node scripts/sync-site-version.mjs",
|
|
43
44
|
"test": "vitest run",
|
|
44
45
|
"typecheck": "tsc --noEmit"
|
|
45
46
|
},
|
|
@@ -20,7 +20,7 @@ Mental model:
|
|
|
20
20
|
## Safety Rules
|
|
21
21
|
|
|
22
22
|
- Prefer `agentwheel plan ...` or `agentwheel install --dry-run` before applying changes.
|
|
23
|
-
- Do not hand-edit generated runtime files such as `.
|
|
23
|
+
- Do not hand-edit generated runtime files such as `skills/`, `.agents/skills`, `.claude/skills`, `.github/skills`, `~/.hermes/skills`, or generated instructions.
|
|
24
24
|
- If a plan reports `drift` or `conflict`, stop and explain it. Do not use `--force` unless the user explicitly approves that scope.
|
|
25
25
|
- Gmail, Drive, registry publishing, git commits, pushes, and runtime restarts are separate external side effects. Get explicit approval for them.
|
|
26
26
|
- Programmatic adapters execute local code. Use `--adapter-module` only with `--allow-adapter-code` after the user approves that local code execution.
|
|
@@ -30,7 +30,7 @@ Mental model:
|
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
32
|
agentwheel registry search tmux
|
|
33
|
-
agentwheel add github:NestDevLab/agent-mesh --skill codex-tmux --adapter codex --mode tracking
|
|
33
|
+
agentwheel add github:NestDevLab/agent-mesh --skill codex-tmux --adapter codex --installation-type local --mode tracking
|
|
34
34
|
agentwheel plan
|
|
35
35
|
agentwheel install
|
|
36
36
|
```
|
|
@@ -45,9 +45,13 @@ agentwheel install
|
|
|
45
45
|
To add and install in one step:
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
agentwheel install github:owner/repo --adapter codex
|
|
48
|
+
agentwheel install github:owner/repo --adapter codex,claude
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
Explicit source installs with explicit adapters default to user-level artifacts. Use `--local` for
|
|
52
|
+
the current directory or `-t/--target-root <project>` for project/workspace installs. Use `--user`, `--local`, or
|
|
53
|
+
`-i/--installation-type <type>` when scope should be explicit.
|
|
54
|
+
|
|
51
55
|
## Workspace Setup
|
|
52
56
|
|
|
53
57
|
Initialize an agentwheel workspace:
|
|
@@ -126,11 +130,11 @@ agentwheel add <source> --driver vercel-skills
|
|
|
126
130
|
`add` saves a package entry in `.agentwheel/config.json`; it does not install runtime files by itself.
|
|
127
131
|
|
|
128
132
|
```bash
|
|
129
|
-
agentwheel add github:owner/repo --adapter openclaw
|
|
130
|
-
agentwheel add github:owner/repo --adapter claude
|
|
131
|
-
agentwheel add github:owner/repo --adapter codex
|
|
132
|
-
agentwheel add github:owner/repo --adapter hermes
|
|
133
|
-
agentwheel add github:owner/repo --adapter copilot
|
|
133
|
+
agentwheel add github:owner/repo --adapter openclaw --installation-type local
|
|
134
|
+
agentwheel add github:owner/repo --adapter claude --installation-type local
|
|
135
|
+
agentwheel add github:owner/repo --adapter codex --installation-type local
|
|
136
|
+
agentwheel add github:owner/repo --adapter hermes --installation-type user
|
|
137
|
+
agentwheel add github:owner/repo --adapter copilot --installation-type local
|
|
134
138
|
```
|
|
135
139
|
|
|
136
140
|
Select only part of a package:
|
|
@@ -146,9 +150,9 @@ source, such as a forked skill overriding a meta-pack dependency. The declaratio
|
|
|
146
150
|
planning fails if it does not match exactly one losing artifact and one selected replacement:
|
|
147
151
|
|
|
148
152
|
```bash
|
|
149
|
-
agentwheel add github:
|
|
153
|
+
agentwheel add github:example-org/agent-toolkit#main \
|
|
150
154
|
--skill self-improve \
|
|
151
|
-
--override 'github:
|
|
155
|
+
--override 'github:example-upstream/agent-toolkit::skills/self-improve'
|
|
152
156
|
```
|
|
153
157
|
|
|
154
158
|
Equivalent config:
|
|
@@ -156,12 +160,13 @@ Equivalent config:
|
|
|
156
160
|
```json
|
|
157
161
|
{
|
|
158
162
|
"name": "agent-toolkit-nestdev",
|
|
159
|
-
"source": "github:
|
|
163
|
+
"source": "github:example-org/agent-toolkit#main",
|
|
160
164
|
"driver": "git",
|
|
161
165
|
"adapter": "codex",
|
|
166
|
+
"installationType": "local",
|
|
162
167
|
"mode": "tracking",
|
|
163
168
|
"select": ["skills/self-improve"],
|
|
164
|
-
"overrides": ["github:
|
|
169
|
+
"overrides": ["github:example-upstream/agent-toolkit::skills/self-improve"]
|
|
165
170
|
}
|
|
166
171
|
```
|
|
167
172
|
|
|
@@ -206,9 +211,14 @@ agentwheel install team-agent-pack
|
|
|
206
211
|
Add and install a source in one step:
|
|
207
212
|
|
|
208
213
|
```bash
|
|
209
|
-
agentwheel install github:owner/repo --adapter codex
|
|
214
|
+
agentwheel install github:owner/repo --adapter codex,claude
|
|
210
215
|
```
|
|
211
216
|
|
|
217
|
+
Explicit source installs with explicit adapters default to documented user-level installs. Pass
|
|
218
|
+
`--local` for the current directory or `-t/--target-root <project>` for project/workspace installs, and use `--user`, `--local`,
|
|
219
|
+
or `-i/--installation-type <type>` when scope should be explicit. If a package can be installed in
|
|
220
|
+
more than one type and no CLI/context default applies, agentwheel fails instead of guessing.
|
|
221
|
+
|
|
212
222
|
Target selection order is `--target-root`, then `--agent`, then runtime auto-detection from the current directory, then the current directory.
|
|
213
223
|
|
|
214
224
|
## Status
|
|
@@ -219,8 +229,15 @@ Use status to see configured packages, lock/manifest state, and pending install
|
|
|
219
229
|
agentwheel status
|
|
220
230
|
agentwheel status --agent lab-codex
|
|
221
231
|
agentwheel status --all
|
|
232
|
+
agentwheel status --profile daily
|
|
222
233
|
```
|
|
223
234
|
|
|
235
|
+
For profile-managed fleets, prefer `agentwheel status --profile <name>` over
|
|
236
|
+
direct agent status; profile status uses the same runtime resolution, adapter
|
|
237
|
+
config, installation type, and graph lock fingerprinting as
|
|
238
|
+
`install --profile <name>`. If a workspace defines a profile named `all`,
|
|
239
|
+
`agentwheel status --all` checks that profile.
|
|
240
|
+
|
|
224
241
|
## Named Agents And Profiles
|
|
225
242
|
|
|
226
243
|
Global config is `~/.agentwheel/config.json`. Project config is `.agentwheel/config.json`; project values win.
|
|
@@ -233,8 +250,8 @@ Current config shape:
|
|
|
233
250
|
"packages": [],
|
|
234
251
|
"registry": {},
|
|
235
252
|
"agents": {
|
|
236
|
-
"lab-codex": { "adapter": "codex", "root": "/
|
|
237
|
-
"lab-claude": { "adapter": "claude", "root": "/
|
|
253
|
+
"lab-codex": { "adapter": "codex", "installationType": "local", "root": "$HOME/project" },
|
|
254
|
+
"lab-claude": { "adapter": "claude", "installationType": "local", "root": "$HOME/project" }
|
|
238
255
|
},
|
|
239
256
|
"profiles": {
|
|
240
257
|
"daily": {
|
|
@@ -300,8 +317,14 @@ Target configured agents:
|
|
|
300
317
|
```bash
|
|
301
318
|
agentwheel update --agent lab-codex --dry-run
|
|
302
319
|
agentwheel update --all --dry-run
|
|
320
|
+
agentwheel update --profile daily --dry-run
|
|
303
321
|
```
|
|
304
322
|
|
|
323
|
+
For profile-managed fleets, use `agentwheel update --profile <name>` before
|
|
324
|
+
`install --profile <name>` when tracking packages should move forward. If a
|
|
325
|
+
workspace defines a profile named `all`, `agentwheel update --all` checks that
|
|
326
|
+
profile.
|
|
327
|
+
|
|
305
328
|
Limit an update to one configured package:
|
|
306
329
|
|
|
307
330
|
```bash
|
|
@@ -418,7 +441,7 @@ agentwheel install
|
|
|
418
441
|
If the current directory has multiple runtime markers:
|
|
419
442
|
|
|
420
443
|
```bash
|
|
421
|
-
agentwheel install --adapter codex --dry-run
|
|
444
|
+
agentwheel install --adapter codex --installation-type local --dry-run
|
|
422
445
|
```
|
|
423
446
|
|
|
424
447
|
If a selected artifact is missing:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agentwheel-smoke
|
|
3
|
+
description: Run a read-only smoke diagnostic for the current AI harness and its Agentwheel-managed installation state. Use when asked to smoke test an agent, verify fleet deployment, inspect whether a harness is installed in the expected Agentwheel path, or summarize local Agentwheel status without mutating files or external systems.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Agentwheel Smoke
|
|
7
|
+
|
|
8
|
+
Use this skill to answer: "What harness am I running in, where was this skill installed, and what does Agentwheel think about this target?"
|
|
9
|
+
|
|
10
|
+
The default workflow is read-only. Do not run `agentwheel install`, `agentwheel update`, `agentwheel uninstall`, `agentwheel remember`, `agentwheel eject`, or any file-writing fix while using this skill unless the user separately approves that mutation scope.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
From the user's current workspace, run the bundled smoke script:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
node path/to/agentwheel-smoke/scripts/smoke.mjs
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Use JSON output only when another tool or script needs to consume the result:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node path/to/agentwheel-smoke/scripts/smoke.mjs --json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
When the user names an Agentwheel target, pass only read-only status target flags:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
node path/to/agentwheel-smoke/scripts/smoke.mjs --agent lab-codex
|
|
30
|
+
node path/to/agentwheel-smoke/scripts/smoke.mjs --profile daily
|
|
31
|
+
node path/to/agentwheel-smoke/scripts/smoke.mjs --all
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Report The Result
|
|
35
|
+
|
|
36
|
+
Summarize the script output in four parts:
|
|
37
|
+
|
|
38
|
+
- Overall: `PASS`, `WARN`, or `FAIL`.
|
|
39
|
+
- Harness: detected adapter, installation type, runtime root, and skill path.
|
|
40
|
+
- Agentwheel: CLI availability, version, config locations, packages, agents, profiles, and status result.
|
|
41
|
+
- Recommended next command: prefer `agentwheel status`, `agentwheel plan`, or `agentwheel install --dry-run`.
|
|
42
|
+
|
|
43
|
+
Treat these as important signals:
|
|
44
|
+
|
|
45
|
+
- `PASS`: skill path maps to a documented Agentwheel harness target and `agentwheel status` reports no pending work.
|
|
46
|
+
- `WARN`: the skill is running from package source, status is missing, config is absent, manifest is missing, or pending work is reported.
|
|
47
|
+
- `FAIL`: Agentwheel CLI is unavailable, the skill path maps to a known wrong target, `agentwheel status` exits unsuccessfully, or drift/conflict appears.
|
|
48
|
+
|
|
49
|
+
## Compatibility Checks
|
|
50
|
+
|
|
51
|
+
Use these documented skill targets when explaining path compatibility:
|
|
52
|
+
|
|
53
|
+
- Codex local: `.agents/skills/<name>/SKILL.md`; Codex user: `~/.agents/skills/<name>/SKILL.md`.
|
|
54
|
+
- Claude local: `.claude/skills/<name>/SKILL.md`; Claude user: `~/.claude/skills/<name>/SKILL.md`.
|
|
55
|
+
- Copilot local: `.github/skills/<name>/SKILL.md`; Copilot user: `~/.copilot/skills/<name>/SKILL.md`.
|
|
56
|
+
- OpenClaw local: `skills/<name>/SKILL.md`; OpenClaw user: `~/.openclaw/skills/<name>/SKILL.md`.
|
|
57
|
+
- Hermes user: `~/.hermes/skills/<name>/SKILL.md`; local Hermes skills are unsupported by default.
|
|
58
|
+
|
|
59
|
+
Flag `.codex/skills/<name>/SKILL.md` as incompatible for Agentwheel-managed Codex skills.
|
|
60
|
+
|
|
61
|
+
## Safety
|
|
62
|
+
|
|
63
|
+
This skill is diagnostic. It may inspect files and run read-only Agentwheel commands, but it must not mutate runtime outputs, package source, Google Workspace data, Git state, registry state, or deployment state.
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
|
|
8
|
+
const skillName = "agentwheel-smoke";
|
|
9
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
10
|
+
const skillRoot = dirname(dirname(scriptPath));
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const home = os.homedir();
|
|
13
|
+
|
|
14
|
+
const parsed = parseArgs(process.argv.slice(2));
|
|
15
|
+
if (parsed.error) {
|
|
16
|
+
console.error(parsed.error);
|
|
17
|
+
process.exit(2);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const config = findConfigs(cwd, home);
|
|
21
|
+
const harness = detectHarness(skillRoot, cwd, home);
|
|
22
|
+
const envHint = detectHarnessFromEnv();
|
|
23
|
+
const agentwheel = inspectAgentwheel(parsed.statusArgs, envHint);
|
|
24
|
+
const manifests = findStateFiles(harness.runtimeRoot ?? projectRootFromConfig(config.projectConfig) ?? cwd);
|
|
25
|
+
const assessment = assess({ harness, agentwheel, manifests, config });
|
|
26
|
+
const report = {
|
|
27
|
+
overall: assessment.overall,
|
|
28
|
+
checks: assessment.checks,
|
|
29
|
+
harness,
|
|
30
|
+
agentwheel,
|
|
31
|
+
config,
|
|
32
|
+
manifests,
|
|
33
|
+
recommendedNextCommand: recommendedNextCommand(assessment, agentwheel.status.args),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (parsed.json) {
|
|
37
|
+
console.log(JSON.stringify(report, null, 2));
|
|
38
|
+
} else {
|
|
39
|
+
console.log(formatMarkdown(report));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.exit(report.overall === "FAIL" ? 1 : 0);
|
|
43
|
+
|
|
44
|
+
function parseArgs(args) {
|
|
45
|
+
const statusArgs = [];
|
|
46
|
+
let json = false;
|
|
47
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
48
|
+
const arg = args[index];
|
|
49
|
+
if (arg === "--json") {
|
|
50
|
+
json = true;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (["--all", "--all-detected"].includes(arg)) {
|
|
54
|
+
statusArgs.push(arg);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (["--agent", "--profile", "--target-root", "--adapter", "--installation-type"].includes(arg)) {
|
|
58
|
+
const value = args[index + 1];
|
|
59
|
+
if (!value || value.startsWith("--")) return { error: `${arg} requires a value.` };
|
|
60
|
+
statusArgs.push(arg, value);
|
|
61
|
+
index += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
return { error: `Unsupported smoke option: ${arg}` };
|
|
65
|
+
}
|
|
66
|
+
return { json, statusArgs };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function inspectAgentwheel(statusArgs, hint) {
|
|
70
|
+
const which = run("command", ["-v", "agentwheel"], { shell: true });
|
|
71
|
+
const version = which.ok ? run("agentwheel", ["--version"]) : { ok: false, code: null, stdout: "", stderr: "agentwheel not found" };
|
|
72
|
+
let effectiveStatusArgs = statusArgs;
|
|
73
|
+
let status = which.ok ? run("agentwheel", ["status", ...effectiveStatusArgs]) : { ok: false, code: null, stdout: "", stderr: "agentwheel not found" };
|
|
74
|
+
if (!status.ok && statusArgs.length === 0 && /Multiple runtime directories detected/i.test(status.stderr) && hint?.adapter) {
|
|
75
|
+
effectiveStatusArgs = ["--adapter", hint.adapter, "--installation-type", hint.installationType ?? "local"];
|
|
76
|
+
status = run("agentwheel", ["status", ...effectiveStatusArgs]);
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
cliAvailable: which.ok,
|
|
80
|
+
cliPath: firstLine(which.stdout),
|
|
81
|
+
version: firstLine(version.stdout),
|
|
82
|
+
status: {
|
|
83
|
+
args: effectiveStatusArgs,
|
|
84
|
+
ok: status.ok,
|
|
85
|
+
code: status.code,
|
|
86
|
+
stdout: status.stdout.trim(),
|
|
87
|
+
stderr: status.stderr.trim(),
|
|
88
|
+
summary: summarizeStatus(status.stdout, status.stderr),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function run(command, args, options = {}) {
|
|
94
|
+
try {
|
|
95
|
+
const stdout = execFileSync(command, args, {
|
|
96
|
+
cwd,
|
|
97
|
+
env: { ...process.env, AGENTWHEEL_NO_UPDATE_CHECK: "1" },
|
|
98
|
+
encoding: "utf8",
|
|
99
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
100
|
+
shell: options.shell ?? false,
|
|
101
|
+
});
|
|
102
|
+
return { ok: true, code: 0, stdout, stderr: "" };
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
code: typeof error.status === "number" ? error.status : null,
|
|
107
|
+
stdout: String(error.stdout ?? ""),
|
|
108
|
+
stderr: String(error.stderr ?? error.message ?? ""),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function findConfigs(start, homeDir) {
|
|
114
|
+
const globalConfig = join(homeDir, ".agentwheel", "config.json");
|
|
115
|
+
const foundConfig = findUp(start, ".agentwheel/config.json");
|
|
116
|
+
const projectConfig = foundConfig && resolve(foundConfig) !== resolve(globalConfig) ? foundConfig : null;
|
|
117
|
+
return {
|
|
118
|
+
projectConfig,
|
|
119
|
+
globalConfig: existsSync(globalConfig) ? globalConfig : null,
|
|
120
|
+
project: projectConfig ? summarizeConfig(projectConfig) : null,
|
|
121
|
+
global: existsSync(globalConfig) ? summarizeConfig(globalConfig) : null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function detectHarnessFromEnv() {
|
|
126
|
+
if (process.env.CODEX_THREAD_ID || process.env.CODEX_CI) return { adapter: "codex", installationType: "local" };
|
|
127
|
+
if (process.env.CLAUDECODE || process.env.CLAUDE_CODE) return { adapter: "claude", installationType: "local" };
|
|
128
|
+
if (process.env.GITHUB_COPILOT_CLI || process.env.COPILOT_AGENT) return { adapter: "copilot", installationType: "local" };
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function summarizeConfig(path) {
|
|
133
|
+
try {
|
|
134
|
+
const json = JSON.parse(readFileSync(path, "utf8"));
|
|
135
|
+
return {
|
|
136
|
+
packages: Array.isArray(json.packages) ? json.packages.map((entry) => entry.name ?? entry.source ?? "unnamed") : [],
|
|
137
|
+
agents: json.agents && typeof json.agents === "object" ? Object.keys(json.agents) : [],
|
|
138
|
+
profiles: json.profiles && typeof json.profiles === "object" ? Object.keys(json.profiles) : [],
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
return { error: error.message };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function detectHarness(path, currentDir, homeDir) {
|
|
146
|
+
const normalized = resolve(path);
|
|
147
|
+
const homeRelative = relative(homeDir, normalized);
|
|
148
|
+
const localPatterns = [
|
|
149
|
+
{ marker: [".agents", "skills", skillName], adapter: "codex", installationType: "local", expected: ".agents/skills/<name>" },
|
|
150
|
+
{ marker: [".claude", "skills", skillName], adapter: "claude", installationType: "local", expected: ".claude/skills/<name>" },
|
|
151
|
+
{ marker: [".github", "skills", skillName], adapter: "copilot", installationType: "local", expected: ".github/skills/<name>" },
|
|
152
|
+
{ marker: ["skills", skillName], adapter: "openclaw", installationType: "local", expected: "skills/<name>" },
|
|
153
|
+
];
|
|
154
|
+
const userPatterns = [
|
|
155
|
+
{ root: join(homeDir, ".agents", "skills", skillName), adapter: "codex", installationType: "user", expected: "~/.agents/skills/<name>" },
|
|
156
|
+
{ root: join(homeDir, ".claude", "skills", skillName), adapter: "claude", installationType: "user", expected: "~/.claude/skills/<name>" },
|
|
157
|
+
{ root: join(homeDir, ".copilot", "skills", skillName), adapter: "copilot", installationType: "user", expected: "~/.copilot/skills/<name>" },
|
|
158
|
+
{ root: join(homeDir, ".openclaw", "skills", skillName), adapter: "openclaw", installationType: "user", expected: "~/.openclaw/skills/<name>" },
|
|
159
|
+
{ root: join(homeDir, ".hermes", "skills", skillName), adapter: "hermes", installationType: "user", expected: "~/.hermes/skills/<name>" },
|
|
160
|
+
];
|
|
161
|
+
const wrongCodex = containsSegments(normalized, [".codex", "skills", skillName]);
|
|
162
|
+
if (wrongCodex) {
|
|
163
|
+
return {
|
|
164
|
+
adapter: "codex",
|
|
165
|
+
installationType: "unknown",
|
|
166
|
+
confidence: "high",
|
|
167
|
+
runtimeRoot: null,
|
|
168
|
+
skillPath: normalized,
|
|
169
|
+
expectedSkillPath: ".agents/skills/<name> or ~/.agents/skills/<name>",
|
|
170
|
+
compatibility: "FAIL",
|
|
171
|
+
note: ".codex/skills is not an Agentwheel-managed Codex skill target.",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
for (const pattern of userPatterns) {
|
|
175
|
+
if (normalized === pattern.root || normalized.startsWith(`${pattern.root}${sep}`)) {
|
|
176
|
+
return {
|
|
177
|
+
adapter: pattern.adapter,
|
|
178
|
+
installationType: pattern.installationType,
|
|
179
|
+
confidence: "high",
|
|
180
|
+
runtimeRoot: homeDir,
|
|
181
|
+
skillPath: normalized,
|
|
182
|
+
expectedSkillPath: pattern.expected,
|
|
183
|
+
compatibility: "PASS",
|
|
184
|
+
note: `Skill path is under ${pattern.expected}.`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const pattern of localPatterns) {
|
|
189
|
+
const root = rootBeforeMarker(normalized, pattern.marker);
|
|
190
|
+
if (!root) continue;
|
|
191
|
+
const packageSource = pattern.adapter === "openclaw" && isAgentwheelSourcePackage(root);
|
|
192
|
+
return {
|
|
193
|
+
adapter: packageSource ? "unknown" : pattern.adapter,
|
|
194
|
+
installationType: packageSource ? "source" : pattern.installationType,
|
|
195
|
+
confidence: packageSource ? "medium" : "high",
|
|
196
|
+
runtimeRoot: root,
|
|
197
|
+
skillPath: normalized,
|
|
198
|
+
expectedSkillPath: pattern.expected,
|
|
199
|
+
compatibility: packageSource ? "WARN" : "PASS",
|
|
200
|
+
note: packageSource
|
|
201
|
+
? "Skill is running from the Agentwheel source package; this is useful for authoring but does not prove fleet deployment."
|
|
202
|
+
: `Skill path is under ${pattern.expected}.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
adapter: "unknown",
|
|
207
|
+
installationType: "unknown",
|
|
208
|
+
confidence: "low",
|
|
209
|
+
runtimeRoot: findUp(currentDir, ".agentwheel") ? dirname(findUp(currentDir, ".agentwheel")) : null,
|
|
210
|
+
skillPath: normalized,
|
|
211
|
+
expectedSkillPath: null,
|
|
212
|
+
compatibility: "WARN",
|
|
213
|
+
note: `Could not map ${homeRelative.startsWith("..") ? normalized : `~/${homeRelative}`} to a documented Agentwheel skill target.`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isAgentwheelSourcePackage(root) {
|
|
218
|
+
const openpackPath = join(root, "openpack.json");
|
|
219
|
+
if (!existsSync(openpackPath)) return false;
|
|
220
|
+
try {
|
|
221
|
+
const json = JSON.parse(readFileSync(openpackPath, "utf8"));
|
|
222
|
+
return json.name === "NestDevLab/agentwheel";
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function findStateFiles(root) {
|
|
229
|
+
const stateDir = join(root, ".agentwheel");
|
|
230
|
+
if (!existsSync(stateDir)) return { root, installManifests: [], sourceLocks: [] };
|
|
231
|
+
const files = readdirSync(stateDir).sort();
|
|
232
|
+
return {
|
|
233
|
+
root,
|
|
234
|
+
installManifests: files.filter((file) => file.endsWith(".install-manifest.json")),
|
|
235
|
+
sourceLocks: files.filter((file) => file.endsWith(".source-lock.json")),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function assess({ harness, agentwheel, manifests, config }) {
|
|
240
|
+
const checks = [];
|
|
241
|
+
pushCheck(checks, harness.compatibility, "harness-path", harness.note);
|
|
242
|
+
pushCheck(checks, agentwheel.cliAvailable ? "PASS" : "FAIL", "agentwheel-cli", agentwheel.cliAvailable ? `Found ${agentwheel.cliPath}` : "agentwheel is not available on PATH.");
|
|
243
|
+
pushCheck(checks, agentwheel.status.ok ? "PASS" : "FAIL", "agentwheel-status", agentwheel.status.ok ? "agentwheel status completed." : statusFailure(agentwheel.status));
|
|
244
|
+
pushCheck(checks, config.projectConfig || config.globalConfig ? "PASS" : "WARN", "agentwheel-config", config.projectConfig || config.globalConfig ? "Agentwheel config found." : "No project or global Agentwheel config found.");
|
|
245
|
+
const statusHasManifest = /Install manifest:\s*(?!missing\b).+/i.test(agentwheel.status.stdout);
|
|
246
|
+
pushCheck(
|
|
247
|
+
checks,
|
|
248
|
+
manifests.installManifests.length > 0 || statusHasManifest ? "PASS" : "WARN",
|
|
249
|
+
"install-manifest",
|
|
250
|
+
manifests.installManifests.length > 0
|
|
251
|
+
? `${manifests.installManifests.length} install manifest file(s) found.`
|
|
252
|
+
: statusHasManifest
|
|
253
|
+
? "agentwheel status reports an install manifest."
|
|
254
|
+
: "No install manifest files found under the target .agentwheel directory.",
|
|
255
|
+
);
|
|
256
|
+
if (statusHasBlockingDriftOrConflict(agentwheel.status.stdout + agentwheel.status.stderr)) {
|
|
257
|
+
pushCheck(checks, "FAIL", "drift-conflict", "agentwheel status reports drift or conflict.");
|
|
258
|
+
}
|
|
259
|
+
if (statusHasPendingInstallWork(agentwheel.status.stdout)) {
|
|
260
|
+
pushCheck(checks, "WARN", "pending-work", "agentwheel status reports pending install work.");
|
|
261
|
+
}
|
|
262
|
+
if (/Install manifest:\s*missing/i.test(agentwheel.status.stdout)) {
|
|
263
|
+
pushCheck(checks, "WARN", "manifest-missing", "agentwheel status reports a missing install manifest.");
|
|
264
|
+
}
|
|
265
|
+
const overall = checks.some((check) => check.status === "FAIL") ? "FAIL" : checks.some((check) => check.status === "WARN") ? "WARN" : "PASS";
|
|
266
|
+
return { overall, checks };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function projectRootFromConfig(configPath) {
|
|
270
|
+
return configPath ? dirname(dirname(configPath)) : null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function statusHasBlockingDriftOrConflict(text) {
|
|
274
|
+
return /^Pending install work:.*(?:\bdrift=[1-9]\d*|\bconflict=[1-9]\d*|\bblocking=[1-9]\d*)/im.test(text)
|
|
275
|
+
|| /^\s*(?:DRIFT|CONFLICT)\s+/im.test(text);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function statusHasPendingInstallWork(text) {
|
|
279
|
+
const line = String(text ?? "").split(/\r?\n/).find((entry) => /^Pending install work:/i.test(entry));
|
|
280
|
+
if (!line) return false;
|
|
281
|
+
const value = line.replace(/^Pending install work:/i, "").trim();
|
|
282
|
+
return value.length > 0 && !/^none$/i.test(value);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function recommendedNextCommand(assessment, statusArgs) {
|
|
286
|
+
const target = statusArgs.length > 0 ? ` ${statusArgs.join(" ")}` : "";
|
|
287
|
+
if (assessment.checks.some((check) => check.id === "agentwheel-cli" && check.status === "FAIL")) return "Install or expose the agentwheel CLI, then rerun the smoke test.";
|
|
288
|
+
if (assessment.checks.some((check) => check.id === "drift-conflict")) return `agentwheel status${target}`;
|
|
289
|
+
if (assessment.checks.some((check) => check.id === "pending-work" || check.id === "manifest-missing")) return `agentwheel plan${target}`;
|
|
290
|
+
return `agentwheel status${target}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function formatMarkdown(report) {
|
|
294
|
+
const lines = [
|
|
295
|
+
"# Agentwheel Smoke Report",
|
|
296
|
+
"",
|
|
297
|
+
`Overall: ${report.overall}`,
|
|
298
|
+
"",
|
|
299
|
+
"## Harness",
|
|
300
|
+
`- Detected adapter: ${report.harness.adapter}`,
|
|
301
|
+
`- Installation type: ${report.harness.installationType}`,
|
|
302
|
+
`- Confidence: ${report.harness.confidence}`,
|
|
303
|
+
`- Runtime root: ${report.harness.runtimeRoot ?? "unknown"}`,
|
|
304
|
+
`- Skill path: ${report.harness.skillPath}`,
|
|
305
|
+
`- Expected skill path: ${report.harness.expectedSkillPath ?? "unknown"}`,
|
|
306
|
+
`- Compatibility: ${report.harness.compatibility}`,
|
|
307
|
+
`- Note: ${report.harness.note}`,
|
|
308
|
+
"",
|
|
309
|
+
"## Agentwheel",
|
|
310
|
+
`- CLI available: ${yesNo(report.agentwheel.cliAvailable)}`,
|
|
311
|
+
`- CLI path: ${report.agentwheel.cliPath || "not found"}`,
|
|
312
|
+
`- Version: ${report.agentwheel.version || "unknown"}`,
|
|
313
|
+
`- Status command: ${report.agentwheel.status.ok ? "PASS" : "FAIL"}`,
|
|
314
|
+
`- Status args: ${report.agentwheel.status.args.length > 0 ? report.agentwheel.status.args.join(" ") : "none"}`,
|
|
315
|
+
`- Status summary: ${report.agentwheel.status.summary}`,
|
|
316
|
+
"",
|
|
317
|
+
"## Config",
|
|
318
|
+
`- Project config: ${report.config.projectConfig ?? "not found"}`,
|
|
319
|
+
`- Global config: ${report.config.globalConfig ?? "not found"}`,
|
|
320
|
+
`- Project packages: ${listOrNone(report.config.project?.packages)}`,
|
|
321
|
+
`- Project agents: ${listOrNone(report.config.project?.agents)}`,
|
|
322
|
+
`- Project profiles: ${listOrNone(report.config.project?.profiles)}`,
|
|
323
|
+
"",
|
|
324
|
+
"## Managed State",
|
|
325
|
+
`- State root checked: ${report.manifests.root}`,
|
|
326
|
+
`- Install manifests: ${listOrNone(report.manifests.installManifests)}`,
|
|
327
|
+
`- Source locks: ${listOrNone(report.manifests.sourceLocks)}`,
|
|
328
|
+
"",
|
|
329
|
+
"## Checks",
|
|
330
|
+
...report.checks.map((check) => `- ${check.status} ${check.id}: ${check.message}`),
|
|
331
|
+
"",
|
|
332
|
+
"## Recommended Next Command",
|
|
333
|
+
`- ${report.recommendedNextCommand}`,
|
|
334
|
+
];
|
|
335
|
+
if (report.agentwheel.status.stdout) {
|
|
336
|
+
lines.push("", "## agentwheel status stdout", "```text", report.agentwheel.status.stdout, "```");
|
|
337
|
+
}
|
|
338
|
+
if (report.agentwheel.status.stderr) {
|
|
339
|
+
lines.push("", "## agentwheel status stderr", "```text", report.agentwheel.status.stderr, "```");
|
|
340
|
+
}
|
|
341
|
+
return lines.join("\n");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function summarizeStatus(stdout, stderr) {
|
|
345
|
+
const text = `${stdout}\n${stderr}`.trim();
|
|
346
|
+
if (!text) return "no status output";
|
|
347
|
+
const interesting = text.split(/\r?\n/).filter((line) =>
|
|
348
|
+
/Status for|Install manifest|Graph lock|Pending install work|No agents configured|No packages configured|drift|conflict/i.test(line),
|
|
349
|
+
);
|
|
350
|
+
return interesting.length > 0 ? interesting.join(" | ") : firstLine(text);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function statusFailure(status) {
|
|
354
|
+
return status.stderr || status.stdout || "agentwheel status failed.";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function pushCheck(checks, status, id, message) {
|
|
358
|
+
checks.push({ status, id, message });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function firstLine(value) {
|
|
362
|
+
return String(value ?? "").trim().split(/\r?\n/)[0] ?? "";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function yesNo(value) {
|
|
366
|
+
return value ? "yes" : "no";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function listOrNone(values) {
|
|
370
|
+
return values && values.length > 0 ? values.join(", ") : "none";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function findUp(start, relativePath) {
|
|
374
|
+
let cursor = resolve(start);
|
|
375
|
+
while (true) {
|
|
376
|
+
const candidate = join(cursor, relativePath);
|
|
377
|
+
if (existsSync(candidate)) return candidate;
|
|
378
|
+
const next = dirname(cursor);
|
|
379
|
+
if (next === cursor) return null;
|
|
380
|
+
cursor = next;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function rootBeforeMarker(path, marker) {
|
|
385
|
+
const parts = resolve(path).split(sep).filter(Boolean);
|
|
386
|
+
for (let index = 0; index <= parts.length - marker.length; index += 1) {
|
|
387
|
+
if (marker.every((part, offset) => parts[index + offset] === part)) {
|
|
388
|
+
const rootParts = parts.slice(0, index);
|
|
389
|
+
return `${sep}${rootParts.join(sep)}`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function containsSegments(path, marker) {
|
|
396
|
+
return Boolean(rootBeforeMarker(path, marker));
|
|
397
|
+
}
|