@lifeaitools/rdc-skills 0.31.2 → 0.34.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 +8 -8
- package/.github/workflows/publish.yml +35 -3
- package/.github/workflows/self-test.yml +34 -34
- package/CHANGELOG.md +348 -348
- package/MANIFEST.md +224 -224
- package/README.md +2 -2
- package/clauth-plugin.json +85 -0
- package/deploy/install-systemd.sh +0 -0
- package/ecosystem.local.config.cjs +18 -0
- package/guides/agent-bootstrap.md +195 -195
- package/guides/agents/backend.md +102 -102
- package/guides/agents/content.md +94 -94
- package/guides/agents/cs2.md +56 -56
- package/guides/agents/data.md +86 -86
- package/guides/agents/design.md +77 -77
- package/guides/agents/frontend.md +91 -91
- package/guides/agents/infrastructure.md +81 -81
- package/guides/agents/setup.md +272 -272
- package/guides/agents/verify.md +119 -119
- package/guides/agents/viz.md +106 -106
- package/guides/lessons-learned-spec.md +1 -1
- package/guides/orchestration-epic.md +17 -17
- package/package.json +3 -2
- package/scripts/clean-malformed-registrations.js +257 -0
- package/scripts/postinstall.js +142 -0
- package/scripts/self-test.mjs +1459 -1459
- package/skills/behavior-audit/SKILL.md +1 -1
- package/skills/behavior-audit/agents/openai.yaml +4 -4
- package/skills/brochure/SKILL.md +1 -1
- package/skills/{rdc-brochurify → brochurify}/SKILL.md +1 -1
- package/skills/build/SKILL.md +1 -1
- package/skills/channel-formatter/SKILL.md +1 -1
- package/skills/co-develop/SKILL.md +1 -1
- package/skills/collab/SKILL.md +1 -1
- package/skills/convert/SKILL.md +1 -1
- package/skills/deploy/SKILL.md +1 -1
- package/skills/design/SKILL.md +1 -1
- package/skills/edit/SKILL.md +1 -1
- package/skills/env/SKILL.md +1 -1
- package/skills/{rdc-extract-verifier-rules → extract-verifier-rules}/SKILL.md +1 -1
- package/skills/fixit/SKILL.md +1 -1
- package/skills/fs-mcp/SKILL.md +1 -1
- package/skills/handoff/SKILL.md +1 -1
- package/skills/help/SKILL.md +1 -1
- package/skills/housekeeping/SKILL.md +1 -1
- package/skills/onramp/SKILL.md +1 -1
- package/skills/overnight/SKILL.md +1 -1
- package/skills/plan/SKILL.md +1 -1
- package/skills/preplan/SKILL.md +1 -1
- package/skills/prototype/SKILL.md +1 -1
- package/skills/refactor/SKILL.md +1 -1
- package/skills/regen-media/SKILL.md +1 -1
- package/skills/release/SKILL.md +1 -1
- package/skills/report/SKILL.md +1 -1
- package/skills/review/SKILL.md +1 -1
- package/skills/self-test/SKILL.md +1 -1
- package/skills/status/SKILL.md +1 -1
- package/skills/terminal-config/SKILL.md +1 -1
- package/skills/tests/MATRIX.md +2 -2
- package/skills/tests/onramp.test.json +101 -101
- package/skills/tests/rdc-env.test.json +12 -12
- package/skills/tests/rdc-new-model.test.json +12 -12
- package/skills/tests/rdc-refactor.test.json +29 -29
- package/skills/tests/rdc-regen-media.test.json +29 -29
- package/skills/watch/SKILL.md +1 -1
- package/skills/workitems/SKILL.md +1 -1
- package/tests/channel-formatter.contract.test.mjs +2 -2
- package/tests/clauth-plugin-postinstall.test.mjs +256 -0
- package/tests/mcp.test.mjs +1 -1
- package/tests/plugin-cache-clean-install.test.mjs +76 -0
- package/tests/plugin-namespace-names.test.mjs +125 -0
- /package/skills/tests/{rdc-rdc-brochurify.test.json → rdc-brochurify.test.json} +0 -0
- /package/skills/tests/{rdc-rdc-extract-verifier-rules.test.json → rdc-extract-verifier-rules.test.json} +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* clean-malformed-registrations — find (and optionally remove) rdc-skills
|
|
4
|
+
* registrations that are malformed or duplicated on an already-installed box.
|
|
5
|
+
*
|
|
6
|
+
* DRY RUN BY DEFAULT. Nothing is deleted unless you pass --apply.
|
|
7
|
+
*
|
|
8
|
+
* node scripts/clean-malformed-registrations.mjs # report only
|
|
9
|
+
* node scripts/clean-malformed-registrations.mjs --apply # remove
|
|
10
|
+
* node scripts/clean-malformed-registrations.mjs --project-root C:/Dev/regen-root
|
|
11
|
+
*
|
|
12
|
+
* What "malformed" means here
|
|
13
|
+
* ---------------------------
|
|
14
|
+
* `.claude-plugin/plugin.json` declares `name: "rdc"`, and Claude Code registers
|
|
15
|
+
* a plugin component as `<plugin>:<declared frontmatter name>`. A component whose
|
|
16
|
+
* own `name:` already spells the namespace gets it twice — `name: rdc:plan`
|
|
17
|
+
* renders `/rdc:rdc:plan`, and a directory called `rdc-brochurify` renders
|
|
18
|
+
* `rdc:rdc-brochurify`.
|
|
19
|
+
*
|
|
20
|
+
* Two independent defects produce what the operator sees, and this script
|
|
21
|
+
* separates them because the remedies differ:
|
|
22
|
+
*
|
|
23
|
+
* PREFIXING — a component declares the namespace itself. Fixed at the
|
|
24
|
+
* source (this repo). An installed copy carrying it is simply
|
|
25
|
+
* an old build: re-run the installer.
|
|
26
|
+
* MULTIPLICATION— the same component is registered from more than one live
|
|
27
|
+
* source at once (extra plugin-cache dirs, a stale marketplace
|
|
28
|
+
* clone that is a full second copy of commands/ + skills/,
|
|
29
|
+
* leftover skills/user orphans). Re-running the installer does
|
|
30
|
+
* not always clear these, which is what this script is for.
|
|
31
|
+
*
|
|
32
|
+
* Safety: only regenerable install artifacts are ever removed. The marketplace
|
|
33
|
+
* directory is a git clone — it is REPORTED with the exact command, never
|
|
34
|
+
* deleted, because destroying it loses nothing but confuses Claude Code's own
|
|
35
|
+
* marketplace sync.
|
|
36
|
+
*/
|
|
37
|
+
'use strict';
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
const os = require('os');
|
|
41
|
+
|
|
42
|
+
const args = process.argv.slice(2);
|
|
43
|
+
const APPLY = args.includes('--apply');
|
|
44
|
+
const argOf = (flag, fallback) => {
|
|
45
|
+
const i = args.indexOf(flag);
|
|
46
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
47
|
+
};
|
|
48
|
+
const CLAUDE_HOME = path.resolve(argOf('--claude-home', path.join(os.homedir(), '.claude')));
|
|
49
|
+
const PROJECT_ROOT = argOf('--project-root', null);
|
|
50
|
+
const MARKETPLACE = 'rdc-skills';
|
|
51
|
+
|
|
52
|
+
// The namespace is read from the installed plugin manifest when available, so
|
|
53
|
+
// this script stays correct if the plugin is ever renamed.
|
|
54
|
+
function pluginNamespace() {
|
|
55
|
+
const candidates = [
|
|
56
|
+
path.join(CLAUDE_HOME, 'plugins', 'cache', MARKETPLACE, 'rdc-skills', 'latest', '.claude-plugin', 'plugin.json'),
|
|
57
|
+
path.resolve(__dirname, '..', '.claude-plugin', 'plugin.json'),
|
|
58
|
+
];
|
|
59
|
+
for (const p of candidates) {
|
|
60
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')).name; } catch {}
|
|
61
|
+
}
|
|
62
|
+
return 'rdc';
|
|
63
|
+
}
|
|
64
|
+
const NS = pluginNamespace();
|
|
65
|
+
|
|
66
|
+
const findings = { prefixing: [], multiplication: [], report: [] };
|
|
67
|
+
const removals = []; // { path, why, kind: 'dir' | 'file' }
|
|
68
|
+
|
|
69
|
+
function declaredName(file) {
|
|
70
|
+
try {
|
|
71
|
+
const raw = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
72
|
+
const block = raw.match(/^---\n([\s\S]*?)\n---/);
|
|
73
|
+
if (!block) return null;
|
|
74
|
+
const line = block[1].split('\n').find((l) => /^name:\s*/.test(l));
|
|
75
|
+
return line ? line.replace(/^name:\s*/, '').trim().replace(/^["']|["']$/g, '') : null;
|
|
76
|
+
} catch { return null; }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isDoubled(effective) {
|
|
80
|
+
return effective === NS || effective.startsWith(`${NS}:`) || effective.startsWith(`${NS}-`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Scan one installed plugin tree (commands/ + skills/) for doubled names. */
|
|
84
|
+
function scanTree(label, treeDir) {
|
|
85
|
+
if (!fs.existsSync(treeDir)) return;
|
|
86
|
+
|
|
87
|
+
const cmdDir = path.join(treeDir, 'commands');
|
|
88
|
+
if (fs.existsSync(cmdDir)) {
|
|
89
|
+
for (const f of fs.readdirSync(cmdDir).filter((f) => f.endsWith('.md')).sort()) {
|
|
90
|
+
const eff = declaredName(path.join(cmdDir, f)) ?? f.replace(/\.md$/, '');
|
|
91
|
+
if (isDoubled(eff)) {
|
|
92
|
+
findings.prefixing.push({ source: label, file: path.join(cmdDir, f), declared: eff, renders: `${NS}:${eff}` });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const skillsDir = path.join(treeDir, 'skills');
|
|
98
|
+
if (fs.existsSync(skillsDir)) {
|
|
99
|
+
for (const e of fs.readdirSync(skillsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
100
|
+
if (!e.isDirectory()) continue;
|
|
101
|
+
const md = path.join(skillsDir, e.name, 'SKILL.md');
|
|
102
|
+
if (!fs.existsSync(md)) continue;
|
|
103
|
+
const eff = declaredName(md) ?? e.name;
|
|
104
|
+
if (isDoubled(eff)) {
|
|
105
|
+
findings.prefixing.push({ source: label, file: md, declared: eff, renders: `${NS}:${eff}` });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── 1. Every live plugin source ───────────────────────────────────────────────
|
|
112
|
+
const pluginsDir = path.join(CLAUDE_HOME, 'plugins');
|
|
113
|
+
const cacheBase = path.join(pluginsDir, 'cache', MARKETPLACE, 'rdc-skills');
|
|
114
|
+
const mktDir = path.join(pluginsDir, 'marketplaces', MARKETPLACE);
|
|
115
|
+
|
|
116
|
+
if (fs.existsSync(cacheBase)) {
|
|
117
|
+
for (const entry of fs.readdirSync(cacheBase)) {
|
|
118
|
+
scanTree(`cache/${entry}`, path.join(cacheBase, entry));
|
|
119
|
+
if (entry !== 'latest') {
|
|
120
|
+
findings.multiplication.push(`extra plugin cache dir registers a second full copy: cache/${entry}`);
|
|
121
|
+
removals.push({ path: path.join(cacheBase, entry), why: 'non-latest plugin cache dir (duplicate source)', kind: 'dir' });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The marketplace directory is a git CLONE of the whole repo, not just a
|
|
127
|
+
// manifest. When it lags, it is a second, older copy of every command and skill,
|
|
128
|
+
// live in the menu beside the current one. Never deleted here.
|
|
129
|
+
if (fs.existsSync(mktDir)) {
|
|
130
|
+
scanTree('marketplaces/rdc-skills', mktDir);
|
|
131
|
+
const hasTree = fs.existsSync(path.join(mktDir, 'commands')) || fs.existsSync(path.join(mktDir, 'skills'));
|
|
132
|
+
if (hasTree) {
|
|
133
|
+
findings.multiplication.push('marketplace dir contains a full commands/ + skills/ tree — a second live source');
|
|
134
|
+
findings.report.push(
|
|
135
|
+
`Marketplace clone is a second source. Bring it to the shipping ref (do NOT delete it):\n` +
|
|
136
|
+
` git -C "${mktDir}" fetch origin master\n` +
|
|
137
|
+
` git -C "${mktDir}" status --porcelain # must be empty first\n` +
|
|
138
|
+
` git -C "${mktDir}" reset --hard origin/master`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── 2. skills/ and skills/user/ orphans ───────────────────────────────────────
|
|
144
|
+
for (const rel of ['skills', path.join('skills', 'user')]) {
|
|
145
|
+
const dir = path.join(CLAUDE_HOME, rel);
|
|
146
|
+
if (!fs.existsSync(dir)) continue;
|
|
147
|
+
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
148
|
+
const p = path.join(dir, e.name);
|
|
149
|
+
let md = null;
|
|
150
|
+
if (e.isDirectory()) {
|
|
151
|
+
md = ['SKILL.md', 'skill.md'].map((n) => path.join(p, n)).find((f) => fs.existsSync(f)) || null;
|
|
152
|
+
} else if (e.name.endsWith('.md')) {
|
|
153
|
+
md = p;
|
|
154
|
+
}
|
|
155
|
+
if (!md) continue;
|
|
156
|
+
const name = declaredName(md);
|
|
157
|
+
if (name && (name === NS || name.startsWith(`${NS}:`) || name.startsWith(`${NS}-`))) {
|
|
158
|
+
findings.multiplication.push(`loose skill in ${rel}/ shadows the plugin copy: ${e.name} (declares "${name}")`);
|
|
159
|
+
removals.push({ path: p, why: `loose rdc skill under ${rel}/ (plugin cache is authoritative)`, kind: e.isDirectory() ? 'dir' : 'file' });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── 3. Codex / .agents skill dirs — stale rdc-* copies ────────────────────────
|
|
165
|
+
const codexTargets = [
|
|
166
|
+
path.join(os.homedir(), '.codex', 'skills'),
|
|
167
|
+
path.join(os.homedir(), '.agents', 'skills'),
|
|
168
|
+
PROJECT_ROOT ? path.join(path.resolve(PROJECT_ROOT), '.agents', 'skills', 'user') : null,
|
|
169
|
+
].filter(Boolean);
|
|
170
|
+
|
|
171
|
+
for (const target of codexTargets) {
|
|
172
|
+
if (!fs.existsSync(target)) continue;
|
|
173
|
+
for (const e of fs.readdirSync(target, { withFileTypes: true })) {
|
|
174
|
+
if (!e.isDirectory() || !new RegExp(`^${NS}-`).test(e.name)) continue;
|
|
175
|
+
const p = path.join(target, e.name);
|
|
176
|
+
findings.multiplication.push(`stale codex skill copy: ${p}`);
|
|
177
|
+
removals.push({ path: p, why: 'legacy rdc-<name>/ codex skill copy (superseded by MCP)', kind: 'dir' });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── 4. Duplicate plugin registration rows ─────────────────────────────────────
|
|
182
|
+
function jsonKeysStartingWithRdc(file, pick) {
|
|
183
|
+
if (!fs.existsSync(file)) return [];
|
|
184
|
+
try {
|
|
185
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
186
|
+
const bag = pick(data) || {};
|
|
187
|
+
return Object.keys(bag).filter((k) => k.startsWith('rdc-skills@'));
|
|
188
|
+
} catch { return []; }
|
|
189
|
+
}
|
|
190
|
+
const ipKeys = jsonKeysStartingWithRdc(path.join(pluginsDir, 'installed_plugins.json'), (d) => d.plugins);
|
|
191
|
+
if (ipKeys.length > 1) {
|
|
192
|
+
findings.multiplication.push(`installed_plugins.json has ${ipKeys.length} rdc-skills keys: ${ipKeys.join(', ')}`);
|
|
193
|
+
findings.report.push('Re-run `node scripts/install-rdc-skills.js` — it collapses these to one row.');
|
|
194
|
+
}
|
|
195
|
+
const spKeys = jsonKeysStartingWithRdc(path.join(CLAUDE_HOME, 'settings.json'), (d) => d.enabledPlugins);
|
|
196
|
+
if (spKeys.length > 1) {
|
|
197
|
+
findings.multiplication.push(`settings.json enabledPlugins has ${spKeys.length} rdc-skills keys: ${spKeys.join(', ')}`);
|
|
198
|
+
findings.report.push('Re-run `node scripts/install-rdc-skills.js` — it collapses these to one key.');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Output ────────────────────────────────────────────────────────────────────
|
|
202
|
+
const B = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
203
|
+
console.log('');
|
|
204
|
+
console.log(B(`rdc-skills malformed-registration scan namespace="${NS}" home=${CLAUDE_HOME}`));
|
|
205
|
+
console.log(APPLY ? ' MODE: APPLY (will remove)' : ' MODE: DRY RUN (nothing will be removed — pass --apply to act)');
|
|
206
|
+
console.log('');
|
|
207
|
+
|
|
208
|
+
console.log(B(`A. PREFIXING — components that spell "${NS}" twice (${findings.prefixing.length})`));
|
|
209
|
+
if (!findings.prefixing.length) console.log(' none');
|
|
210
|
+
for (const f of findings.prefixing) {
|
|
211
|
+
console.log(` [${f.source}] ${f.declared} -> registers as ${f.renders}`);
|
|
212
|
+
console.log(` ${f.file}`);
|
|
213
|
+
}
|
|
214
|
+
console.log('');
|
|
215
|
+
console.log(' Remedy: these are stale BUILDS, not stray files — fixing them by hand');
|
|
216
|
+
console.log(' would be overwritten on the next install. Update the source package and');
|
|
217
|
+
console.log(' re-run the installer: npm i -g @lifeaitools/rdc-skills && rdc-skills-install');
|
|
218
|
+
console.log('');
|
|
219
|
+
|
|
220
|
+
console.log(B(`B. MULTIPLICATION — the same component registered from >1 source (${findings.multiplication.length})`));
|
|
221
|
+
if (!findings.multiplication.length) console.log(' none');
|
|
222
|
+
for (const m of findings.multiplication) console.log(` ${m}`);
|
|
223
|
+
console.log('');
|
|
224
|
+
|
|
225
|
+
console.log(B(`REMOVABLE ARTIFACTS (${removals.length})`));
|
|
226
|
+
if (!removals.length) console.log(' none');
|
|
227
|
+
for (const r of removals) console.log(` ${r.kind.padEnd(4)} ${r.path}\n why: ${r.why}`);
|
|
228
|
+
console.log('');
|
|
229
|
+
|
|
230
|
+
if (findings.report.length) {
|
|
231
|
+
console.log(B('MANUAL STEPS (not removable by this script)'));
|
|
232
|
+
for (const r of findings.report) console.log(` - ${r}`);
|
|
233
|
+
console.log('');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!APPLY) {
|
|
237
|
+
console.log(B('DRY RUN — nothing was removed.'));
|
|
238
|
+
console.log(` Re-run with --apply to remove the ${removals.length} artifact(s) listed above.`);
|
|
239
|
+
console.log('');
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let removed = 0;
|
|
244
|
+
for (const r of removals) {
|
|
245
|
+
try {
|
|
246
|
+
fs.rmSync(r.path, { recursive: r.kind === 'dir', force: true });
|
|
247
|
+
console.log(` removed ${r.path}`);
|
|
248
|
+
removed++;
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.log(` FAILED ${r.path} — ${e.message}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
console.log('');
|
|
254
|
+
console.log(` removed ${removed}/${removals.length} artifact(s).`);
|
|
255
|
+
console.log(' Now re-run the installer so the single authoritative copy is rebuilt:');
|
|
256
|
+
console.log(' node scripts/install-rdc-skills.js');
|
|
257
|
+
console.log('');
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/**
|
|
4
|
+
* scripts/postinstall.js — runs after `npm install -g @lifeaitools/rdc-skills`.
|
|
5
|
+
*
|
|
6
|
+
* Self-registers this package's `clauth-plugin.json` with the local clauth
|
|
7
|
+
* supervisor so an install is the registration. `@lifeaitools/rdc-skills` is
|
|
8
|
+
* the only MCP besides clauth itself that is npm-published, so it is the only
|
|
9
|
+
* one that can use npm's `postinstall` lifecycle this way; the unpublished
|
|
10
|
+
* Docker/Coolify MCPs are covered by a repo-side sweep instead.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ THE LOAD-BEARING PROPERTY: this file can never fail an install.
|
|
13
|
+
* It mirrors `C:/Dev/clauth/scripts/postinstall.js` — every failure path
|
|
14
|
+
* downgrades to a printed one-line hint, nothing throws, and the entry point
|
|
15
|
+
* ends in `main().catch(() => {})`. A missing, locked, or broken clauth must
|
|
16
|
+
* never turn `npm i -g @lifeaitools/rdc-skills` into a failed install.
|
|
17
|
+
*
|
|
18
|
+
* `clauth plugin register` is DAEMON-FREE — it validates the manifest, writes
|
|
19
|
+
* to the managed-plugin root, and re-runs discovery. A stopped daemon is not
|
|
20
|
+
* an error case here; only "clauth binary missing" and "register returned
|
|
21
|
+
* non-zero" are.
|
|
22
|
+
*
|
|
23
|
+
* Paths are anchored to the INSTALLED PACKAGE directory via `__dirname`, not
|
|
24
|
+
* to `process.cwd()` — during a global install the cwd is not the package root.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs');
|
|
28
|
+
const path = require('path');
|
|
29
|
+
const { spawnSync } = require('child_process');
|
|
30
|
+
|
|
31
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
32
|
+
const MANIFEST_NAME = 'clauth-plugin.json';
|
|
33
|
+
const CLAUTH_BIN = 'clauth';
|
|
34
|
+
const PROBE_TIMEOUT_MS = 15000;
|
|
35
|
+
const REGISTER_TIMEOUT_MS = 60000;
|
|
36
|
+
const SKIP_ENV = 'RDC_SKILLS_SKIP_CLAUTH_REGISTER';
|
|
37
|
+
|
|
38
|
+
/** Absolute path to the manifest, anchored to the package root — never to cwd. */
|
|
39
|
+
function manifestPath(packageRoot = PACKAGE_ROOT) {
|
|
40
|
+
return path.join(packageRoot, MANIFEST_NAME);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The argv `clauth` is invoked with. Exported so tests assert it without spawning. */
|
|
44
|
+
function registerArgs(packageRoot = PACKAGE_ROOT) {
|
|
45
|
+
return ['plugin', 'register', manifestPath(packageRoot)];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The copy/pasteable command a human runs to finish registration by hand. */
|
|
49
|
+
function manualCommand(packageRoot = PACKAGE_ROOT) {
|
|
50
|
+
return `${CLAUTH_BIN} ${registerArgs(packageRoot).join(' ')}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Windows npm shims are `clauth.cmd`, which Node refuses to spawn directly, so
|
|
55
|
+
* the shell is required there — and that means quoting args ourselves.
|
|
56
|
+
*/
|
|
57
|
+
function shellQuote(arg) {
|
|
58
|
+
return /[\s"&|<>^]/.test(arg) ? `"${String(arg).replace(/"/g, '\\"')}"` : arg;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function invokeClauth(args, timeout, spawn) {
|
|
62
|
+
const useShell = process.platform === 'win32';
|
|
63
|
+
return spawn(CLAUTH_BIN, useShell ? args.map(shellQuote) : args, {
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
66
|
+
timeout,
|
|
67
|
+
windowsHide: true,
|
|
68
|
+
shell: useShell,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** True when a `clauth` on PATH answers `--version` cleanly. Never throws. */
|
|
73
|
+
function clauthAvailable(spawn) {
|
|
74
|
+
try {
|
|
75
|
+
const probe = invokeClauth(['--version'], PROBE_TIMEOUT_MS, spawn);
|
|
76
|
+
return !probe.error && probe.status === 0;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Register the manifest with the local clauth supervisor.
|
|
84
|
+
* Returns a result object; NEVER throws, NEVER exits non-zero.
|
|
85
|
+
*/
|
|
86
|
+
function register({ packageRoot = PACKAGE_ROOT, spawn = spawnSync, log = console.log } = {}) {
|
|
87
|
+
const hint = (message) => log(` ${message}`);
|
|
88
|
+
const manifest = manifestPath(packageRoot);
|
|
89
|
+
|
|
90
|
+
if (process.env[SKIP_ENV]) {
|
|
91
|
+
hint(`○ clauth plugin registration skipped (${SKIP_ENV} set)`);
|
|
92
|
+
return { outcome: 'skipped' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!fs.existsSync(manifest)) {
|
|
96
|
+
hint(`! ${MANIFEST_NAME} not found at ${manifest} — nothing to register`);
|
|
97
|
+
return { outcome: 'no-manifest' };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!clauthAvailable(spawn)) {
|
|
101
|
+
hint(`○ clauth not found — register later with: ${manualCommand(packageRoot)}`);
|
|
102
|
+
return { outcome: 'no-clauth' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let result;
|
|
106
|
+
try {
|
|
107
|
+
result = invokeClauth(registerArgs(packageRoot), REGISTER_TIMEOUT_MS, spawn);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
hint(`! clauth plugin register could not start (${err && err.message}) — retry with: ${manualCommand(packageRoot)}`);
|
|
110
|
+
return { outcome: 'spawn-failed' };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (result && !result.error && result.status === 0) {
|
|
114
|
+
hint('✓ clauth plugin manifest registered (rdc-skills)');
|
|
115
|
+
return { outcome: 'registered' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const detail = ((result && (result.stderr || result.stdout)) || '').trim().split('\n')[0];
|
|
119
|
+
hint(`! clauth plugin register failed${detail ? `: ${detail}` : ''} — retry with: ${manualCommand(packageRoot)}`);
|
|
120
|
+
return { outcome: 'register-failed' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function main(options) {
|
|
124
|
+
register(options);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
MANIFEST_NAME,
|
|
129
|
+
CLAUTH_BIN,
|
|
130
|
+
SKIP_ENV,
|
|
131
|
+
PACKAGE_ROOT,
|
|
132
|
+
manifestPath,
|
|
133
|
+
registerArgs,
|
|
134
|
+
manualCommand,
|
|
135
|
+
shellQuote,
|
|
136
|
+
register,
|
|
137
|
+
main,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
if (require.main === module) {
|
|
141
|
+
main().catch(() => {});
|
|
142
|
+
}
|