@lifeaitools/rdc-skills 0.8.7
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/settings.json +15 -0
- package/.claude-plugin/marketplace.json +21 -0
- package/.claude-plugin/plugin.json +11 -0
- package/.github/workflows/publish.yml +25 -0
- package/.github/workflows/self-test.yml +53 -0
- package/CHANGELOG.md +246 -0
- package/LICENSE +21 -0
- package/MANIFEST.md +190 -0
- package/README.md +188 -0
- package/README.sandbox.md +3 -0
- package/assets/watcher/viewer.html +164 -0
- package/commands/build.md +183 -0
- package/commands/collab.md +180 -0
- package/commands/deploy.md +138 -0
- package/commands/fixit.md +112 -0
- package/commands/handoff.md +173 -0
- package/commands/help.md +88 -0
- package/commands/overnight.md +220 -0
- package/commands/plan.md +158 -0
- package/commands/preplan.md +131 -0
- package/commands/prototype.md +145 -0
- package/commands/release.md +159 -0
- package/commands/report.md +99 -0
- package/commands/review.md +120 -0
- package/commands/self-test.md +107 -0
- package/commands/status.md +86 -0
- package/commands/watch.md +92 -0
- package/commands/workitems.md +132 -0
- package/guides/.gitkeep +0 -0
- package/guides/agent-bootstrap.md +191 -0
- package/guides/agents/backend.md +104 -0
- package/guides/agents/content.md +94 -0
- package/guides/agents/cs2.md +56 -0
- package/guides/agents/data.md +87 -0
- package/guides/agents/design.md +77 -0
- package/guides/agents/frontend.md +92 -0
- package/guides/agents/infrastructure.md +81 -0
- package/guides/agents/setup.md +279 -0
- package/guides/agents/verify.md +132 -0
- package/guides/agents/viz.md +106 -0
- package/guides/backend.md +146 -0
- package/guides/content.md +147 -0
- package/guides/cs2.md +190 -0
- package/guides/data.md +123 -0
- package/guides/design.md +116 -0
- package/guides/frontend.md +151 -0
- package/guides/infrastructure.md +179 -0
- package/guides/output-contract.md +98 -0
- package/hooks/no-stop-open-epics.js +125 -0
- package/package.json +31 -0
- package/rules/work-items-rpc.md +399 -0
- package/scripts/install-rdc-skills.js +559 -0
- package/scripts/install.ps1 +165 -0
- package/scripts/install.sh +132 -0
- package/scripts/lib/assertions.mjs +264 -0
- package/scripts/lib/manifest-schema.mjs +607 -0
- package/scripts/lib/runner.mjs +429 -0
- package/scripts/lib/sandbox.mjs +435 -0
- package/scripts/self-test.mjs +1108 -0
- package/scripts/uninstall.ps1 +77 -0
- package/scripts/uninstall.sh +69 -0
- package/scripts/update.ps1 +43 -0
- package/scripts/update.sh +43 -0
- package/scripts/watch-init.mjs +100 -0
- package/skills/.gitkeep +0 -0
- package/skills/build/SKILL.md +238 -0
- package/skills/collab/SKILL.md +218 -0
- package/skills/deploy/SKILL.md +144 -0
- package/skills/fixit/SKILL.md +112 -0
- package/skills/handoff/SKILL.md +175 -0
- package/skills/help/SKILL.md +101 -0
- package/skills/overnight/SKILL.md +220 -0
- package/skills/plan/SKILL.md +96 -0
- package/skills/preplan/SKILL.md +87 -0
- package/skills/prototype/SKILL.md +151 -0
- package/skills/release/SKILL.md +221 -0
- package/skills/report/SKILL.md +101 -0
- package/skills/review/SKILL.md +120 -0
- package/skills/self-test/SKILL.md +127 -0
- package/skills/status/SKILL.md +86 -0
- package/skills/tests/README.md +29 -0
- package/skills/tests/rdc-build.test.json +22 -0
- package/skills/tests/rdc-deploy.test.json +15 -0
- package/skills/tests/rdc-fixit.test.json +21 -0
- package/skills/tests/rdc-handoff.test.json +14 -0
- package/skills/tests/rdc-overnight.test.json +21 -0
- package/skills/tests/rdc-plan.test.json +14 -0
- package/skills/tests/rdc-preplan.test.json +15 -0
- package/skills/tests/rdc-prototype.test.json +14 -0
- package/skills/tests/rdc-release.test.json +15 -0
- package/skills/tests/rdc-report.test.json +14 -0
- package/skills/tests/rdc-review.test.json +14 -0
- package/skills/tests/rdc-status.test.json +16 -0
- package/skills/tests/rdc-workitems.test.json +15 -0
- package/skills/watch/SKILL.md +92 -0
- package/skills/workitems/SKILL.md +147 -0
- package/tests/validate-skills.js +183 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* install-rdc-skills — registers rdc-skills on every Claude surface.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node scripts/install-rdc-skills.js ← standard
|
|
7
|
+
* node scripts/install-rdc-skills.js --skip-hooks ← skip hook wiring
|
|
8
|
+
* node scripts/install-rdc-skills.js --claude-home <path> ← custom CLI home
|
|
9
|
+
* node scripts/install-rdc-skills.js --migrate <path> ← migrate docs/ → .rdc/
|
|
10
|
+
*
|
|
11
|
+
* What it does:
|
|
12
|
+
* 1. git pull (latest commands + guides)
|
|
13
|
+
* 2. CLI plugin — registers in ~/.claude/plugins/ + settings.json
|
|
14
|
+
* 3. Cowork — registers in Desktop cowork_plugins/ + cowork_settings.json
|
|
15
|
+
* 4. Hook files — copies hooks/*.js → ~/.claude/hooks/
|
|
16
|
+
* 5. Hook wiring — wires hooks into ~/.claude/settings.json
|
|
17
|
+
* 6. Zip — builds dist/rdc-skills-plugin.zip for claude.ai / distribution
|
|
18
|
+
* 7. Preflight — Node version, clauth daemon
|
|
19
|
+
* 8. Commands — lists all /rdc:* commands
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
'use strict';
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const os = require('os');
|
|
26
|
+
const readline = require('readline');
|
|
27
|
+
const { execSync } = require('child_process');
|
|
28
|
+
|
|
29
|
+
// ── Args ──────────────────────────────────────────────────────────────────────
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
const skipHooks = args.includes('--skip-hooks');
|
|
32
|
+
const homeIdx = args.indexOf('--claude-home');
|
|
33
|
+
const claudeHome = homeIdx >= 0 ? args[homeIdx + 1] : path.join(os.homedir(), '.claude');
|
|
34
|
+
const migrateIdx = args.indexOf('--migrate');
|
|
35
|
+
const doMigrate = migrateIdx >= 0;
|
|
36
|
+
const migratePath = doMigrate ? (args[migrateIdx + 1] || process.cwd()) : null;
|
|
37
|
+
|
|
38
|
+
const repoRoot = path.resolve(__dirname, '..');
|
|
39
|
+
const hooksSrc = path.join(repoRoot, 'hooks');
|
|
40
|
+
const hooksDst = path.join(claudeHome, 'hooks');
|
|
41
|
+
const settingsPath = path.join(claudeHome, 'settings.json');
|
|
42
|
+
|
|
43
|
+
const PLUGIN_KEY = 'rdc-skills@rdc-skills';
|
|
44
|
+
const MARKETPLACE = 'rdc-skills';
|
|
45
|
+
|
|
46
|
+
// ── Logging ───────────────────────────────────────────────────────────────────
|
|
47
|
+
const ok = msg => console.log(` \x1b[32m✓\x1b[0m ${msg}`);
|
|
48
|
+
const info = msg => console.log(` \x1b[36m→\x1b[0m ${msg}`);
|
|
49
|
+
const warn = msg => console.log(` \x1b[33m⚠\x1b[0m ${msg}`);
|
|
50
|
+
const fail = msg => console.log(` \x1b[31m✗\x1b[0m ${msg}`);
|
|
51
|
+
|
|
52
|
+
// ── Filesystem helpers ────────────────────────────────────────────────────────
|
|
53
|
+
function copyDir(src, dst, ext) {
|
|
54
|
+
if (!fs.existsSync(src)) { warn(`Source not found: ${src}`); return 0; }
|
|
55
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
56
|
+
const files = fs.readdirSync(src).filter(f => !ext || f.endsWith(ext));
|
|
57
|
+
let count = 0;
|
|
58
|
+
for (const f of files) {
|
|
59
|
+
const s = path.join(src, f);
|
|
60
|
+
if (fs.statSync(s).isFile()) { fs.copyFileSync(s, path.join(dst, f)); count++; }
|
|
61
|
+
}
|
|
62
|
+
return count;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function copyDirRecursive(src, dst) {
|
|
66
|
+
if (!fs.existsSync(src)) return;
|
|
67
|
+
fs.mkdirSync(dst, { recursive: true });
|
|
68
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
69
|
+
const s = path.join(src, entry.name);
|
|
70
|
+
const d = path.join(dst, entry.name);
|
|
71
|
+
if (entry.isDirectory()) copyDirRecursive(s, d);
|
|
72
|
+
else fs.copyFileSync(s, d);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readJson(p, fallback = {}) {
|
|
77
|
+
if (!fs.existsSync(p)) return fallback;
|
|
78
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return fallback; }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function writeJson(p, data, indent = 2) {
|
|
82
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
83
|
+
fs.writeFileSync(p, JSON.stringify(data, null, indent));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Frontmatter parser ────────────────────────────────────────────────────────
|
|
87
|
+
function readFrontmatter(filePath) {
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
90
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
91
|
+
if (!match) return {};
|
|
92
|
+
const fm = {};
|
|
93
|
+
let key = null, multiline = false, multilineVal = '';
|
|
94
|
+
for (const line of match[1].split('\n')) {
|
|
95
|
+
if (multiline) {
|
|
96
|
+
if (/^\s+/.test(line)) { multilineVal += ' ' + line.trim(); continue; }
|
|
97
|
+
fm[key] = multilineVal.trim();
|
|
98
|
+
multiline = false;
|
|
99
|
+
}
|
|
100
|
+
const kv = line.match(/^(\w+):\s*(>-|>)?\s*(.*)?$/);
|
|
101
|
+
if (!kv) continue;
|
|
102
|
+
key = kv[1];
|
|
103
|
+
if (kv[2]) { multiline = true; multilineVal = kv[3] || ''; }
|
|
104
|
+
else fm[key] = kv[3] || '';
|
|
105
|
+
}
|
|
106
|
+
if (multiline && key) fm[key] = multilineVal.trim();
|
|
107
|
+
return fm;
|
|
108
|
+
} catch { return {}; }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Plugin cache builder (shared between CLI + Cowork) ────────────────────────
|
|
112
|
+
function buildPluginCache(cacheDir, version, gitSha) {
|
|
113
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
114
|
+
for (const item of ['.claude-plugin', 'commands', 'skills', 'guides', 'hooks', 'package.json', 'README.md']) {
|
|
115
|
+
const src = path.join(repoRoot, item);
|
|
116
|
+
if (!fs.existsSync(src)) continue;
|
|
117
|
+
const dst = path.join(cacheDir, item);
|
|
118
|
+
if (fs.statSync(src).isDirectory()) copyDirRecursive(src, dst);
|
|
119
|
+
else fs.copyFileSync(src, dst);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── Cache flush helper ────────────────────────────────────────────────────────
|
|
124
|
+
function flushOldCaches(cacheBase, keepVersion) {
|
|
125
|
+
if (!fs.existsSync(cacheBase)) return 0;
|
|
126
|
+
let flushed = 0;
|
|
127
|
+
for (const entry of fs.readdirSync(cacheBase)) {
|
|
128
|
+
if (entry === keepVersion || entry === 'latest') continue;
|
|
129
|
+
try {
|
|
130
|
+
fs.rmSync(path.join(cacheBase, entry), { recursive: true, force: true });
|
|
131
|
+
flushed++;
|
|
132
|
+
} catch {}
|
|
133
|
+
}
|
|
134
|
+
return flushed;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ── Step 2: CLI plugin registration (→ ~/.claude/plugins/) ───────────────────
|
|
138
|
+
function registerCLI(version, gitSha) {
|
|
139
|
+
const pluginDir = path.join(claudeHome, 'plugins');
|
|
140
|
+
const mktDir = path.join(pluginDir, 'marketplaces', MARKETPLACE);
|
|
141
|
+
const mktPlugDir = path.join(mktDir, '.claude-plugin');
|
|
142
|
+
const cacheBase = path.join(pluginDir, 'cache', MARKETPLACE, 'rdc-skills');
|
|
143
|
+
const cacheDir = path.join(cacheBase, version);
|
|
144
|
+
const latestDir = path.join(cacheBase, 'latest');
|
|
145
|
+
|
|
146
|
+
// 1. Marketplace manifest
|
|
147
|
+
fs.mkdirSync(mktPlugDir, { recursive: true });
|
|
148
|
+
fs.copyFileSync(path.join(repoRoot, '.claude-plugin', 'marketplace.json'), path.join(mktPlugDir, 'marketplace.json'));
|
|
149
|
+
|
|
150
|
+
// 2. known_marketplaces.json
|
|
151
|
+
const kmpPath = path.join(pluginDir, 'known_marketplaces.json');
|
|
152
|
+
const knownMp = readJson(kmpPath);
|
|
153
|
+
knownMp[MARKETPLACE] = { source: { source: 'github', repo: 'LIFEAI/rdc-skills' }, installLocation: mktDir, lastUpdated: new Date().toISOString() };
|
|
154
|
+
writeJson(kmpPath, knownMp, 4);
|
|
155
|
+
|
|
156
|
+
// 3. Flush stale version caches, then write versioned + stable latest
|
|
157
|
+
const flushed = flushOldCaches(cacheBase, version);
|
|
158
|
+
if (flushed > 0) info(` flushed : ${flushed} stale cache dir(s)`);
|
|
159
|
+
buildPluginCache(cacheDir, version, gitSha);
|
|
160
|
+
// Write to stable 'latest/' so open terminals can pick up changes if they
|
|
161
|
+
// re-read installed_plugins.json between skill invocations.
|
|
162
|
+
if (fs.existsSync(latestDir)) fs.rmSync(latestDir, { recursive: true, force: true });
|
|
163
|
+
buildPluginCache(latestDir, version, gitSha);
|
|
164
|
+
|
|
165
|
+
// 4. installed_plugins.json — register 'latest/' as stable installPath so
|
|
166
|
+
// re-installs overwrite in-place rather than creating orphaned version dirs.
|
|
167
|
+
// Open terminals that re-read installed_plugins.json mid-session will pick
|
|
168
|
+
// up the updated path; otherwise a terminal restart is needed.
|
|
169
|
+
const ipPath = path.join(pluginDir, 'installed_plugins.json');
|
|
170
|
+
const installed = readJson(ipPath, { version: 2, plugins: {} });
|
|
171
|
+
// Also overwrite whichever installPath the old entry had, so any open
|
|
172
|
+
// terminal that already loaded that path sees fresh files.
|
|
173
|
+
const oldEntries = installed.plugins[PLUGIN_KEY] || [];
|
|
174
|
+
for (const old of oldEntries) {
|
|
175
|
+
if (old.installPath && fs.existsSync(old.installPath) && old.installPath !== latestDir) {
|
|
176
|
+
try { buildPluginCache(old.installPath, version, gitSha); } catch {}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
for (const key of Object.keys(installed.plugins || {})) {
|
|
180
|
+
if (key.startsWith('rdc-skills@')) delete installed.plugins[key];
|
|
181
|
+
}
|
|
182
|
+
installed.plugins[PLUGIN_KEY] = [{ scope: 'user', installPath: latestDir, version, installedAt: new Date().toISOString(), lastUpdated: new Date().toISOString(), gitCommitSha: gitSha }];
|
|
183
|
+
writeJson(ipPath, installed, 4);
|
|
184
|
+
|
|
185
|
+
// 5. settings.json enabledPlugins
|
|
186
|
+
const settings = readJson(settingsPath);
|
|
187
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
188
|
+
for (const key of Object.keys(settings.enabledPlugins)) {
|
|
189
|
+
if (key.startsWith('rdc-skills@')) delete settings.enabledPlugins[key];
|
|
190
|
+
}
|
|
191
|
+
settings.enabledPlugins[PLUGIN_KEY] = true;
|
|
192
|
+
writeJson(settingsPath, settings);
|
|
193
|
+
|
|
194
|
+
return latestDir;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Step 3: Cowork (Claude Desktop) registration ──────────────────────────────
|
|
198
|
+
function findCoworkBases() {
|
|
199
|
+
// Cowork stores per-workspace state at:
|
|
200
|
+
// %LOCALAPPDATA%/Packages/Claude_*/LocalCache/Roaming/Claude/local-agent-mode-sessions/<workspace>/<device>/
|
|
201
|
+
// Each has cowork_settings.json + cowork_plugins/
|
|
202
|
+
const results = [];
|
|
203
|
+
|
|
204
|
+
// Candidate MSIX package roots
|
|
205
|
+
const localAppData = process.env.LOCALAPPDATA || '';
|
|
206
|
+
const pkgsDir = path.join(localAppData, 'Packages');
|
|
207
|
+
if (!fs.existsSync(pkgsDir)) return results;
|
|
208
|
+
|
|
209
|
+
let claudePkg = null;
|
|
210
|
+
for (const dir of fs.readdirSync(pkgsDir)) {
|
|
211
|
+
if (/^Claude_/i.test(dir)) { claudePkg = path.join(pkgsDir, dir); break; }
|
|
212
|
+
}
|
|
213
|
+
if (!claudePkg) return results;
|
|
214
|
+
|
|
215
|
+
const sessionsRoot = path.join(claudePkg, 'LocalCache', 'Roaming', 'Claude', 'local-agent-mode-sessions');
|
|
216
|
+
if (!fs.existsSync(sessionsRoot)) return results;
|
|
217
|
+
|
|
218
|
+
// Walk two levels: <workspace>/<device>/cowork_settings.json
|
|
219
|
+
for (const ws of fs.readdirSync(sessionsRoot)) {
|
|
220
|
+
const wsDir = path.join(sessionsRoot, ws);
|
|
221
|
+
if (!fs.statSync(wsDir).isDirectory()) continue;
|
|
222
|
+
for (const dev of fs.readdirSync(wsDir)) {
|
|
223
|
+
const devDir = path.join(wsDir, dev);
|
|
224
|
+
if (!fs.statSync(devDir).isDirectory()) continue;
|
|
225
|
+
const settingsFile = path.join(devDir, 'cowork_settings.json');
|
|
226
|
+
if (fs.existsSync(settingsFile)) {
|
|
227
|
+
results.push({ dir: devDir, settingsFile });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function registerCowork(version, gitSha) {
|
|
235
|
+
const bases = findCoworkBases();
|
|
236
|
+
if (bases.length === 0) {
|
|
237
|
+
warn('Cowork — Claude Desktop not found (MSIX package missing)');
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
for (const { dir, settingsFile } of bases) {
|
|
242
|
+
const pluginsDir = path.join(dir, 'cowork_plugins');
|
|
243
|
+
const cacheBase = path.join(pluginsDir, 'cache', MARKETPLACE, 'rdc-skills');
|
|
244
|
+
const cacheDir = path.join(cacheBase, version);
|
|
245
|
+
const latestDir = path.join(cacheBase, 'latest');
|
|
246
|
+
const mktDir = path.join(pluginsDir, 'marketplaces', MARKETPLACE);
|
|
247
|
+
const mktPlugDir = path.join(mktDir, '.claude-plugin');
|
|
248
|
+
|
|
249
|
+
// Marketplace manifest
|
|
250
|
+
fs.mkdirSync(mktPlugDir, { recursive: true });
|
|
251
|
+
fs.copyFileSync(path.join(repoRoot, '.claude-plugin', 'marketplace.json'), path.join(mktPlugDir, 'marketplace.json'));
|
|
252
|
+
|
|
253
|
+
// known_marketplaces.json
|
|
254
|
+
const kmpPath = path.join(pluginsDir, 'known_marketplaces.json');
|
|
255
|
+
const knownMp = readJson(kmpPath);
|
|
256
|
+
knownMp[MARKETPLACE] = { source: { source: 'github', repo: 'LIFEAI/rdc-skills' }, installLocation: mktDir, lastUpdated: new Date().toISOString() };
|
|
257
|
+
writeJson(kmpPath, knownMp, 4);
|
|
258
|
+
|
|
259
|
+
// Flush stale caches, write versioned + stable latest
|
|
260
|
+
flushOldCaches(cacheBase, version);
|
|
261
|
+
buildPluginCache(cacheDir, version, gitSha);
|
|
262
|
+
if (fs.existsSync(latestDir)) fs.rmSync(latestDir, { recursive: true, force: true });
|
|
263
|
+
buildPluginCache(latestDir, version, gitSha);
|
|
264
|
+
|
|
265
|
+
// installed_plugins.json — use stable 'latest/' path
|
|
266
|
+
const ipPath = path.join(pluginsDir, 'installed_plugins.json');
|
|
267
|
+
const installed = readJson(ipPath, { version: 2, plugins: {} });
|
|
268
|
+
for (const key of Object.keys(installed.plugins || {})) {
|
|
269
|
+
if (key.startsWith('rdc-skills@')) delete installed.plugins[key];
|
|
270
|
+
}
|
|
271
|
+
installed.plugins[PLUGIN_KEY] = [{ scope: 'user', installPath: latestDir, version, installedAt: new Date().toISOString(), lastUpdated: new Date().toISOString(), gitCommitSha: gitSha }];
|
|
272
|
+
writeJson(ipPath, installed, 4);
|
|
273
|
+
|
|
274
|
+
// cowork_settings.json — enabledPlugins + extraKnownMarketplaces
|
|
275
|
+
const settings = readJson(settingsFile);
|
|
276
|
+
if (!settings.enabledPlugins) settings.enabledPlugins = {};
|
|
277
|
+
for (const key of Object.keys(settings.enabledPlugins)) {
|
|
278
|
+
if (key.startsWith('rdc-skills@')) delete settings.enabledPlugins[key];
|
|
279
|
+
}
|
|
280
|
+
settings.enabledPlugins[PLUGIN_KEY] = true;
|
|
281
|
+
if (!settings.extraKnownMarketplaces) settings.extraKnownMarketplaces = {};
|
|
282
|
+
settings.extraKnownMarketplaces[MARKETPLACE] = { source: { source: 'github', repo: 'LIFEAI/rdc-skills' } };
|
|
283
|
+
writeJson(settingsFile, settings);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return bases.length;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── Step 6: Zip for claude.ai / distribution ─────────────────────────────────
|
|
290
|
+
function buildZip(version) {
|
|
291
|
+
const distDir = path.join(repoRoot, 'dist');
|
|
292
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
293
|
+
const zipPath = path.join(distDir, `rdc-skills-plugin-v${version}.zip`);
|
|
294
|
+
|
|
295
|
+
// Remove old zips
|
|
296
|
+
if (fs.existsSync(distDir)) {
|
|
297
|
+
for (const f of fs.readdirSync(distDir)) {
|
|
298
|
+
if (f.startsWith('rdc-skills-plugin') && f.endsWith('.zip')) {
|
|
299
|
+
fs.unlinkSync(path.join(distDir, f));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Use PowerShell Compress-Archive on Windows, zip on Unix
|
|
305
|
+
const items = ['.claude-plugin', 'commands', 'skills', 'guides', 'hooks', 'package.json', 'README.md']
|
|
306
|
+
.filter(i => fs.existsSync(path.join(repoRoot, i)));
|
|
307
|
+
|
|
308
|
+
try {
|
|
309
|
+
if (process.platform === 'win32') {
|
|
310
|
+
// Stage to a temp dir so we get a clean zip root
|
|
311
|
+
const tmp = path.join(os.tmpdir(), `rdc-skills-zip-${Date.now()}`);
|
|
312
|
+
fs.mkdirSync(tmp, { recursive: true });
|
|
313
|
+
for (const item of items) {
|
|
314
|
+
const src = path.join(repoRoot, item);
|
|
315
|
+
const dst = path.join(tmp, item);
|
|
316
|
+
if (fs.statSync(src).isDirectory()) copyDirRecursive(src, dst);
|
|
317
|
+
else fs.copyFileSync(src, dst);
|
|
318
|
+
}
|
|
319
|
+
// Try pwsh first (PowerShell 7+), fall back to Windows PowerShell
|
|
320
|
+
const psExe = fs.existsSync('C:\\Program Files\\PowerShell\\7\\pwsh.exe')
|
|
321
|
+
? 'C:\\Program Files\\PowerShell\\7\\pwsh.exe'
|
|
322
|
+
: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
323
|
+
execSync(
|
|
324
|
+
`"${psExe}" -NoProfile -Command "Compress-Archive -Path '${tmp}\\*' -DestinationPath '${zipPath}' -Force"`,
|
|
325
|
+
{ stdio: 'pipe' }
|
|
326
|
+
);
|
|
327
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
328
|
+
} else {
|
|
329
|
+
const itemList = items.join(' ');
|
|
330
|
+
execSync(`zip -r "${zipPath}" ${itemList}`, { cwd: repoRoot, stdio: 'pipe' });
|
|
331
|
+
}
|
|
332
|
+
return zipPath;
|
|
333
|
+
} catch (e) {
|
|
334
|
+
warn(`Zip failed: ${e.message}`);
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ── Hook config ───────────────────────────────────────────────────────────────
|
|
340
|
+
function buildHooksConfig(hooksDir) {
|
|
341
|
+
const base = hooksDir.replace(/\\/g, '/');
|
|
342
|
+
const cmd = (file, msg) => {
|
|
343
|
+
const entry = { type: 'command', command: `node "${base}/${file}"` };
|
|
344
|
+
if (msg) entry.statusMessage = msg;
|
|
345
|
+
return entry;
|
|
346
|
+
};
|
|
347
|
+
return {
|
|
348
|
+
SessionStart: [{ hooks: [
|
|
349
|
+
cmd('check-cwd.js'),
|
|
350
|
+
cmd('check-stale-work-items.js', 'Checking for stale work items...'),
|
|
351
|
+
]}],
|
|
352
|
+
PreToolUse: [{ matcher: 'Bash', hooks: [
|
|
353
|
+
cmd('require-work-item-on-commit.js'),
|
|
354
|
+
]}],
|
|
355
|
+
PostToolUse: [{ hooks: [
|
|
356
|
+
cmd('check-services.js'),
|
|
357
|
+
]}],
|
|
358
|
+
PreCompact: [{ hooks: [
|
|
359
|
+
cmd('precompact-log.js'),
|
|
360
|
+
]}],
|
|
361
|
+
PostCompact: [{ hooks: [
|
|
362
|
+
cmd('postcompact-log.js'),
|
|
363
|
+
cmd('restart-brief.js', 'Writing restart brief...'),
|
|
364
|
+
]}],
|
|
365
|
+
Stop: [{ hooks: [
|
|
366
|
+
cmd('rate-limit-retry.js', 'Checking for rate limits...'),
|
|
367
|
+
cmd('post-work-check.js', 'Checking for undocumented work...'),
|
|
368
|
+
cmd('no-stop-open-epics.js', 'Checking for open epics...'),
|
|
369
|
+
]}],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── Preflight ─────────────────────────────────────────────────────────────────
|
|
374
|
+
function runPreflight() {
|
|
375
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
376
|
+
if (nodeMajor < 18) { fail(`Node.js >= 18 required — found v${process.versions.node}`); process.exit(1); }
|
|
377
|
+
ok(`Node.js v${process.versions.node}`);
|
|
378
|
+
try {
|
|
379
|
+
execSync('curl -s --max-time 2 http://127.0.0.1:52437/ping', { stdio: 'pipe' });
|
|
380
|
+
ok('clauth daemon is running');
|
|
381
|
+
} catch {
|
|
382
|
+
warn('clauth daemon not responding — start it before using credential-dependent commands');
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ── Commands listing ──────────────────────────────────────────────────────────
|
|
387
|
+
function listCommands() {
|
|
388
|
+
const cmdsDir = path.join(repoRoot, 'commands');
|
|
389
|
+
if (!fs.existsSync(cmdsDir)) return;
|
|
390
|
+
const files = fs.readdirSync(cmdsDir).filter(f => f.endsWith('.md')).sort();
|
|
391
|
+
console.log('');
|
|
392
|
+
console.log(` \x1b[32mAvailable /rdc:* commands (${files.length}):\x1b[0m`);
|
|
393
|
+
console.log('');
|
|
394
|
+
const COL = 18;
|
|
395
|
+
for (const f of files) {
|
|
396
|
+
const name = 'rdc:' + f.replace(/\.md$/, '');
|
|
397
|
+
const fm = readFrontmatter(path.join(cmdsDir, f));
|
|
398
|
+
const desc = (fm.description || '').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
|
|
399
|
+
const short = desc.length > 70 ? desc.slice(0, 70) + '…' : desc;
|
|
400
|
+
const pad = ' '.repeat(Math.max(1, COL - name.length));
|
|
401
|
+
console.log(` \x1b[36m/${name}\x1b[0m${pad}${short}`);
|
|
402
|
+
}
|
|
403
|
+
console.log('');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ── Migrate helper ────────────────────────────────────────────────────────────
|
|
407
|
+
async function runMigrate(projectRoot) {
|
|
408
|
+
const absRoot = path.resolve(projectRoot);
|
|
409
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
410
|
+
const ask = q => new Promise(resolve => rl.question(q, resolve));
|
|
411
|
+
|
|
412
|
+
console.log('\n \x1b[35mrdc-skills — Migrate to .rdc/ Layout\x1b[0m\n');
|
|
413
|
+
console.log(` Project root: ${absRoot}\n`);
|
|
414
|
+
|
|
415
|
+
const candidates = [
|
|
416
|
+
{ src: 'docs/guides', dst: '.rdc/guides' }, { src: 'docs/plans', dst: '.rdc/plans' },
|
|
417
|
+
{ src: 'docs/reports', dst: '.rdc/reports' }, { src: 'docs/research', dst: '.rdc/research' },
|
|
418
|
+
].filter(c => fs.existsSync(path.join(absRoot, c.src)));
|
|
419
|
+
|
|
420
|
+
if (candidates.length === 0) { info('No migratable directories found.'); rl.close(); return; }
|
|
421
|
+
console.log(' Found:');
|
|
422
|
+
candidates.forEach(c => console.log(` ${c.src}`));
|
|
423
|
+
console.log('');
|
|
424
|
+
|
|
425
|
+
for (const c of candidates) {
|
|
426
|
+
const srcAbs = path.join(absRoot, c.src);
|
|
427
|
+
const dstAbs = path.join(absRoot, c.dst);
|
|
428
|
+
const ans = await ask(` Move ${c.src} → ${c.dst}? [Y/n]: `);
|
|
429
|
+
if (ans.toLowerCase() === 'n') { info(`Skipped ${c.src}`); continue; }
|
|
430
|
+
if (fs.existsSync(dstAbs)) {
|
|
431
|
+
warn(`${c.dst} exists — merging`);
|
|
432
|
+
for (const f of fs.readdirSync(srcAbs)) {
|
|
433
|
+
const sf = path.join(srcAbs, f), df = path.join(dstAbs, f);
|
|
434
|
+
if (!fs.existsSync(df)) fs.renameSync(sf, df);
|
|
435
|
+
else warn(` Skipped ${f} (exists in dst)`);
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
fs.mkdirSync(path.dirname(dstAbs), { recursive: true });
|
|
439
|
+
fs.renameSync(srcAbs, dstAbs);
|
|
440
|
+
}
|
|
441
|
+
ok(`Moved ${c.src} → ${c.dst}`);
|
|
442
|
+
}
|
|
443
|
+
rl.close();
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
447
|
+
async function main() {
|
|
448
|
+
if (doMigrate) { await runMigrate(migratePath); return; }
|
|
449
|
+
|
|
450
|
+
const bannerVersion = (readJson(path.join(repoRoot, 'package.json')).version || '?');
|
|
451
|
+
const bannerLine = `║ install-rdc-skills v${bannerVersion}`;
|
|
452
|
+
const bannerPadded = bannerLine.padEnd(41) + '║';
|
|
453
|
+
console.log('');
|
|
454
|
+
console.log(' \x1b[32m╔═══════════════════════════════════════╗\x1b[0m');
|
|
455
|
+
console.log(` \x1b[32m${bannerPadded}\x1b[0m`);
|
|
456
|
+
console.log(' \x1b[32m╚═══════════════════════════════════════╝\x1b[0m');
|
|
457
|
+
console.log('');
|
|
458
|
+
console.log(` CLAUDE_HOME : ${claudeHome}`);
|
|
459
|
+
console.log(` Plugin root : ${repoRoot}`);
|
|
460
|
+
console.log('');
|
|
461
|
+
|
|
462
|
+
if (!fs.existsSync(claudeHome)) {
|
|
463
|
+
fail(`CLAUDE_HOME not found: ${claudeHome}`);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Read version + git SHA once
|
|
468
|
+
const pkg = readJson(path.join(repoRoot, 'package.json'));
|
|
469
|
+
const version = pkg.version || '0.7.0';
|
|
470
|
+
let gitSha = '';
|
|
471
|
+
try { gitSha = execSync('git rev-parse HEAD', { cwd: repoRoot, encoding: 'utf8', stdio: 'pipe' }).trim(); } catch {}
|
|
472
|
+
|
|
473
|
+
// 0. Pull latest
|
|
474
|
+
try {
|
|
475
|
+
const before = execSync('git rev-parse HEAD', { cwd: repoRoot, encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
476
|
+
execSync('git pull --ff-only', { cwd: repoRoot, stdio: 'pipe' });
|
|
477
|
+
const after = execSync('git rev-parse HEAD', { cwd: repoRoot, encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
478
|
+
ok(`[0/6] git pull — ${before === after ? after.slice(0, 7) : `${before.slice(0,7)} → ${after.slice(0,7)}`}`);
|
|
479
|
+
} catch {
|
|
480
|
+
warn('[0/6] git pull failed — installing from local copy');
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// 0.5. Legacy cleanup — remove old commands/rdc/ (pre-plugin-system format)
|
|
484
|
+
const legacyCommandsDir = path.join(claudeHome, 'commands', 'rdc');
|
|
485
|
+
if (fs.existsSync(legacyCommandsDir)) {
|
|
486
|
+
fs.rmSync(legacyCommandsDir, { recursive: true, force: true });
|
|
487
|
+
ok('[0.5] Cleanup — removed legacy commands/rdc/');
|
|
488
|
+
}
|
|
489
|
+
// Also remove extraKnownMarketplaces.rdc-skills from settings.json to prevent
|
|
490
|
+
// Claude Code from syncing a directory-source marketplace back to known_marketplaces.json
|
|
491
|
+
// (which causes skills to load from both the cache AND the live source directory)
|
|
492
|
+
{
|
|
493
|
+
const st = readJson(settingsPath);
|
|
494
|
+
if (st.extraKnownMarketplaces && st.extraKnownMarketplaces[MARKETPLACE]) {
|
|
495
|
+
delete st.extraKnownMarketplaces[MARKETPLACE];
|
|
496
|
+
if (Object.keys(st.extraKnownMarketplaces).length === 0) delete st.extraKnownMarketplaces;
|
|
497
|
+
writeJson(settingsPath, st);
|
|
498
|
+
ok('[0.5] Cleanup — removed extraKnownMarketplaces.rdc-skills from settings.json');
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// 1. CLI registration
|
|
503
|
+
const cliCacheDir = registerCLI(version, gitSha);
|
|
504
|
+
ok(`[1/6] CLI plugin — ${PLUGIN_KEY} v${version}`);
|
|
505
|
+
info(` cache : ${cliCacheDir}`);
|
|
506
|
+
|
|
507
|
+
// 2. Cowork registration
|
|
508
|
+
const coworkCount = registerCowork(version, gitSha);
|
|
509
|
+
if (coworkCount > 0) {
|
|
510
|
+
ok(`[2/6] Cowork — registered in ${coworkCount} workspace(s)`);
|
|
511
|
+
} else {
|
|
512
|
+
warn('[2/6] Cowork — no Desktop workspaces found (open Claude Desktop once to create them)');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 3. Hook files
|
|
516
|
+
const hookCount = copyDir(hooksSrc, hooksDst, '.js');
|
|
517
|
+
ok(`[3/6] Hook files — ${hookCount} file(s) → ${hooksDst}`);
|
|
518
|
+
|
|
519
|
+
// 4. Hook wiring
|
|
520
|
+
if (skipHooks) {
|
|
521
|
+
info('[4/6] Hook wiring — skipped (--skip-hooks)');
|
|
522
|
+
} else {
|
|
523
|
+
const settings = readJson(settingsPath);
|
|
524
|
+
settings.hooks = buildHooksConfig(hooksDst);
|
|
525
|
+
writeJson(settingsPath, settings);
|
|
526
|
+
ok(`[4/6] Hook wiring — ${settingsPath}`);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// 5. Zip for claude.ai / distribution
|
|
530
|
+
const zipPath = buildZip(version);
|
|
531
|
+
if (zipPath) {
|
|
532
|
+
ok(`[5/6] Plugin zip — ${zipPath}`);
|
|
533
|
+
info(' claude.ai : upload this zip when prompted to add a plugin');
|
|
534
|
+
} else {
|
|
535
|
+
warn('[5/6] Plugin zip — build failed (zip/powershell missing?)');
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// 6. Preflight
|
|
539
|
+
console.log('');
|
|
540
|
+
console.log(' \x1b[36mPreflight:\x1b[0m');
|
|
541
|
+
runPreflight();
|
|
542
|
+
|
|
543
|
+
// Done
|
|
544
|
+
console.log('');
|
|
545
|
+
console.log(' \x1b[32mDone!\x1b[0m');
|
|
546
|
+
console.log('');
|
|
547
|
+
console.log(' \x1b[33mNext steps:\x1b[0m');
|
|
548
|
+
console.log(' CLI : restart Claude Code — /rdc:status to verify');
|
|
549
|
+
console.log(' Cowork : restart Claude Desktop — /rdc:status in a new Cowork session');
|
|
550
|
+
console.log(' claude.ai : no plugin install needed — use FS MCP to read commands on demand');
|
|
551
|
+
console.log(' dist/rdc-skills-plugin-v' + version + '.zip available if needed');
|
|
552
|
+
|
|
553
|
+
listCommands();
|
|
554
|
+
|
|
555
|
+
console.log(' Docs: https://github.com/LIFEAI/rdc-skills#readme');
|
|
556
|
+
console.log('');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
main().catch(e => { fail(e.message); process.exit(1); });
|