nearly-cli 0.1.2 → 0.1.5
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 +27 -1
- package/package.json +2 -2
- package/scripts/attach.mjs +32 -11
- package/scripts/update-check.mjs +4 -1
package/README.md
CHANGED
|
@@ -24,7 +24,27 @@ Watch the first thirty seconds. The cover says what the diff cannot: an action t
|
|
|
24
24
|
|
|
25
25
|
Node 18 or newer, Claude Code signed in, and git. No dependencies and no API key: agents run on your existing Claude subscription.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
### Platforms
|
|
28
|
+
|
|
29
|
+
The suite runs on every push against macOS, Linux and Windows, on Node 18 and 22.
|
|
30
|
+
|
|
31
|
+
| | macOS | Linux | Windows |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| Gate, recording, records | tested in CI | tested in CI | tested in CI |
|
|
34
|
+
| Turning it on and off | tested in CI | tested in CI | tested in CI |
|
|
35
|
+
| Updating itself | tested in CI | tested in CI | tested in CI |
|
|
36
|
+
| Driving a real agent end to end | **run by hand** | not yet | not yet |
|
|
37
|
+
| Handing the record over at `git push` | **run by hand** | not yet | not yet |
|
|
38
|
+
| Spoken narration | yes | no | no |
|
|
39
|
+
|
|
40
|
+
Narration uses the built-in `say`, so it is macOS only. Everywhere else the
|
|
41
|
+
record is built identically and reads from its captions, or you can record the
|
|
42
|
+
lines yourself with `--voice-dir`.
|
|
43
|
+
|
|
44
|
+
The bottom two rows are the honest gap: CI proves the pieces work on all three,
|
|
45
|
+
but a whole session with a real agent, and a real push through the hook, have
|
|
46
|
+
only been done on macOS. If you are the first to try either on Windows or Linux,
|
|
47
|
+
an issue with what broke would be genuinely useful.
|
|
28
48
|
|
|
29
49
|
## Use it on your own repo
|
|
30
50
|
|
|
@@ -34,6 +54,12 @@ One command, in the repo you want recorded.
|
|
|
34
54
|
npx nearly-cli
|
|
35
55
|
```
|
|
36
56
|
|
|
57
|
+
> **`npx` not recognised?** It comes with Node.js, so that error means Node is
|
|
58
|
+
> not installed. Get it from [nodejs.org](https://nodejs.org) or, on Windows,
|
|
59
|
+
> `winget install OpenJS.NodeJS.LTS`. Then **open a new terminal** so it picks up
|
|
60
|
+
> the change, and check with `node --version`. You also need Claude Code signed
|
|
61
|
+
> in: Nearly gates Claude Code sessions and does nothing without one.
|
|
62
|
+
|
|
37
63
|
```
|
|
38
64
|
✓ Nearly is on for my-app
|
|
39
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nearly-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "A pull request tells you what changed. Nearly tells you what nearly happened: the commands a human refused, the pushes policy blocked, the turns rolled back.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://anujpatel06.github.io/nearly/",
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "node --test --test-concurrency=1 test/"
|
|
37
|
+
"test": "node --test --test-concurrency=1 test/policy.test.mjs test/server.test.mjs test/record.test.mjs test/resilience.test.mjs"
|
|
38
38
|
}
|
|
39
39
|
}
|
package/scripts/attach.mjs
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// it, and if it cannot start, Claude Code falls back to its own prompts and
|
|
17
17
|
// nothing breaks.
|
|
18
18
|
|
|
19
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
19
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, realpathSync } from 'node:fs';
|
|
20
20
|
import { join, resolve, dirname, basename } from 'node:path';
|
|
21
21
|
import { fileURLToPath } from 'node:url';
|
|
22
22
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
@@ -40,15 +40,31 @@ function pkgVersion() {
|
|
|
40
40
|
catch { return 'latest'; }
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Only trust a `nearly` on PATH if it really is this tool
|
|
43
|
+
// Only trust a `nearly` on PATH if it really is this tool and it will still be
|
|
44
|
+
// there tomorrow.
|
|
45
|
+
//
|
|
46
|
+
// npx puts its own temporary bin first on PATH for the life of the process, so
|
|
47
|
+
// during `npx nearly-cli` a naive lookup finds a `nearly` that ceases to exist
|
|
48
|
+
// the moment npx exits. Believing it meant writing hooks that call a command
|
|
49
|
+
// nobody has, which fail on every tool call, which means no gate at all.
|
|
44
50
|
function onPath() {
|
|
51
|
+
const args = process.platform === 'win32' ? ['nearly'] : ['-a', 'nearly'];
|
|
52
|
+
let found = [];
|
|
45
53
|
try {
|
|
46
|
-
|
|
47
|
-
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split('\n')
|
|
48
|
-
if (!p) return null;
|
|
49
|
-
const out = execFileSync(p, ['--which'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
50
|
-
return out ? p : null;
|
|
54
|
+
found = execFileSync(process.platform === 'win32' ? 'where' : 'which', args,
|
|
55
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim().split('\n').filter(Boolean);
|
|
51
56
|
} catch { return null; }
|
|
57
|
+
|
|
58
|
+
for (const p of found) {
|
|
59
|
+
let resolved = p;
|
|
60
|
+
try { resolved = realpathSync(p); } catch { /* keep the literal path */ }
|
|
61
|
+
if (/[\\/]_npx[\\/]/.test(p) || /[\\/]_npx[\\/]/.test(resolved)) continue; // vanishes when npx exits
|
|
62
|
+
try {
|
|
63
|
+
const out = execFileSync(p, ['--which'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
64
|
+
if (out) return p;
|
|
65
|
+
} catch { /* not our command; try the next one */ }
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
52
68
|
}
|
|
53
69
|
|
|
54
70
|
const fromPackage = /[\\/]node_modules[\\/]/.test(root) || /[\\/]_npx[\\/]/.test(root);
|
|
@@ -62,7 +78,7 @@ const viaNpx = /[\\/]_npx[\\/]/.test(root);
|
|
|
62
78
|
function installGlobally() {
|
|
63
79
|
if (!viaNpx || onPath() || argv.includes('--no-install') || process.env.NEARLY_NO_INSTALL === '1') return false;
|
|
64
80
|
process.stdout.write(dim(` Installing nearly so upgrades reach you… `));
|
|
65
|
-
const r = spawnSync(
|
|
81
|
+
const r = spawnSync(NPM, ['install', '-g', `nearly-cli@${pkgVersion()}`, '--silent', '--no-fund', '--no-audit'],
|
|
66
82
|
{ encoding: 'utf8', timeout: 180_000 });
|
|
67
83
|
if (r.status === 0 && onPath()) { console.log('done'); return true; }
|
|
68
84
|
console.log(dim('skipped'));
|
|
@@ -72,11 +88,13 @@ function installGlobally() {
|
|
|
72
88
|
}
|
|
73
89
|
let installed = onPath();
|
|
74
90
|
const hookCmd = (ev) => installed
|
|
75
|
-
? `nearly hook ${ev}`
|
|
91
|
+
? `nearly hook ${ev}` // verified above to survive this process
|
|
76
92
|
: fromPackage
|
|
77
93
|
? `npx -y nearly-cli@${pkgVersion()} hook ${ev}`
|
|
78
94
|
: `node ${JSON.stringify(HOOK)} ${ev}`;
|
|
79
|
-
|
|
95
|
+
// A function, not a value: the install below changes the answer, and computing
|
|
96
|
+
// it early told a fresh install it was pinned when it was not.
|
|
97
|
+
const updateNote = () => installed
|
|
80
98
|
? 'upgrades reach this repo automatically'
|
|
81
99
|
: fromPackage
|
|
82
100
|
? `pinned to v${pkgVersion()} — run nearly again here after upgrading`
|
|
@@ -89,6 +107,9 @@ const nameIdx = argv.indexOf('--name');
|
|
|
89
107
|
const name = (nameIdx !== -1 ? argv[nameIdx + 1] : basename(repo))
|
|
90
108
|
.replace(/[^a-z0-9-]/gi, '-').toLowerCase().slice(0, 24) || 'repo';
|
|
91
109
|
|
|
110
|
+
// npm is a .cmd shim on Windows and Node will not run one through spawn unless
|
|
111
|
+
// it is named exactly. Without this, installing and upgrading both fail there.
|
|
112
|
+
const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
92
113
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
93
114
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
94
115
|
const ok = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
@@ -201,7 +222,7 @@ if (off) {
|
|
|
201
222
|
console.log(`${ok('✓')} ${bold('Nearly is on')} for ${bold(name)} ${dim(repo)}`);
|
|
202
223
|
console.log('');
|
|
203
224
|
console.log(` ${ok('·')} every Claude Code session here is gated and recorded`);
|
|
204
|
-
console.log(` ${ok('·')} ${dim(updateNote)}`);
|
|
225
|
+
console.log(` ${ok('·')} ${dim(updateNote())}`);
|
|
205
226
|
console.log(` ${ok('·')} ${push.status === 0 ? 'the record is offered when you push' : dim('pre-push hook skipped: ' + (push.stderr || '').trim().split('\n')[0])}`);
|
|
206
227
|
if (base) {
|
|
207
228
|
console.log(` ${ok('·')} records publish to ${base}`);
|
package/scripts/update-check.mjs
CHANGED
|
@@ -24,6 +24,9 @@ import { spawnSync, execFileSync } from 'node:child_process';
|
|
|
24
24
|
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
25
25
|
const DAY = 24 * 60 * 60 * 1000;
|
|
26
26
|
|
|
27
|
+
// npm is a .cmd shim on Windows and Node will not run one through spawn unless
|
|
28
|
+
// it is named exactly. Without this, installing and upgrading both fail there.
|
|
29
|
+
const NPM = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
27
30
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
28
31
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
29
32
|
|
|
@@ -100,7 +103,7 @@ export function applyUpdate(u) {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
process.stdout.write(dim(` Updating Nearly ${u.from} → ${u.to}… `));
|
|
103
|
-
const r = spawnSync(
|
|
106
|
+
const r = spawnSync(NPM, ['install', '-g', `${u.name}@${u.to}`, '--silent', '--no-fund', '--no-audit'],
|
|
104
107
|
{ encoding: 'utf8', timeout: 120_000 });
|
|
105
108
|
|
|
106
109
|
if (r.status === 0) {
|