@ericrisco/rsc 0.1.19 → 0.1.21
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/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/install-apply.js +29 -1
- package/scripts/rsc.js +15 -2
- package/targets/_md-block.js +13 -0
- package/targets/claude.js +23 -0
- package/targets/cursor.js +8 -1
- package/targets/index.js +10 -0
package/manifest.json
CHANGED
package/package.json
CHANGED
package/scripts/install-apply.js
CHANGED
|
@@ -3,7 +3,7 @@ import { rmSync, existsSync, cpSync, mkdirSync, readFileSync, writeFileSync } fr
|
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { planInstall } from './install-plan.js';
|
|
6
|
-
import { targetPaths, writeSkill, wireHook, baseDir } from '../targets/index.js';
|
|
6
|
+
import { targetPaths, writeSkill, wireHook, unwireHook, baseDir, TARGET_IDS } from '../targets/index.js';
|
|
7
7
|
import { readState, writeState } from './lib/state.js';
|
|
8
8
|
|
|
9
9
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
@@ -77,6 +77,34 @@ export async function uninstall({ skillIds, target, home, cwd = process.cwd(), d
|
|
|
77
77
|
return removed;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
// Remove EVERYTHING rsc put in this project: installed skills across all targets,
|
|
81
|
+
// the wired hooks (settings.json entries / AGENTS-blocks / cursor rules), and the
|
|
82
|
+
// shared `.rsc/` (base + hook scripts + version marker). `02-DOCS/` is the user's
|
|
83
|
+
// own knowledge — kept unless `withDocs` is set. Returns the paths touched.
|
|
84
|
+
export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRun = false } = {}) {
|
|
85
|
+
const removed = [];
|
|
86
|
+
const drop = (p, recursive = false) => {
|
|
87
|
+
if (!existsSync(p)) return;
|
|
88
|
+
removed.push(p);
|
|
89
|
+
if (!dryRun) rmSync(p, { recursive, force: true });
|
|
90
|
+
};
|
|
91
|
+
for (const target of TARGET_IDS) {
|
|
92
|
+
const paths = targetPaths(target, home, cwd);
|
|
93
|
+
if (existsSync(paths.stateFile)) {
|
|
94
|
+
const state = readState(paths.stateFile);
|
|
95
|
+
for (const id of Object.keys(state.skills || {})) {
|
|
96
|
+
for (const f of state.skills[id].files || []) drop(f, true);
|
|
97
|
+
}
|
|
98
|
+
drop(paths.stateFile);
|
|
99
|
+
}
|
|
100
|
+
// unwireHook mutates files, so only run it for real (dry runs skip it).
|
|
101
|
+
if (!dryRun) removed.push(...unwireHook(target, paths));
|
|
102
|
+
}
|
|
103
|
+
drop(join(cwd, '.rsc'), true);
|
|
104
|
+
if (withDocs) drop(join(cwd, '02-DOCS'), true);
|
|
105
|
+
return removed;
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
81
109
|
const ids = process.argv.slice(2);
|
|
82
110
|
applyInstall({ skillIds: ids, target: 'claude' }).then(() => console.log('installed', ids.join(', ')));
|
package/scripts/rsc.js
CHANGED
|
@@ -4,7 +4,7 @@ import { detectTarget, TARGETS } from '../targets/index.js';
|
|
|
4
4
|
import { detectRepo } from './detect-repo.js';
|
|
5
5
|
import { rank } from './consult.js';
|
|
6
6
|
import { expandRecommends, toOutcomes, hasOutcome } from './lib/recommend.js';
|
|
7
|
-
import { applyInstall, listInstalled, uninstall } from './install-apply.js';
|
|
7
|
+
import { applyInstall, listInstalled, uninstall, purge } from './install-apply.js';
|
|
8
8
|
import { doctor } from './doctor.js';
|
|
9
9
|
import { say, select, pickFrom, banner, confirm } from './lib/ui.js';
|
|
10
10
|
import { refreshRegistry, registryStatus } from './lib/registry.js';
|
|
@@ -19,6 +19,15 @@ function flag(name) {
|
|
|
19
19
|
return i >= 0 ? (argv[i + 1] || true) : undefined;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// Remove everything rsc installed in this project (skills, hooks, .rsc/), across
|
|
23
|
+
// every assistant. Keeps 02-DOCS/ unless --with-docs. `purge` / `uninstall --all`.
|
|
24
|
+
async function runPurge(dryRun, withDocs) {
|
|
25
|
+
const removed = await purge({ cwd: process.cwd(), withDocs, dryRun });
|
|
26
|
+
say(`${dryRun ? 'Would remove' : 'Removed'} ${removed.length} path(s):`);
|
|
27
|
+
for (const r of removed) say(` - ${r}`);
|
|
28
|
+
if (!withDocs) say('\nKept 02-DOCS/ (your knowledge base). Add --with-docs to remove it too.');
|
|
29
|
+
}
|
|
30
|
+
|
|
22
31
|
async function recommendIds(query, { labeledOnly = false } = {}) {
|
|
23
32
|
const m = loadManifest();
|
|
24
33
|
const repo = detectRepo();
|
|
@@ -207,13 +216,17 @@ async function main() {
|
|
|
207
216
|
}
|
|
208
217
|
case 'uninstall': {
|
|
209
218
|
const dry = argv.includes('--dry-run');
|
|
219
|
+
// `uninstall --all` is an alias for a full purge.
|
|
220
|
+
if (argv.includes('--all')) return void (await runPurge(dry, argv.includes('--with-docs')));
|
|
210
221
|
const ids = argv.slice(1).filter((a) => !a.startsWith('--'));
|
|
211
222
|
const removed = await uninstall({ skillIds: ids, target, dryRun: dry });
|
|
212
223
|
return void say((dry ? 'Would remove:\n' : 'Removed:\n') + (removed.join('\n') || '(nothing)'));
|
|
213
224
|
}
|
|
225
|
+
case 'purge':
|
|
226
|
+
return void (await runPurge(argv.includes('--dry-run'), argv.includes('--with-docs')));
|
|
214
227
|
default:
|
|
215
228
|
say(`rsc: unknown command '${cmd}'.`);
|
|
216
|
-
say('Use: npx @ericrisco/rsc | add <id...> | install --profile <p> | consult "<text>" | list | audit | registry refresh | doctor | uninstall <id>');
|
|
229
|
+
say('Use: npx @ericrisco/rsc | add <id...> | install --profile <p> | consult "<text>" | list | audit | registry refresh | doctor | uninstall <id> | purge');
|
|
217
230
|
}
|
|
218
231
|
}
|
|
219
232
|
|
package/targets/_md-block.js
CHANGED
|
@@ -28,6 +28,19 @@ export function wireHook(paths, sourceMd) {
|
|
|
28
28
|
return [paths.hookTarget];
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Inverse of wireHook: remove the marked rsc-suggest block from the shared
|
|
32
|
+
// instructions file, leaving the user's own content intact. No-op when absent.
|
|
33
|
+
export function unwireHook(paths) {
|
|
34
|
+
if (!existsSync(paths.hookTarget)) return [];
|
|
35
|
+
const doc = readFileSync(paths.hookTarget, 'utf8');
|
|
36
|
+
if (!doc.includes(MARK_START)) return [];
|
|
37
|
+
const cleaned = doc
|
|
38
|
+
.replace(new RegExp(`\\n*${MARK_START}[\\s\\S]*?${MARK_END}\\n*`), '\n')
|
|
39
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
40
|
+
writeFileSync(paths.hookTarget, cleaned);
|
|
41
|
+
return [paths.hookTarget];
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
function stripFrontmatter(md) {
|
|
32
45
|
return md.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
33
46
|
}
|
package/targets/claude.js
CHANGED
|
@@ -14,6 +14,29 @@ export function writeSkill(id, fromDir, toPath) {
|
|
|
14
14
|
return linkOrCopy(fromDir, toPath);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// Inverse of wireHook: drop every rsc-wired hook entry (any command pointing at a
|
|
18
|
+
// .rsc/ script — session-start, worklog-checkpoint, ship-guard, danger-guard, … —
|
|
19
|
+
// plus the legacy cat-form) from settings.json, across all events. User hooks and
|
|
20
|
+
// other settings are preserved. Empty event arrays (and an empty hooks object) are
|
|
21
|
+
// pruned so we don't leave noise behind.
|
|
22
|
+
export function unwireHook(paths) {
|
|
23
|
+
const file = paths.hookTarget;
|
|
24
|
+
if (!existsSync(file)) return [];
|
|
25
|
+
let settings;
|
|
26
|
+
try { settings = JSON.parse(readFileSync(file, 'utf8')); } catch { return []; }
|
|
27
|
+
if (!settings.hooks) return [];
|
|
28
|
+
for (const event of Object.keys(settings.hooks)) {
|
|
29
|
+
settings.hooks[event] = (settings.hooks[event] || []).filter((e) => {
|
|
30
|
+
const s = JSON.stringify(e);
|
|
31
|
+
return !s.includes('.rsc/') && !s.includes('skills/rsc/suggest');
|
|
32
|
+
});
|
|
33
|
+
if (settings.hooks[event].length === 0) delete settings.hooks[event];
|
|
34
|
+
}
|
|
35
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
36
|
+
writeFileSync(file, JSON.stringify(settings, null, 2) + '\n');
|
|
37
|
+
return [file];
|
|
38
|
+
}
|
|
39
|
+
|
|
17
40
|
// SessionStart runs a project-local session-start.mjs via `node`: it prints
|
|
18
41
|
// suggest's always-on body, an onboarding banner when the workspace has no harness
|
|
19
42
|
// profile yet, and an auto-ingest nudge when the inbox has un-ingested material. We
|
package/targets/cursor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'node:fs';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
|
|
4
4
|
export function writeSkill(id, fromDir, toPath) {
|
|
@@ -15,6 +15,13 @@ export function wireHook(paths, sourceMd) {
|
|
|
15
15
|
return [paths.hookTarget];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Inverse of wireHook: the cursor detector is its own file, so just remove it.
|
|
19
|
+
export function unwireHook(paths) {
|
|
20
|
+
if (!existsSync(paths.hookTarget)) return [];
|
|
21
|
+
rmSync(paths.hookTarget, { force: true });
|
|
22
|
+
return [paths.hookTarget];
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
function stripFrontmatter(md) {
|
|
19
26
|
return md.replace(/^---\n[\s\S]*?\n---\n?/, '');
|
|
20
27
|
}
|
package/targets/index.js
CHANGED
|
@@ -125,3 +125,13 @@ export function writeSkill(target, id, fromDir, toPath) {
|
|
|
125
125
|
export function wireHook(target, paths, sourceMd) {
|
|
126
126
|
return ADAPTER[SPEC[target].adapter].wireHook(paths, sourceMd);
|
|
127
127
|
}
|
|
128
|
+
|
|
129
|
+
// Inverse of wireHook — remove rsc's always-on surface for a target (settings.json
|
|
130
|
+
// hook entries / AGENTS-block / cursor rule file). Returns the paths it touched.
|
|
131
|
+
export function unwireHook(target, paths) {
|
|
132
|
+
const adapter = ADAPTER[SPEC[target].adapter];
|
|
133
|
+
return adapter.unwireHook ? adapter.unwireHook(paths) : [];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Every known target id — used by `purge` to sweep all assistants, installed or not.
|
|
137
|
+
export const TARGET_IDS = Object.keys(SPEC);
|