version-history-widget 0.1.4 → 0.1.6
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 +22 -0
- package/middleware.js +9 -2
- package/package.json +1 -1
- package/skill/SKILL.md +51 -0
- package/vh.js +155 -16
package/README.md
CHANGED
|
@@ -21,6 +21,27 @@ Or use without installing:
|
|
|
21
21
|
npx version-history-widget init
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
## Auto-recording (so nothing is ever missed)
|
|
25
|
+
|
|
26
|
+
A version only exists if something records it. Rather than relying on you
|
|
27
|
+
(or your AI assistant) to remember, let it record on its own:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx vh watch # auto-saves every change as a new version, debounced
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Run that alongside whatever dev server you use — Vite, Next, Astro, plain
|
|
34
|
+
HTML, anything. A burst of edits becomes one version, not twenty.
|
|
35
|
+
|
|
36
|
+
You usually don't even need to run it: **`.versions/middleware.js` starts the
|
|
37
|
+
same watcher automatically whenever it's mounted**, so `node .versions/serve.js`
|
|
38
|
+
or a dev server with the middleware mounted records changes on its own. For
|
|
39
|
+
Vite projects, `vh init` writes a `vite.config.js` that mounts it for you (an
|
|
40
|
+
existing config is never modified — it just prints the snippet to add).
|
|
41
|
+
|
|
42
|
+
Explicitly running `vh record "Some title"` is still worth it when you want a
|
|
43
|
+
meaningful title instead of an auto-generated "Updated src/main.js".
|
|
44
|
+
|
|
24
45
|
## Project mode
|
|
25
46
|
|
|
26
47
|
```bash
|
|
@@ -28,6 +49,7 @@ npx vh init # sets up .versions/, injects the widget, saves vers
|
|
|
28
49
|
npx vh record "Title" -d "what changed and why"
|
|
29
50
|
npx vh restore 4 # safety-snapshots current state, then restores version 4
|
|
30
51
|
npx vh list [query] # list versions, optionally filtered
|
|
52
|
+
npx vh watch # auto-save every change as a new version
|
|
31
53
|
npx vh update # refresh .versions/*.js from the currently installed package
|
|
32
54
|
```
|
|
33
55
|
|
package/middleware.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
/* Connect/Express/Vite-compatible middleware: serves /.versions
|
|
1
|
+
/* Connect/Express/Vite-compatible middleware: serves /.versions/*, handles POST /__vh/restore/:id,
|
|
2
|
+
and auto-records every change as a new version while the dev server is running.
|
|
2
3
|
Express: app.use(require('./.versions/middleware.js')())
|
|
3
4
|
Vite: plugins:[{ name:'vh', configureServer(s){ s.middlewares.use(require('./.versions/middleware.js')()) } }]
|
|
4
|
-
Next: use serve.js alongside, or add a route handler that calls require('./.versions/vh.js').restore(id)
|
|
5
|
+
Next: use serve.js alongside, or add a route handler that calls require('./.versions/vh.js').restore(id)
|
|
6
|
+
Pass { watch: false } to opt out of auto-recording. */
|
|
5
7
|
const fs = require('fs'), path = require('path');
|
|
6
8
|
module.exports = function (opts = {}) {
|
|
7
9
|
const root = path.resolve(opts.root || process.env.VH_ROOT || process.cwd()), vdir = path.join(root, '.versions');
|
|
8
10
|
const types = { '.js': 'text/javascript', '.json': 'application/json', '.diff': 'text/plain' };
|
|
11
|
+
if (opts.watch !== false && fs.existsSync(vdir)) {
|
|
12
|
+
process.env.VH_ROOT = root; // vh.js resolves its root from this at require time
|
|
13
|
+
try { require(path.join(vdir, 'vh.js')).watch({ keepAlive: false }); }
|
|
14
|
+
catch (e) { console.error('[vh] auto-record unavailable: ' + e.message); }
|
|
15
|
+
}
|
|
9
16
|
return function (req, res, next) {
|
|
10
17
|
const url = (req.url || '').split('?')[0];
|
|
11
18
|
if (req.method === 'POST' && url.startsWith('/__vh/restore/')) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "version-history-widget",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Zero-dependency version history for web projects: snapshots every change and adds a floating widget to browse, search, and restore any version.",
|
|
5
5
|
"keywords": ["version history", "undo", "snapshot", "restore", "dev-tool", "widget"],
|
|
6
6
|
"license": "MIT",
|
package/skill/SKILL.md
CHANGED
|
@@ -14,6 +14,23 @@ Two modes — pick based on what the user is working on:
|
|
|
14
14
|
- **Project mode**: a normal multi-file site/app. History lives in `.versions/`.
|
|
15
15
|
- **Single-file mode**: one standalone `.html` file. History lives inside that file.
|
|
16
16
|
|
|
17
|
+
## Record after you change things
|
|
18
|
+
|
|
19
|
+
**If a project has a `.versions/` directory, it has version history enabled,
|
|
20
|
+
and every meaningful change you make should end with a recorded version.**
|
|
21
|
+
Don't leave the user's history stuck on an old snapshot — after editing
|
|
22
|
+
files, run `record` with a short human title describing what you changed:
|
|
23
|
+
|
|
24
|
+
`npx version-history-widget record "Tweak icon animation timing" -d "what changed and why"`
|
|
25
|
+
|
|
26
|
+
If the project is set up for auto-recording (`vh watch`, or the middleware
|
|
27
|
+
mounted in their dev server), versions are captured on their own and you
|
|
28
|
+
don't need to record manually — but recording explicitly is still better,
|
|
29
|
+
since it gives the version a meaningful title instead of "Updated src/main.js".
|
|
30
|
+
|
|
31
|
+
To set a project up so changes are never missed again, see "Auto-recording"
|
|
32
|
+
below.
|
|
33
|
+
|
|
17
34
|
## Project mode
|
|
18
35
|
|
|
19
36
|
Run these with Bash from the project root (`npx` needs no local install):
|
|
@@ -35,6 +52,40 @@ Run these with Bash from the project root (`npx` needs no local install):
|
|
|
35
52
|
|
|
36
53
|
If `.versions/` doesn't exist yet in the project, run `init` first.
|
|
37
54
|
|
|
55
|
+
## Auto-recording
|
|
56
|
+
|
|
57
|
+
So that changes are never silently lost, a project can record versions on
|
|
58
|
+
its own. Two ways, both framework-agnostic:
|
|
59
|
+
|
|
60
|
+
- Run a watcher alongside whatever dev server they use:
|
|
61
|
+
`npx version-history-widget watch`
|
|
62
|
+
It debounces, so a burst of edits becomes one version.
|
|
63
|
+
- Or mount the middleware in their dev server, which starts the same
|
|
64
|
+
watcher automatically whenever the server runs. For Vite, a
|
|
65
|
+
`vite.config.js` containing:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { createRequire } from 'module';
|
|
69
|
+
const require = createRequire(import.meta.url);
|
|
70
|
+
|
|
71
|
+
export default {
|
|
72
|
+
plugins: [{
|
|
73
|
+
name: 'version-history',
|
|
74
|
+
configureServer(server) {
|
|
75
|
+
server.middlewares.use(require('./.versions/middleware.js')());
|
|
76
|
+
},
|
|
77
|
+
}],
|
|
78
|
+
};
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
(`createRequire` is needed because Vite configs are ES modules and the
|
|
82
|
+
vendored `.versions/` files are CommonJS.)
|
|
83
|
+
|
|
84
|
+
If the user reports that a change they made isn't showing up in the widget,
|
|
85
|
+
it is almost always because nothing recorded it — check
|
|
86
|
+
`.versions/manifest.json` against the current files, record the missing
|
|
87
|
+
version, then set up one of the two options above.
|
|
88
|
+
|
|
38
89
|
## Single-file mode
|
|
39
90
|
|
|
40
91
|
Use when the user names one `.html` file rather than a project:
|
package/vh.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
node vh.js record "Title" [-d "details" | -f details.txt] — snapshot changed files as a new version
|
|
5
5
|
node vh.js restore <id> — snapshot current state, then restore version <id>
|
|
6
6
|
node vh.js list [query] — list versions (optionally filtered by title/details)
|
|
7
|
+
node vh.js watch — auto-record every change as a new version (run alongside
|
|
8
|
+
your dev server; also runs inside serve.js/middleware.js)
|
|
7
9
|
node vh.js update — refresh the vendored widget.js/vh.js/serve.js/middleware.js
|
|
8
10
|
in .versions/ from the installed package (run after upgrading)
|
|
9
11
|
node vh.js skill — install the Claude Code skill (~/.claude/skills) so
|
|
@@ -12,7 +14,8 @@
|
|
|
12
14
|
const fs = require('fs'), path = require('path'), os = require('os');
|
|
13
15
|
const ROOT = path.resolve(process.env.VH_ROOT || process.cwd());
|
|
14
16
|
const VDIR = path.join(ROOT, '.versions'), SNAP = path.join(VDIR, 'snapshots'), MAN = path.join(VDIR, 'manifest.json');
|
|
15
|
-
const
|
|
17
|
+
const LOCK = path.join(VDIR, '.restoring');
|
|
18
|
+
const IGNORE = new Set(['node_modules', '.git', '.versions', 'dist', 'build', '.next', '.nuxt', 'coverage', '.cache', '.DS_Store', '.claude']);
|
|
16
19
|
const MAX = 2 * 1024 * 1024;
|
|
17
20
|
|
|
18
21
|
const readMan = () => fs.existsSync(MAN) ? JSON.parse(fs.readFileSync(MAN, 'utf8')) : [];
|
|
@@ -86,19 +89,146 @@ function injectWidget() {
|
|
|
86
89
|
return null;
|
|
87
90
|
}
|
|
88
91
|
|
|
92
|
+
const VITE_CONFIG = `import { createRequire } from 'module';
|
|
93
|
+
const require = createRequire(import.meta.url);
|
|
94
|
+
|
|
95
|
+
export default {
|
|
96
|
+
plugins: [{
|
|
97
|
+
name: 'version-history',
|
|
98
|
+
configureServer(server) {
|
|
99
|
+
server.middlewares.use(require('./.versions/middleware.js')());
|
|
100
|
+
},
|
|
101
|
+
}],
|
|
102
|
+
};
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
function usesVite() {
|
|
106
|
+
const pj = path.join(ROOT, 'package.json');
|
|
107
|
+
if (!fs.existsSync(pj)) return false;
|
|
108
|
+
try {
|
|
109
|
+
const p = JSON.parse(fs.readFileSync(pj, 'utf8'));
|
|
110
|
+
if ((p.devDependencies && p.devDependencies.vite) || (p.dependencies && p.dependencies.vite)) return true;
|
|
111
|
+
return /\bvite\b/.test(Object.values(p.scripts || {}).join(' '));
|
|
112
|
+
} catch (e) { return false; }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function viteConfigPath() {
|
|
116
|
+
for (const f of ['vite.config.js', 'vite.config.mjs', 'vite.config.ts', 'vite.config.mts', 'vite.config.cjs'])
|
|
117
|
+
if (fs.existsSync(path.join(ROOT, f))) return path.join(ROOT, f);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Wire the dev server up so changes keep getting auto-recorded. Only ever
|
|
122
|
+
creates a config when there isn't one — an existing config is never edited,
|
|
123
|
+
since that's the user's file. Re-run after a restore so restoring to a
|
|
124
|
+
version that predates the setup doesn't silently switch auto-saving off. */
|
|
125
|
+
function ensureDevIntegration(quiet) {
|
|
126
|
+
if (!usesVite()) return null;
|
|
127
|
+
const existing = viteConfigPath();
|
|
128
|
+
if (existing) {
|
|
129
|
+
if (!quiet && !fs.readFileSync(existing, 'utf8').includes('.versions/middleware.js'))
|
|
130
|
+
console.log('Note: ' + path.basename(existing) + ' does not mount .versions/middleware.js, so changes are only auto-recorded while `vh watch` is running. See the README for the snippet to add.');
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
const p = path.join(ROOT, 'vite.config.js');
|
|
134
|
+
fs.writeFileSync(p, VITE_CONFIG);
|
|
135
|
+
if (!quiet) console.log('Created vite.config.js mounting .versions/middleware.js — restart your dev server and every change is auto-recorded.');
|
|
136
|
+
return p;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/* Files that differ from the newest recorded version. */
|
|
140
|
+
function pendingChanges() {
|
|
141
|
+
const man = readMan(), prev = man.length ? stateAt(man[man.length - 1].id, man) : {};
|
|
142
|
+
const cur = walk(ROOT), out = [];
|
|
143
|
+
for (const f of Object.keys(cur)) { const old = prev[f]; if (!old || !old.equals(cur[f])) out.push(f); }
|
|
144
|
+
for (const f of Object.keys(prev)) if (!cur[f]) out.push(f);
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Record a version in-process. Returns null when nothing changed. */
|
|
149
|
+
function recordNow(title, details) {
|
|
150
|
+
const man = readMan(), prev = man.length ? stateAt(man[man.length - 1].id, man) : {};
|
|
151
|
+
const r = snapshot(title, details, prev);
|
|
152
|
+
if (!r.changed) { const m = readMan(); m.pop(); writeMan(m); fs.rmSync(path.join(SNAP, String(r.id)), { recursive: true, force: true }); return null; }
|
|
153
|
+
return r;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function autoTitle(files) {
|
|
157
|
+
if (files.length === 1) return 'Updated ' + files[0];
|
|
158
|
+
return 'Updated ' + files[0] + ' and ' + (files.length - 1) + ' more file' + (files.length > 2 ? 's' : '');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ignoredPath(rel) {
|
|
162
|
+
if (!rel) return false;
|
|
163
|
+
for (const seg of String(rel).split(/[\\/]/)) if (IGNORE.has(seg) || seg.startsWith('.env')) return true;
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Auto-record changes as they happen. Debounced so a burst of edits (an editor
|
|
168
|
+
or an agent writing several files) becomes one version, not twenty. */
|
|
169
|
+
function startWatch(opts = {}) {
|
|
170
|
+
const wait = opts.debounce || 1500, log = opts.log !== false;
|
|
171
|
+
let timer = null, busy = false;
|
|
172
|
+
function fire() {
|
|
173
|
+
timer = null;
|
|
174
|
+
if (busy || fs.existsSync(LOCK)) return schedule(); // mid-restore: check again later
|
|
175
|
+
busy = true;
|
|
176
|
+
try {
|
|
177
|
+
const files = pendingChanges();
|
|
178
|
+
if (files.length) {
|
|
179
|
+
const title = autoTitle(files);
|
|
180
|
+
const r = recordNow(title, 'Auto-saved.\n\nChanged files:\n' + files.map(f => ' ' + f).join('\n'));
|
|
181
|
+
if (r && log) console.log('[vh] saved version ' + r.id + ': ' + title);
|
|
182
|
+
}
|
|
183
|
+
} catch (e) { if (log) console.error('[vh] auto-save failed: ' + e.message); }
|
|
184
|
+
finally { busy = false; }
|
|
185
|
+
}
|
|
186
|
+
function schedule() { clearTimeout(timer); timer = setTimeout(fire, wait); }
|
|
187
|
+
|
|
188
|
+
let watcher = null;
|
|
189
|
+
try { watcher = fs.watch(ROOT, { recursive: true }, (evt, name) => { if (!ignoredPath(name)) schedule(); }); }
|
|
190
|
+
catch (e) { watcher = null; } // recursive watch unsupported (older Linux)
|
|
191
|
+
let poller = null;
|
|
192
|
+
if (!watcher) {
|
|
193
|
+
let seen = 0;
|
|
194
|
+
poller = setInterval(() => {
|
|
195
|
+
let newest = 0;
|
|
196
|
+
(function scan(dir) {
|
|
197
|
+
let entries; try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return; }
|
|
198
|
+
for (const e of entries) {
|
|
199
|
+
if (IGNORE.has(e.name) || e.name.startsWith('.env')) continue;
|
|
200
|
+
const p = path.join(dir, e.name);
|
|
201
|
+
if (e.isDirectory()) scan(p);
|
|
202
|
+
else { try { const m = fs.statSync(p).mtimeMs; if (m > newest) newest = m; } catch (e2) { } }
|
|
203
|
+
}
|
|
204
|
+
})(ROOT);
|
|
205
|
+
if (newest > seen) { seen = newest; schedule(); }
|
|
206
|
+
}, opts.poll || 2000);
|
|
207
|
+
}
|
|
208
|
+
if (opts.keepAlive === false) { if (watcher && watcher.unref) watcher.unref(); if (poller && poller.unref) poller.unref(); }
|
|
209
|
+
schedule(); // catch up on anything changed while nothing was watching
|
|
210
|
+
return { stop() { clearTimeout(timer); if (watcher) watcher.close(); if (poller) clearInterval(poller); } };
|
|
211
|
+
}
|
|
212
|
+
|
|
89
213
|
function vendorFiles() {
|
|
90
214
|
fs.mkdirSync(VDIR, { recursive: true });
|
|
91
215
|
for (const f of ['widget.js', 'vh.js', 'serve.js', 'middleware.js']) { const src = path.join(__dirname, f); if (fs.existsSync(src) && src !== path.join(VDIR, f)) fs.copyFileSync(src, path.join(VDIR, f)); }
|
|
216
|
+
// Force CommonJS for everything under .versions/, regardless of the host
|
|
217
|
+
// project's own package.json — otherwise a host with "type":"module" makes
|
|
218
|
+
// Node treat these vendored .js files as ESM and `require` disappears.
|
|
219
|
+
fs.writeFileSync(path.join(VDIR, 'package.json'), JSON.stringify({ type: 'commonjs' }, null, 2) + '\n');
|
|
92
220
|
}
|
|
93
221
|
|
|
94
|
-
const cmd = process.argv[2], args = process.argv.slice(3);
|
|
95
|
-
if (cmd ===
|
|
222
|
+
const cmd = require.main === module ? process.argv[2] : null, args = process.argv.slice(3);
|
|
223
|
+
if (cmd === null) { // required as a library (serve.js / middleware.js)
|
|
224
|
+
} else if (cmd === 'init') {
|
|
96
225
|
fs.mkdirSync(SNAP, { recursive: true });
|
|
97
226
|
vendorFiles();
|
|
98
227
|
if (!fs.existsSync(MAN)) writeMan([]);
|
|
99
228
|
const gi = path.join(ROOT, '.gitignore'); const line = '.versions/snapshots/';
|
|
100
229
|
if (!fs.existsSync(gi) || !fs.readFileSync(gi, 'utf8').includes(line)) fs.appendFileSync(gi, '\n' + line + '\n');
|
|
101
230
|
const where = injectWidget();
|
|
231
|
+
ensureDevIntegration(false);
|
|
102
232
|
const r = readMan().length ? null : snapshot('Initial version', 'State of the project when version history was enabled.', {});
|
|
103
233
|
console.log('version-history ready.' + (where ? ' Widget injected into ' + path.relative(ROOT, where) + '.' : ' Could not find an entry HTML — add <script src="/.versions/widget.js"></script> manually.') + (r ? ' Saved version 1.' : ''));
|
|
104
234
|
console.log('Serve with restore endpoint: node .versions/serve.js (or mount .versions/middleware.js in your dev server)');
|
|
@@ -106,21 +236,27 @@ if (cmd === 'init') {
|
|
|
106
236
|
const title = args[0]; if (!title) { console.error('usage: vh record "Title" [-d "details" | -f file]'); process.exit(1); }
|
|
107
237
|
const di = args.indexOf('-d'), fi = args.indexOf('-f');
|
|
108
238
|
const details = di >= 0 ? args[di + 1] : fi >= 0 ? fs.readFileSync(args[fi + 1], 'utf8') : '';
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
console.log(r.changed ? 'Saved as version ' + r.id + ': ' + title : 'No changes since last version — nothing saved.');
|
|
112
|
-
if (!r.changed) { const m = readMan(); m.pop(); writeMan(m); fs.rmSync(path.join(SNAP, String(r.id)), { recursive: true, force: true }); }
|
|
239
|
+
const r = recordNow(title, details);
|
|
240
|
+
console.log(r ? 'Saved as version ' + r.id + ': ' + title : 'No changes since last version — nothing saved.');
|
|
113
241
|
} else if (cmd === 'restore') {
|
|
114
242
|
const id = Number(args[0]), man = readMan(), target = man.find(v => v.id === id);
|
|
115
243
|
if (!target) { console.error('No version ' + args[0]); process.exit(1); }
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
244
|
+
fs.mkdirSync(VDIR, { recursive: true }); fs.writeFileSync(LOCK, String(process.pid)); // pause any watcher
|
|
245
|
+
try {
|
|
246
|
+
const cur = man[man.length - 1], prev = stateAt(cur.id, man);
|
|
247
|
+
snapshot('Snapshot before restoring "' + target.title + '"', 'Automatic safety snapshot taken before restore.', prev);
|
|
248
|
+
const want = stateAt(id, readMan()), have = walk(ROOT);
|
|
249
|
+
for (const f of Object.keys(have)) if (!want[f]) fs.rmSync(path.join(ROOT, f));
|
|
250
|
+
for (const f of Object.keys(want)) { const p = path.join(ROOT, f); fs.mkdirSync(path.dirname(p), { recursive: true }); fs.writeFileSync(p, want[f]); }
|
|
251
|
+
injectWidget();
|
|
252
|
+
ensureDevIntegration(true);
|
|
253
|
+
const r = snapshot('Restored "' + target.title + '"', 'Restored the state saved as version ' + id + '.', stateAt(readMan().slice(-1)[0].id, readMan()));
|
|
254
|
+
console.log('Restored version ' + id + ' (recorded as version ' + r.id + ').');
|
|
255
|
+
} finally { fs.rmSync(LOCK, { force: true }); }
|
|
256
|
+
} else if (cmd === 'watch') {
|
|
257
|
+
if (!fs.existsSync(VDIR)) { console.error('No .versions/ here — run `vh init` first.'); process.exit(1); }
|
|
258
|
+
startWatch({});
|
|
259
|
+
console.log('Watching ' + ROOT + ' — every change is auto-saved as a new version. Ctrl-C to stop.');
|
|
124
260
|
} else if (cmd === 'list') {
|
|
125
261
|
const q = (args[0] || '').toLowerCase();
|
|
126
262
|
for (const v of readMan()) if (!q || v.title.toLowerCase().includes(q) || (v.details || '').toLowerCase().includes(q)) console.log(String(v.id).padStart(3) + ' ' + v.time.slice(0, 16).replace('T', ' ') + ' ' + v.title);
|
|
@@ -134,4 +270,7 @@ if (cmd === 'init') {
|
|
|
134
270
|
fs.copyFileSync(path.join(__dirname, 'skill', 'SKILL.md'), path.join(dest, 'SKILL.md'));
|
|
135
271
|
console.log('Installed Claude Code skill to ' + dest + '. Restart Claude Code (or start a new session) and try /version-history-widget.');
|
|
136
272
|
} else { console.log(fs.readFileSync(__filename, 'utf8').split('*/')[0].split('\n').slice(1).join('\n')); }
|
|
137
|
-
module.exports = {
|
|
273
|
+
module.exports = {
|
|
274
|
+
restore: id => { const { execFileSync } = require('child_process'); return execFileSync(process.execPath, [__filename, 'restore', String(id)], { cwd: ROOT, env: process.env }).toString(); },
|
|
275
|
+
watch: startWatch, record: recordNow, pending: pendingChanges, root: ROOT,
|
|
276
|
+
};
|