@hanv89/arch-skill 0.0.0 → 0.2.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/dist/__test_fixtures__/synthetic-bundle.js +147 -0
- package/dist/adapters/_shared.js +710 -0
- package/dist/adapters/claude-code.js +47 -0
- package/dist/adapters/codex.js +56 -0
- package/dist/adapters/cursor.js +198 -0
- package/dist/adapters/registry.js +20 -0
- package/dist/adapters/types.js +2 -0
- package/dist/all.js +66 -0
- package/dist/index.js +61 -0
- package/package.json +40 -6
- package/README.md +0 -7
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.claudeCodeAdapter = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const _shared_1 = require("./_shared");
|
|
40
|
+
function claudeRootDir() {
|
|
41
|
+
return path.join(os.homedir(), ".claude");
|
|
42
|
+
}
|
|
43
|
+
exports.claudeCodeAdapter = (0, _shared_1.makeFolderInstallAdapter)({
|
|
44
|
+
rootDir: claudeRootDir,
|
|
45
|
+
rootDisplay: () => "~/.claude",
|
|
46
|
+
agentFlag: "claude-code",
|
|
47
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.codexAdapter = void 0;
|
|
37
|
+
const path = __importStar(require("node:path"));
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
const _shared_1 = require("./_shared");
|
|
40
|
+
// Codex CLI discovers user-installed skills at $CODEX_HOME/skills/<name>/SKILL.md,
|
|
41
|
+
// defaulting to ~/.codex/skills/ when CODEX_HOME is unset. Verified against the
|
|
42
|
+
// Codex Rust binary's bundled prompt strings. See https://github.com/openai/codex.
|
|
43
|
+
function codexRootDir() {
|
|
44
|
+
const explicit = process.env.CODEX_HOME;
|
|
45
|
+
if (explicit)
|
|
46
|
+
return path.resolve(explicit);
|
|
47
|
+
return path.join(os.homedir(), ".codex");
|
|
48
|
+
}
|
|
49
|
+
function codexRootDisplay() {
|
|
50
|
+
return process.env.CODEX_HOME ? `$CODEX_HOME=${process.env.CODEX_HOME}` : "~/.codex";
|
|
51
|
+
}
|
|
52
|
+
exports.codexAdapter = (0, _shared_1.makeFolderInstallAdapter)({
|
|
53
|
+
rootDir: codexRootDir,
|
|
54
|
+
rootDisplay: codexRootDisplay,
|
|
55
|
+
agentFlag: "codex",
|
|
56
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.cursorAdapter = exports.PROVENANCE_RE = void 0;
|
|
37
|
+
exports.provenanceMarker = provenanceMarker;
|
|
38
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const _shared_1 = require("./_shared");
|
|
41
|
+
// Cursor's project-scoped rules live at <cwd>/.cursor/rules/*.mdc with a
|
|
42
|
+
// distinct frontmatter shape (description / globs / alwaysApply). User Rules
|
|
43
|
+
// are settings-only and not programmatically writable from an external CLI,
|
|
44
|
+
// so this adapter targets Project Rules only.
|
|
45
|
+
//
|
|
46
|
+
// Unlike Claude Code + Codex (per-user folder containing SKILL.md + examples/),
|
|
47
|
+
// Cursor installs a SINGLE .mdc file. The skill body is concatenated under a
|
|
48
|
+
// Cursor-shaped frontmatter; examples are NOT bundled locally — the skill body
|
|
49
|
+
// references their public raw GitHub URLs and the agent fetches them at
|
|
50
|
+
// diagram-authoring time.
|
|
51
|
+
//
|
|
52
|
+
// See https://cursor.com/docs/context/rules for the format reference.
|
|
53
|
+
const RULE_BASENAME = "arch-skill.mdc";
|
|
54
|
+
const RULE_DESCRIPTION = "Use this rule when drawing Microsoft Azure or Microsoft Fabric architecture diagrams using PlantUML. " +
|
|
55
|
+
"Triggers on \"draw Azure architecture\", \"draw Azure diagram\", \"create deployment diagram\", " +
|
|
56
|
+
"\"Lakehouse + Notebook + Warehouse diagram\", \"PlantUML diagram for [project]\". " +
|
|
57
|
+
"Also triggers on the Vietnamese phrase \"vẽ Azure\".";
|
|
58
|
+
// Cursor's rule discovery is per-project: <cwd>/.cursor/rules/*.mdc is the
|
|
59
|
+
// canonical install location, so the team picks up the rule via the project's
|
|
60
|
+
// git repo. A HOME-relative install would only help the local developer. The
|
|
61
|
+
// trade-off — running install from an unintended cwd — is mitigated by a
|
|
62
|
+
// runtime warn-line (see `install` below) and the `--target=<path>` override.
|
|
63
|
+
function defaultTarget() {
|
|
64
|
+
return path.join(process.cwd(), ".cursor", "rules");
|
|
65
|
+
}
|
|
66
|
+
const CWD_DISPLAY = "<cwd>/.cursor/rules";
|
|
67
|
+
async function resolveTarget(target) {
|
|
68
|
+
return (0, _shared_1.safeResolveTarget)(target, process.cwd(), CWD_DISPLAY);
|
|
69
|
+
}
|
|
70
|
+
// `provenanceMarker` and `PROVENANCE_RE` are paired: the regex MUST parse what
|
|
71
|
+
// the marker writes. Keep them in lockstep — a unit test in version.test.ts
|
|
72
|
+
// asserts the round-trip.
|
|
73
|
+
function provenanceMarker(version, requiresIcons) {
|
|
74
|
+
return `<!-- ${_shared_1.SKILL_NAME} v${version} (requires_icons: ${requiresIcons}) -->`;
|
|
75
|
+
}
|
|
76
|
+
const PROVENANCE_RE = new RegExp(`<!--\\s*${_shared_1.SKILL_NAME}\\s+v([^\\s]+)\\s+\\(requires_icons:\\s*([^)]+)\\)\\s*-->`);
|
|
77
|
+
exports.PROVENANCE_RE = PROVENANCE_RE;
|
|
78
|
+
function renderRule(skillBody, version, requiresIcons) {
|
|
79
|
+
return [
|
|
80
|
+
"---",
|
|
81
|
+
`description: ${RULE_DESCRIPTION}`,
|
|
82
|
+
"alwaysApply: false",
|
|
83
|
+
"---",
|
|
84
|
+
"",
|
|
85
|
+
provenanceMarker(version, requiresIcons),
|
|
86
|
+
"",
|
|
87
|
+
skillBody.trimStart(),
|
|
88
|
+
].join("\n");
|
|
89
|
+
}
|
|
90
|
+
async function isOurRuleFile(file) {
|
|
91
|
+
try {
|
|
92
|
+
const body = await fs.readFile(file, "utf8");
|
|
93
|
+
const match = body.match(PROVENANCE_RE);
|
|
94
|
+
if (!match)
|
|
95
|
+
return { ours: false };
|
|
96
|
+
return { ours: true, version: match[1] };
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return { ours: false };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async function install(opts) {
|
|
103
|
+
return (0, _shared_1.withFatalReturn)(async () => {
|
|
104
|
+
if (opts.target === undefined) {
|
|
105
|
+
process.stderr.write(`note: Cursor target resolved to ${defaultTarget()} (per-project install). Pass --target=<path> to override.\n`);
|
|
106
|
+
}
|
|
107
|
+
const targetDir = await resolveTarget(opts.target ?? defaultTarget());
|
|
108
|
+
const base = (0, _shared_1.baseUrl)(opts.version);
|
|
109
|
+
const ruleFile = path.join(targetDir, RULE_BASENAME);
|
|
110
|
+
// Fetch manifest first so we know which file is the canonical SKILL.md
|
|
111
|
+
// and what version / requires_icons to embed in the provenance marker.
|
|
112
|
+
const manifest = await (0, _shared_1.fetchManifest)(base);
|
|
113
|
+
if (manifest.name !== _shared_1.SKILL_NAME) {
|
|
114
|
+
throw new Error(`manifest name mismatch: expected '${_shared_1.SKILL_NAME}', got '${manifest.name}'. CLI and bundle are out of sync.`);
|
|
115
|
+
}
|
|
116
|
+
const skillFile = manifest.files[0];
|
|
117
|
+
const skillUrl = `${base}/${skillFile.src}`;
|
|
118
|
+
const skillMd = await (0, _shared_1.fetchText)(skillUrl);
|
|
119
|
+
// Integrity gate (verify-before-write): the Cursor adapter doesn't use
|
|
120
|
+
// makeFolderInstallAdapter, so it must apply the same sha256 check the
|
|
121
|
+
// folder adapters get for free. Keeps the manifest-checksum guarantee
|
|
122
|
+
// uniform across all three adapters.
|
|
123
|
+
(0, _shared_1.verifyBundleFile)(skillFile, skillMd);
|
|
124
|
+
const fm = (0, _shared_1.parseFrontmatter)(skillMd);
|
|
125
|
+
if (!fm.requires_icons) {
|
|
126
|
+
throw new Error("SKILL.md missing requires_icons frontmatter");
|
|
127
|
+
}
|
|
128
|
+
await (0, _shared_1.verifyIconsAvailability)(base, manifest, opts.version);
|
|
129
|
+
const exists = await fs.stat(ruleFile).then(() => true).catch(() => false);
|
|
130
|
+
if (exists && !opts.overwrite) {
|
|
131
|
+
const probe = await isOurRuleFile(ruleFile);
|
|
132
|
+
throw new Error(probe.ours
|
|
133
|
+
? `${ruleFile} already contains an install. Run 'arch-skill update --agent=cursor' to refresh.`
|
|
134
|
+
: `${ruleFile} exists but is not one of ours (no '${_shared_1.SKILL_NAME}' provenance marker). Move/rename the file or remove it manually if intentional.`);
|
|
135
|
+
}
|
|
136
|
+
await fs.mkdir(targetDir, { recursive: true });
|
|
137
|
+
const body = (0, _shared_1.stripFrontmatter)(skillMd);
|
|
138
|
+
const rendered = renderRule(body, fm.version ?? manifest.version, fm.requires_icons);
|
|
139
|
+
await fs.writeFile(ruleFile, rendered, "utf8");
|
|
140
|
+
process.stdout.write(`installed ${_shared_1.SKILL_NAME} to ${ruleFile}\n`);
|
|
141
|
+
return 0;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
async function uninstall(opts) {
|
|
145
|
+
return (0, _shared_1.withFatalReturn)(async () => {
|
|
146
|
+
const targetDir = await resolveTarget(opts.target ?? defaultTarget());
|
|
147
|
+
const ruleFile = path.join(targetDir, RULE_BASENAME);
|
|
148
|
+
const exists = await fs.stat(ruleFile).then(() => true).catch(() => false);
|
|
149
|
+
if (!exists) {
|
|
150
|
+
process.stdout.write(`(nothing to uninstall at ${ruleFile})\n`);
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
const probe = await isOurRuleFile(ruleFile);
|
|
154
|
+
if (!probe.ours) {
|
|
155
|
+
throw new Error(`refusing to remove ${ruleFile} - missing '${_shared_1.SKILL_NAME}' provenance marker. Move/rename the file or remove it manually if intentional.`);
|
|
156
|
+
}
|
|
157
|
+
await fs.unlink(ruleFile);
|
|
158
|
+
process.stdout.write(`uninstalled ${_shared_1.SKILL_NAME} from ${ruleFile}\n`);
|
|
159
|
+
return 0;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async function update(opts) {
|
|
163
|
+
return (0, _shared_1.withFatalReturn)(async () => {
|
|
164
|
+
const targetDir = await resolveTarget(opts.target ?? defaultTarget());
|
|
165
|
+
const ruleFile = path.join(targetDir, RULE_BASENAME);
|
|
166
|
+
const base = (0, _shared_1.baseUrl)(opts.version);
|
|
167
|
+
// Already-at-version short-circuit (parity with the folder-install
|
|
168
|
+
// adapters). Read the installed .mdc, extract version from the
|
|
169
|
+
// provenance marker, compare to the upstream manifest's version.
|
|
170
|
+
const probe = await isOurRuleFile(ruleFile);
|
|
171
|
+
if (probe.ours && probe.version) {
|
|
172
|
+
const manifest = await (0, _shared_1.fetchManifest)(base);
|
|
173
|
+
if (manifest.name !== _shared_1.SKILL_NAME) {
|
|
174
|
+
throw new Error(`manifest name mismatch: expected '${_shared_1.SKILL_NAME}', got '${manifest.name}'. CLI and bundle are out of sync.`);
|
|
175
|
+
}
|
|
176
|
+
if (probe.version === manifest.version) {
|
|
177
|
+
process.stdout.write(`${_shared_1.SKILL_NAME} already at version ${manifest.version} (no-op)\n`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Otherwise, overwriting install.
|
|
182
|
+
return install({ ...opts, overwrite: true });
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
async function list(opts) {
|
|
186
|
+
return (0, _shared_1.withFatalReturn)(async () => {
|
|
187
|
+
const targetDir = await resolveTarget(opts.target ?? defaultTarget());
|
|
188
|
+
const ruleFile = path.join(targetDir, RULE_BASENAME);
|
|
189
|
+
const probe = await isOurRuleFile(ruleFile);
|
|
190
|
+
if (!probe.ours) {
|
|
191
|
+
process.stdout.write("(no skills installed)\n");
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
process.stdout.write(`${_shared_1.SKILL_NAME}\t${probe.version ?? "?"}\n`);
|
|
195
|
+
return 0;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
exports.cursorAdapter = { install, uninstall, update, list };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SUPPORTED_TARGETS = exports.ALL_TARGET = exports.SUPPORTED_AGENTS = exports.ADAPTERS = void 0;
|
|
4
|
+
const claude_code_1 = require("./claude-code");
|
|
5
|
+
const codex_1 = require("./codex");
|
|
6
|
+
const cursor_1 = require("./cursor");
|
|
7
|
+
// Single source of truth for the supported-agent set. Add a new adapter by
|
|
8
|
+
// importing it here and adding one entry below; both `src/index.ts` (CLI
|
|
9
|
+
// dispatch + --agent help text) and `adapters-roundtrip.test.ts` (test loop)
|
|
10
|
+
// read from this map directly.
|
|
11
|
+
exports.ADAPTERS = {
|
|
12
|
+
"claude-code": claude_code_1.claudeCodeAdapter,
|
|
13
|
+
"codex": codex_1.codexAdapter,
|
|
14
|
+
"cursor": cursor_1.cursorAdapter,
|
|
15
|
+
};
|
|
16
|
+
exports.SUPPORTED_AGENTS = Object.keys(exports.ADAPTERS);
|
|
17
|
+
/** Sentinel value for `--agent=all` — iterate every adapter under one command. */
|
|
18
|
+
exports.ALL_TARGET = "all";
|
|
19
|
+
/** Full target set the CLI's `--agent` flag accepts. */
|
|
20
|
+
exports.SUPPORTED_TARGETS = [...exports.SUPPORTED_AGENTS, exports.ALL_TARGET];
|
package/dist/all.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runOverAll = runOverAll;
|
|
4
|
+
const registry_1 = require("./adapters/registry");
|
|
5
|
+
async function runOverAll(sub, opts) {
|
|
6
|
+
if (registry_1.SUPPORTED_AGENTS.length === 0) {
|
|
7
|
+
throw new Error("no adapters loaded in registry (registry.ts ADAPTERS is empty)");
|
|
8
|
+
}
|
|
9
|
+
const completed = [];
|
|
10
|
+
const successes = [];
|
|
11
|
+
const failures = [];
|
|
12
|
+
const isTransactional = sub === "install" || sub === "update";
|
|
13
|
+
for (const agent of registry_1.SUPPORTED_AGENTS) {
|
|
14
|
+
const adapter = registry_1.ADAPTERS[agent];
|
|
15
|
+
const outcome = await runOne(adapter, agent, sub, opts);
|
|
16
|
+
if (outcome.exit === 0) {
|
|
17
|
+
successes.push(agent);
|
|
18
|
+
if (isTransactional)
|
|
19
|
+
completed.push(agent);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
failures.push(outcome);
|
|
23
|
+
if (isTransactional)
|
|
24
|
+
break; // halt + roll back below
|
|
25
|
+
// uninstall / list: best-effort, keep going
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const rolledBack = [];
|
|
29
|
+
if (isTransactional && failures.length > 0) {
|
|
30
|
+
for (const agent of completed.slice().reverse()) {
|
|
31
|
+
try {
|
|
32
|
+
const exit = await registry_1.ADAPTERS[agent].uninstall(opts);
|
|
33
|
+
if (exit === 0) {
|
|
34
|
+
rolledBack.push(agent);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
process.stderr.write(`fatal: rollback failed for ${agent}: uninstall exited ${exit}\n`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
42
|
+
process.stderr.write(`fatal: rollback failed for ${agent}: ${msg}\n`);
|
|
43
|
+
// continue with the rest (best-effort cleanup)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
process.stderr.write(formatSummary(sub, successes, failures, rolledBack, isTransactional));
|
|
48
|
+
return failures.length > 0 ? 1 : 0;
|
|
49
|
+
}
|
|
50
|
+
async function runOne(adapter, agent, sub, opts) {
|
|
51
|
+
try {
|
|
52
|
+
// All four Adapter methods now accept the same AdapterOpts shape (see types.ts).
|
|
53
|
+
const exit = await adapter[sub](opts);
|
|
54
|
+
return { agent, exit };
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
return { agent, exit: 1, error: err instanceof Error ? err : new Error(String(err)) };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function formatSummary(sub, successes, failures, rolledBack, isTransactional) {
|
|
61
|
+
const fmt = (list, fallback) => `[${list.length > 0 ? list.join(", ") : fallback}]`;
|
|
62
|
+
const succLabel = fmt(successes, "(none)");
|
|
63
|
+
const failLabel = fmt(failures.map(f => f.agent), "(none)");
|
|
64
|
+
const rbLabel = isTransactional ? fmt(rolledBack, "(none)") : "[(n/a)]";
|
|
65
|
+
return `--agent=all (${sub}): succeeded=${succLabel} failed=${failLabel} rolled-back=${rbLabel}\n`;
|
|
66
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
9
|
+
const registry_1 = require("./adapters/registry");
|
|
10
|
+
const all_1 = require("./all");
|
|
11
|
+
const program = new commander_1.Command()
|
|
12
|
+
.name("arch-skill")
|
|
13
|
+
.description("Install the architecture diagram skill into your AI coding agent.")
|
|
14
|
+
// Top-level flag uses `--cli-version` (not `--version`) so subcommand
|
|
15
|
+
// `--version <semver>` (skill pin) dispatches correctly. Earlier shipping
|
|
16
|
+
// cycles bound both at `--version`; Commander short-circuited to the
|
|
17
|
+
// top-level printer before invoking the subcommand action, breaking
|
|
18
|
+
// documented tag-pin examples.
|
|
19
|
+
.version(package_json_1.default.version, "-V, --cli-version");
|
|
20
|
+
const VERSION_RE = /^\d+\.\d+\.\d+$/;
|
|
21
|
+
function defineSubcommand(name, description) {
|
|
22
|
+
program
|
|
23
|
+
.command(name)
|
|
24
|
+
.description(description)
|
|
25
|
+
.requiredOption("--agent <name>", `target AI agent (${registry_1.SUPPORTED_TARGETS.join("|")})`)
|
|
26
|
+
.option("--target <dir>", "override target directory (validation use)")
|
|
27
|
+
// Subcommand-scoped `--version <semver>` pins which skill bundle to
|
|
28
|
+
// fetch. Distinct from the top-level `--cli-version` flag which prints
|
|
29
|
+
// the CLI tool's own version. The two are namespaced cleanly now that
|
|
30
|
+
// the top-level flag is renamed.
|
|
31
|
+
.option("--version <semver>", "pin to a specific skill version (X.Y.Z); default = latest from main")
|
|
32
|
+
.action(async (opts) => {
|
|
33
|
+
// Validate --version pre-dispatch as defense-in-depth; baseUrl() in
|
|
34
|
+
// _shared.ts also rejects malformed values when it builds the URL.
|
|
35
|
+
if (opts.version !== undefined && !VERSION_RE.test(opts.version)) {
|
|
36
|
+
throw new Error(`--version must match X.Y.Z (got: ${opts.version})`);
|
|
37
|
+
}
|
|
38
|
+
const optsForAdapter = { target: opts.target, version: opts.version };
|
|
39
|
+
if (opts.agent === registry_1.ALL_TARGET) {
|
|
40
|
+
process.exitCode = await (0, all_1.runOverAll)(name, optsForAdapter);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!registry_1.SUPPORTED_AGENTS.includes(opts.agent)) {
|
|
44
|
+
throw new Error(`unknown agent: ${opts.agent} (supported: ${registry_1.SUPPORTED_AGENTS.join(", ")})`);
|
|
45
|
+
}
|
|
46
|
+
process.exitCode = await registry_1.ADAPTERS[opts.agent][name](optsForAdapter);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
defineSubcommand("install", "Install the skill into an AI agent's skill folder.");
|
|
50
|
+
defineSubcommand("uninstall", "Remove a previously installed skill.");
|
|
51
|
+
defineSubcommand("update", "Update an installed skill to the latest version.");
|
|
52
|
+
defineSubcommand("list", "List installed skills and their versions.");
|
|
53
|
+
// Top-level catch: setting process.exitCode (instead of process.exit(1)) lets
|
|
54
|
+
// any pending async cleanup (file handles, the override-warning stderr write)
|
|
55
|
+
// drain before the event loop empties. Both this path and adapter-internal
|
|
56
|
+
// failures emit a single '^fatal: ' prefix line on stderr — log-parsers can
|
|
57
|
+
// rely on the prefix.
|
|
58
|
+
program.parseAsync(process.argv).catch(err => {
|
|
59
|
+
process.stderr.write(`fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanv89/arch-skill",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Install the architecture-diagram skill into your AI coding agent (Claude Code, Codex CLI, Cursor).",
|
|
5
|
+
"bin": {
|
|
6
|
+
"arch-skill": "dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"!dist/**/*.test.*",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"test": "tsc && node --test dist/**/*.test.js dist/*.test.js",
|
|
19
|
+
"test:coverage": "tsc && node --test --experimental-test-coverage dist/**/*.test.js dist/*.test.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
5
25
|
"license": "MIT",
|
|
6
|
-
"
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/hanv89/archicon.git",
|
|
29
|
+
"directory": "packages/cli"
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/hanv89/archicon/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/hanv89/archicon#readme",
|
|
7
35
|
"publishConfig": {
|
|
8
36
|
"access": "public"
|
|
9
37
|
},
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"commander": "14.0.3",
|
|
40
|
+
"js-yaml": "4.1.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/js-yaml": "4.0.9",
|
|
44
|
+
"@types/node": "22.18.0",
|
|
45
|
+
"typescript": "5.9.3"
|
|
46
|
+
}
|
|
13
47
|
}
|
package/README.md
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# @hanv89/arch-skill
|
|
2
|
-
|
|
3
|
-
Placeholder release to bootstrap npm **trusted publishing (OIDC)**. npm cannot
|
|
4
|
-
publish a package's first version via OIDC and cannot configure a trusted
|
|
5
|
-
publisher until the package exists, so this `0.0.0` placeholder is published
|
|
6
|
-
once manually to create the package. Real releases begin at `0.1.0` and are
|
|
7
|
-
published from CI via OIDC — see the project repository.
|