aiden-runtime 4.9.0 → 4.9.1
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 +1 -1
- package/dist/cli/v4/aidenCLI.js +2 -2
- package/dist/cli/v4/chatSession.js +19 -4
- package/dist/cli/v4/commands/daemon.js +6 -1
- package/dist/cli/v4/commands/daemonDoctor.js +1 -1
- package/dist/cli/v4/commands/daemonStatus.js +45 -26
- package/dist/cli/v4/commands/help.js +3 -0
- package/dist/cli/v4/commands/hooks.js +39 -1
- package/dist/cli/v4/commands/hooksSlash.js +33 -0
- package/dist/cli/v4/commands/index.js +9 -1
- package/dist/cli/v4/commands/memory.js +6 -1
- package/dist/cli/v4/commands/memorySlash.js +38 -0
- package/dist/cli/v4/ui/progressBar.js +179 -0
- package/dist/cli/v4/util/closestAction.js +48 -0
- package/dist/core/v4/update/depWarningFilter.js +76 -0
- package/dist/core/v4/update/executeInstall.js +41 -35
- package/dist/core/v4/update/platformInstructions.js +128 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ Windows · Linux · WSL · macOS (API Mode)
|
|
|
94
94
|

|
|
95
95
|

|
|
96
96
|

|
|
97
|
-

|
|
98
98
|
|
|
99
99
|
</div>
|
|
100
100
|
|
package/dist/cli/v4/aidenCLI.js
CHANGED
|
@@ -568,7 +568,7 @@ async function main(argv, opts = {}) {
|
|
|
568
568
|
});
|
|
569
569
|
program
|
|
570
570
|
.command('voice [args...]')
|
|
571
|
-
.description('Voice diagnostics + one-shot TTS / transcribe
|
|
571
|
+
.description('Voice diagnostics + one-shot TTS / transcribe. ' +
|
|
572
572
|
'Usage: aiden voice doctor | tts "<text>" | transcribe <file>')
|
|
573
573
|
.allowUnknownOption()
|
|
574
574
|
.action(async (args) => {
|
|
@@ -611,7 +611,7 @@ async function main(argv, opts = {}) {
|
|
|
611
611
|
// v4.1 placeholders. (`tui` graduated to a real flag in Phase 15.)
|
|
612
612
|
program
|
|
613
613
|
.command('cron [args...]')
|
|
614
|
-
.description('Cron diagnostics + one-shot list / run
|
|
614
|
+
.description('Cron diagnostics + one-shot list / run. ' +
|
|
615
615
|
'Usage: aiden cron status | list | run <id>')
|
|
616
616
|
.allowUnknownOption()
|
|
617
617
|
.action(async (args) => {
|
|
@@ -1656,6 +1656,10 @@ class ChatSession {
|
|
|
1656
1656
|
// .update_check.json cache so subsequent boots stay quiet until
|
|
1657
1657
|
// a newer release ships.
|
|
1658
1658
|
try {
|
|
1659
|
+
// v4.9.1 — modal sits BELOW the welcome banner with a blank
|
|
1660
|
+
// separator. Prevents the box from visually overlapping the
|
|
1661
|
+
// boot card on first-paint (smoke-reported regression).
|
|
1662
|
+
display.write('\n');
|
|
1659
1663
|
await this.maybeShowBootUpdatePrompt();
|
|
1660
1664
|
}
|
|
1661
1665
|
catch { /* never let the update prompt crash boot */ }
|
|
@@ -1713,13 +1717,24 @@ class ChatSession {
|
|
|
1713
1717
|
});
|
|
1714
1718
|
if (choice === 'install') {
|
|
1715
1719
|
if (method.inProcessInstallSupported) {
|
|
1716
|
-
|
|
1717
|
-
|
|
1720
|
+
// v4.9.1 — drive a live progress bar off the executor's
|
|
1721
|
+
// phase callback. The bar degrades cleanly on non-TTY, NO_COLOR,
|
|
1722
|
+
// and dumb terminals — see cli/v4/ui/progressBar.ts.
|
|
1723
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
1724
|
+
const pb = require('./ui/progressBar');
|
|
1725
|
+
const bar = pb.startProgressBar({
|
|
1726
|
+
label: `Installing aiden-runtime ${status.latest}...`,
|
|
1727
|
+
phases: ['spawning', 'resolving', 'downloading', 'extracting', 'verifying', 'installed'],
|
|
1728
|
+
});
|
|
1729
|
+
const result = await ei.executeInstall({
|
|
1730
|
+
packageSpec: `aiden-runtime@${status.latest}`,
|
|
1731
|
+
onPhase: (p) => { bar.setPhase(p); bar.setPercent(pb.npmInstallPhasePercent(p)); },
|
|
1732
|
+
});
|
|
1718
1733
|
if (result.success) {
|
|
1719
|
-
|
|
1720
|
-
this.opts.display.dim('Restart Aiden to apply: type /quit then re-run `aiden`.');
|
|
1734
|
+
bar.complete(`aiden-runtime ${result.installedVersion ?? status.latest} installed. Restart Aiden to apply: type /quit then re-run \`aiden\`.`);
|
|
1721
1735
|
}
|
|
1722
1736
|
else {
|
|
1737
|
+
bar.fail('Install failed.');
|
|
1723
1738
|
this.opts.display.warn(result.error ?? 'Install failed (no error message).');
|
|
1724
1739
|
}
|
|
1725
1740
|
}
|
|
@@ -104,10 +104,15 @@ async function runDaemonSubcommand(action, args, opts = {}) {
|
|
|
104
104
|
writeErr: err,
|
|
105
105
|
});
|
|
106
106
|
}
|
|
107
|
-
default:
|
|
107
|
+
default: {
|
|
108
108
|
err(`Unknown daemon action: ${action}\n`);
|
|
109
|
+
const { closestAction } = await Promise.resolve().then(() => __importStar(require('../util/closestAction')));
|
|
110
|
+
const m = closestAction(action, ['install', 'uninstall', 'start', 'stop', 'restart', 'status', 'logs', 'doctor']);
|
|
111
|
+
if (m)
|
|
112
|
+
err(`Did you mean: ${m}?\n\n`);
|
|
109
113
|
err('Actions: install, uninstall, start, stop, restart, status, logs, doctor\n');
|
|
110
114
|
return 2;
|
|
115
|
+
}
|
|
111
116
|
}
|
|
112
117
|
}
|
|
113
118
|
const SYSTEMD_UNIT_NAME = 'aiden.service';
|
|
@@ -89,7 +89,7 @@ function collectDoctorChecks(rootDir) {
|
|
|
89
89
|
ORDER BY started_at DESC LIMIT 1`).get();
|
|
90
90
|
if (!inc) {
|
|
91
91
|
checks.push({ name: 'recent incarnation', status: 'warn',
|
|
92
|
-
detail: 'no daemon_incarnations rows (daemon never
|
|
92
|
+
detail: 'no daemon_incarnations rows (daemon never started in this root)',
|
|
93
93
|
fixable: false });
|
|
94
94
|
}
|
|
95
95
|
else {
|
|
@@ -22,44 +22,63 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
22
22
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
23
23
|
};
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.daemonStatus = void 0;
|
|
25
|
+
exports.daemonStatus = exports.DAEMON_SHELL_ONLY = void 0;
|
|
26
|
+
exports.dispatchDaemonSlash = dispatchDaemonSlash;
|
|
26
27
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
27
28
|
const node_os_1 = __importDefault(require("node:os"));
|
|
28
29
|
const node_path_1 = __importDefault(require("node:path"));
|
|
29
|
-
const daemon_1 = require("
|
|
30
|
+
const daemon_1 = require("./daemon");
|
|
31
|
+
const daemon_2 = require("../../../core/v4/daemon");
|
|
30
32
|
const paths_1 = require("../../../core/v4/paths");
|
|
33
|
+
/**
|
|
34
|
+
* v4.9.1 amendment — `/daemon` defaults to `doctor` and routes
|
|
35
|
+
* `doctor` / `logs` to the existing `runDaemonSubcommand`. The pure-
|
|
36
|
+
* REPL `/daemon status` (in-process snapshot) stays inline. Lifecycle
|
|
37
|
+
* ops shell-hint — they need terminal control we can't grant inside chat.
|
|
38
|
+
*/
|
|
39
|
+
exports.DAEMON_SHELL_ONLY = new Set(['install', 'uninstall', 'start', 'stop', 'restart']);
|
|
40
|
+
async function dispatchDaemonSlash(opts) {
|
|
41
|
+
const a = (opts.action || 'doctor').toLowerCase();
|
|
42
|
+
if (exports.DAEMON_SHELL_ONLY.has(a)) {
|
|
43
|
+
opts.write(`⚠ /daemon ${a} not available inside chat (requires terminal control)\n`);
|
|
44
|
+
opts.write(' Quit (/quit) and run from shell:\n\n');
|
|
45
|
+
const tail = opts.args.length > 0 ? ' ' + opts.args.join(' ') : '';
|
|
46
|
+
opts.write(` aiden daemon ${a}${tail}\n`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (a === 'status' && opts.paintStatus) {
|
|
50
|
+
try {
|
|
51
|
+
opts.paintStatus();
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
opts.warn(`/daemon status: failed to read state (${e instanceof Error ? e.message : String(e)})`);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
await opts.runDaemon(a, opts.args, { writeOut: opts.write, writeErr: opts.write });
|
|
59
|
+
}
|
|
31
60
|
exports.daemonStatus = {
|
|
32
61
|
name: 'daemon',
|
|
33
|
-
description: '
|
|
62
|
+
description: 'Daemon diagnostics (doctor / status / logs).',
|
|
34
63
|
category: 'system',
|
|
35
64
|
icon: '⚙',
|
|
36
65
|
handler: async (ctx) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ctx.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
' aiden daemon logs');
|
|
46
|
-
return {};
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
const snapshot = readSnapshot();
|
|
50
|
-
printSnapshot(snapshot, ctx);
|
|
51
|
-
}
|
|
52
|
-
catch (e) {
|
|
53
|
-
ctx.display.warn(`/daemon status: failed to read state (${e instanceof Error ? e.message : String(e)})`);
|
|
54
|
-
}
|
|
66
|
+
await dispatchDaemonSlash({
|
|
67
|
+
action: ctx.args[0] ?? 'doctor',
|
|
68
|
+
args: ctx.args.slice(1),
|
|
69
|
+
write: (s) => ctx.display.write(s),
|
|
70
|
+
warn: (s) => ctx.display.warn(s),
|
|
71
|
+
runDaemon: daemon_1.runDaemonSubcommand,
|
|
72
|
+
paintStatus: () => printSnapshot(readSnapshot(), ctx),
|
|
73
|
+
});
|
|
55
74
|
return {};
|
|
56
75
|
},
|
|
57
76
|
};
|
|
58
77
|
// ── Snapshot collector ─────────────────────────────────────────────────────
|
|
59
78
|
function readSnapshot() {
|
|
60
79
|
const aidenRoot = (0, paths_1.resolveAidenRoot)();
|
|
61
|
-
const dbPath = (0,
|
|
62
|
-
const lockPath = (0,
|
|
80
|
+
const dbPath = (0, daemon_2.daemonDbPath)(aidenRoot);
|
|
81
|
+
const lockPath = (0, daemon_2.daemonRuntimeLockPath)(aidenRoot);
|
|
63
82
|
// ── Liveness via the in-process bootstrap handle first, then fall
|
|
64
83
|
// back to the runtime.lock PID check (covers the case where the
|
|
65
84
|
// daemon is another process and we're a REPL inspecting its db).
|
|
@@ -67,11 +86,11 @@ function readSnapshot() {
|
|
|
67
86
|
let instanceId = null;
|
|
68
87
|
let port = null;
|
|
69
88
|
let uptimeMs = null;
|
|
70
|
-
const handle = (0,
|
|
89
|
+
const handle = (0, daemon_2.getDaemonHandle)();
|
|
71
90
|
if (handle?.active && handle.instanceId) {
|
|
72
91
|
running = true;
|
|
73
92
|
instanceId = handle.instanceId;
|
|
74
|
-
port = (0,
|
|
93
|
+
port = (0, daemon_2.getDaemonConfig)().port;
|
|
75
94
|
if (handle.instanceTracker) {
|
|
76
95
|
// instanceTracker has a `getStartedAt` if exposed; otherwise
|
|
77
96
|
// derive from daemon_instances row below.
|
|
@@ -105,7 +124,7 @@ function readSnapshot() {
|
|
|
105
124
|
dailyBudget: null,
|
|
106
125
|
};
|
|
107
126
|
}
|
|
108
|
-
const db = (0,
|
|
127
|
+
const db = (0, daemon_2.openDaemonDb)(dbPath);
|
|
109
128
|
// Uptime from the instance row when we have an instanceId.
|
|
110
129
|
if (running && instanceId) {
|
|
111
130
|
try {
|
|
@@ -76,6 +76,9 @@ exports.SUBSECTION_MAP = {
|
|
|
76
76
|
recovery: 'System',
|
|
77
77
|
// v4.6 ONB1 slice 10 — new-user guided tour.
|
|
78
78
|
walkthrough: 'System',
|
|
79
|
+
// v4.9.1 amendment — REPL surfaces for memory + hooks (daemon already mapped).
|
|
80
|
+
memory: 'System',
|
|
81
|
+
hooks: 'System',
|
|
79
82
|
// ── Authentication ──
|
|
80
83
|
auth: 'Authentication',
|
|
81
84
|
// ── Help ──
|
|
@@ -23,6 +23,39 @@
|
|
|
23
23
|
* directory and runs a rescan, but NEVER auto-trusts anything (trust
|
|
24
24
|
* remains an explicit, deliberate user action).
|
|
25
25
|
*/
|
|
26
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
27
|
+
if (k2 === undefined) k2 = k;
|
|
28
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
29
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
30
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
31
|
+
}
|
|
32
|
+
Object.defineProperty(o, k2, desc);
|
|
33
|
+
}) : (function(o, m, k, k2) {
|
|
34
|
+
if (k2 === undefined) k2 = k;
|
|
35
|
+
o[k2] = m[k];
|
|
36
|
+
}));
|
|
37
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
38
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
39
|
+
}) : function(o, v) {
|
|
40
|
+
o["default"] = v;
|
|
41
|
+
});
|
|
42
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
43
|
+
var ownKeys = function(o) {
|
|
44
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
45
|
+
var ar = [];
|
|
46
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
47
|
+
return ar;
|
|
48
|
+
};
|
|
49
|
+
return ownKeys(o);
|
|
50
|
+
};
|
|
51
|
+
return function (mod) {
|
|
52
|
+
if (mod && mod.__esModule) return mod;
|
|
53
|
+
var result = {};
|
|
54
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
55
|
+
__setModuleDefault(result, mod);
|
|
56
|
+
return result;
|
|
57
|
+
};
|
|
58
|
+
})();
|
|
26
59
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
60
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
61
|
};
|
|
@@ -78,10 +111,15 @@ async function runHooksSubcommand(action, args, opts = {}) {
|
|
|
78
111
|
case 'audit': return await cmdAudit(ctx);
|
|
79
112
|
case '--help':
|
|
80
113
|
case 'help': return cmdHelp(out);
|
|
81
|
-
default:
|
|
114
|
+
default: {
|
|
82
115
|
err(`Unknown hooks action: ${effective}\n`);
|
|
116
|
+
const { closestAction } = await Promise.resolve().then(() => __importStar(require('../util/closestAction')));
|
|
117
|
+
const m = closestAction(effective, ['list', 'show', 'trust', 'revoke', 'rescan', 'test', 'doctor', 'audit']);
|
|
118
|
+
if (m)
|
|
119
|
+
err(`Did you mean: ${m}?\n\n`);
|
|
83
120
|
cmdHelp(err);
|
|
84
121
|
return 2;
|
|
122
|
+
}
|
|
85
123
|
}
|
|
86
124
|
}
|
|
87
125
|
finally {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hooks = exports.HOOKS_SHELL_ONLY = void 0;
|
|
4
|
+
exports.dispatchHooksSlash = dispatchHooksSlash;
|
|
5
|
+
const hooks_1 = require("./hooks");
|
|
6
|
+
/** Actions that need an interactive confirmation prompt. */
|
|
7
|
+
exports.HOOKS_SHELL_ONLY = new Set(['trust', 'revoke']);
|
|
8
|
+
async function dispatchHooksSlash(opts) {
|
|
9
|
+
const a = (opts.action || 'list').toLowerCase();
|
|
10
|
+
if (exports.HOOKS_SHELL_ONLY.has(a)) {
|
|
11
|
+
opts.write(`⚠ /hooks ${a} not available inside chat (needs confirmation prompt)\n`);
|
|
12
|
+
opts.write(' Quit (/quit) and run from shell:\n\n');
|
|
13
|
+
const tail = opts.args.length > 0 ? ' ' + opts.args.join(' ') : '';
|
|
14
|
+
opts.write(` aiden hooks ${a}${tail}\n`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
await opts.runHooks(a, opts.args, { writeOut: opts.write, writeErr: opts.write });
|
|
18
|
+
}
|
|
19
|
+
exports.hooks = {
|
|
20
|
+
name: 'hooks',
|
|
21
|
+
description: 'Manage hooks (list / show / rescan / test / doctor / audit).',
|
|
22
|
+
category: 'system',
|
|
23
|
+
icon: '🪝',
|
|
24
|
+
handler: async (ctx) => {
|
|
25
|
+
await dispatchHooksSlash({
|
|
26
|
+
action: ctx.args[0] ?? 'list',
|
|
27
|
+
args: ctx.args.slice(1),
|
|
28
|
+
write: (s) => ctx.display.write(s),
|
|
29
|
+
runHooks: hooks_1.runHooksSubcommand,
|
|
30
|
+
});
|
|
31
|
+
return {};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* and registers each on the global CommandRegistry at boot.
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.allCommands = exports.walkthrough = exports.recovery = exports.spawnPause = exports.plannerGuard = exports.suggestions = exports.daemonStatus = exports.browserDepth = exports.tce = exports.sandbox = exports.update = exports.reloadSoul = exports.history = exports.show = exports.status = exports.voice = exports.channel = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.theme = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
|
|
15
|
+
exports.allCommands = exports.hooks = exports.memory = exports.walkthrough = exports.recovery = exports.spawnPause = exports.plannerGuard = exports.suggestions = exports.daemonStatus = exports.browserDepth = exports.tce = exports.sandbox = exports.update = exports.reloadSoul = exports.history = exports.show = exports.status = exports.voice = exports.channel = exports.setup = exports.cron = exports.doctor = exports.license = exports.auth = exports.plugins = exports.streaming = exports.debugPrompt = exports.identity = exports.providers = exports.quit = exports.clear = exports.verbose = exports.reasoning = exports.reloadMcp = exports.skills = exports.theme = exports.skin = exports.yolo = exports.usage = exports.compress = exports.title = exports.save = exports.personality = exports.model = exports.tools = exports.help = void 0;
|
|
16
16
|
const help_1 = require("./help");
|
|
17
17
|
Object.defineProperty(exports, "help", { enumerable: true, get: function () { return help_1.help; } });
|
|
18
18
|
const tools_1 = require("./tools");
|
|
@@ -106,6 +106,11 @@ Object.defineProperty(exports, "recovery", { enumerable: true, get: function ()
|
|
|
106
106
|
// ONB1 slice 10 — new-user guided tour.
|
|
107
107
|
const walkthrough_1 = require("./walkthrough");
|
|
108
108
|
Object.defineProperty(exports, "walkthrough", { enumerable: true, get: function () { return walkthrough_1.walkthrough; } });
|
|
109
|
+
// v4.9.1 amendment — REPL slash surfaces for memory + hooks (mirrors CLI).
|
|
110
|
+
const memorySlash_1 = require("./memorySlash");
|
|
111
|
+
Object.defineProperty(exports, "memory", { enumerable: true, get: function () { return memorySlash_1.memory; } });
|
|
112
|
+
const hooksSlash_1 = require("./hooksSlash");
|
|
113
|
+
Object.defineProperty(exports, "hooks", { enumerable: true, get: function () { return hooksSlash_1.hooks; } });
|
|
109
114
|
/** All built-in system commands, in canonical order. */
|
|
110
115
|
exports.allCommands = [
|
|
111
116
|
help_1.help,
|
|
@@ -158,6 +163,9 @@ exports.allCommands = [
|
|
|
158
163
|
recovery_1.recovery,
|
|
159
164
|
// ONB1 slice 10 — new-user guided tour.
|
|
160
165
|
walkthrough_1.walkthrough,
|
|
166
|
+
// v4.9.1 amendment — REPL slash surfaces mirroring CLI subcommands.
|
|
167
|
+
memorySlash_1.memory,
|
|
168
|
+
hooksSlash_1.hooks,
|
|
161
169
|
clear_1.clear,
|
|
162
170
|
quit_1.quit,
|
|
163
171
|
];
|
|
@@ -155,10 +155,15 @@ async function runMemorySubcommand(action, args, opts = {}) {
|
|
|
155
155
|
case 'review': return cmdReview(args, paths, opts, out, err, json);
|
|
156
156
|
case '--help':
|
|
157
157
|
case 'help': return cmdHelp(out);
|
|
158
|
-
default:
|
|
158
|
+
default: {
|
|
159
159
|
err(`Unknown memory action: ${effective}\n`);
|
|
160
|
+
const { closestAction } = await Promise.resolve().then(() => __importStar(require('../util/closestAction')));
|
|
161
|
+
const m = closestAction(effective, ['list', 'show', 'add', 'remove', 'edit', 'backup', 'restore', 'diff', 'namespaces', 'pending', 'approve', 'reject', 'review']);
|
|
162
|
+
if (m)
|
|
163
|
+
err(`Did you mean: ${m}?\n\n`);
|
|
160
164
|
cmdHelp(err);
|
|
161
165
|
return 2;
|
|
166
|
+
}
|
|
162
167
|
}
|
|
163
168
|
}
|
|
164
169
|
function cmdHelp(write) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.memory = exports.MEMORY_SHELL_ONLY = void 0;
|
|
4
|
+
exports.dispatchMemorySlash = dispatchMemorySlash;
|
|
5
|
+
const memory_1 = require("./memory");
|
|
6
|
+
/** Actions that need the full CLI surface (confirmation / destructive). */
|
|
7
|
+
exports.MEMORY_SHELL_ONLY = new Set(['remove', 'restore']);
|
|
8
|
+
/**
|
|
9
|
+
* Pure dispatch — exported for tests + reuse. Either prints a shell
|
|
10
|
+
* hint OR delegates to the provided `runMemory` runner. Side effects
|
|
11
|
+
* confined to the supplied `write` sink.
|
|
12
|
+
*/
|
|
13
|
+
async function dispatchMemorySlash(opts) {
|
|
14
|
+
const a = (opts.action || 'list').toLowerCase();
|
|
15
|
+
if (exports.MEMORY_SHELL_ONLY.has(a)) {
|
|
16
|
+
opts.write(`⚠ /memory ${a} not available inside chat (destructive operation)\n`);
|
|
17
|
+
opts.write(' Quit (/quit) and run from shell:\n\n');
|
|
18
|
+
const tail = opts.args.length > 0 ? ' ' + opts.args.join(' ') : '';
|
|
19
|
+
opts.write(` aiden memory ${a}${tail}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
await opts.runMemory(a, opts.args, { writeOut: opts.write, writeErr: opts.write });
|
|
23
|
+
}
|
|
24
|
+
exports.memory = {
|
|
25
|
+
name: 'memory',
|
|
26
|
+
description: 'Manage memory (list / show / add / namespaces / pending / approve / review).',
|
|
27
|
+
category: 'system',
|
|
28
|
+
icon: '🧠',
|
|
29
|
+
handler: async (ctx) => {
|
|
30
|
+
await dispatchMemorySlash({
|
|
31
|
+
action: ctx.args[0] ?? 'list',
|
|
32
|
+
args: ctx.args.slice(1),
|
|
33
|
+
write: (s) => ctx.display.write(s),
|
|
34
|
+
runMemory: memory_1.runMemorySubcommand,
|
|
35
|
+
});
|
|
36
|
+
return {};
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* cli/v4/ui/progressBar.ts — v4.9.1 reusable progress animation.
|
|
10
|
+
* Auto-detects TTY / NO_COLOR / TERM=dumb / CI to pick render mode
|
|
11
|
+
* (block glyphs vs `#-`, color vs plain, animated vs once-per-second
|
|
12
|
+
* non-TTY lines). Cursor hidden during animation, restored on exit.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.detectRenderMode = detectRenderMode;
|
|
16
|
+
exports.renderLine = renderLine;
|
|
17
|
+
exports.startProgressBar = startProgressBar;
|
|
18
|
+
exports.npmInstallPhasePercent = npmInstallPhasePercent;
|
|
19
|
+
exports.detectNpmPhase = detectNpmPhase;
|
|
20
|
+
const DEFAULT_WIDTH = 28;
|
|
21
|
+
const DEFAULT_TICK_MS = 100;
|
|
22
|
+
/** Minimum elapsed before we paint anything — avoids flicker on sub-300ms ops. */
|
|
23
|
+
const PAINT_AFTER_MS = 300;
|
|
24
|
+
const ANSI_HIDE_CURSOR = '\x1b[?25l';
|
|
25
|
+
const ANSI_SHOW_CURSOR = '\x1b[?25h';
|
|
26
|
+
const ANSI_CLEAR_LINE = '\r\x1b[2K';
|
|
27
|
+
const ANSI_BRAND = '\x1b[38;2;255;107;53m'; // RGB 255,107,53 (Aiden orange)
|
|
28
|
+
const ANSI_MUTED = '\x1b[38;2;106;106;106m';
|
|
29
|
+
const ANSI_SUCCESS = '\x1b[38;2;127;194;139m';
|
|
30
|
+
const ANSI_ERROR = '\x1b[38;2;224;90;90m';
|
|
31
|
+
const ANSI_RESET = '\x1b[0m';
|
|
32
|
+
/** Detect the right render mode from TTY + env. Pure function. */
|
|
33
|
+
function detectRenderMode(isTTY, env = process.env) {
|
|
34
|
+
if (!isTTY)
|
|
35
|
+
return { color: false, blocks: false, animated: false };
|
|
36
|
+
const noColor = env.NO_COLOR !== undefined && env.NO_COLOR !== '';
|
|
37
|
+
const dumb = env.TERM === 'dumb' || env.CI === 'true' || env.CI === '1';
|
|
38
|
+
return {
|
|
39
|
+
color: !noColor && !dumb,
|
|
40
|
+
blocks: !dumb,
|
|
41
|
+
animated: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the rendered line (without trailing newline). Pure so tests
|
|
46
|
+
* can assert byte-for-byte without timing.
|
|
47
|
+
*/
|
|
48
|
+
function renderLine(opts) {
|
|
49
|
+
const pct = Math.max(0, Math.min(100, Math.round(opts.percent)));
|
|
50
|
+
const filled = Math.round((pct / 100) * opts.width);
|
|
51
|
+
const empty = opts.width - filled;
|
|
52
|
+
const full = opts.mode.blocks ? '█' : '#';
|
|
53
|
+
const blank = opts.mode.blocks ? '░' : '-';
|
|
54
|
+
const elapsed = `${(opts.elapsedMs / 1000).toFixed(1)}s`;
|
|
55
|
+
const bar = full.repeat(filled) + blank.repeat(empty);
|
|
56
|
+
if (opts.mode.color) {
|
|
57
|
+
return `${ANSI_BRAND}[${bar}]${ANSI_RESET} ${pct}% ${ANSI_MUTED}${opts.phase}${ANSI_RESET} ${ANSI_MUTED}${elapsed}${ANSI_RESET}`;
|
|
58
|
+
}
|
|
59
|
+
return `[${bar}] ${pct}% ${opts.phase} ${elapsed}`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Start a progress bar. Returns a controller object. Never throws —
|
|
63
|
+
* any I/O failure on output degrades the bar to a silent no-op while
|
|
64
|
+
* still honoring `complete` / `fail` semantics for the caller.
|
|
65
|
+
*/
|
|
66
|
+
function startProgressBar(opts) {
|
|
67
|
+
const width = opts.width ?? DEFAULT_WIDTH;
|
|
68
|
+
const out = opts.out ?? process.stdout;
|
|
69
|
+
const env = opts.env ?? process.env;
|
|
70
|
+
const tickMs = opts.tickMs ?? DEFAULT_TICK_MS;
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
const isTTY = opts.isTTY ?? Boolean(out.isTTY);
|
|
73
|
+
const mode = detectRenderMode(isTTY, env);
|
|
74
|
+
const startedAt = Date.now();
|
|
75
|
+
let phase = opts.phases[0] ?? '';
|
|
76
|
+
let percent = 0;
|
|
77
|
+
let painted = false;
|
|
78
|
+
let closed = false;
|
|
79
|
+
const write = (s) => {
|
|
80
|
+
try {
|
|
81
|
+
out.write(s);
|
|
82
|
+
}
|
|
83
|
+
catch { /* swallow — never break caller */ }
|
|
84
|
+
};
|
|
85
|
+
// SIGINT: restore cursor + clear the partial line before bubbling.
|
|
86
|
+
const onSigint = () => {
|
|
87
|
+
try {
|
|
88
|
+
write(ANSI_CLEAR_LINE + ANSI_SHOW_CURSOR);
|
|
89
|
+
}
|
|
90
|
+
catch { /* noop */ }
|
|
91
|
+
};
|
|
92
|
+
if (mode.animated) {
|
|
93
|
+
try {
|
|
94
|
+
process.once('SIGINT', onSigint);
|
|
95
|
+
}
|
|
96
|
+
catch { /* noop */ }
|
|
97
|
+
}
|
|
98
|
+
// Label line paints once, immediately.
|
|
99
|
+
write(`${mode.color ? ANSI_MUTED : ''}${opts.label}${mode.color ? ANSI_RESET : ''}\n`);
|
|
100
|
+
const paint = () => {
|
|
101
|
+
if (closed)
|
|
102
|
+
return;
|
|
103
|
+
const elapsedMs = Date.now() - startedAt;
|
|
104
|
+
if (elapsedMs < PAINT_AFTER_MS)
|
|
105
|
+
return;
|
|
106
|
+
const line = renderLine({ width, percent, phase, elapsedMs, mode });
|
|
107
|
+
if (mode.animated) {
|
|
108
|
+
if (!painted) {
|
|
109
|
+
write(ANSI_HIDE_CURSOR);
|
|
110
|
+
painted = true;
|
|
111
|
+
}
|
|
112
|
+
write(ANSI_CLEAR_LINE + line);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
write(line + '\n');
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
let timer = null;
|
|
119
|
+
if (mode.animated) {
|
|
120
|
+
timer = setInterval(paint, tickMs);
|
|
121
|
+
if (typeof timer.unref === 'function')
|
|
122
|
+
timer.unref();
|
|
123
|
+
}
|
|
124
|
+
const close = (icon, color, message) => {
|
|
125
|
+
if (closed)
|
|
126
|
+
return;
|
|
127
|
+
closed = true;
|
|
128
|
+
if (timer)
|
|
129
|
+
clearInterval(timer);
|
|
130
|
+
try {
|
|
131
|
+
process.removeListener('SIGINT', onSigint);
|
|
132
|
+
}
|
|
133
|
+
catch { /* noop */ }
|
|
134
|
+
const finalLine = mode.color
|
|
135
|
+
? `${color}${icon}${ANSI_RESET} ${message}`
|
|
136
|
+
: `${icon} ${message}`;
|
|
137
|
+
if (mode.animated && painted)
|
|
138
|
+
write(ANSI_CLEAR_LINE);
|
|
139
|
+
write(finalLine + '\n');
|
|
140
|
+
if (mode.animated)
|
|
141
|
+
write(ANSI_SHOW_CURSOR);
|
|
142
|
+
};
|
|
143
|
+
return {
|
|
144
|
+
setPhase(name) { phase = name; if (!mode.animated)
|
|
145
|
+
paint(); },
|
|
146
|
+
setPercent(p) { percent = p; if (!mode.animated)
|
|
147
|
+
paint(); },
|
|
148
|
+
complete(message) { close('✓', ANSI_SUCCESS, message); },
|
|
149
|
+
fail(message) { close('✗', ANSI_ERROR, message); },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/** npm install phase → default percent. Best-effort bar shaping. */
|
|
153
|
+
function npmInstallPhasePercent(phase) {
|
|
154
|
+
switch (phase) {
|
|
155
|
+
case 'spawning': return 3;
|
|
156
|
+
case 'resolving': return 15;
|
|
157
|
+
case 'downloading': return 50;
|
|
158
|
+
case 'extracting': return 85;
|
|
159
|
+
case 'verifying': return 97;
|
|
160
|
+
case 'installed': return 100;
|
|
161
|
+
case 'failed': return 100;
|
|
162
|
+
default: return 0;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Detect npm phase from a stdout/stderr line. Checks ordered for npm 9/10/11. */
|
|
166
|
+
function detectNpmPhase(line) {
|
|
167
|
+
const l = line.toLowerCase();
|
|
168
|
+
if (l.includes('added ') && l.includes('package'))
|
|
169
|
+
return 'verifying';
|
|
170
|
+
if (l.includes('http fetch'))
|
|
171
|
+
return 'downloading';
|
|
172
|
+
if (l.includes('extracting') || l.includes('extract'))
|
|
173
|
+
return 'extracting';
|
|
174
|
+
if (l.includes('reify:'))
|
|
175
|
+
return 'downloading';
|
|
176
|
+
if (l.includes('resolved') || l.includes('audit'))
|
|
177
|
+
return 'resolving';
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.closestAction = closestAction;
|
|
4
|
+
/**
|
|
5
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
6
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
7
|
+
*
|
|
8
|
+
* Aiden — local-first agent.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* cli/v4/util/closestAction.ts — v4.9.1 amendment.
|
|
12
|
+
* Suggest the closest known action when the user mis-types a subcommand.
|
|
13
|
+
* Matches if input is a substring of a known action OR Levenshtein
|
|
14
|
+
* distance ≤ 2. Returns null when nothing is reasonably close.
|
|
15
|
+
*/
|
|
16
|
+
function lev(a, b) {
|
|
17
|
+
const m = a.length, n = b.length;
|
|
18
|
+
if (m === 0)
|
|
19
|
+
return n;
|
|
20
|
+
if (n === 0)
|
|
21
|
+
return m;
|
|
22
|
+
const row = Array.from({ length: n + 1 }, (_, i) => i);
|
|
23
|
+
for (let i = 1; i <= m; i++) {
|
|
24
|
+
let prev = i - 1;
|
|
25
|
+
row[0] = i;
|
|
26
|
+
for (let j = 1; j <= n; j++) {
|
|
27
|
+
const cur = row[j];
|
|
28
|
+
row[j] = a[i - 1] === b[j - 1] ? prev : Math.min(prev, row[j - 1], row[j]) + 1;
|
|
29
|
+
prev = cur;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return row[n];
|
|
33
|
+
}
|
|
34
|
+
function closestAction(input, known) {
|
|
35
|
+
if (!input)
|
|
36
|
+
return null;
|
|
37
|
+
const lo = input.toLowerCase();
|
|
38
|
+
let best = null;
|
|
39
|
+
for (const k of known) {
|
|
40
|
+
const kl = k.toLowerCase();
|
|
41
|
+
if (kl.includes(lo) || lo.includes(kl))
|
|
42
|
+
return k;
|
|
43
|
+
const d = lev(lo, kl);
|
|
44
|
+
if (d <= 2 && (!best || d < best.d))
|
|
45
|
+
best = { name: k, d };
|
|
46
|
+
}
|
|
47
|
+
return best?.name ?? null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* core/v4/update/depWarningFilter.ts — v4.9.1.
|
|
10
|
+
*
|
|
11
|
+
* Strip Node `DeprecationWarning` noise (DEP0190 and friends) from
|
|
12
|
+
* npm install stderr before it reaches the user. Filtered lines are
|
|
13
|
+
* preserved in `~/.aiden/logs/update.log` so diagnostics aren't lost.
|
|
14
|
+
*
|
|
15
|
+
* Conservative match — only filters lines that BOTH look like a Node
|
|
16
|
+
* deprecation header AND name a DEP code or the trace-deprecation hint.
|
|
17
|
+
* Legitimate npm errors (EACCES, ENOTFOUND, ENOENT, etc.) pass through.
|
|
18
|
+
*/
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.isDeprecationLine = isDeprecationLine;
|
|
24
|
+
exports.splitStderr = splitStderr;
|
|
25
|
+
exports.logFilteredWarnings = logFilteredWarnings;
|
|
26
|
+
const node_fs_1 = require("node:fs");
|
|
27
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
28
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
29
|
+
/** True iff the line is Node-deprecation chatter we should hide. */
|
|
30
|
+
function isDeprecationLine(line) {
|
|
31
|
+
// Node's deprecation banner header: "(node:NNNN) [DEP0190] DeprecationWarning: ..."
|
|
32
|
+
if (/^\s*\(node:\d+\)\s*(?:\[DEP\d+\]\s*)?DeprecationWarning:/.test(line))
|
|
33
|
+
return true;
|
|
34
|
+
// The follow-up hint Node emits underneath the header.
|
|
35
|
+
if (/Use `node --trace-deprecation/.test(line))
|
|
36
|
+
return true;
|
|
37
|
+
// Bare DEP code lines (some Node versions emit these stand-alone).
|
|
38
|
+
if (/^\s*\[DEP\d+\]/.test(line))
|
|
39
|
+
return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Split an stderr blob into `kept` (user-visible) and `filtered`
|
|
44
|
+
* (deprecation noise, routed to the diagnostic log).
|
|
45
|
+
*/
|
|
46
|
+
function splitStderr(blob) {
|
|
47
|
+
if (!blob)
|
|
48
|
+
return { kept: '', filtered: '' };
|
|
49
|
+
const lines = blob.split(/\r?\n/);
|
|
50
|
+
const kept = [];
|
|
51
|
+
const filtered = [];
|
|
52
|
+
for (const ln of lines) {
|
|
53
|
+
if (isDeprecationLine(ln))
|
|
54
|
+
filtered.push(ln);
|
|
55
|
+
else
|
|
56
|
+
kept.push(ln);
|
|
57
|
+
}
|
|
58
|
+
return { kept: kept.join('\n'), filtered: filtered.join('\n') };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Append filtered lines to `~/.aiden/logs/update.log` with an ISO
|
|
62
|
+
* timestamp header. Fail-open: a log-write failure must NEVER crash
|
|
63
|
+
* the install path.
|
|
64
|
+
*/
|
|
65
|
+
async function logFilteredWarnings(filtered, opts = {}) {
|
|
66
|
+
if (!filtered || !filtered.trim())
|
|
67
|
+
return;
|
|
68
|
+
try {
|
|
69
|
+
const root = opts.aidenRoot ?? node_path_1.default.join(node_os_1.default.homedir(), '.aiden');
|
|
70
|
+
const logDir = node_path_1.default.join(root, 'logs');
|
|
71
|
+
await node_fs_1.promises.mkdir(logDir, { recursive: true });
|
|
72
|
+
const entry = `[${new Date().toISOString()}] update.npm-deprecation:\n${filtered}\n\n`;
|
|
73
|
+
await node_fs_1.promises.appendFile(node_path_1.default.join(logDir, 'update.log'), entry, 'utf8');
|
|
74
|
+
}
|
|
75
|
+
catch { /* fail-open */ }
|
|
76
|
+
}
|
|
@@ -42,11 +42,18 @@
|
|
|
42
42
|
* - No registry probe — call `checkForUpdate` first if you need to
|
|
43
43
|
* know whether an install is warranted.
|
|
44
44
|
*/
|
|
45
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
46
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
47
|
+
};
|
|
45
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
49
|
exports.INSTALL_TIMEOUT_MS = void 0;
|
|
47
50
|
exports.executeInstall = executeInstall;
|
|
48
51
|
exports.parseInstalledVersion = parseInstalledVersion;
|
|
49
52
|
const node_child_process_1 = require("node:child_process");
|
|
53
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
54
|
+
const depWarningFilter_1 = require("./depWarningFilter");
|
|
55
|
+
const platformInstructions_1 = require("./platformInstructions");
|
|
56
|
+
const progressBar_1 = require("../../../cli/v4/ui/progressBar");
|
|
50
57
|
/** 90 s wall-clock cap. Generous on cold caches / slow networks. */
|
|
51
58
|
exports.INSTALL_TIMEOUT_MS = 90000;
|
|
52
59
|
const DEFAULT_PACKAGE_SPEC = 'aiden-runtime@latest';
|
|
@@ -64,6 +71,9 @@ async function executeInstall(opts = {}) {
|
|
|
64
71
|
const timeoutMs = opts.timeoutMs ?? exports.INSTALL_TIMEOUT_MS;
|
|
65
72
|
const packageSpec = opts.packageSpec ?? DEFAULT_PACKAGE_SPEC;
|
|
66
73
|
const platform = opts.platform ?? process.platform;
|
|
74
|
+
const home = opts.home ?? node_os_1.default.homedir();
|
|
75
|
+
const env = opts.env ?? process.env;
|
|
76
|
+
const onPhase = opts.onPhase ?? ((_p) => { });
|
|
67
77
|
return new Promise((resolve) => {
|
|
68
78
|
const args = ['install', '-g', packageSpec];
|
|
69
79
|
// v4.8.1 Slice 2 — drop `shell: true`. Node 20+ emits
|
|
@@ -78,6 +88,7 @@ async function executeInstall(opts = {}) {
|
|
|
78
88
|
const spawnOpts = {
|
|
79
89
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
90
|
};
|
|
91
|
+
onPhase('spawning');
|
|
81
92
|
let child;
|
|
82
93
|
try {
|
|
83
94
|
child = spawn(cmd, args, spawnOpts);
|
|
@@ -92,11 +103,23 @@ async function executeInstall(opts = {}) {
|
|
|
92
103
|
}
|
|
93
104
|
let stdoutBuf = '';
|
|
94
105
|
let stderrBuf = '';
|
|
106
|
+
// v4.9.1 — parse phase signal off each chunk.
|
|
107
|
+
const tryEmitPhase = (chunk) => {
|
|
108
|
+
for (const ln of chunk.split(/\r?\n/)) {
|
|
109
|
+
const p = (0, progressBar_1.detectNpmPhase)(ln);
|
|
110
|
+
if (p)
|
|
111
|
+
onPhase(p);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
95
114
|
child.stdout?.on('data', (chunk) => {
|
|
96
|
-
|
|
115
|
+
const s = chunk.toString();
|
|
116
|
+
stdoutBuf += s;
|
|
117
|
+
tryEmitPhase(s);
|
|
97
118
|
});
|
|
98
119
|
child.stderr?.on('data', (chunk) => {
|
|
99
|
-
|
|
120
|
+
const s = chunk.toString();
|
|
121
|
+
stderrBuf += s;
|
|
122
|
+
tryEmitPhase(s);
|
|
100
123
|
});
|
|
101
124
|
// Timeout — kill the child + resolve as a failure with the captured
|
|
102
125
|
// output so the user sees what npm was doing.
|
|
@@ -121,7 +144,11 @@ async function executeInstall(opts = {}) {
|
|
|
121
144
|
child.on('close', (code) => {
|
|
122
145
|
clearTimeout(timer);
|
|
123
146
|
const stdout = stdoutBuf;
|
|
124
|
-
|
|
147
|
+
// v4.9.1 — strip Node DEP* noise from stderr before any surfacing
|
|
148
|
+
// to the user. Filtered lines land in ~/.aiden/logs/update.log.
|
|
149
|
+
const { kept: stderr, filtered } = (0, depWarningFilter_1.splitStderr)(stderrBuf);
|
|
150
|
+
if (filtered)
|
|
151
|
+
void (0, depWarningFilter_1.logFilteredWarnings)(filtered);
|
|
125
152
|
const exitCode = code ?? -1;
|
|
126
153
|
if (timedOut) {
|
|
127
154
|
resolve({
|
|
@@ -134,14 +161,16 @@ async function executeInstall(opts = {}) {
|
|
|
134
161
|
}
|
|
135
162
|
// Permission-denied: surface platform-specific remediations.
|
|
136
163
|
if (isPermissionDenied(stdout, stderr, exitCode)) {
|
|
164
|
+
onPhase('failed');
|
|
137
165
|
resolve({
|
|
138
166
|
success: false,
|
|
139
|
-
error: permissionDeniedMessage(platform),
|
|
167
|
+
error: permissionDeniedMessage(platform, home, env),
|
|
140
168
|
stdout, stderr, exitCode,
|
|
141
169
|
});
|
|
142
170
|
return;
|
|
143
171
|
}
|
|
144
172
|
if (exitCode !== 0) {
|
|
173
|
+
onPhase('failed');
|
|
145
174
|
resolve({
|
|
146
175
|
success: false,
|
|
147
176
|
error: `Install failed (npm exit ${exitCode}). ` +
|
|
@@ -153,6 +182,7 @@ async function executeInstall(opts = {}) {
|
|
|
153
182
|
}
|
|
154
183
|
// Success — parse installed version from npm output. Pattern:
|
|
155
184
|
// "+ aiden-runtime@4.1.3" or "added 1 package ... aiden-runtime@4.1.3"
|
|
185
|
+
onPhase('installed');
|
|
156
186
|
const installedVersion = parseInstalledVersion(stdout) ?? parseInstalledVersion(stderr) ?? undefined;
|
|
157
187
|
resolve({
|
|
158
188
|
success: true,
|
|
@@ -190,38 +220,14 @@ function isPermissionDenied(stdout, stderr, exitCode) {
|
|
|
190
220
|
return false;
|
|
191
221
|
}
|
|
192
222
|
/**
|
|
193
|
-
* Build the platform-specific copy-paste remediation.
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
* from inside the running REPL.
|
|
223
|
+
* v4.9.1 — Build the platform-specific copy-paste remediation. Delegates
|
|
224
|
+
* to `platformInstructions.ts` for the heavy lifting so the same builder
|
|
225
|
+
* powers both EPERM remediation + stale-prefix warnings + the future
|
|
226
|
+
* `aiden update --setup-user-prefix` helper.
|
|
198
227
|
*/
|
|
199
|
-
function permissionDeniedMessage(platform) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
' export PATH=~/.npm-global/bin:$PATH # add to your shell profile\n' +
|
|
203
|
-
' npm install -g aiden-runtime@latest';
|
|
204
|
-
if (platform === 'win32') {
|
|
205
|
-
return [
|
|
206
|
-
'Install failed: permission denied (npm needs Administrator for global install).',
|
|
207
|
-
'',
|
|
208
|
-
'To update manually:',
|
|
209
|
-
' Windows: Open PowerShell as Administrator, then:',
|
|
210
|
-
' npm install -g aiden-runtime@latest',
|
|
211
|
-
'',
|
|
212
|
-
userLocal,
|
|
213
|
-
].join('\n');
|
|
214
|
-
}
|
|
215
|
-
// darwin / linux / others — sudo path.
|
|
216
|
-
return [
|
|
217
|
-
'Install failed: permission denied (npm needs sudo for global install).',
|
|
218
|
-
'',
|
|
219
|
-
'To update manually:',
|
|
220
|
-
' macOS / Linux:',
|
|
221
|
-
' sudo npm install -g aiden-runtime@latest',
|
|
222
|
-
'',
|
|
223
|
-
userLocal,
|
|
224
|
-
].join('\n');
|
|
228
|
+
function permissionDeniedMessage(platform, home, env) {
|
|
229
|
+
const instr = (0, platformInstructions_1.permissionDeniedInstructions)({ platform, home, env });
|
|
230
|
+
return [instr.headline, '', ...instr.steps].join('\n');
|
|
225
231
|
}
|
|
226
232
|
/**
|
|
227
233
|
* Find the installed version in npm output. Two common patterns:
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2026 Shiva Deore (Taracod).
|
|
4
|
+
* Licensed under AGPL-3.0. See LICENSE for details.
|
|
5
|
+
*
|
|
6
|
+
* Aiden — local-first agent.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* core/v4/update/platformInstructions.ts — v4.9.1.
|
|
10
|
+
*
|
|
11
|
+
* Per-platform copy-paste remediation text for:
|
|
12
|
+
* - EPERM / EACCES during global npm install
|
|
13
|
+
* - Stale / risky npm prefix detection
|
|
14
|
+
*
|
|
15
|
+
* Branches purely on `process.platform` (and `$SHELL` for unix shell-
|
|
16
|
+
* rc-file recommendations). Shell syntax MUST be correct per-platform:
|
|
17
|
+
* PowerShell on Windows, bash/zsh on Unix. Cross-contamination is a
|
|
18
|
+
* regression (the v4.9.0 bug we're hot-fixing).
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.detectShell = detectShell;
|
|
22
|
+
exports.permissionDeniedInstructions = permissionDeniedInstructions;
|
|
23
|
+
exports.detectStalePrefix = detectStalePrefix;
|
|
24
|
+
/**
|
|
25
|
+
* Detect the user's interactive shell on POSIX. Returns the basename
|
|
26
|
+
* (`zsh` / `bash` / `sh`) or null when undetectable. Pure — env is
|
|
27
|
+
* injected so tests pin a value.
|
|
28
|
+
*/
|
|
29
|
+
function detectShell(env = process.env) {
|
|
30
|
+
const sh = env.SHELL;
|
|
31
|
+
if (!sh)
|
|
32
|
+
return null;
|
|
33
|
+
const last = sh.split(/[\\/]/).pop() || '';
|
|
34
|
+
return last.toLowerCase() || null;
|
|
35
|
+
}
|
|
36
|
+
/** rc-file path the user should edit, per shell. POSIX only. */
|
|
37
|
+
function rcFileFor(shell, home) {
|
|
38
|
+
if (shell === 'zsh')
|
|
39
|
+
return `${home}/.zshrc`;
|
|
40
|
+
if (shell === 'bash')
|
|
41
|
+
return `${home}/.bashrc (or ~/.bash_profile on macOS)`;
|
|
42
|
+
return `${home}/.profile`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build the EPERM/EACCES remediation. Two options per platform —
|
|
46
|
+
* elevation (one-time) and user-local prefix (permanent, no privs).
|
|
47
|
+
*/
|
|
48
|
+
function permissionDeniedInstructions(opts) {
|
|
49
|
+
const env = opts.env ?? process.env;
|
|
50
|
+
if (opts.platform === 'win32') {
|
|
51
|
+
return {
|
|
52
|
+
headline: 'Install failed: permission denied. npm needs Administrator for a global install on Windows.',
|
|
53
|
+
shell: 'powershell',
|
|
54
|
+
steps: [
|
|
55
|
+
'Option 1 — Run once with elevated privileges:',
|
|
56
|
+
' • Open PowerShell as Administrator (right-click → "Run as administrator")',
|
|
57
|
+
' • npm install -g aiden-runtime@latest',
|
|
58
|
+
'',
|
|
59
|
+
'Option 2 — Permanent: switch npm to a user-local prefix (no admin needed ever again):',
|
|
60
|
+
' • npm config set prefix "$env:USERPROFILE\\AppData\\Roaming\\npm"',
|
|
61
|
+
' • [Environment]::SetEnvironmentVariable("Path", "$env:USERPROFILE\\AppData\\Roaming\\npm;" + [Environment]::GetEnvironmentVariable("Path", "User"), "User")',
|
|
62
|
+
' • Close + reopen PowerShell, then: npm install -g aiden-runtime@latest',
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// POSIX: darwin / linux / *bsd / etc.
|
|
67
|
+
const shell = detectShell(env);
|
|
68
|
+
const rcFile = rcFileFor(shell, opts.home);
|
|
69
|
+
return {
|
|
70
|
+
headline: `Install failed: permission denied. npm needs sudo for a global install on ${opts.platform}.`,
|
|
71
|
+
shell: shell ?? undefined,
|
|
72
|
+
rcFile,
|
|
73
|
+
steps: [
|
|
74
|
+
'Option 1 — Run once with elevated privileges:',
|
|
75
|
+
' • sudo npm install -g aiden-runtime@latest',
|
|
76
|
+
'',
|
|
77
|
+
'Option 2 — Permanent: switch npm to a user-local prefix (no sudo needed ever again):',
|
|
78
|
+
` • npm config set prefix "${opts.home}/.npm-global"`,
|
|
79
|
+
` • echo 'export PATH="${opts.home}/.npm-global/bin:$PATH"' >> ${rcFile}`,
|
|
80
|
+
` • source ${rcFile}`,
|
|
81
|
+
' • npm install -g aiden-runtime@latest',
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Stale / risky prefix detection. Returns a warning when the npm
|
|
87
|
+
* `prefix` config points at a location that needs elevation OR is
|
|
88
|
+
* known to cause permission churn. `null` when the prefix is safe.
|
|
89
|
+
*
|
|
90
|
+
* `writable` is the result of a `fs.access` check the caller does
|
|
91
|
+
* before invoking us (we don't want to do filesystem I/O in a pure
|
|
92
|
+
* builder — caller controls the side effect).
|
|
93
|
+
*/
|
|
94
|
+
function detectStalePrefix(opts) {
|
|
95
|
+
const env = opts.env ?? process.env;
|
|
96
|
+
const p = opts.prefix;
|
|
97
|
+
// Windows risk: Program Files.
|
|
98
|
+
if (opts.platform === 'win32') {
|
|
99
|
+
if (/^[a-zA-Z]:\\Program Files/i.test(p)) {
|
|
100
|
+
return {
|
|
101
|
+
warning: `npm prefix is "${p}" — global installs here require Administrator every time.`,
|
|
102
|
+
switchSteps: [
|
|
103
|
+
'Switch to a user-local prefix to avoid the prompt forever:',
|
|
104
|
+
' • npm config set prefix "$env:USERPROFILE\\AppData\\Roaming\\npm"',
|
|
105
|
+
' • [Environment]::SetEnvironmentVariable("Path", "$env:USERPROFILE\\AppData\\Roaming\\npm;" + [Environment]::GetEnvironmentVariable("Path", "User"), "User")',
|
|
106
|
+
' • Close + reopen PowerShell.',
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
// POSIX risk: /usr or /usr/local without write access.
|
|
113
|
+
const risky = p === '/usr' || p === '/usr/local' || p.startsWith('/usr/');
|
|
114
|
+
if (risky && !opts.writable) {
|
|
115
|
+
const shell = detectShell(env);
|
|
116
|
+
const rcFile = rcFileFor(shell, opts.home);
|
|
117
|
+
return {
|
|
118
|
+
warning: `npm prefix is "${p}" — global installs here require sudo every time.`,
|
|
119
|
+
switchSteps: [
|
|
120
|
+
'Switch to a user-local prefix to avoid sudo forever:',
|
|
121
|
+
` • npm config set prefix "${opts.home}/.npm-global"`,
|
|
122
|
+
` • echo 'export PATH="${opts.home}/.npm-global/bin:$PATH"' >> ${rcFile}`,
|
|
123
|
+
` • source ${rcFile}`,
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|