supered 0.2.1 → 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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +9 -1
- package/bin/supered.mjs +39 -0
- package/docs/install.md +16 -0
- package/docs/roadmap.md +1 -0
- package/gemini-extension.json +1 -1
- package/lib/install-doctor.js +181 -0
- package/lib/package-verification.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,6 +30,12 @@ Install with npx:
|
|
|
30
30
|
npx supered install --target codex
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
Check the install:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx supered doctor --target codex
|
|
37
|
+
```
|
|
38
|
+
|
|
33
39
|
Or install the default Codex skill set with curl:
|
|
34
40
|
|
|
35
41
|
```bash
|
|
@@ -50,6 +56,7 @@ cd Supered
|
|
|
50
56
|
npm test
|
|
51
57
|
npm run validate
|
|
52
58
|
node ./bin/supered.mjs install --target codex
|
|
59
|
+
node ./bin/supered.mjs doctor --target codex
|
|
53
60
|
```
|
|
54
61
|
|
|
55
62
|
For Claude or Gemini, replace `codex` with `claude` or `gemini`.
|
|
@@ -91,13 +98,14 @@ npm run smoke-install
|
|
|
91
98
|
npm run verify-site
|
|
92
99
|
npm run verify-package
|
|
93
100
|
node ./bin/supered.mjs skills --json
|
|
101
|
+
node ./bin/supered.mjs doctor --target codex --json
|
|
94
102
|
```
|
|
95
103
|
|
|
96
104
|
The validator checks package metadata, plugin metadata, and skill frontmatter so the public repo does not drift into a half-installable state.
|
|
97
105
|
|
|
98
106
|
`npm run verify-site` opens the landing page in Chromium at desktop and mobile sizes, checks the logo and workflow text, and writes screenshots to `artifacts/site/`.
|
|
99
107
|
|
|
100
|
-
`npm run verify-package` builds the npm tarball and verifies `npx`-style installs for every supported host target.
|
|
108
|
+
`npm run verify-package` builds the npm tarball and verifies `npx`-style installs plus Doctor checks for every supported host target.
|
|
101
109
|
|
|
102
110
|
## Design Principles
|
|
103
111
|
|
package/bin/supered.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { dirname, resolve } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
5
|
import { installSuperedSkills } from "../lib/host-install.js";
|
|
6
|
+
import { inspectSuperedInstall } from "../lib/install-doctor.js";
|
|
6
7
|
import { listSkills } from "../lib/manifest.js";
|
|
7
8
|
import { validateReleaseBundle } from "../lib/release-bundle.js";
|
|
8
9
|
|
|
@@ -16,10 +17,12 @@ Usage:
|
|
|
16
17
|
supered skills [--json]
|
|
17
18
|
supered validate
|
|
18
19
|
supered install --target <codex|claude|cursor|gemini|opencode> [--dest <path>]
|
|
20
|
+
supered doctor --target <codex|claude|cursor|gemini|opencode> [--dest <path>] [--json]
|
|
19
21
|
|
|
20
22
|
Examples:
|
|
21
23
|
npx supered install --target codex
|
|
22
24
|
npx supered install --target gemini --dest ~/.gemini/skills
|
|
25
|
+
supered doctor --target codex
|
|
23
26
|
`);
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -62,6 +65,40 @@ async function installCommand() {
|
|
|
62
65
|
console.log(`Installed Supered skills for ${result.target} at ${result.dest}.`);
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
async function doctorCommand() {
|
|
69
|
+
const targetIndex = args.indexOf("--target");
|
|
70
|
+
const destIndex = args.indexOf("--dest");
|
|
71
|
+
const target = targetIndex === -1 ? "" : args[targetIndex + 1];
|
|
72
|
+
const dest = destIndex === -1 ? undefined : args[destIndex + 1];
|
|
73
|
+
const json = args.includes("--json");
|
|
74
|
+
|
|
75
|
+
if (!target || (destIndex !== -1 && !dest)) {
|
|
76
|
+
throw new Error("Doctor requires --target <codex|claude|cursor|gemini|opencode>.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = await inspectSuperedInstall({ root, target, dest });
|
|
80
|
+
if (json) {
|
|
81
|
+
console.log(JSON.stringify(result, null, 2));
|
|
82
|
+
if (result.status !== "ok") {
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (result.status === "ok") {
|
|
89
|
+
console.log(`Supered doctor passed for ${result.target} at ${result.dest}.`);
|
|
90
|
+
console.log(`${result.installedSkills.length} skills installed and current.`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log(`Supered doctor found ${result.issues.length} issue${result.issues.length === 1 ? "" : "s"} for ${result.target} at ${result.dest}.`);
|
|
95
|
+
for (const installIssue of result.issues) {
|
|
96
|
+
console.log(`- [${installIssue.code}] ${installIssue.message}`);
|
|
97
|
+
}
|
|
98
|
+
console.log(`Fix: ${result.fixCommand}`);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
}
|
|
101
|
+
|
|
65
102
|
try {
|
|
66
103
|
if (!command || command === "help" || command === "--help") {
|
|
67
104
|
printHelp();
|
|
@@ -71,6 +108,8 @@ try {
|
|
|
71
108
|
await validateCommand();
|
|
72
109
|
} else if (command === "install") {
|
|
73
110
|
await installCommand();
|
|
111
|
+
} else if (command === "doctor") {
|
|
112
|
+
await doctorCommand();
|
|
74
113
|
} else {
|
|
75
114
|
throw new Error(`Unknown command: ${command}`);
|
|
76
115
|
}
|
package/docs/install.md
CHANGED
|
@@ -6,12 +6,14 @@ Supered ships plain skill folders plus lightweight plugin manifests.
|
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx supered install --target codex
|
|
9
|
+
npx supered doctor --target codex
|
|
9
10
|
```
|
|
10
11
|
|
|
11
12
|
Choose another host:
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
15
|
npx supered install --target opencode
|
|
16
|
+
npx supered doctor --target opencode
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
## One-line install
|
|
@@ -30,20 +32,34 @@ curl -fsSL https://raw.githubusercontent.com/fhajjej-ship-it/Supered/main/instal
|
|
|
30
32
|
|
|
31
33
|
```bash
|
|
32
34
|
node ./bin/supered.mjs install --target codex
|
|
35
|
+
node ./bin/supered.mjs doctor --target codex
|
|
33
36
|
```
|
|
34
37
|
|
|
35
38
|
## Claude
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
41
|
node ./bin/supered.mjs install --target claude
|
|
42
|
+
node ./bin/supered.mjs doctor --target claude
|
|
39
43
|
```
|
|
40
44
|
|
|
41
45
|
## Gemini
|
|
42
46
|
|
|
43
47
|
```bash
|
|
44
48
|
node ./bin/supered.mjs install --target gemini
|
|
49
|
+
node ./bin/supered.mjs doctor --target gemini
|
|
45
50
|
```
|
|
46
51
|
|
|
52
|
+
## Doctor
|
|
53
|
+
|
|
54
|
+
Doctor checks Install Health without changing files:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
supered doctor --target codex
|
|
58
|
+
supered doctor --target codex --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
It reports missing skills, changed skill files, missing destinations, and unsafe symlinks. The fix it prints is a reinstall command for the same target and destination.
|
|
62
|
+
|
|
47
63
|
## Manual
|
|
48
64
|
|
|
49
65
|
Copy each folder in `skills/` into the skill directory used by your agent host.
|
package/docs/roadmap.md
CHANGED
package/gemini-extension.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { defaultInstallDest, SKILL_ORDER } from "./supered-policy.js";
|
|
5
|
+
|
|
6
|
+
function shellQuote(value) {
|
|
7
|
+
if (!/[\s'"\\]/.test(value)) {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function fixCommand(target, dest) {
|
|
14
|
+
return `npx supered@latest install --target ${target} --dest ${shellQuote(dest)}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function issue(code, message, extra = {}) {
|
|
18
|
+
return {
|
|
19
|
+
code,
|
|
20
|
+
message,
|
|
21
|
+
...extra
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function lstatOrNull(path) {
|
|
26
|
+
try {
|
|
27
|
+
return await lstat(path);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === "ENOENT") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function findSymlink(path) {
|
|
37
|
+
const entry = await lstatOrNull(path);
|
|
38
|
+
if (!entry) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
if (entry.isSymbolicLink()) {
|
|
42
|
+
return path;
|
|
43
|
+
}
|
|
44
|
+
if (!entry.isDirectory()) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const children = await readdir(path);
|
|
49
|
+
for (const child of children) {
|
|
50
|
+
const linked = await findSymlink(join(path, child));
|
|
51
|
+
if (linked) {
|
|
52
|
+
return linked;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readInstalledSkill(dest, skill) {
|
|
59
|
+
const skillDir = join(dest, skill);
|
|
60
|
+
const linked = await findSymlink(skillDir);
|
|
61
|
+
if (linked) {
|
|
62
|
+
return {
|
|
63
|
+
linked
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const skillFile = join(skillDir, "SKILL.md");
|
|
68
|
+
const fileStat = await lstatOrNull(skillFile);
|
|
69
|
+
if (!fileStat) {
|
|
70
|
+
return {
|
|
71
|
+
missing: true
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (fileStat.isSymbolicLink()) {
|
|
75
|
+
return {
|
|
76
|
+
linked: skillFile
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
contents: await readFile(skillFile, "utf8")
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function inspectSuperedInstall({ root = process.cwd(), target, dest, home = process.env.HOME } = {}) {
|
|
86
|
+
if (!target) {
|
|
87
|
+
throw new Error("Doctor requires --target.");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const installDest = dest ?? defaultInstallDest(target, home);
|
|
91
|
+
const sourceSkillsDir = resolve(root, "skills");
|
|
92
|
+
const issues = [];
|
|
93
|
+
const missingSkills = [];
|
|
94
|
+
const changedSkills = [];
|
|
95
|
+
const installedSkills = [];
|
|
96
|
+
const command = fixCommand(target, installDest);
|
|
97
|
+
const destStat = await lstatOrNull(installDest);
|
|
98
|
+
|
|
99
|
+
if (!destStat) {
|
|
100
|
+
return {
|
|
101
|
+
target,
|
|
102
|
+
dest: installDest,
|
|
103
|
+
status: "issues",
|
|
104
|
+
issues: [
|
|
105
|
+
issue("missing-destination", `Install destination does not exist: ${installDest}`, {
|
|
106
|
+
path: installDest
|
|
107
|
+
})
|
|
108
|
+
],
|
|
109
|
+
installedSkills,
|
|
110
|
+
missingSkills: [...SKILL_ORDER],
|
|
111
|
+
changedSkills,
|
|
112
|
+
fixCommand: command
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (destStat.isSymbolicLink()) {
|
|
117
|
+
return {
|
|
118
|
+
target,
|
|
119
|
+
dest: installDest,
|
|
120
|
+
status: "issues",
|
|
121
|
+
issues: [
|
|
122
|
+
issue("symlinked-destination", `Install destination is a symlink: ${installDest}`, {
|
|
123
|
+
path: installDest
|
|
124
|
+
})
|
|
125
|
+
],
|
|
126
|
+
installedSkills,
|
|
127
|
+
missingSkills,
|
|
128
|
+
changedSkills,
|
|
129
|
+
fixCommand: command
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const skill of SKILL_ORDER) {
|
|
134
|
+
const expectedPath = join(sourceSkillsDir, skill, "SKILL.md");
|
|
135
|
+
const expected = await readFile(expectedPath, "utf8");
|
|
136
|
+
const installed = await readInstalledSkill(installDest, skill);
|
|
137
|
+
|
|
138
|
+
if (installed.linked) {
|
|
139
|
+
issues.push(
|
|
140
|
+
issue("unsafe-symlink", `Managed skill contains a symlink: ${installed.linked}`, {
|
|
141
|
+
skill,
|
|
142
|
+
path: installed.linked
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (installed.missing) {
|
|
149
|
+
missingSkills.push(skill);
|
|
150
|
+
issues.push(
|
|
151
|
+
issue("missing-skill", `Missing installed skill file: ${join(installDest, skill, "SKILL.md")}`, {
|
|
152
|
+
skill,
|
|
153
|
+
path: join(installDest, skill, "SKILL.md")
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
installedSkills.push(skill);
|
|
160
|
+
if (installed.contents !== expected) {
|
|
161
|
+
changedSkills.push(skill);
|
|
162
|
+
issues.push(
|
|
163
|
+
issue("changed-skill", `Installed skill differs from this Supered bundle: ${skill}`, {
|
|
164
|
+
skill,
|
|
165
|
+
path: join(installDest, skill, "SKILL.md")
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
target,
|
|
173
|
+
dest: installDest,
|
|
174
|
+
status: issues.length === 0 ? "ok" : "issues",
|
|
175
|
+
issues,
|
|
176
|
+
installedSkills,
|
|
177
|
+
missingSkills,
|
|
178
|
+
changedSkills,
|
|
179
|
+
fixCommand: command
|
|
180
|
+
};
|
|
181
|
+
}
|
|
@@ -90,6 +90,12 @@ export async function verifyNpmPackage({ root = process.cwd(), tempRoot } = {})
|
|
|
90
90
|
for (const skill of SKILL_ORDER) {
|
|
91
91
|
await stat(join(dest, skill, "SKILL.md"));
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
await execFileAsync(
|
|
95
|
+
"npm",
|
|
96
|
+
["exec", "--yes", "--package", tarball, "--", "supered", "doctor", "--target", target, "--dest", dest],
|
|
97
|
+
{ cwd: root, env: npmPackEnv }
|
|
98
|
+
);
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
const result = {
|
|
@@ -98,6 +104,7 @@ export async function verifyNpmPackage({ root = process.cwd(), tempRoot } = {})
|
|
|
98
104
|
tarballName: pack.filename,
|
|
99
105
|
files,
|
|
100
106
|
installedTargets: [...PACKAGE_TARGETS],
|
|
107
|
+
doctorTargets: [...PACKAGE_TARGETS],
|
|
101
108
|
installedSkills: [...SKILL_ORDER],
|
|
102
109
|
...fileInspection
|
|
103
110
|
};
|
package/package.json
CHANGED