harness-alchemist 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.
Files changed (49) hide show
  1. package/.claude-plugin/plugin.json +14 -0
  2. package/.codex-plugin/plugin.json +13 -0
  3. package/LICENSE +21 -0
  4. package/README.md +140 -0
  5. package/bin/harness-alchemist.mjs +43 -0
  6. package/cordis.patch.yml +3 -0
  7. package/dist/deepseek.d.ts +4 -0
  8. package/dist/deepseek.d.ts.map +1 -0
  9. package/dist/deepseek.js +5 -0
  10. package/dist/deepseek.js.map +1 -0
  11. package/dist/opencode.d.ts +3 -0
  12. package/dist/opencode.d.ts.map +1 -0
  13. package/dist/opencode.js +5 -0
  14. package/dist/opencode.js.map +1 -0
  15. package/lib/create.mjs +319 -0
  16. package/lib/validate.mjs +433 -0
  17. package/package.json +86 -0
  18. package/plugin.json +5 -0
  19. package/skills/harness-alchemist/SKILL.md +58 -0
  20. package/skills/harness-alchemist/references/antigravity.md +37 -0
  21. package/skills/harness-alchemist/references/claude-code.md +51 -0
  22. package/skills/harness-alchemist/references/codex.md +37 -0
  23. package/skills/harness-alchemist/references/compatibility.md +53 -0
  24. package/skills/harness-alchemist/references/deepseek-harness.md +56 -0
  25. package/skills/harness-alchemist/references/opencode.md +51 -0
  26. package/skills/harness-alchemist/references/publishing.md +66 -0
  27. package/templates/README.md +9 -0
  28. package/templates/v0.1.0/licenses/Apache-2.0.txt +201 -0
  29. package/templates/v0.1.0/template.json +13 -0
  30. package/templates/v0.1.0/universal-typescript/.agents/plugins/marketplace.json.tpl +20 -0
  31. package/templates/v0.1.0/universal-typescript/.agents/skills/develop-template/SKILL.md.tpl +33 -0
  32. package/templates/v0.1.0/universal-typescript/.agents/skills/develop-template/references/compatibility.md.tpl +23 -0
  33. package/templates/v0.1.0/universal-typescript/.agents/skills/develop-template/scripts/check-package.mjs.tpl +69 -0
  34. package/templates/v0.1.0/universal-typescript/.agents/skills/develop-template/scripts/sync-metadata.mjs.tpl +124 -0
  35. package/templates/v0.1.0/universal-typescript/.claude-plugin/marketplace.json.tpl +19 -0
  36. package/templates/v0.1.0/universal-typescript/.claude-plugin/plugin.json.tpl +14 -0
  37. package/templates/v0.1.0/universal-typescript/.codex-plugin/plugin.json.tpl +13 -0
  38. package/templates/v0.1.0/universal-typescript/.github/workflows/npm-publish.yml.tpl +43 -0
  39. package/templates/v0.1.0/universal-typescript/.gitignore.tpl +9 -0
  40. package/templates/v0.1.0/universal-typescript/AGENTS.md.tpl +16 -0
  41. package/templates/v0.1.0/universal-typescript/README.md.tpl +131 -0
  42. package/templates/v0.1.0/universal-typescript/cordis.patch.yml.tpl +3 -0
  43. package/templates/v0.1.0/universal-typescript/package.json.tpl +70 -0
  44. package/templates/v0.1.0/universal-typescript/plugin.json.tpl +5 -0
  45. package/templates/v0.1.0/universal-typescript/skills/shared-skill/SKILL.md.tpl +18 -0
  46. package/templates/v0.1.0/universal-typescript/src/deepseek.ts.tpl +7 -0
  47. package/templates/v0.1.0/universal-typescript/src/opencode.ts.tpl +7 -0
  48. package/templates/v0.1.0/universal-typescript/tests/runtimes.test.mjs.tpl +15 -0
  49. package/templates/v0.1.0/universal-typescript/tsconfig.json.tpl +17 -0
@@ -0,0 +1,433 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs"
4
+ import { readdir, readFile, realpath } from "node:fs/promises"
5
+ import { basename, dirname, join, resolve, sep } from "node:path"
6
+ import { fileURLToPath } from "node:url"
7
+ import { spawnSync } from "node:child_process"
8
+
9
+ const REQUIRED_FILES = [
10
+ ".agents/plugins/marketplace.json",
11
+ ".claude-plugin/marketplace.json",
12
+ ".claude-plugin/plugin.json",
13
+ ".codex-plugin/plugin.json",
14
+ ".github/workflows/npm-publish.yml",
15
+ ".gitignore",
16
+ "AGENTS.md",
17
+ "LICENSE",
18
+ "README.md",
19
+ "cordis.patch.yml",
20
+ "package.json",
21
+ "plugin.json",
22
+ "src/deepseek.ts",
23
+ "src/opencode.ts",
24
+ "tsconfig.json",
25
+ ]
26
+
27
+ function usage() {
28
+ return `Usage: harness-alchemist validate [project-directory] [--external] [--json]
29
+
30
+ Validates a universal Claude, Codex, OpenCode, Antigravity, and DeepSeek
31
+ plugin scaffold. By default the project is resolved from the current working
32
+ directory or from the script's containing generated project.
33
+
34
+ Options:
35
+ --external Run installed platform validators, currently Claude Code.
36
+ --json Print a machine-readable result.
37
+ --help Show this help.`
38
+ }
39
+
40
+ function findProjectRoot(start) {
41
+ let current = resolve(start)
42
+ while (true) {
43
+ if (
44
+ existsSync(join(current, "package.json")) &&
45
+ existsSync(join(current, ".claude-plugin")) &&
46
+ existsSync(join(current, ".codex-plugin"))
47
+ ) {
48
+ return current
49
+ }
50
+ const parent = dirname(current)
51
+ if (parent === current) return undefined
52
+ current = parent
53
+ }
54
+ }
55
+
56
+ function parseArgs(argv) {
57
+ let project
58
+ let external = false
59
+ let json = false
60
+
61
+ for (const arg of argv) {
62
+ if (arg === "--help" || arg === "-h") return { help: true }
63
+ if (arg === "--external") external = true
64
+ else if (arg === "--json") json = true
65
+ else if (arg.startsWith("-")) throw new Error(`Unknown option: ${arg}`)
66
+ else if (project) throw new Error("Only one project directory may be supplied")
67
+ else project = arg
68
+ }
69
+
70
+ return { project, external, json, help: false }
71
+ }
72
+
73
+ async function readJson(path, errors) {
74
+ try {
75
+ return JSON.parse(await readFile(path, "utf8"))
76
+ } catch (error) {
77
+ errors.push(`${path}: invalid JSON (${error.message})`)
78
+ return undefined
79
+ }
80
+ }
81
+
82
+ function unquote(value) {
83
+ const trimmed = value.trim()
84
+ if (
85
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
86
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
87
+ ) {
88
+ return trimmed.slice(1, -1)
89
+ }
90
+ return trimmed
91
+ }
92
+
93
+ function parseFrontmatter(content) {
94
+ if (!content.startsWith("---\n")) return undefined
95
+ const end = content.indexOf("\n---", 4)
96
+ if (end < 0) return undefined
97
+ const block = content.slice(4, end)
98
+ const values = {}
99
+ for (const line of block.split("\n")) {
100
+ const match = line.match(/^([a-zA-Z0-9-]+):\s*(.*)$/)
101
+ if (match) values[match[1]] = unquote(match[2])
102
+ }
103
+ return values
104
+ }
105
+
106
+ function parseYamlScalar(value) {
107
+ const scalar = value.trim()
108
+ if (scalar.startsWith("'") && scalar.endsWith("'")) {
109
+ return scalar.slice(1, -1).replaceAll("''", "'")
110
+ }
111
+ if (scalar.startsWith('"') && scalar.endsWith('"')) {
112
+ try {
113
+ return JSON.parse(scalar)
114
+ } catch {
115
+ return undefined
116
+ }
117
+ }
118
+ return /^[A-Za-z0-9@/_.-]+$/.test(scalar) ? scalar : undefined
119
+ }
120
+
121
+ function parseCordisInsertEntries(content) {
122
+ if (content.includes("\t")) return []
123
+ const lines = content.split("\n")
124
+ const entries = []
125
+
126
+ for (let index = 0; index < lines.length; index += 1) {
127
+ const insertMatch = lines[index].match(/^(\s*)-\s+insert:\s*(?:#.*)?$/)
128
+ if (!insertMatch || insertMatch[1].length !== 0) continue
129
+
130
+ let entryIndent
131
+ for (let next = index + 1; next < lines.length; next += 1) {
132
+ const line = lines[next]
133
+ if (line.trim() && line.search(/\S/) === 0) break
134
+ if (!line.trim() || line.trimStart().startsWith("#")) continue
135
+ const idMatch = line.match(/^(\s*)-\s+id:\s*(.+?)\s*$/)
136
+ if (entryIndent === undefined) {
137
+ if (!idMatch || idMatch[1].length === 0) break
138
+ entryIndent = idMatch[1].length
139
+ }
140
+ if (!idMatch || idMatch[1].length !== entryIndent) continue
141
+ const id = parseYamlScalar(idMatch[2])
142
+ if (!id) continue
143
+
144
+ const idIndent = idMatch[1].length
145
+ let name
146
+ let nameFields = 0
147
+ for (let field = next + 1; field < lines.length; field += 1) {
148
+ const fieldLine = lines[field]
149
+ const fieldIndent = fieldLine.search(/\S/)
150
+ if (fieldLine.trim() && fieldIndent <= idIndent) break
151
+ const nameMatch = fieldLine.match(/^(\s*)name:\s*(.+?)\s*$/)
152
+ if (nameMatch && nameMatch[1].length === idIndent + 2) {
153
+ nameFields += 1
154
+ name = parseYamlScalar(nameMatch[2])
155
+ }
156
+ }
157
+ entries.push({ id, name, valid: name !== undefined && nameFields === 1 })
158
+ }
159
+ }
160
+
161
+ return entries
162
+ }
163
+
164
+ async function collectSkillFiles(root, relativeRoot) {
165
+ const start = join(root, relativeRoot)
166
+ if (!existsSync(start)) return []
167
+ const results = []
168
+
169
+ async function walk(directory, depth) {
170
+ if (depth > 5) return
171
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
172
+ const path = join(directory, entry.name)
173
+ if (entry.isDirectory()) await walk(path, depth + 1)
174
+ else if (entry.isFile() && entry.name === "SKILL.md") results.push(path)
175
+ }
176
+ }
177
+
178
+ await walk(start, 0)
179
+ return results
180
+ }
181
+
182
+ async function scanForTokens(root) {
183
+ const matches = []
184
+ const ignored = new Set([".git", "dist", "node_modules", "coverage", "templates"])
185
+
186
+ async function walk(directory, depth) {
187
+ if (depth > 8) return
188
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
189
+ if (ignored.has(entry.name)) continue
190
+ const path = join(directory, entry.name)
191
+ if (entry.isDirectory()) {
192
+ await walk(path, depth + 1)
193
+ } else if (entry.isFile()) {
194
+ const content = await readFile(path, "utf8").catch(() => "")
195
+ if (/\{\{[A-Z0-9_]+\}\}/.test(content)) matches.push(path)
196
+ }
197
+ }
198
+ }
199
+
200
+ await walk(root, 0)
201
+ return matches
202
+ }
203
+
204
+ function requirePath(root, relativePath, errors) {
205
+ if (!existsSync(join(root, relativePath))) errors.push(`Missing required file: ${relativePath}`)
206
+ }
207
+
208
+ async function requireManifestPath(root, value, label, errors) {
209
+ if (typeof value !== "string" || !value.startsWith("./")) {
210
+ errors.push(`${label} must be a ./-relative path`)
211
+ return
212
+ }
213
+ const target = resolve(root, value)
214
+ if (!(target === root || target.startsWith(`${root}${sep}`))) {
215
+ errors.push(`${label} escapes the plugin root`)
216
+ } else if (!existsSync(target)) {
217
+ errors.push(`${label} points to missing path ${value}`)
218
+ } else {
219
+ const [canonicalRoot, canonicalTarget] = await Promise.all([realpath(root), realpath(target)])
220
+ if (!(canonicalTarget === canonicalRoot || canonicalTarget.startsWith(`${canonicalRoot}${sep}`))) {
221
+ errors.push(`${label} resolves through a symlink outside the plugin root`)
222
+ }
223
+ }
224
+ }
225
+
226
+ function exportTarget(exports, key, field = "import") {
227
+ const value = exports?.[key]
228
+ if (typeof value === "string") return value
229
+ if (value && typeof value === "object") return value[field]
230
+ return undefined
231
+ }
232
+
233
+ export async function validateProject(projectRoot, options = {}) {
234
+ const root = resolve(projectRoot)
235
+ const errors = []
236
+ const warnings = []
237
+
238
+ for (const file of REQUIRED_FILES) requirePath(root, file, errors)
239
+ if (errors.length > 0) return { root, errors, warnings }
240
+
241
+ const packageJson = await readJson(join(root, "package.json"), errors)
242
+ const claudePlugin = await readJson(join(root, ".claude-plugin/plugin.json"), errors)
243
+ const claudeMarketplace = await readJson(
244
+ join(root, ".claude-plugin/marketplace.json"),
245
+ errors,
246
+ )
247
+ const codexPlugin = await readJson(join(root, ".codex-plugin/plugin.json"), errors)
248
+ const codexMarketplace = await readJson(
249
+ join(root, ".agents/plugins/marketplace.json"),
250
+ errors,
251
+ )
252
+ const antigravityPlugin = await readJson(join(root, "plugin.json"), errors)
253
+
254
+ if (errors.length > 0) return { root, errors, warnings }
255
+
256
+ const packageBase = packageJson.name?.split("/").at(-1)
257
+ const pluginName = claudePlugin.name
258
+ const validName = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
259
+
260
+ if (!validName.test(pluginName ?? "")) {
261
+ errors.push("Plugin name must contain lowercase letters, digits, and single hyphens")
262
+ }
263
+ if ((pluginName?.length ?? 0) > 56) {
264
+ errors.push("Plugin name must be at most 56 characters")
265
+ }
266
+ if (packageBase !== pluginName) {
267
+ errors.push(`npm package basename '${packageBase}' must match plugin name '${pluginName}'`)
268
+ }
269
+
270
+ for (const [label, value] of [
271
+ ["Codex plugin", codexPlugin.name],
272
+ ["Antigravity plugin", antigravityPlugin.name],
273
+ ]) {
274
+ if (value !== pluginName) errors.push(`${label} name '${value}' does not match '${pluginName}'`)
275
+ }
276
+
277
+ for (const [label, manifest] of [
278
+ ["Claude plugin", claudePlugin],
279
+ ["Codex plugin", codexPlugin],
280
+ ]) {
281
+ if (manifest.version !== packageJson.version) {
282
+ errors.push(`${label} version does not match package.json`)
283
+ }
284
+ if (manifest.description !== packageJson.description) {
285
+ errors.push(`${label} description does not match package.json`)
286
+ }
287
+ }
288
+ if (antigravityPlugin.description !== packageJson.description) {
289
+ errors.push("Antigravity description does not match package.json")
290
+ }
291
+
292
+ if (packageJson.type !== "module") errors.push("package.json type must be 'module'")
293
+ if (packageJson.engines?.node !== ">=22.20.0") {
294
+ errors.push("package.json engines.node must be '>=22.20.0'")
295
+ }
296
+ const requiredExports = [
297
+ [".", "import", "./dist/opencode.js"],
298
+ [".", "types", "./dist/opencode.d.ts"],
299
+ ["./deepseek", "import", "./dist/deepseek.js"],
300
+ ["./deepseek", "types", "./dist/deepseek.d.ts"],
301
+ ["./cordis.patch.yml", "import", "./cordis.patch.yml"],
302
+ ]
303
+ for (const [key, field, expected] of requiredExports) {
304
+ const target = exportTarget(packageJson.exports, key, field)
305
+ if (target !== expected) {
306
+ errors.push(`package.json export '${key}' ${field} target must be '${expected}'`)
307
+ }
308
+ }
309
+ for (const entry of [
310
+ "dist",
311
+ "skills",
312
+ "cordis.patch.yml",
313
+ ".claude-plugin/plugin.json",
314
+ ".codex-plugin/plugin.json",
315
+ "plugin.json",
316
+ ]) {
317
+ if (!packageJson.files?.includes(entry)) errors.push(`package.json files is missing '${entry}'`)
318
+ }
319
+ if (packageJson.dsh?.bundle?.patch !== "./cordis.patch.yml") {
320
+ errors.push("package.json dsh.bundle.patch must be './cordis.patch.yml'")
321
+ }
322
+
323
+ await requireManifestPath(root, claudePlugin.skills, "Claude skills", errors)
324
+ await requireManifestPath(root, codexPlugin.skills, "Codex skills", errors)
325
+
326
+ const claudeEntry = claudeMarketplace.plugins?.find((entry) => entry.name === pluginName)
327
+ if (!claudeEntry) errors.push("Claude marketplace is missing the plugin entry")
328
+ else if (claudeEntry.source !== "./") errors.push("Claude marketplace source must be './'")
329
+
330
+ const codexEntry = codexMarketplace.plugins?.find((entry) => entry.name === pluginName)
331
+ if (!codexEntry) errors.push("Codex marketplace is missing the plugin entry")
332
+ else {
333
+ if (codexEntry.source?.source !== "local" || codexEntry.source?.path !== "./") {
334
+ errors.push("Codex marketplace source must be a local './' path")
335
+ }
336
+ if (!codexEntry.policy?.installation || !codexEntry.policy?.authentication) {
337
+ errors.push("Codex marketplace entry requires installation and authentication policies")
338
+ }
339
+ if (!codexEntry.category) errors.push("Codex marketplace entry requires a category")
340
+ }
341
+
342
+ const patch = await readFile(join(root, "cordis.patch.yml"), "utf8")
343
+ const expectedModule = `${packageJson.name}/deepseek`
344
+ const cordisEntries = parseCordisInsertEntries(patch)
345
+ .filter((entry) => entry.id === pluginName)
346
+ const cordisEntry = cordisEntries[0]
347
+ if (cordisEntries.length !== 1 || !cordisEntry?.valid) {
348
+ errors.push(`cordis.patch.yml must contain a valid insert entry for '${pluginName}'`)
349
+ } else if (cordisEntry.name !== expectedModule) {
350
+ errors.push(`cordis.patch.yml entry '${pluginName}' must load '${expectedModule}'`)
351
+ }
352
+
353
+ const skillFiles = [
354
+ ...(await collectSkillFiles(root, "skills")),
355
+ ...(await collectSkillFiles(root, ".agents/skills")),
356
+ ]
357
+ if (skillFiles.length < 2) errors.push("Expected a shared skill and a project development skill")
358
+
359
+ for (const skillFile of skillFiles) {
360
+ const frontmatter = parseFrontmatter(await readFile(skillFile, "utf8"))
361
+ const directoryName = basename(dirname(skillFile))
362
+ if (!frontmatter) errors.push(`${skillFile}: missing YAML frontmatter`)
363
+ else {
364
+ if (frontmatter.name !== directoryName) {
365
+ errors.push(`${skillFile}: frontmatter name must match directory '${directoryName}'`)
366
+ }
367
+ if (!frontmatter.description) errors.push(`${skillFile}: description is required`)
368
+ if (!validName.test(frontmatter.name ?? "") || (frontmatter.name?.length ?? 0) > 64) {
369
+ errors.push(`${skillFile}: invalid Agent Skill name`)
370
+ }
371
+ }
372
+ }
373
+
374
+ for (const path of await scanForTokens(root)) {
375
+ errors.push(`${path}: unresolved scaffold token`)
376
+ }
377
+
378
+ if (options.external) {
379
+ const result = spawnSync("claude", ["plugin", "validate", root, "--strict"], {
380
+ encoding: "utf8",
381
+ })
382
+ if (result.error?.code === "ENOENT") warnings.push("Claude CLI not found; skipped external validation")
383
+ else if (result.status !== 0) {
384
+ errors.push(`Claude plugin validation failed: ${(result.stderr || result.stdout).trim()}`)
385
+ }
386
+ }
387
+
388
+ return { root, errors, warnings }
389
+ }
390
+
391
+ export async function runValidate(argv) {
392
+ let args
393
+ try {
394
+ args = parseArgs(argv)
395
+ } catch (error) {
396
+ console.error(error.message)
397
+ console.error(usage())
398
+ return 2
399
+ }
400
+
401
+ if (args.help) {
402
+ console.log(usage())
403
+ return 0
404
+ }
405
+
406
+ const scriptDirectory = dirname(fileURLToPath(import.meta.url))
407
+ const root = args.project
408
+ ? resolve(args.project)
409
+ : findProjectRoot(process.cwd()) ?? findProjectRoot(scriptDirectory)
410
+
411
+ if (!root) {
412
+ console.error("Could not find a universal plugin project. Pass its directory explicitly.")
413
+ return 2
414
+ }
415
+
416
+ const result = await validateProject(root, { external: args.external })
417
+ if (args.json) {
418
+ console.log(JSON.stringify({ valid: result.errors.length === 0, ...result }, null, 2))
419
+ } else if (result.errors.length === 0) {
420
+ console.log(`Validated universal plugin scaffold at ${result.root}`)
421
+ for (const warning of result.warnings) console.warn(`Warning: ${warning}`)
422
+ } else {
423
+ console.error(`Validation failed for ${result.root}:`)
424
+ for (const error of result.errors) console.error(`- ${error}`)
425
+ for (const warning of result.warnings) console.warn(`Warning: ${warning}`)
426
+ }
427
+
428
+ return result.errors.length > 0 ? 1 : 0
429
+ }
430
+
431
+ if (resolve(process.argv[1] ?? "") === fileURLToPath(import.meta.url)) {
432
+ process.exitCode = await runValidate(process.argv.slice(2))
433
+ }
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "harness-alchemist",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold, validate, and publish portable coding-agent plugins across Claude Code, Codex, OpenCode, Antigravity, and DeepSeek Harness.",
5
+ "type": "module",
6
+ "bin": {
7
+ "harness-alchemist": "./bin/harness-alchemist.mjs"
8
+ },
9
+ "main": "./dist/opencode.js",
10
+ "types": "./dist/opencode.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/opencode.d.ts",
14
+ "import": "./dist/opencode.js"
15
+ },
16
+ "./deepseek": {
17
+ "types": "./dist/deepseek.d.ts",
18
+ "import": "./dist/deepseek.js"
19
+ },
20
+ "./cordis.patch.yml": "./cordis.patch.yml"
21
+ },
22
+ "files": [
23
+ "dist",
24
+ "bin",
25
+ "lib",
26
+ "templates",
27
+ "skills",
28
+ "cordis.patch.yml",
29
+ ".claude-plugin/plugin.json",
30
+ ".codex-plugin/plugin.json",
31
+ "plugin.json",
32
+ "README.md",
33
+ "LICENSE"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "check": "tsc -p tsconfig.json --noEmit",
38
+ "test": "npm run build && node --test tests/*.test.mjs",
39
+ "sync": "node .agents/skills/develop-harness-alchemist/scripts/sync-metadata.mjs",
40
+ "validate": "node .agents/skills/develop-harness-alchemist/scripts/validate.mjs",
41
+ "pack:check": "node .agents/skills/develop-harness-alchemist/scripts/check-package.mjs",
42
+ "verify": "npm run check && npm test && npm run validate && npm run pack:check",
43
+ "prepack": "npm run verify"
44
+ },
45
+ "engines": {
46
+ "node": ">=22.20.0",
47
+ "bun": ">=1.2.0"
48
+ },
49
+ "keywords": [
50
+ "agent-skill",
51
+ "antigravity",
52
+ "claude-code",
53
+ "codex",
54
+ "deepseek",
55
+ "opencode",
56
+ "scaffolding"
57
+ ],
58
+ "peerDependencies": {
59
+ "@deepseek-ai/cordis": "^4.0.1",
60
+ "@opencode-ai/plugin": "^1.18.21"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "@deepseek-ai/cordis": {
64
+ "optional": true
65
+ },
66
+ "@opencode-ai/plugin": {
67
+ "optional": true
68
+ }
69
+ },
70
+ "devDependencies": {
71
+ "@deepseek-ai/cordis": "^4.0.1",
72
+ "@opencode-ai/plugin": "^1.18.21",
73
+ "typescript": "^5.9.3"
74
+ },
75
+ "author": "Haochuan Zhang",
76
+ "license": "MIT",
77
+ "repository": {
78
+ "type": "git",
79
+ "url": "https://github.com/lunarmoon26/harness-alchemist"
80
+ },
81
+ "dsh": {
82
+ "bundle": {
83
+ "patch": "./cordis.patch.yml"
84
+ }
85
+ }
86
+ }
package/plugin.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://antigravity.google/schemas/v1/plugin.json",
3
+ "name": "harness-alchemist",
4
+ "description": "Scaffold, validate, and publish portable coding-agent plugins across Claude Code, Codex, OpenCode, Antigravity, and DeepSeek Harness."
5
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: harness-alchemist
3
+ description: "Use the Harness Alchemist CLI to create or validate universal coding-agent plugins for Claude Code, Codex/ChatGPT, OpenCode, Google Antigravity, and DeepSeek Harness/Cordis."
4
+ compatibility: Requires Node.js 22.20+ or Bun 1.2+ for the CLI. Platform CLIs are optional and only needed for live installation checks.
5
+ ---
6
+
7
+ # Harness Alchemist
8
+
9
+ Use the CLI to create or validate a universal plugin repository. This is the
10
+ tier-2 end-user workflow: it does not maintain Harness Alchemist itself or
11
+ replace the generated repository's local maintenance skill.
12
+
13
+ ## Start Here
14
+
15
+ 1. Inspect the target directory before changing it.
16
+ 2. Read [references/compatibility.md](references/compatibility.md) before choosing paths or manifests.
17
+ 3. For a new project, run `harness-alchemist create --help` and use the CLI instead of recreating the structure manually.
18
+ 4. After creation, load `.agents/skills/develop-<name>/` in the generated project before maintaining its files.
19
+ 5. Run `harness-alchemist validate` and the generated package's `verify` script before reporting completion.
20
+
21
+ ## Create A Project
22
+
23
+ Gather a lowercase kebab-case plugin name, description, npm package name, author, repository, and license. Then run:
24
+
25
+ ```bash
26
+ harness-alchemist create /absolute/path/to/project \
27
+ --name my-plugin \
28
+ --description "What the plugin does" \
29
+ --package @scope/my-plugin \
30
+ --author "Example Team" \
31
+ --repository example/my-plugin
32
+ ```
33
+
34
+ Use `bunx harness-alchemist create ...` or `npx harness-alchemist create ...` when the CLI is not installed. The CLI only writes to a missing or empty destination.
35
+
36
+ ## After Generation
37
+
38
+ The generated `skills/<name>/SKILL.md` is the plugin's end-user workflow
39
+ starter. The generated `.agents/skills/develop-<name>/` is its maintenance
40
+ guide; use that local skill for file ownership, metadata, and harness changes.
41
+
42
+ List supported template versions with:
43
+
44
+ ```bash
45
+ harness-alchemist templates
46
+ ```
47
+
48
+ ## Verify
49
+
50
+ ```bash
51
+ harness-alchemist validate /path/to/project
52
+ cd /path/to/project
53
+ npm install
54
+ npm run verify
55
+ npm pack --dry-run
56
+ ```
57
+
58
+ Static validation is structural evidence, not proof that remote services or host-specific lifecycle events work.
@@ -0,0 +1,37 @@
1
+ # Google Antigravity Plugins
2
+
3
+ ## Plugin Shape
4
+
5
+ ```text
6
+ plugin.json
7
+ skills/<name>/SKILL.md
8
+ rules/<name>.md
9
+ hooks.json
10
+ mcp_config.json
11
+ ```
12
+
13
+ Only `plugin.json` is required. The CLI manifest schema accepts a machine-readable name and optional description. Keep the root manifest separate from Claude and Codex manifests.
14
+
15
+ Current Antigravity Agent Skills use the open Agent Skills directory format with nested `<name>/SKILL.md`. This is also the documented plugin skill shape.
16
+
17
+ ## Installation
18
+
19
+ Antigravity 2 scans workspace plugins under `.agents/plugins/` or `_agents/plugins/`, and global plugins under `~/.gemini/config/plugins/`.
20
+
21
+ Antigravity CLI stages installed plugins under `~/.gemini/antigravity-cli/plugins/<name>/` and supports:
22
+
23
+ ```bash
24
+ agy plugin install /path/to/plugin
25
+ agy plugin list
26
+ agy plugin disable plugin-name
27
+ agy plugin enable plugin-name
28
+ agy plugin uninstall plugin-name
29
+ ```
30
+
31
+ The CLI documentation also shows a legacy flat Markdown workspace-skill example. Prefer the nested Agent Skills shape because the current Antigravity Skills and plugin documentation both specify it.
32
+
33
+ Official references:
34
+
35
+ - https://antigravity.google/docs/plugins/
36
+ - https://antigravity.google/docs/skills/
37
+ - https://antigravity.google/docs/cli/plugins/
@@ -0,0 +1,51 @@
1
+ # Claude Code Plugins
2
+
3
+ ## Required Shape
4
+
5
+ For a manifest-backed plugin:
6
+
7
+ ```text
8
+ .claude-plugin/plugin.json
9
+ skills/<name>/SKILL.md
10
+ ```
11
+
12
+ For repository installation, add `.claude-plugin/marketplace.json`. A one-plugin repository may use `"source": "./"`.
13
+
14
+ Only `plugin.json` belongs inside `.claude-plugin/`. Components remain at the plugin root.
15
+
16
+ ## Supported Components
17
+
18
+ - `skills/<name>/SKILL.md`; prefer this over legacy `commands/*.md`.
19
+ - `agents/*.md`.
20
+ - `hooks/hooks.json`.
21
+ - `.mcp.json`.
22
+ - `.lsp.json`.
23
+ - `monitors/monitors.json`.
24
+ - `bin/` for executables added to the Bash tool path.
25
+ - Root `settings.json` for supported plugin defaults.
26
+
27
+ Use `${CLAUDE_PLUGIN_ROOT}` for files within the copied plugin. Do not reference `../` paths outside the repository payload.
28
+
29
+ ## Development
30
+
31
+ ```bash
32
+ claude --plugin-dir /absolute/path/to/plugin
33
+ claude plugin validate /absolute/path/to/plugin --strict
34
+ ```
35
+
36
+ Use `/reload-plugins` after component changes when supported.
37
+
38
+ ## Distribution
39
+
40
+ ```bash
41
+ claude plugin marketplace add owner/repo
42
+ claude plugin install plugin-name@marketplace-name
43
+ ```
44
+
45
+ Marketplace installation copies plugins into a cache. Versioned manifests require a version bump for updates.
46
+
47
+ Official references:
48
+
49
+ - https://code.claude.com/docs/en/plugins
50
+ - https://code.claude.com/docs/en/plugins-reference
51
+ - https://code.claude.com/docs/en/plugin-marketplaces