node-command 2.0.4 → 2.0.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/bin/nc.mjs +7 -27
- package/lib/setup.mjs +39 -1
- package/package.json +4 -18
package/bin/nc.mjs
CHANGED
|
@@ -2875,7 +2875,7 @@ async function runRemoval(args, { command, emptyMessage, elevateOnWindows = fals
|
|
|
2875
2875
|
}
|
|
2876
2876
|
if (unreadable.length > 0) {
|
|
2877
2877
|
console.log(
|
|
2878
|
-
`
|
|
2878
|
+
`Explicit root maintenance required for system install(s): ${unreadable.join(', ')} (not readable as the current user).`
|
|
2879
2879
|
);
|
|
2880
2880
|
}
|
|
2881
2881
|
if (!yes) {
|
|
@@ -2924,33 +2924,13 @@ async function runRemoval(args, { command, emptyMessage, elevateOnWindows = fals
|
|
|
2924
2924
|
lock.release();
|
|
2925
2925
|
}
|
|
2926
2926
|
}
|
|
2927
|
-
// Unreadable system roots remain visible, but never
|
|
2927
|
+
// Unreadable system roots remain visible, but are never executed: an
|
|
2928
|
+
// ordinary caller cannot verify the tree, so removal is explicit root
|
|
2929
|
+
// maintenance. This deliberately adds no implicit sudo.
|
|
2928
2930
|
for (const root of unreadable) {
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
lock = acquireInstallLock(root);
|
|
2933
|
-
} catch (err) {
|
|
2934
|
-
if (err instanceof PairingError && err.code === 'SETUP_IN_PROGRESS') {
|
|
2935
|
-
reportSetupError(err);
|
|
2936
|
-
failed += 1;
|
|
2937
|
-
continue;
|
|
2938
|
-
}
|
|
2939
|
-
// Best effort: an unreadable system root usually cannot host a writable
|
|
2940
|
-
// lock from an ordinary caller. The privileged uninstaller remains the
|
|
2941
|
-
// authority; do not turn a lock-permission failure into a false refusal.
|
|
2942
|
-
}
|
|
2943
|
-
try {
|
|
2944
|
-
runUninstallerScript(script);
|
|
2945
|
-
console.log(`Removed ${root} (exit 0).`);
|
|
2946
|
-
} catch (err) {
|
|
2947
|
-
const rc = typeof err?.status === 'number' ? err.status : 1;
|
|
2948
|
-
console.error(`error: [UNINSTALL_FAILED] uninstaller for ${root} exited with status ${rc}: ${sanitizeForDisplay(err.message, 300)}`);
|
|
2949
|
-
console.error(uninstallFailureSuggestion());
|
|
2950
|
-
failed += 1;
|
|
2951
|
-
} finally {
|
|
2952
|
-
lock?.release();
|
|
2953
|
-
}
|
|
2931
|
+
console.error(`error: [TRUSTED_ROOT_MAINTENANCE_REQUIRED] The system installation at ${root} is not readable by the current user; it was not modified.`);
|
|
2932
|
+
console.error('suggested_action: Run `sudo npx --yes node-command@latest uninstall --yes` to remove this trusted root installation explicitly.');
|
|
2933
|
+
failed += 1;
|
|
2954
2934
|
}
|
|
2955
2935
|
if (failed > 0) process.exit(1);
|
|
2956
2936
|
console.log(completionMessage);
|
package/lib/setup.mjs
CHANGED
|
@@ -391,6 +391,44 @@ export function probeInstallRoot(root, io = {}) {
|
|
|
391
391
|
return 'foreign';
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Removal-strict install-tree probe. Removing a tree is destructive, so a
|
|
396
|
+
* readable `config/nc-client.env` marker is required; an `app/` directory
|
|
397
|
+
* alone (an incomplete or foreign tree) is never a removal target.
|
|
398
|
+
* Distinguishes:
|
|
399
|
+
* - 'install' config marker is readable
|
|
400
|
+
* - 'absent' root directory does not exist
|
|
401
|
+
* - 'foreign' root exists but is not a directory, or the config marker is
|
|
402
|
+
* missing / not a regular file
|
|
403
|
+
* - 'unreadable' root or config marker denies access (EACCES/EPERM)
|
|
404
|
+
* @param {string} root
|
|
405
|
+
* @param {{ statSync?: (p: string) => { isDirectory(): boolean, isFile?(): boolean }, accessSync?: (p: string, mode?: number) => void }} [io]
|
|
406
|
+
* @returns {'install'|'absent'|'foreign'|'unreadable'}
|
|
407
|
+
*/
|
|
408
|
+
export function probeRemovalRoot(root, io = {}) {
|
|
409
|
+
const statSync = io.statSync || fs.statSync;
|
|
410
|
+
const accessSync = io.accessSync || fs.accessSync;
|
|
411
|
+
try {
|
|
412
|
+
const st = statSync(root);
|
|
413
|
+
if (!st.isDirectory()) return 'foreign';
|
|
414
|
+
} catch (err) {
|
|
415
|
+
return err && err.code === 'ENOENT' ? 'absent' : 'unreadable';
|
|
416
|
+
}
|
|
417
|
+
const marker = path.join(root, 'config', 'nc-client.env');
|
|
418
|
+
try {
|
|
419
|
+
const st = statSync(marker);
|
|
420
|
+
if (typeof st.isFile === 'function' && !st.isFile()) return 'foreign';
|
|
421
|
+
} catch (err) {
|
|
422
|
+
return err && (err.code === 'EACCES' || err.code === 'EPERM') ? 'unreadable' : 'foreign';
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
accessSync(marker, fs.constants.R_OK);
|
|
426
|
+
return 'install';
|
|
427
|
+
} catch (err) {
|
|
428
|
+
return err && (err.code === 'EACCES' || err.code === 'EPERM') ? 'unreadable' : 'foreign';
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
394
432
|
/**
|
|
395
433
|
* Canonical machine-wide roots that exist but cannot be inspected by the
|
|
396
434
|
* current unprivileged caller. Setup must escalate before deciding anything
|
|
@@ -424,7 +462,7 @@ export function findRemovalTargets({ io = {}, ...rest } = {}) {
|
|
|
424
462
|
const roots = candidateInstallRoots(pair ? { ...rest, env: { ...env, NC_ROOT: undefined } } : rest);
|
|
425
463
|
for (const root of pair ? [...new Set([...roots, pair.ncRoot])] : roots) {
|
|
426
464
|
if (root === pair?.helperRoot && root !== pair.ncRoot) continue;
|
|
427
|
-
const status =
|
|
465
|
+
const status = probeRemovalRoot(root, io);
|
|
428
466
|
if (status === 'install') install.push(root);
|
|
429
467
|
else if (status === 'unreadable') unreadable.push(root);
|
|
430
468
|
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-command",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
6
9
|
"bin": {
|
|
7
10
|
"nc": "bin/nc.mjs",
|
|
8
11
|
"nodecommand": "bin/nc.mjs",
|
|
@@ -12,22 +15,5 @@
|
|
|
12
15
|
"files": [
|
|
13
16
|
"bin/",
|
|
14
17
|
"lib/"
|
|
15
|
-
],
|
|
16
|
-
"description": "Node Command agent CLI — connect this machine to your AI assistant fleet",
|
|
17
|
-
"repository": {
|
|
18
|
-
"url": "git+https://github.com/huseyincig/NodeCommand.git",
|
|
19
|
-
"type": "git"
|
|
20
|
-
},
|
|
21
|
-
"engines": {
|
|
22
|
-
"node": ">=20"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"node-command",
|
|
26
|
-
"nodecommand",
|
|
27
|
-
"mcp",
|
|
28
|
-
"remote",
|
|
29
|
-
"ai-assistant",
|
|
30
|
-
"agent",
|
|
31
|
-
"cli"
|
|
32
18
|
]
|
|
33
19
|
}
|