buddy-agent-harness 0.1.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 +12 -0
- package/.codex-plugin/plugin.json +16 -0
- package/.cursor-plugin/plugin.json +14 -0
- package/.plugin/plugin.json +30 -0
- package/README.md +37 -0
- package/bin/buddy-agent-harness.mjs +7 -0
- package/dist/cli.d.mts +5 -0
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +20 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +57 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/init.command-wxC4cgUc.mjs +203 -0
- package/dist/init.command-wxC4cgUc.mjs.map +1 -0
- package/package.json +69 -0
- package/plugin.json +14 -0
- package/skills/init/README.md +29 -0
- package/skills/init/SKILL.md +75 -0
- package/skills/init/agents/openai.yaml +4 -0
- package/skills/init/references/agents-md.md +131 -0
- package/skills/init/references/detection.md +34 -0
- package/skills/init/references/frontmatter.md +63 -0
- package/skills/init/references/harnesses/claude-code.md +51 -0
- package/skills/init/references/harnesses/codex.md +13 -0
- package/skills/init/references/harnesses/copilot-cli.md +16 -0
- package/skills/init/references/harnesses/cursor.md +23 -0
- package/skills/init/references/harnesses/devin-desktop.md +19 -0
- package/skills/init/references/harnesses/gemini-cli.md +33 -0
- package/skills/init/references/standard.md +26 -0
- package/src/cli.ts +16 -0
- package/src/harness-registry/harness-registry.ts +60 -0
- package/src/index.ts +5 -0
- package/src/initialize-harnesses/init.command.ts +76 -0
- package/src/initialize-harnesses/initialize-harnesses.ts +44 -0
- package/src/skill-projection/skill-projection.ts +78 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buddy-agent-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Initialize standards-based agent configuration across coding-agent harnesses",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "repobuddy"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/repobuddy/buddy-agent-harness",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["agent", "skills", "harness", "claude-code", "codex"],
|
|
11
|
+
"skills": "./skills/"
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buddy-agent-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Initialize standards-based agent configuration across coding-agent harnesses",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "repobuddy"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/repobuddy/buddy-agent-harness",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["agent", "skills", "harness", "claude-code", "codex"],
|
|
11
|
+
"skills": "./skills/",
|
|
12
|
+
"interface": {
|
|
13
|
+
"displayName": "Buddy Agent Harness",
|
|
14
|
+
"category": "productivity"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buddy-agent-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Initialize standards-based agent configuration across coding-agent harnesses",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "repobuddy"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/repobuddy/buddy-agent-harness",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["agent", "skills", "harness", "claude-code", "codex"],
|
|
11
|
+
"skills": "./skills/",
|
|
12
|
+
"category": "productivity",
|
|
13
|
+
"tags": ["agent", "skills"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/cyberuni/universal-plugin/refs/heads/main/schema/v1.json",
|
|
3
|
+
"name": "buddy-agent-harness",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Initialize standards-based agent configuration across coding-agent harnesses",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "repobuddy"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/repobuddy/buddy-agent-harness",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["agent", "skills", "harness", "claude-code", "codex"],
|
|
12
|
+
"skills": "./skills/",
|
|
13
|
+
"vendorExtensions": {
|
|
14
|
+
"claude-code": {},
|
|
15
|
+
"cursor": {
|
|
16
|
+
"category": "productivity",
|
|
17
|
+
"tags": ["agent", "skills"]
|
|
18
|
+
},
|
|
19
|
+
"codex": {
|
|
20
|
+
"interface": {
|
|
21
|
+
"displayName": "Buddy Agent Harness",
|
|
22
|
+
"category": "productivity"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"copilot-cli": {
|
|
26
|
+
"category": "productivity",
|
|
27
|
+
"tags": ["agent", "skills"]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Buddy Agent Harness
|
|
2
|
+
|
|
3
|
+
[](https://github.com/repobuddy/buddy-agent-harness/actions/workflows/release.yml)
|
|
4
|
+
[](https://codecov.io/gh/repobuddy/buddy-agent-harness)
|
|
5
|
+
[](https://www.npmjs.com/package/buddy-agent-harness)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
An agent plugin and CLI for initializing or updating a consumer repository's standards-based agent configuration across coding-agent harnesses.
|
|
9
|
+
|
|
10
|
+
## Install the plugin
|
|
11
|
+
|
|
12
|
+
Install the portable plugin and its `init` skill with `npx skills`:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx skills add repobuddy/buddy-agent-harness --plugin
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
In Claude Code, add the marketplace and install the plugin:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
/plugin marketplace add repobuddy/buddy-agent-harness
|
|
22
|
+
/plugin install buddy-agent-harness@repobuddy
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Other agent clients can install the same repository through their plugin marketplace or use the `npx skills` command above.
|
|
26
|
+
|
|
27
|
+
## CLI
|
|
28
|
+
|
|
29
|
+
After the npm package is published, run the CLI without a global installation:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npx -y buddy-agent-harness init
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The repository root's `AGENTS.md` and `.agents/` tree are canonical: `.agents/AGENTS.md` holds shared behavior, `.agents/skills/**/SKILL.md` holds capabilities, and separately named files hold tool settings. The active harness is enabled by default; explicit user preferences add others. The command preserves user-authored configuration and projects only supported mappings; it records nothing on disk.
|
|
36
|
+
|
|
37
|
+
Use `buddy-agent-harness init --help` to see `--root`, `--copy`, `--force`, and `--format`.
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";iBAGsB,QAAQ"}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { r as initCommand } from "./init.command-wxC4cgUc.mjs";
|
|
2
|
+
import { cli } from "clibuilder";
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
async function main() {
|
|
5
|
+
const app = cli({
|
|
6
|
+
name: "buddy-agent-harness",
|
|
7
|
+
version: "0.1.0",
|
|
8
|
+
description: "Initialize agent harness skill compatibility in consumer repositories."
|
|
9
|
+
});
|
|
10
|
+
try {
|
|
11
|
+
await app.command(initCommand).parse(process.argv);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
process.stdout.write(`error: ${error instanceof Error ? error.message : "Invalid command."}\n`);
|
|
14
|
+
process.exitCode = 2;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { main };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["import { cli } from 'clibuilder'\nimport { initCommand } from './initialize-harnesses/init.command.ts'\n\nexport async function main(): Promise<void> {\n\tconst app = cli({\n\t\tname: 'buddy-agent-harness',\n\t\tversion: '0.1.0',\n\t\tdescription: 'Initialize agent harness skill compatibility in consumer repositories.',\n\t})\n\ttry {\n\t\tawait app.command(initCommand).parse(process.argv)\n\t} catch (error) {\n\t\tprocess.stdout.write(`error: ${error instanceof Error ? error.message : 'Invalid command.'}\\n`)\n\t\tprocess.exitCode = 2\n\t}\n}\n"],"mappings":";;;AAGA,eAAsB,OAAsB;CAC3C,MAAM,MAAM,IAAI;EACf,MAAM;EACN,SAAS;EACT,aAAa;CACd,CAAC;CACD,IAAI;EACH,MAAM,IAAI,QAAQ,WAAW,CAAC,CAAC,MAAM,QAAQ,IAAI;CAClD,SAAS,OAAO;EACf,QAAQ,OAAO,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,mBAAmB,GAAG;EAC9F,QAAQ,WAAW;CACpB;AACD"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { cli } from "clibuilder";
|
|
2
|
+
//#region src/harness-registry/harness-registry.d.ts
|
|
3
|
+
type HarnessName = "claude-code" | "cursor" | "codex" | "copilot-cli" | "gemini-cli" | "devin-desktop" | "windsurf";
|
|
4
|
+
/**
|
|
5
|
+
* `skillsDirectory` is the projection target a harness needs to see `.agents/skills`.
|
|
6
|
+
* A harness that reads `.agents/skills` natively has none, and is never projected into.
|
|
7
|
+
*/
|
|
8
|
+
type Harness = {
|
|
9
|
+
name: HarnessName;
|
|
10
|
+
detect: string;
|
|
11
|
+
skillsDirectory?: string;
|
|
12
|
+
/** Set when this name has been superseded; the value is the name that replaces it. */
|
|
13
|
+
deprecated?: HarnessName;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Codex, Cursor, Copilot CLI, and Devin Desktop read `.agents/skills` directly, so they need no
|
|
17
|
+
* projection. Only Claude Code and Gemini CLI read solely their own directory.
|
|
18
|
+
*
|
|
19
|
+
* `windsurf` is the former name of Devin Desktop, rebranded 2026-06-02. It is retained as a
|
|
20
|
+
* deprecated alias: Devin still scans the legacy `.windsurf/skills` path, so its projection keeps
|
|
21
|
+
* working, but new repositories should enable `devin-desktop` and have nothing written for them.
|
|
22
|
+
*
|
|
23
|
+
* See `.research/agentic-configuration-standards/` for the per-harness sources.
|
|
24
|
+
*/
|
|
25
|
+
declare const harnessRegistry: readonly Harness[];
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/initialize-harnesses/init.command.d.ts
|
|
28
|
+
declare const initCommand: cli.Command;
|
|
29
|
+
declare const harnessCommand: cli.Command;
|
|
30
|
+
declare function activate({ addCommand }: {
|
|
31
|
+
addCommand(command: typeof harnessCommand): void;
|
|
32
|
+
}): void;
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/initialize-harnesses/initialize-harnesses.d.ts
|
|
35
|
+
type InitializeOptions = {
|
|
36
|
+
root: string;
|
|
37
|
+
harnesses?: HarnessName[];
|
|
38
|
+
copy?: boolean;
|
|
39
|
+
force?: boolean;
|
|
40
|
+
};
|
|
41
|
+
type InitializeResult = {
|
|
42
|
+
root: string;
|
|
43
|
+
harnesses: HarnessName[];
|
|
44
|
+
native: HarnessName[];
|
|
45
|
+
linked: HarnessName[];
|
|
46
|
+
/** Enabled harnesses whose name has been superseded, as `{ name, replacedBy }`. */
|
|
47
|
+
deprecated: {
|
|
48
|
+
name: HarnessName;
|
|
49
|
+
replacedBy: HarnessName;
|
|
50
|
+
}[];
|
|
51
|
+
skills: number;
|
|
52
|
+
copied: boolean;
|
|
53
|
+
};
|
|
54
|
+
declare function initializeHarnesses({ root, harnesses: preferred, copy, force }: InitializeOptions): InitializeResult;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { type HarnessName, type InitializeOptions, type InitializeResult, activate, harnessCommand, harnessRegistry, initCommand, initializeHarnesses };
|
|
57
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/harness-registry/harness-registry.ts","../src/initialize-harnesses/init.command.ts","../src/initialize-harnesses/initialize-harnesses.ts"],"mappings":";;KAGY;;;;;KAaA;EACX,MAAM;EACN;EACA;;EAEA,aAAa;;;;;;;;;;;;cAaD,0BAA0B;;;cCxB1B,aAAa,IAAI;cAyDjB,gBAAgB,IAAI;iBAMjB,WAAW;EAAgB,WAAW,gBAAgB;;;;KCrE1D;EACX;EACA,YAAY;EACZ;EACA;;KAEW;EACX;EACA,WAAW;EACX,QAAQ;EACR,QAAQ;;EAER;IAAc,MAAM;IAAa,YAAY;;EAC7C;EACA;;iBAGe,sBACf,MACA,WAAW,WACX,MACA,SACE,oBAAoB"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { command, z } from "clibuilder";
|
|
2
|
+
import { encode } from "@toon-format/toon";
|
|
3
|
+
import { cpSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from "node:fs";
|
|
4
|
+
import { dirname, join, relative } from "node:path";
|
|
5
|
+
//#region src/harness-registry/harness-registry.ts
|
|
6
|
+
/**
|
|
7
|
+
* Codex, Cursor, Copilot CLI, and Devin Desktop read `.agents/skills` directly, so they need no
|
|
8
|
+
* projection. Only Claude Code and Gemini CLI read solely their own directory.
|
|
9
|
+
*
|
|
10
|
+
* `windsurf` is the former name of Devin Desktop, rebranded 2026-06-02. It is retained as a
|
|
11
|
+
* deprecated alias: Devin still scans the legacy `.windsurf/skills` path, so its projection keeps
|
|
12
|
+
* working, but new repositories should enable `devin-desktop` and have nothing written for them.
|
|
13
|
+
*
|
|
14
|
+
* See `.research/agentic-configuration-standards/` for the per-harness sources.
|
|
15
|
+
*/
|
|
16
|
+
const harnessRegistry = [
|
|
17
|
+
{
|
|
18
|
+
name: "claude-code",
|
|
19
|
+
detect: ".claude",
|
|
20
|
+
skillsDirectory: ".claude/skills"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "cursor",
|
|
24
|
+
detect: ".cursor"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "codex",
|
|
28
|
+
detect: ".codex"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "copilot-cli",
|
|
32
|
+
detect: ".github/skills"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "gemini-cli",
|
|
36
|
+
detect: ".gemini",
|
|
37
|
+
skillsDirectory: ".gemini/skills"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "devin-desktop",
|
|
41
|
+
detect: ".devin"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "windsurf",
|
|
45
|
+
detect: ".windsurf",
|
|
46
|
+
skillsDirectory: ".windsurf/skills",
|
|
47
|
+
deprecated: "devin-desktop"
|
|
48
|
+
}
|
|
49
|
+
];
|
|
50
|
+
/** Enabled whether or not the repository already contains their directories. */
|
|
51
|
+
const defaultHarnesses = ["claude-code", "cursor"];
|
|
52
|
+
function directoryExists(path) {
|
|
53
|
+
try {
|
|
54
|
+
return lstatSync(path).isDirectory();
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** The default harnesses, plus the preferred ones, plus every harness whose directory is present. */
|
|
60
|
+
function selectHarnesses(root, preferred) {
|
|
61
|
+
const enabled = /* @__PURE__ */ new Set([...defaultHarnesses, ...preferred]);
|
|
62
|
+
return harnessRegistry.filter((harness) => enabled.has(harness.name) || directoryExists(join(root, harness.detect)));
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/skill-projection/skill-projection.ts
|
|
66
|
+
function skillNames(skillsDirectory) {
|
|
67
|
+
return readdirSync(skillsDirectory, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
68
|
+
}
|
|
69
|
+
function pathOccupied(path) {
|
|
70
|
+
try {
|
|
71
|
+
lstatSync(path);
|
|
72
|
+
return true;
|
|
73
|
+
} catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isCorrectLink(target, source) {
|
|
78
|
+
return lstatSync(target).isSymbolicLink() && readlinkSync(target) === relative(dirname(target), source);
|
|
79
|
+
}
|
|
80
|
+
/** Creates the canonical directory when absent, so a fresh repository reports zero skills. */
|
|
81
|
+
function countSkills(canonicalSkills) {
|
|
82
|
+
mkdirSync(canonicalSkills, { recursive: true });
|
|
83
|
+
return skillNames(canonicalSkills).length;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Points every harness that cannot read `.agents/skills` at the canonical directory. Symlinking is
|
|
87
|
+
* preferred; a platform that refuses the link falls back to a copy. Returns the projected harnesses.
|
|
88
|
+
*/
|
|
89
|
+
function projectSkills({ root, canonicalSkills, harnesses, copy, force }) {
|
|
90
|
+
const projections = harnesses.filter((harness) => harness.skillsDirectory).map((harness) => ({
|
|
91
|
+
harness,
|
|
92
|
+
target: join(root, harness.skillsDirectory)
|
|
93
|
+
}));
|
|
94
|
+
const conflicts = projections.filter(({ target }) => pathOccupied(target) && !isCorrectLink(target, canonicalSkills)).map(({ target }) => target);
|
|
95
|
+
if (conflicts.length && !force) throw new Error(`Refusing to replace existing skill targets:\n${conflicts.map((target) => `- ${target}`).join("\n")}`);
|
|
96
|
+
for (const { target } of projections) {
|
|
97
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
98
|
+
if (pathOccupied(target) && isCorrectLink(target, canonicalSkills)) continue;
|
|
99
|
+
if (pathOccupied(target)) rmSync(target, {
|
|
100
|
+
recursive: true,
|
|
101
|
+
force: true
|
|
102
|
+
});
|
|
103
|
+
if (copy) {
|
|
104
|
+
cpSync(canonicalSkills, target, { recursive: true });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
symlinkSync(relative(dirname(target), canonicalSkills), target, "junction");
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (pathOccupied(target)) throw error;
|
|
111
|
+
cpSync(canonicalSkills, target, { recursive: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return projections.map(({ harness }) => harness.name);
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/initialize-harnesses/initialize-harnesses.ts
|
|
118
|
+
function initializeHarnesses({ root, harnesses: preferred = [], copy = false, force = false }) {
|
|
119
|
+
const canonicalSkills = join(root, ".agents", "skills");
|
|
120
|
+
const skills = countSkills(canonicalSkills);
|
|
121
|
+
const harnesses = selectHarnesses(root, preferred);
|
|
122
|
+
const linked = projectSkills({
|
|
123
|
+
root,
|
|
124
|
+
canonicalSkills,
|
|
125
|
+
harnesses,
|
|
126
|
+
copy,
|
|
127
|
+
force
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
root,
|
|
131
|
+
harnesses: harnesses.map((harness) => harness.name),
|
|
132
|
+
native: harnesses.filter((harness) => !harness.skillsDirectory).map((harness) => harness.name),
|
|
133
|
+
linked,
|
|
134
|
+
deprecated: harnesses.filter((harness) => harness.deprecated).map((harness) => ({
|
|
135
|
+
name: harness.name,
|
|
136
|
+
replacedBy: harness.deprecated
|
|
137
|
+
})),
|
|
138
|
+
skills,
|
|
139
|
+
copied: copy
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/initialize-harnesses/init.command.ts
|
|
144
|
+
function write(value, format) {
|
|
145
|
+
process.stdout.write(`${format === "json" ? JSON.stringify(value) : encode(value)}\n`);
|
|
146
|
+
}
|
|
147
|
+
const initCommand = command({
|
|
148
|
+
name: "init",
|
|
149
|
+
description: "Set up canonical agent skills for the harnesses already enabled in a repository.",
|
|
150
|
+
options: {
|
|
151
|
+
root: {
|
|
152
|
+
description: "Repository or package directory. Defaults to the current directory.",
|
|
153
|
+
type: z.optional(z.string())
|
|
154
|
+
},
|
|
155
|
+
harness: {
|
|
156
|
+
description: "Comma-separated harnesses to enable in addition to Claude Code and Cursor, e.g. codex,windsurf.",
|
|
157
|
+
type: z.optional(z.string())
|
|
158
|
+
},
|
|
159
|
+
copy: {
|
|
160
|
+
description: "Copy skills instead of linking them.",
|
|
161
|
+
type: z.optional(z.boolean())
|
|
162
|
+
},
|
|
163
|
+
force: {
|
|
164
|
+
description: "Replace conflicting target skill directories.",
|
|
165
|
+
type: z.optional(z.boolean())
|
|
166
|
+
},
|
|
167
|
+
format: {
|
|
168
|
+
description: "Output format: toon (default) or json.",
|
|
169
|
+
type: z.optional(z.string()),
|
|
170
|
+
default: "toon"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
run(args) {
|
|
174
|
+
try {
|
|
175
|
+
const format = args.format;
|
|
176
|
+
if (format !== "toon" && format !== "json") throw new Error("--format must be toon or json.");
|
|
177
|
+
const requested = args.harness?.split(",").map((name) => name.trim()).filter(Boolean);
|
|
178
|
+
const unsupported = requested?.filter((name) => !harnessRegistry.some((harness) => harness.name === name));
|
|
179
|
+
if (unsupported?.length) throw new Error(`Unsupported harness: ${unsupported.join(", ")}. Supported: ${harnessRegistry.map((harness) => harness.name).join(", ")}.`);
|
|
180
|
+
write(initializeHarnesses({
|
|
181
|
+
root: args.root ?? process.cwd(),
|
|
182
|
+
...requested?.length ? { harnesses: requested } : {},
|
|
183
|
+
...args.copy === void 0 ? {} : { copy: args.copy },
|
|
184
|
+
...args.force === void 0 ? {} : { force: args.force }
|
|
185
|
+
}), format);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
process.stderr.write(`error: ${error instanceof Error ? error.message : "Harness initialization failed."}\n`);
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
const harnessCommand = command({
|
|
193
|
+
name: "harness",
|
|
194
|
+
description: "Commands for configuring agent harness compatibility.",
|
|
195
|
+
commands: [initCommand]
|
|
196
|
+
});
|
|
197
|
+
function activate({ addCommand }) {
|
|
198
|
+
addCommand(harnessCommand);
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
export { harnessRegistry as a, initializeHarnesses as i, harnessCommand as n, initCommand as r, activate as t };
|
|
202
|
+
|
|
203
|
+
//# sourceMappingURL=init.command-wxC4cgUc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.command-wxC4cgUc.mjs","names":[],"sources":["../src/harness-registry/harness-registry.ts","../src/skill-projection/skill-projection.ts","../src/initialize-harnesses/initialize-harnesses.ts","../src/initialize-harnesses/init.command.ts"],"sourcesContent":["import { lstatSync } from 'node:fs'\nimport { join } from 'node:path'\n\nexport type HarnessName =\n\t| 'claude-code'\n\t| 'cursor'\n\t| 'codex'\n\t| 'copilot-cli'\n\t| 'gemini-cli'\n\t| 'devin-desktop'\n\t| 'windsurf'\n\n/**\n * `skillsDirectory` is the projection target a harness needs to see `.agents/skills`.\n * A harness that reads `.agents/skills` natively has none, and is never projected into.\n */\nexport type Harness = {\n\tname: HarnessName\n\tdetect: string\n\tskillsDirectory?: string\n\t/** Set when this name has been superseded; the value is the name that replaces it. */\n\tdeprecated?: HarnessName\n}\n\n/**\n * Codex, Cursor, Copilot CLI, and Devin Desktop read `.agents/skills` directly, so they need no\n * projection. Only Claude Code and Gemini CLI read solely their own directory.\n *\n * `windsurf` is the former name of Devin Desktop, rebranded 2026-06-02. It is retained as a\n * deprecated alias: Devin still scans the legacy `.windsurf/skills` path, so its projection keeps\n * working, but new repositories should enable `devin-desktop` and have nothing written for them.\n *\n * See `.research/agentic-configuration-standards/` for the per-harness sources.\n */\nexport const harnessRegistry: readonly Harness[] = [\n\t{ name: 'claude-code', detect: '.claude', skillsDirectory: '.claude/skills' },\n\t{ name: 'cursor', detect: '.cursor' },\n\t{ name: 'codex', detect: '.codex' },\n\t{ name: 'copilot-cli', detect: '.github/skills' },\n\t{ name: 'gemini-cli', detect: '.gemini', skillsDirectory: '.gemini/skills' },\n\t{ name: 'devin-desktop', detect: '.devin' },\n\t{ name: 'windsurf', detect: '.windsurf', skillsDirectory: '.windsurf/skills', deprecated: 'devin-desktop' },\n]\n\n/** Enabled whether or not the repository already contains their directories. */\nconst defaultHarnesses: readonly HarnessName[] = ['claude-code', 'cursor']\n\nfunction directoryExists(path: string): boolean {\n\ttry {\n\t\treturn lstatSync(path).isDirectory()\n\t} catch {\n\t\treturn false\n\t}\n}\n\n/** The default harnesses, plus the preferred ones, plus every harness whose directory is present. */\nexport function selectHarnesses(root: string, preferred: readonly HarnessName[]): Harness[] {\n\tconst enabled = new Set<HarnessName>([...defaultHarnesses, ...preferred])\n\treturn harnessRegistry.filter((harness) => enabled.has(harness.name) || directoryExists(join(root, harness.detect)))\n}\n","import { cpSync, lstatSync, mkdirSync, readdirSync, readlinkSync, rmSync, symlinkSync } from 'node:fs'\nimport { dirname, join, relative } from 'node:path'\nimport type { Harness, HarnessName } from '../harness-registry/harness-registry.ts'\n\nfunction skillNames(skillsDirectory: string): string[] {\n\treturn readdirSync(skillsDirectory, { withFileTypes: true })\n\t\t.filter((entry) => entry.isDirectory())\n\t\t.map((entry) => entry.name)\n\t\t.sort()\n}\n\nfunction pathOccupied(path: string): boolean {\n\ttry {\n\t\tlstatSync(path)\n\t\treturn true\n\t} catch {\n\t\treturn false\n\t}\n}\n\nfunction isCorrectLink(target: string, source: string): boolean {\n\treturn lstatSync(target).isSymbolicLink() && readlinkSync(target) === relative(dirname(target), source)\n}\n\n/** Creates the canonical directory when absent, so a fresh repository reports zero skills. */\nexport function countSkills(canonicalSkills: string): number {\n\tmkdirSync(canonicalSkills, { recursive: true })\n\treturn skillNames(canonicalSkills).length\n}\n\nexport type ProjectSkillsOptions = {\n\troot: string\n\tcanonicalSkills: string\n\tharnesses: readonly Harness[]\n\tcopy: boolean\n\tforce: boolean\n}\n\n/**\n * Points every harness that cannot read `.agents/skills` at the canonical directory. Symlinking is\n * preferred; a platform that refuses the link falls back to a copy. Returns the projected harnesses.\n */\nexport function projectSkills({ root, canonicalSkills, harnesses, copy, force }: ProjectSkillsOptions): HarnessName[] {\n\tconst projections = harnesses\n\t\t.filter((harness) => harness.skillsDirectory)\n\t\t.map((harness) => ({\n\t\t\tharness,\n\t\t\ttarget: join(root, harness.skillsDirectory as string),\n\t\t}))\n\n\tconst conflicts = projections\n\t\t.filter(({ target }) => pathOccupied(target) && !isCorrectLink(target, canonicalSkills))\n\t\t.map(({ target }) => target)\n\n\tif (conflicts.length && !force) {\n\t\tthrow new Error(\n\t\t\t`Refusing to replace existing skill targets:\\n${conflicts.map((target) => `- ${target}`).join('\\n')}`,\n\t\t)\n\t}\n\n\tfor (const { target } of projections) {\n\t\tmkdirSync(dirname(target), { recursive: true })\n\t\tif (pathOccupied(target) && isCorrectLink(target, canonicalSkills)) continue\n\t\tif (pathOccupied(target)) rmSync(target, { recursive: true, force: true })\n\t\tif (copy) {\n\t\t\tcpSync(canonicalSkills, target, { recursive: true })\n\t\t\tcontinue\n\t\t}\n\t\ttry {\n\t\t\tsymlinkSync(relative(dirname(target), canonicalSkills), target, 'junction')\n\t\t} catch (error) {\n\t\t\tif (pathOccupied(target)) throw error\n\t\t\tcpSync(canonicalSkills, target, { recursive: true })\n\t\t}\n\t}\n\n\treturn projections.map(({ harness }) => harness.name)\n}\n","import { join } from 'node:path'\nimport { type HarnessName, selectHarnesses } from '../harness-registry/harness-registry.ts'\nimport { countSkills, projectSkills } from '../skill-projection/skill-projection.ts'\n\nexport type InitializeOptions = {\n\troot: string\n\tharnesses?: HarnessName[]\n\tcopy?: boolean\n\tforce?: boolean\n}\nexport type InitializeResult = {\n\troot: string\n\tharnesses: HarnessName[]\n\tnative: HarnessName[]\n\tlinked: HarnessName[]\n\t/** Enabled harnesses whose name has been superseded, as `{ name, replacedBy }`. */\n\tdeprecated: { name: HarnessName; replacedBy: HarnessName }[]\n\tskills: number\n\tcopied: boolean\n}\n\nexport function initializeHarnesses({\n\troot,\n\tharnesses: preferred = [],\n\tcopy = false,\n\tforce = false,\n}: InitializeOptions): InitializeResult {\n\tconst canonicalSkills = join(root, '.agents', 'skills')\n\tconst skills = countSkills(canonicalSkills)\n\tconst harnesses = selectHarnesses(root, preferred)\n\tconst linked = projectSkills({ root, canonicalSkills, harnesses, copy, force })\n\n\treturn {\n\t\troot,\n\t\tharnesses: harnesses.map((harness) => harness.name),\n\t\tnative: harnesses.filter((harness) => !harness.skillsDirectory).map((harness) => harness.name),\n\t\tlinked,\n\t\tdeprecated: harnesses\n\t\t\t.filter((harness) => harness.deprecated)\n\t\t\t.map((harness) => ({ name: harness.name, replacedBy: harness.deprecated as HarnessName })),\n\t\tskills,\n\t\tcopied: copy,\n\t}\n}\n","import { encode } from '@toon-format/toon'\nimport type { cli } from 'clibuilder'\nimport { command, z } from 'clibuilder'\nimport { type HarnessName, harnessRegistry } from '../harness-registry/harness-registry.ts'\nimport { initializeHarnesses } from './initialize-harnesses.ts'\n\nfunction write(value: object, format: 'json' | 'toon' | undefined): void {\n\tprocess.stdout.write(`${format === 'json' ? JSON.stringify(value) : encode(value)}\\n`)\n}\n\nexport const initCommand: cli.Command = command({\n\tname: 'init',\n\tdescription: 'Set up canonical agent skills for the harnesses already enabled in a repository.',\n\toptions: {\n\t\troot: {\n\t\t\tdescription: 'Repository or package directory. Defaults to the current directory.',\n\t\t\ttype: z.optional(z.string()),\n\t\t},\n\t\tharness: {\n\t\t\tdescription: 'Comma-separated harnesses to enable in addition to Claude Code and Cursor, e.g. codex,windsurf.',\n\t\t\ttype: z.optional(z.string()),\n\t\t},\n\t\tcopy: {\n\t\t\tdescription: 'Copy skills instead of linking them.',\n\t\t\ttype: z.optional(z.boolean()),\n\t\t},\n\t\tforce: {\n\t\t\tdescription: 'Replace conflicting target skill directories.',\n\t\t\ttype: z.optional(z.boolean()),\n\t\t},\n\t\tformat: {\n\t\t\tdescription: 'Output format: toon (default) or json.',\n\t\t\ttype: z.optional(z.string()),\n\t\t\tdefault: 'toon',\n\t\t},\n\t},\n\trun(args) {\n\t\ttry {\n\t\t\tconst format = args.format\n\t\t\tif (format !== 'toon' && format !== 'json') throw new Error('--format must be toon or json.')\n\t\t\tconst requested = args.harness\n\t\t\t\t?.split(',')\n\t\t\t\t.map((name) => name.trim())\n\t\t\t\t.filter(Boolean)\n\t\t\tconst unsupported = requested?.filter((name) => !harnessRegistry.some((harness) => harness.name === name))\n\t\t\tif (unsupported?.length)\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Unsupported harness: ${unsupported.join(', ')}. Supported: ${harnessRegistry\n\t\t\t\t\t\t.map((harness) => harness.name)\n\t\t\t\t\t\t.join(', ')}.`,\n\t\t\t\t)\n\t\t\twrite(\n\t\t\t\tinitializeHarnesses({\n\t\t\t\t\troot: args.root ?? process.cwd(),\n\t\t\t\t\t...(requested?.length ? { harnesses: requested as HarnessName[] } : {}),\n\t\t\t\t\t...(args.copy === undefined ? {} : { copy: args.copy }),\n\t\t\t\t\t...(args.force === undefined ? {} : { force: args.force }),\n\t\t\t\t}),\n\t\t\t\tformat,\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tprocess.stderr.write(`error: ${error instanceof Error ? error.message : 'Harness initialization failed.'}\\n`)\n\t\t\tprocess.exitCode = 1\n\t\t}\n\t},\n})\n\nexport const harnessCommand: cli.Command = command({\n\tname: 'harness',\n\tdescription: 'Commands for configuring agent harness compatibility.',\n\tcommands: [initCommand],\n})\n\nexport function activate({ addCommand }: { addCommand(command: typeof harnessCommand): void }): void {\n\taddCommand(harnessCommand)\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buddy-agent-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Initialize standards-based agent configuration across coding-agent harnesses",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"repobuddy",
|
|
7
|
+
"agent",
|
|
8
|
+
"skills",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"codex",
|
|
11
|
+
"cursor"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/repobuddy/buddy-agent-harness.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": "https://github.com/repobuddy/buddy-agent-harness/issues",
|
|
19
|
+
"homepage": "https://github.com/repobuddy/buddy-agent-harness#readme",
|
|
20
|
+
"author": {
|
|
21
|
+
"name": "repobuddy"
|
|
22
|
+
},
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"buddy-agent-harness": "./bin/buddy-agent-harness.mjs"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"bin",
|
|
35
|
+
"dist",
|
|
36
|
+
"src",
|
|
37
|
+
"!**/*.{test,spec}.ts",
|
|
38
|
+
"skills",
|
|
39
|
+
"plugin.json",
|
|
40
|
+
".plugin",
|
|
41
|
+
".claude-plugin",
|
|
42
|
+
".cursor-plugin",
|
|
43
|
+
".codex-plugin"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"coverage": "vitest run --coverage",
|
|
48
|
+
"dev": "node bin/buddy-agent-harness.mjs",
|
|
49
|
+
"prepack": "pnpm build",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:dist": "tsc --noEmit -p tsconfig.dist.json && vitest run --config vitest.dist.config.ts",
|
|
52
|
+
"typecheck": "tsc --noEmit"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=22"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@toon-format/toon": "^4.1.1",
|
|
59
|
+
"clibuilder": "^10.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^24.12.4",
|
|
63
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
64
|
+
"tsdown": "^0.22.0",
|
|
65
|
+
"tsx": "^4.22.3",
|
|
66
|
+
"typescript": "^7.0.0",
|
|
67
|
+
"vitest": "^4.1.0"
|
|
68
|
+
}
|
|
69
|
+
}
|
package/plugin.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "buddy-agent-harness",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Initialize standards-based agent configuration across coding-agent harnesses",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "repobuddy"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/repobuddy/buddy-agent-harness",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"keywords": ["agent", "skills", "harness", "claude-code", "codex"],
|
|
11
|
+
"skills": "./skills/",
|
|
12
|
+
"category": "productivity",
|
|
13
|
+
"tags": ["agent", "skills"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Harness Init
|
|
2
|
+
|
|
3
|
+
Give a repository one canonical agent configuration — a root `AGENTS.md` and an `.agents/` tree — and bridge the harnesses that cannot read it directly.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
The skill runs a five-phase workflow: survey the repository, classify what it finds, confirm the plan with you, apply it, then verify.
|
|
8
|
+
|
|
9
|
+
It detects agent configuration you already have — instruction files, skills, commands, subagents, rules, MCP servers, hooks — and either consolidates it into the canonical source, links it back out, or reports it as canonical-only where no safe cross-harness mapping exists. It never removes or rewrites a file you authored without asking first.
|
|
10
|
+
|
|
11
|
+
## Why the projection step is small
|
|
12
|
+
|
|
13
|
+
Codex, Cursor, GitHub Copilot CLI, and Devin Desktop read `.agents/skills/` natively, so they need nothing. Only Claude Code and Gemini CLI need a link:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npx -y buddy-agent-harness init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Claude Code also reads `CLAUDE.md` rather than `AGENTS.md`, so the skill sets up a `CLAUDE.md` that imports `@AGENTS.md`.
|
|
20
|
+
|
|
21
|
+
`references/standard.md` defines the baseline every repository gets from the open standards. `references/harnesses/<harness>.md` covers what each harness needs on top of that baseline — and how well-sourced each claim is. `SKILL.md` routes to them directly.
|
|
22
|
+
|
|
23
|
+
## The part that needs care
|
|
24
|
+
|
|
25
|
+
Linking is easy; frontmatter is not. Each harness recognizes a different set of `SKILL.md` fields and silently drops the rest, so anything that must hold everywhere belongs in the Markdown body rather than in a harness-specific field. See `references/frontmatter.md`.
|
|
26
|
+
|
|
27
|
+
## Boundaries
|
|
28
|
+
|
|
29
|
+
Local agent configuration only. The skill does not invent instructions, convert tool settings without a documented mapping, or change CI, GitHub settings, security scanning, or branch rules.
|