@ericrisco/rsc 0.1.22 → 0.1.24
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/README.md +5 -0
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/doctor.js +9 -0
- package/scripts/install-apply.js +62 -5
- package/scripts/lib/backups.js +154 -0
- package/scripts/lib/upgrade.js +18 -0
- package/scripts/rsc.js +41 -2
package/README.md
CHANGED
|
@@ -150,6 +150,11 @@ rsc consult "I want to launch a SaaS" # recommend only, no install
|
|
|
150
150
|
rsc registry refresh # write .rsc/skill-registry.{json,md}
|
|
151
151
|
rsc list # what rsc has installed
|
|
152
152
|
rsc doctor # health check (state, hook, counts)
|
|
153
|
+
rsc sync --target claude,codex # refresh managed skills/hooks from the current package version
|
|
154
|
+
rsc backups # list project-local snapshots
|
|
155
|
+
rsc restore latest --dry-run # preview restoring the newest snapshot
|
|
156
|
+
rsc restore <snapshot-id> # restore a project-local snapshot
|
|
157
|
+
rsc upgrade --dry-run # show npm upgrade + sync commands
|
|
153
158
|
rsc uninstall postgresdb --dry-run # preview a removal
|
|
154
159
|
```
|
|
155
160
|
|
package/manifest.json
CHANGED
package/package.json
CHANGED
package/scripts/doctor.js
CHANGED
|
@@ -1,18 +1,27 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
2
3
|
import { targetPaths } from '../targets/index.js';
|
|
3
4
|
import { readState } from './lib/state.js';
|
|
4
5
|
import { loadManifest } from './lib/manifest.js';
|
|
6
|
+
import { listBackups } from './lib/backups.js';
|
|
5
7
|
|
|
6
8
|
export function doctor({ target, home, cwd }) {
|
|
9
|
+
const root = cwd || process.cwd();
|
|
7
10
|
const paths = targetPaths(target, home, cwd);
|
|
8
11
|
const state = readState(paths.stateFile);
|
|
9
12
|
const manifest = loadManifest();
|
|
13
|
+
const backups = listBackups({ cwd: root });
|
|
10
14
|
const report = {
|
|
11
15
|
target,
|
|
12
16
|
installed: Object.keys(state.skills),
|
|
13
17
|
missing: [],
|
|
14
18
|
hookWired: existsSync(paths.hookTarget),
|
|
15
19
|
manifestSkills: manifest.counts.skills,
|
|
20
|
+
backups: {
|
|
21
|
+
exists: existsSync(join(root, '.rsc', 'backups')),
|
|
22
|
+
count: backups.length,
|
|
23
|
+
latest: backups[0]?.id || null,
|
|
24
|
+
},
|
|
16
25
|
};
|
|
17
26
|
for (const [id, e] of Object.entries(state.skills)) {
|
|
18
27
|
for (const f of e.files) if (!existsSync(f)) report.missing.push(`${id}:${f}`);
|
package/scripts/install-apply.js
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { planInstall } from './install-plan.js';
|
|
6
6
|
import { targetPaths, writeSkill, wireHook, unwireHook, baseDir, TARGET_IDS } from '../targets/index.js';
|
|
7
7
|
import { readState, writeState } from './lib/state.js';
|
|
8
|
+
import { createBackup } from './lib/backups.js';
|
|
8
9
|
|
|
9
10
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
11
|
const CLI_VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
|
|
@@ -32,10 +33,37 @@ function ensureBase(id, cwd, refresh) {
|
|
|
32
33
|
return dest;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
function generatedHookFiles({ target, cwd }) {
|
|
37
|
+
if (target !== 'claude') return [];
|
|
38
|
+
return [
|
|
39
|
+
join(cwd, '.rsc', 'session-start.mjs'),
|
|
40
|
+
join(cwd, '.rsc', 'worklog-checkpoint.mjs'),
|
|
41
|
+
join(cwd, '.rsc', 'ship-guard.mjs'),
|
|
42
|
+
join(cwd, '.rsc', 'danger-guard.mjs'),
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function managedPathsForInstall({ skillIds, target, home, cwd }) {
|
|
47
|
+
const paths = targetPaths(target, home, cwd);
|
|
48
|
+
const plan = planInstall({ skillIds, target, home, cwd });
|
|
49
|
+
const out = [paths.stateFile, versionFile(cwd)];
|
|
50
|
+
for (const step of plan) {
|
|
51
|
+
if (step.kind === 'skill') {
|
|
52
|
+
out.push(step.to, baseDir(step.id, cwd));
|
|
53
|
+
} else if (step.kind === 'hook') {
|
|
54
|
+
out.push(step.to, ...generatedHookFiles({ target, cwd }));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return [...new Set(out)];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function applyInstall({ skillIds, target, home, cwd = process.cwd(), operation = 'install', dryRun = false }) {
|
|
36
61
|
const paths = targetPaths(target, home, cwd);
|
|
37
62
|
const plan = planInstall({ skillIds, target, home, cwd });
|
|
63
|
+
const managedPaths = managedPathsForInstall({ skillIds, target, home, cwd });
|
|
64
|
+
if (dryRun) return { dryRun: true, skills: skillIds, paths: managedPaths };
|
|
38
65
|
const state = readState(paths.stateFile);
|
|
66
|
+
const backup = createBackup({ cwd, operation, target, paths: managedPaths, cliVersion: CLI_VERSION });
|
|
39
67
|
// Refresh bases when installing a different version than they were materialized at
|
|
40
68
|
// (or a pre-versioning install where the marker is absent). Same version → no-op.
|
|
41
69
|
const refresh = readBaseVersion(cwd) !== CLI_VERSION;
|
|
@@ -52,7 +80,7 @@ export async function applyInstall({ skillIds, target, home, cwd = process.cwd()
|
|
|
52
80
|
writeState(paths.stateFile, state);
|
|
53
81
|
mkdirSync(dirname(versionFile(cwd)), { recursive: true });
|
|
54
82
|
writeFileSync(versionFile(cwd), CLI_VERSION + '\n');
|
|
55
|
-
return state;
|
|
83
|
+
return { ...state, backup };
|
|
56
84
|
}
|
|
57
85
|
|
|
58
86
|
export function listInstalled({ target, home, cwd = process.cwd() }) {
|
|
@@ -64,23 +92,52 @@ export async function uninstall({ skillIds, target, home, cwd = process.cwd(), d
|
|
|
64
92
|
const paths = targetPaths(target, home, cwd);
|
|
65
93
|
const state = readState(paths.stateFile);
|
|
66
94
|
const removed = [];
|
|
95
|
+
const managedPaths = [paths.stateFile];
|
|
67
96
|
for (const id of skillIds) {
|
|
68
97
|
const entry = state.skills[id];
|
|
69
98
|
if (!entry) continue;
|
|
70
99
|
for (const f of entry.files) {
|
|
100
|
+
managedPaths.push(f);
|
|
71
101
|
removed.push(f);
|
|
72
|
-
if (!dryRun && existsSync(f)) rmSync(f, { recursive: true, force: true });
|
|
73
102
|
}
|
|
74
|
-
if (!dryRun) delete state.skills[id];
|
|
75
103
|
}
|
|
76
|
-
if (!
|
|
104
|
+
if (!removed.length) return removed;
|
|
105
|
+
if (dryRun) return removed;
|
|
106
|
+
createBackup({ cwd, operation: 'uninstall', target, paths: managedPaths, cliVersion: CLI_VERSION });
|
|
107
|
+
for (const id of skillIds) {
|
|
108
|
+
const entry = state.skills[id];
|
|
109
|
+
if (!entry) continue;
|
|
110
|
+
for (const f of entry.files) {
|
|
111
|
+
if (existsSync(f)) rmSync(f, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
delete state.skills[id];
|
|
114
|
+
}
|
|
115
|
+
writeState(paths.stateFile, state);
|
|
77
116
|
return removed;
|
|
78
117
|
}
|
|
79
118
|
|
|
119
|
+
export async function syncInstalled({ target, home, cwd = process.cwd(), dryRun = false }) {
|
|
120
|
+
const paths = targetPaths(target, home, cwd);
|
|
121
|
+
const state = readState(paths.stateFile);
|
|
122
|
+
const ids = Object.keys(state.skills || {});
|
|
123
|
+
if (!ids.length) return dryRun ? { dryRun: true, synced: [], paths: [] } : { synced: [], backup: null };
|
|
124
|
+
if (dryRun) {
|
|
125
|
+
return {
|
|
126
|
+
dryRun: true,
|
|
127
|
+
synced: ids,
|
|
128
|
+
paths: managedPathsForInstall({ skillIds: ids, target, home, cwd }),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const nextState = await applyInstall({ skillIds: ids, target, home, cwd, operation: 'sync' });
|
|
132
|
+
return { synced: ids, backup: nextState.backup };
|
|
133
|
+
}
|
|
134
|
+
|
|
80
135
|
// Remove EVERYTHING rsc put in this project: installed skills across all targets,
|
|
81
136
|
// the wired hooks (settings.json entries / AGENTS-blocks / cursor rules), and the
|
|
82
137
|
// shared `.rsc/` (base + hook scripts + version marker). `02-DOCS/` is the user's
|
|
83
138
|
// own knowledge — kept unless `withDocs` is set. Returns the paths touched.
|
|
139
|
+
// Note: backups live under `.rsc/backups/`, which this removes — so purge does not
|
|
140
|
+
// snapshot (a pre-purge backup would delete itself). It is the deliberate escape hatch.
|
|
84
141
|
export async function purge({ home, cwd = process.cwd(), withDocs = false, dryRun = false } = {}) {
|
|
85
142
|
const removed = [];
|
|
86
143
|
const drop = (p, recursive = false) => {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cpSync,
|
|
3
|
+
existsSync,
|
|
4
|
+
lstatSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readlinkSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
symlinkSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { dirname, isAbsolute, join, relative, sep } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const SCHEMA_VERSION = 1;
|
|
16
|
+
|
|
17
|
+
export function backupsDir(cwd = process.cwd()) {
|
|
18
|
+
return join(cwd, '.rsc', 'backups');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createBackup({ cwd = process.cwd(), operation, target, paths, cliVersion, now = new Date() }) {
|
|
22
|
+
const uniquePaths = [...new Set((paths || []).filter(Boolean))];
|
|
23
|
+
const id = uniqueSnapshotId({ cwd, now, operation, target });
|
|
24
|
+
const root = join(backupsDir(cwd), id);
|
|
25
|
+
const filesRoot = join(root, 'files');
|
|
26
|
+
mkdirSync(filesRoot, { recursive: true });
|
|
27
|
+
|
|
28
|
+
const entries = uniquePaths.map((absPath) => snapshotEntry({ cwd, root, absPath }));
|
|
29
|
+
const manifest = {
|
|
30
|
+
schemaVersion: SCHEMA_VERSION,
|
|
31
|
+
id,
|
|
32
|
+
createdAt: now.toISOString(),
|
|
33
|
+
operation,
|
|
34
|
+
target,
|
|
35
|
+
cwd,
|
|
36
|
+
cliVersion,
|
|
37
|
+
entries,
|
|
38
|
+
};
|
|
39
|
+
writeFileSync(join(root, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
40
|
+
return manifest;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function listBackups({ cwd = process.cwd() } = {}) {
|
|
44
|
+
const dir = backupsDir(cwd);
|
|
45
|
+
if (!existsSync(dir)) return [];
|
|
46
|
+
return readdirSync(dir)
|
|
47
|
+
.map((id) => readManifest({ cwd, id }))
|
|
48
|
+
.filter(Boolean)
|
|
49
|
+
.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function restoreBackup({ cwd = process.cwd(), id, dryRun = false }) {
|
|
53
|
+
const snapshot = resolveSnapshot({ cwd, id });
|
|
54
|
+
const changed = [];
|
|
55
|
+
for (const entry of snapshot.entries) {
|
|
56
|
+
const absPath = safeJoin(cwd, entry.path);
|
|
57
|
+
changed.push(absPath);
|
|
58
|
+
if (dryRun) continue;
|
|
59
|
+
restoreEntry({ cwd, snapshot, entry, absPath });
|
|
60
|
+
}
|
|
61
|
+
return { snapshot, changed };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function snapshotEntry({ cwd, root, absPath }) {
|
|
65
|
+
const rel = safeRelative(cwd, absPath);
|
|
66
|
+
if (!existsSync(absPath)) return { path: rel, existed: false, kind: 'missing' };
|
|
67
|
+
|
|
68
|
+
const stat = lstatSync(absPath);
|
|
69
|
+
if (stat.isSymbolicLink()) {
|
|
70
|
+
return { path: rel, existed: true, kind: 'symlink', linkTarget: readlinkSync(absPath) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const contentPath = join('files', rel);
|
|
74
|
+
const contentAbs = join(root, contentPath);
|
|
75
|
+
mkdirSync(dirname(contentAbs), { recursive: true });
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
cpSync(absPath, contentAbs, { recursive: true });
|
|
78
|
+
return { path: rel, existed: true, kind: 'dir', contentPath };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
cpSync(absPath, contentAbs);
|
|
82
|
+
return { path: rel, existed: true, kind: 'file', contentPath };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function restoreEntry({ cwd, snapshot, entry, absPath }) {
|
|
86
|
+
if (!entry.existed) {
|
|
87
|
+
rmSync(absPath, { recursive: true, force: true });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
rmSync(absPath, { recursive: true, force: true });
|
|
92
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
93
|
+
if (entry.kind === 'symlink') {
|
|
94
|
+
symlinkSync(entry.linkTarget, absPath, process.platform === 'win32' ? 'junction' : undefined);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const contentAbs = join(backupsDir(cwd), snapshot.id, entry.contentPath);
|
|
99
|
+
cpSync(contentAbs, absPath, { recursive: entry.kind === 'dir' });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function resolveSnapshot({ cwd, id }) {
|
|
103
|
+
if (!id) throw new Error('restore requires a snapshot id or latest');
|
|
104
|
+
if (id === 'latest') {
|
|
105
|
+
const latest = listBackups({ cwd })[0];
|
|
106
|
+
if (!latest) throw new Error('no backups found');
|
|
107
|
+
return latest;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const manifest = readManifest({ cwd, id });
|
|
111
|
+
if (!manifest) throw new Error(`backup not found: ${id}`);
|
|
112
|
+
return manifest;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readManifest({ cwd, id }) {
|
|
116
|
+
const path = join(backupsDir(cwd), id, 'manifest.json');
|
|
117
|
+
if (!existsSync(path)) return undefined;
|
|
118
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function uniqueSnapshotId({ cwd, now, operation, target }) {
|
|
122
|
+
const base = snapshotId({ now, operation, target });
|
|
123
|
+
let id = base;
|
|
124
|
+
let counter = 2;
|
|
125
|
+
while (existsSync(join(backupsDir(cwd), id))) {
|
|
126
|
+
id = `${base}-${counter}`;
|
|
127
|
+
counter += 1;
|
|
128
|
+
}
|
|
129
|
+
return id;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function snapshotId({ now, operation, target }) {
|
|
133
|
+
const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\..*/, '').replace('T', '-');
|
|
134
|
+
return `${stamp}-${safeId(operation)}-${safeId(target || 'all')}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function safeId(value) {
|
|
138
|
+
return String(value).replace(/[^a-z0-9._-]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function safeRelative(cwd, absPath) {
|
|
142
|
+
const rel = relative(cwd, absPath);
|
|
143
|
+
if (!rel || rel.startsWith('..') || rel.split(sep).includes('..')) {
|
|
144
|
+
throw new Error(`path is outside project root: ${absPath}`);
|
|
145
|
+
}
|
|
146
|
+
return rel.split(sep).join('/');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function safeJoin(cwd, relPath) {
|
|
150
|
+
if (!relPath || isAbsolute(relPath) || relPath.split('/').includes('..')) {
|
|
151
|
+
throw new Error(`path is outside project root: ${relPath}`);
|
|
152
|
+
}
|
|
153
|
+
return join(cwd, ...relPath.split('/'));
|
|
154
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export function upgradePlan({ targets = [] } = {}) {
|
|
4
|
+
const targetArg = targets.length ? targets.join(',') : '<target>';
|
|
5
|
+
return {
|
|
6
|
+
installCommand: 'npm install -g @ericrisco/rsc@latest',
|
|
7
|
+
syncCommand: `rsc sync --target ${targetArg}`,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function runUpgrade({ targets = [], dryRun = false, global = false } = {}) {
|
|
12
|
+
const plan = upgradePlan({ targets });
|
|
13
|
+
if (dryRun || !global) return { ran: false, plan };
|
|
14
|
+
|
|
15
|
+
const result = spawnSync('npm', ['install', '-g', '@ericrisco/rsc@latest'], { stdio: 'inherit' });
|
|
16
|
+
if (result.status !== 0) throw new Error('npm global upgrade failed');
|
|
17
|
+
return { ran: true, plan };
|
|
18
|
+
}
|
package/scripts/rsc.js
CHANGED
|
@@ -4,12 +4,14 @@ 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, purge } from './install-apply.js';
|
|
7
|
+
import { applyInstall, listInstalled, uninstall, syncInstalled, 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';
|
|
11
11
|
import { audit, writeAuditReport } from './audit.js';
|
|
12
12
|
import { DOMAINS } from './lib/domains.js';
|
|
13
|
+
import { listBackups, restoreBackup } from './lib/backups.js';
|
|
14
|
+
import { runUpgrade } from './lib/upgrade.js';
|
|
13
15
|
|
|
14
16
|
const argv = process.argv.slice(2);
|
|
15
17
|
const cmd = argv[0];
|
|
@@ -200,6 +202,43 @@ async function main() {
|
|
|
200
202
|
return void say(listInstalled({ target }).join('\n') || '(nothing installed)');
|
|
201
203
|
case 'doctor':
|
|
202
204
|
return void say(JSON.stringify(doctor({ target }), null, 2));
|
|
205
|
+
case 'sync': {
|
|
206
|
+
const dry = argv.includes('--dry-run');
|
|
207
|
+
for (const t of targets) {
|
|
208
|
+
const result = await syncInstalled({ target: t, dryRun: dry });
|
|
209
|
+
const verb = dry ? 'Would sync' : 'Synced';
|
|
210
|
+
say(`${verb} ${t}: ${result.synced.length ? result.synced.join(', ') : '(nothing to sync)'}`);
|
|
211
|
+
if (dry && result.paths?.length) {
|
|
212
|
+
for (const p of result.paths) say(` ${p}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
case 'backups': {
|
|
218
|
+
const backups = listBackups();
|
|
219
|
+
if (!backups.length) return void say('(no backups)');
|
|
220
|
+
for (const b of backups) {
|
|
221
|
+
say(`${b.id}\t${b.operation}\t${b.target}\t${b.entries.length} files\t${b.createdAt}`);
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
case 'restore': {
|
|
226
|
+
const dry = argv.includes('--dry-run');
|
|
227
|
+
const id = argv.slice(1).find((a) => !a.startsWith('--'));
|
|
228
|
+
const result = restoreBackup({ id, dryRun: dry });
|
|
229
|
+
say(`${dry ? 'Would restore' : 'Restored'} ${result.snapshot.id}`);
|
|
230
|
+
for (const p of result.changed) say(` ${p}`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
case 'upgrade': {
|
|
234
|
+
const dry = argv.includes('--dry-run');
|
|
235
|
+
const global = argv.includes('--global');
|
|
236
|
+
const result = runUpgrade({ targets, dryRun: dry, global });
|
|
237
|
+
if (result.ran) say('Upgraded global @ericrisco/rsc. Restart your shell if needed.');
|
|
238
|
+
else say(`${dry ? 'Would run' : 'Upgrade guide'}: ${result.plan.installCommand}`);
|
|
239
|
+
say(`After upgrade: ${result.plan.syncCommand}`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
203
242
|
case 'registry': {
|
|
204
243
|
const sub = argv[1];
|
|
205
244
|
if (sub === 'refresh') {
|
|
@@ -226,7 +265,7 @@ async function main() {
|
|
|
226
265
|
return void (await runPurge(argv.includes('--dry-run'), argv.includes('--with-docs')));
|
|
227
266
|
default:
|
|
228
267
|
say(`rsc: unknown command '${cmd}'.`);
|
|
229
|
-
say('Use: npx @ericrisco/rsc | add <id...> | install --profile <p> | consult "<text>" | list | audit | registry refresh | doctor | uninstall <id> | purge');
|
|
268
|
+
say('Use: npx @ericrisco/rsc | add <id...> | install --profile <p> | consult "<text>" | list | audit | registry refresh | doctor | sync | backups | restore <id|latest> | upgrade | uninstall <id> | purge');
|
|
230
269
|
}
|
|
231
270
|
}
|
|
232
271
|
|