aiden-runtime 4.1.1 → 4.1.2
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 +78 -26
- package/dist/cli/v4/aidenCLI.js +159 -9
- package/dist/cli/v4/callbacks.js +5 -2
- package/dist/cli/v4/chatSession.js +525 -15
- package/dist/cli/v4/commands/auth.js +6 -3
- package/dist/cli/v4/commands/help.js +4 -0
- package/dist/cli/v4/commands/index.js +10 -1
- package/dist/cli/v4/commands/reloadSoul.js +37 -0
- package/dist/cli/v4/commands/update.js +102 -0
- package/dist/cli/v4/defaultSoul.js +68 -2
- package/dist/cli/v4/display.js +28 -10
- package/dist/cli/v4/doctor.js +112 -0
- package/dist/cli/v4/doctorLiveness.js +65 -10
- package/dist/cli/v4/promotionPrompt.js +202 -0
- package/dist/cli/v4/providerBootSelector.js +144 -0
- package/dist/cli/v4/sessionSummaryGate.js +66 -0
- package/dist/cli/v4/toolPreview.js +139 -0
- package/dist/core/v4/aidenAgent.js +91 -29
- package/dist/core/v4/capabilities.js +89 -0
- package/dist/core/v4/contextCompressor.js +25 -8
- package/dist/core/v4/distillationIndex.js +167 -0
- package/dist/core/v4/distillationStore.js +98 -0
- package/dist/core/v4/logger/logger.js +40 -9
- package/dist/core/v4/promotionCandidates.js +234 -0
- package/dist/core/v4/promptBuilder.js +145 -1
- package/dist/core/v4/sessionDistiller.js +405 -0
- package/dist/core/v4/skillMining/skillMiner.js +43 -6
- package/dist/core/v4/skillOutcomeTracker.js +323 -0
- package/dist/core/v4/subsystemHealth.js +143 -0
- package/dist/core/v4/update/executeInstall.js +233 -0
- package/dist/core/version.js +1 -1
- package/dist/moat/memoryGuard.js +111 -0
- package/dist/moat/skillTeacher.js +14 -5
- package/dist/providers/v4/chatCompletionsAdapter.js +9 -0
- package/dist/providers/v4/errors.js +20 -4
- package/dist/providers/v4/modelDefaults.js +65 -0
- package/dist/providers/v4/registry.js +9 -2
- package/dist/providers/v4/runtimeResolver.js +6 -0
- package/dist/tools/v4/index.js +57 -1
- package/dist/tools/v4/memory/memoryRemove.js +57 -2
- package/dist/tools/v4/memory/sessionSummary.js +151 -0
- package/dist/tools/v4/sessions/recallSession.js +163 -0
- package/dist/tools/v4/sessions/sessionSearch.js +5 -1
- package/dist/tools/v4/system/_psHelpers.js +55 -0
- package/dist/tools/v4/system/aidenSelfUpdate.js +162 -0
- package/dist/tools/v4/system/appClose.js +79 -0
- package/dist/tools/v4/system/appLaunch.js +92 -0
- package/dist/tools/v4/system/clipboardRead.js +54 -0
- package/dist/tools/v4/system/clipboardWrite.js +84 -0
- package/dist/tools/v4/system/mediaKey.js +78 -0
- package/dist/tools/v4/system/osProcessList.js +99 -0
- package/dist/tools/v4/system/screenshot.js +106 -0
- package/dist/tools/v4/system/volumeSet.js +157 -0
- package/package.json +4 -1
- package/skills/system_control.md +135 -69
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
* tools/v4/system/appLaunch.ts — `app_launch` tool.
|
|
10
|
+
*
|
|
11
|
+
* Start a Windows application by executable name or absolute path via
|
|
12
|
+
* PowerShell `Start-Process`. Resolves bare names through PATH and
|
|
13
|
+
* `App Paths` registry (so `spotify`, `notepad`, `chrome` all work
|
|
14
|
+
* without the user supplying the full path).
|
|
15
|
+
*
|
|
16
|
+
* Returns the PID of the launched process when available — useful for
|
|
17
|
+
* a subsequent `app_close` invocation or for confirming "did the
|
|
18
|
+
* launch succeed?" without a `window_list` round-trip.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.appLaunchTool = void 0;
|
|
22
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
23
|
+
function buildPs(appName, args) {
|
|
24
|
+
// Single-quote escape the app name for PowerShell.
|
|
25
|
+
const safeApp = appName.replace(/'/g, "''");
|
|
26
|
+
const argString = args && args.length > 0
|
|
27
|
+
? `-ArgumentList @(${args.map((a) => `'${a.replace(/'/g, "''")}'`).join(',')})`
|
|
28
|
+
: '';
|
|
29
|
+
return [
|
|
30
|
+
`try {`,
|
|
31
|
+
` $p = Start-Process '${safeApp}' ${argString} -PassThru -ErrorAction Stop;`,
|
|
32
|
+
` Write-Output ('PID=' + $p.Id);`,
|
|
33
|
+
`} catch {`,
|
|
34
|
+
// App Paths registry resolution fallback: Start-Process sometimes
|
|
35
|
+
// fails on bare names that Windows would otherwise resolve via
|
|
36
|
+
// App Paths (Spotify, Chrome). Fall back to `start <name>` which
|
|
37
|
+
// honours App Paths.
|
|
38
|
+
` cmd /c "start '' '${safeApp}'";`,
|
|
39
|
+
` Write-Output 'PID=unknown (launched via cmd start fallback)';`,
|
|
40
|
+
`}`,
|
|
41
|
+
].join(' ');
|
|
42
|
+
}
|
|
43
|
+
exports.appLaunchTool = {
|
|
44
|
+
schema: {
|
|
45
|
+
name: 'app_launch',
|
|
46
|
+
description: 'Launch a Windows application by exe name, friendly name (resolved via App Paths registry), or absolute path. Returns the launched PID when available. Use for "open Spotify" / "start Chrome" / etc. Windows-only in v4.1.2.',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
app: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Application identifier. Accepts: bare name (e.g. "spotify", "notepad", "chrome"), exe basename ("notepad.exe"), or absolute path ("C:\\\\Program Files\\\\App\\\\app.exe").',
|
|
53
|
+
},
|
|
54
|
+
args: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
description: 'Optional command-line arguments to pass to the app.',
|
|
57
|
+
items: { type: 'string', description: 'A single CLI argument string.' },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['app'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
category: 'execute',
|
|
64
|
+
mutates: true,
|
|
65
|
+
toolset: 'system',
|
|
66
|
+
async execute(args, _ctx) {
|
|
67
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
68
|
+
return (0, _psHelpers_1.windowsOnlyError)('app_launch');
|
|
69
|
+
const app = typeof args.app === 'string' ? args.app.trim() : '';
|
|
70
|
+
if (!app) {
|
|
71
|
+
return { success: false, error: '`app` is required and must be non-empty.' };
|
|
72
|
+
}
|
|
73
|
+
const rawArgs = Array.isArray(args.args) ? args.args : undefined;
|
|
74
|
+
const cliArgs = rawArgs?.filter((a) => typeof a === 'string');
|
|
75
|
+
try {
|
|
76
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)(buildPs(app, cliArgs), {
|
|
77
|
+
timeoutMs: 20000,
|
|
78
|
+
});
|
|
79
|
+
const out = stdout.trim();
|
|
80
|
+
// Extract PID when Start-Process succeeded; null for cmd-fallback path.
|
|
81
|
+
const pidMatch = out.match(/PID=(\d+)/);
|
|
82
|
+
const pid = pidMatch ? Number(pidMatch[1]) : null;
|
|
83
|
+
return { success: true, app, pid, raw: out };
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
error: e instanceof Error ? e.message : String(e),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
* tools/v4/system/clipboardRead.ts — `clipboard_read` tool.
|
|
10
|
+
*
|
|
11
|
+
* Read the current Windows clipboard contents as text via PowerShell
|
|
12
|
+
* `Get-Clipboard`. Non-text clipboard contents (image, file list, RTF)
|
|
13
|
+
* return an empty string — text-only by design; binary surfaces would
|
|
14
|
+
* need a different rendering contract.
|
|
15
|
+
*
|
|
16
|
+
* Privacy note: clipboard contents can include passwords, OTPs, and
|
|
17
|
+
* personal text. Tool description flags this so the model can warn
|
|
18
|
+
* the user before reading sensitive contexts.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.clipboardReadTool = void 0;
|
|
22
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
23
|
+
exports.clipboardReadTool = {
|
|
24
|
+
schema: {
|
|
25
|
+
name: 'clipboard_read',
|
|
26
|
+
description: 'Read the current Windows clipboard contents as text. Non-text clipboard data returns an empty string. Privacy-sensitive: clipboard may contain passwords, OTPs, or personal text — only invoke when the user has clearly asked. Windows-only in v4.1.2.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
category: 'read',
|
|
33
|
+
mutates: false,
|
|
34
|
+
toolset: 'system',
|
|
35
|
+
async execute(_args, _ctx) {
|
|
36
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
37
|
+
return (0, _psHelpers_1.windowsOnlyError)('clipboard_read');
|
|
38
|
+
try {
|
|
39
|
+
// -Raw returns the whole buffer as one string (including newlines)
|
|
40
|
+
// rather than splitting on line breaks.
|
|
41
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)('Get-Clipboard -Raw', { timeoutMs: 5000 });
|
|
42
|
+
// PowerShell appends a trailing CRLF — strip ONE trailing newline
|
|
43
|
+
// so the model sees what the user actually copied.
|
|
44
|
+
const text = stdout.replace(/\r?\n$/, '');
|
|
45
|
+
return { success: true, text, length: text.length };
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: e instanceof Error ? e.message : String(e),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
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
|
+
* tools/v4/system/clipboardWrite.ts — `clipboard_write` tool.
|
|
10
|
+
*
|
|
11
|
+
* Write text to the Windows clipboard via PowerShell `Set-Clipboard`.
|
|
12
|
+
* Caller passes the text as a string arg; we route it through stdin to
|
|
13
|
+
* the PowerShell process to side-step shell-argument quoting issues
|
|
14
|
+
* with newlines / special chars (Aiden's existing `shellInterpolation`
|
|
15
|
+
* pattern doesn't apply to tool args, but stdin is still the safest
|
|
16
|
+
* conduit for arbitrary text).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.clipboardWriteTool = void 0;
|
|
20
|
+
const node_child_process_1 = require("node:child_process");
|
|
21
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
22
|
+
/**
|
|
23
|
+
* Spawn `powershell.exe Set-Clipboard` with the text piped on stdin.
|
|
24
|
+
* Wrapper Promise so the tool's `execute` can `await` it.
|
|
25
|
+
*/
|
|
26
|
+
function setClipboardViaStdin(text, timeoutMs) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const ps = (0, node_child_process_1.exec)(
|
|
29
|
+
// -Command - reads the script from stdin; but we want to PIPE the
|
|
30
|
+
// *value* not the script. Cleanest cross-version PowerShell path:
|
|
31
|
+
// read stdin in PowerShell and pass to Set-Clipboard.
|
|
32
|
+
'powershell.exe -NoProfile -Command "$input | Set-Clipboard"', { timeout: timeoutMs, windowsHide: true }, (err) => {
|
|
33
|
+
if (err)
|
|
34
|
+
reject(err);
|
|
35
|
+
else
|
|
36
|
+
resolve();
|
|
37
|
+
});
|
|
38
|
+
if (!ps.stdin) {
|
|
39
|
+
reject(new Error('PowerShell child has no stdin'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
ps.stdin.write(text);
|
|
43
|
+
ps.stdin.end();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
exports.clipboardWriteTool = {
|
|
47
|
+
schema: {
|
|
48
|
+
name: 'clipboard_write',
|
|
49
|
+
description: 'Write text to the Windows clipboard. Replaces existing clipboard contents. Handles multi-line strings and special characters safely (text routed via stdin). Windows-only in v4.1.2.',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
text: {
|
|
54
|
+
type: 'string',
|
|
55
|
+
description: 'Text to place on the clipboard. Replaces whatever is currently there.',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
required: ['text'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
category: 'execute',
|
|
62
|
+
mutates: true,
|
|
63
|
+
toolset: 'system',
|
|
64
|
+
async execute(args, _ctx) {
|
|
65
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
66
|
+
return (0, _psHelpers_1.windowsOnlyError)('clipboard_write');
|
|
67
|
+
const text = typeof args.text === 'string' ? args.text : '';
|
|
68
|
+
// Empty string IS valid — it clears the clipboard. Distinguished
|
|
69
|
+
// from "no arg supplied" by the explicit type check.
|
|
70
|
+
if (typeof args.text !== 'string') {
|
|
71
|
+
return { success: false, error: '`text` is required and must be a string.' };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
await setClipboardViaStdin(text, 5000);
|
|
75
|
+
return { success: true, length: text.length };
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: e instanceof Error ? e.message : String(e),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
* tools/v4/system/mediaKey.ts — `media_key` tool.
|
|
10
|
+
*
|
|
11
|
+
* Send Windows media-control keys (play/pause, next, previous, stop)
|
|
12
|
+
* via PowerShell `System.Windows.Forms.SendKeys`. Works against any
|
|
13
|
+
* app that registers with SMTC (Spotify, YouTube in browser, Windows
|
|
14
|
+
* Media Player, Apple Music for Windows, VLC with MediaKey plugin,
|
|
15
|
+
* etc.) — the OS routes the keypress to the currently-active media
|
|
16
|
+
* session, so no per-app integration is required.
|
|
17
|
+
*
|
|
18
|
+
* Pairs with `now_playing` (read-only probe): the model reads what's
|
|
19
|
+
* playing, then issues the right media_key to control it.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.mediaKeyTool = void 0;
|
|
23
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
24
|
+
const ACTION_KEYS = {
|
|
25
|
+
play_pause: '{MEDIA_PLAY_PAUSE}',
|
|
26
|
+
next: '{MEDIA_NEXT_TRACK}',
|
|
27
|
+
previous: '{MEDIA_PREV_TRACK}',
|
|
28
|
+
stop: '{MEDIA_STOP}',
|
|
29
|
+
};
|
|
30
|
+
exports.mediaKeyTool = {
|
|
31
|
+
schema: {
|
|
32
|
+
name: 'media_key',
|
|
33
|
+
description: 'Send a media-control key to the active media session (Spotify, YouTube, etc.). Pair with `now_playing` to inspect current state. Windows-only in v4.1.2.',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
action: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
enum: ['play_pause', 'next', 'previous', 'stop'],
|
|
40
|
+
description: "'play_pause' toggles play/pause on the active media session. " +
|
|
41
|
+
"'next' / 'previous' skip tracks. 'stop' halts playback.",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ['action'],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
category: 'execute',
|
|
48
|
+
mutates: true,
|
|
49
|
+
toolset: 'system',
|
|
50
|
+
async execute(args, _ctx) {
|
|
51
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
52
|
+
return (0, _psHelpers_1.windowsOnlyError)('media_key');
|
|
53
|
+
const action = args.action;
|
|
54
|
+
if (!ACTION_KEYS[action]) {
|
|
55
|
+
return {
|
|
56
|
+
success: false,
|
|
57
|
+
error: `Unknown media action: ${String(args.action)}. ` +
|
|
58
|
+
`Valid: ${Object.keys(ACTION_KEYS).join(', ')}`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const sendkey = ACTION_KEYS[action];
|
|
62
|
+
const script = [
|
|
63
|
+
'Add-Type -AssemblyName System.Windows.Forms;',
|
|
64
|
+
`[System.Windows.Forms.SendKeys]::SendWait('${sendkey}');`,
|
|
65
|
+
`Write-Output 'sent:${action}';`,
|
|
66
|
+
].join(' ');
|
|
67
|
+
try {
|
|
68
|
+
await (0, _psHelpers_1.runPowerShell)(script, { timeoutMs: 5000 });
|
|
69
|
+
return { success: true, action };
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
error: e instanceof Error ? e.message : String(e),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
* tools/v4/system/osProcessList.ts — `os_process_list` tool.
|
|
10
|
+
*
|
|
11
|
+
* Lists OS-wide running processes via `Get-Process`. Distinct from the
|
|
12
|
+
* existing `process_list` tool which only enumerates child processes
|
|
13
|
+
* Aiden itself spawned via `process_spawn` — that's the wrong shape
|
|
14
|
+
* for questions like "is claude code running?" (the answer's process
|
|
15
|
+
* was started by the user, not Aiden).
|
|
16
|
+
*
|
|
17
|
+
* Filtering is supported via a substring on the process name. Default
|
|
18
|
+
* (no filter) lists the top-CPU processes capped at 30 so the model
|
|
19
|
+
* doesn't drown in a 200-row dump.
|
|
20
|
+
*
|
|
21
|
+
* Read-only. Cross-platform fallback returns a structured error
|
|
22
|
+
* pointing at the issue tracker.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.osProcessListTool = void 0;
|
|
26
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
27
|
+
const DEFAULT_LIMIT = 30;
|
|
28
|
+
const MAX_LIMIT = 200;
|
|
29
|
+
function buildPs(nameFilter, limit) {
|
|
30
|
+
// Escape single quotes for the PowerShell -Name argument.
|
|
31
|
+
const filter = (nameFilter ?? '').trim();
|
|
32
|
+
// Get-Process picks `Name` (short), `Id` (pid), `CPU` (seconds), and
|
|
33
|
+
// `WorkingSet` (memory). ConvertTo-Json emits an array we parse.
|
|
34
|
+
const base = filter.length > 0
|
|
35
|
+
? `Get-Process -Name '*${filter.replace(/'/g, "''")}*' -ErrorAction SilentlyContinue`
|
|
36
|
+
: 'Get-Process';
|
|
37
|
+
return [
|
|
38
|
+
base,
|
|
39
|
+
`| Sort-Object CPU -Descending`,
|
|
40
|
+
`| Select-Object -First ${limit} Name, Id, @{N='CPU';E={[math]::Round($_.CPU,2)}}, @{N='MemoryMB';E={[math]::Round($_.WorkingSet64/1MB,1)}}`,
|
|
41
|
+
`| ConvertTo-Json -Compress -Depth 2`,
|
|
42
|
+
].join(' ');
|
|
43
|
+
}
|
|
44
|
+
exports.osProcessListTool = {
|
|
45
|
+
schema: {
|
|
46
|
+
name: 'os_process_list',
|
|
47
|
+
description: 'List OS-wide running processes (top by CPU). Use this to answer questions like "is X running?" or "what apps are using CPU?". Supports an optional name substring filter. Distinct from `process_list` which only shows processes Aiden itself spawned. Windows-only in v4.1.2.',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
name: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Optional process-name substring filter, e.g. "claude" to find "claude.exe" / "claude_code.exe" / "claude-helper.exe". Omit to list top-CPU processes.',
|
|
54
|
+
},
|
|
55
|
+
limit: {
|
|
56
|
+
type: 'number',
|
|
57
|
+
description: 'Max rows to return (default 30, max 200). Use a higher value when answering "list everything running" style questions.',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
category: 'read',
|
|
63
|
+
mutates: false,
|
|
64
|
+
toolset: 'system',
|
|
65
|
+
async execute(args, _ctx) {
|
|
66
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
67
|
+
return (0, _psHelpers_1.windowsOnlyError)('os_process_list');
|
|
68
|
+
const nameArg = typeof args.name === 'string' ? args.name : undefined;
|
|
69
|
+
const rawLimit = typeof args.limit === 'number' ? args.limit : DEFAULT_LIMIT;
|
|
70
|
+
const limit = Math.min(Math.max(1, Math.floor(rawLimit)), MAX_LIMIT);
|
|
71
|
+
try {
|
|
72
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)(buildPs(nameArg, limit), {
|
|
73
|
+
timeoutMs: 15000,
|
|
74
|
+
});
|
|
75
|
+
const trimmed = stdout.trim();
|
|
76
|
+
// Get-Process returns nothing when the filter matches zero processes;
|
|
77
|
+
// PowerShell pipeline prints empty. Treat as "no matches" success.
|
|
78
|
+
if (trimmed.length === 0) {
|
|
79
|
+
return { success: true, processes: [], count: 0, filter: nameArg };
|
|
80
|
+
}
|
|
81
|
+
// ConvertTo-Json emits an object (single result) or array (multiple).
|
|
82
|
+
// Normalise to array.
|
|
83
|
+
const parsed = JSON.parse(trimmed);
|
|
84
|
+
const processes = Array.isArray(parsed) ? parsed : [parsed];
|
|
85
|
+
return {
|
|
86
|
+
success: true,
|
|
87
|
+
count: processes.length,
|
|
88
|
+
filter: nameArg,
|
|
89
|
+
processes,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: e instanceof Error ? e.message : String(e),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
* tools/v4/system/screenshot.ts — `screenshot` tool.
|
|
10
|
+
*
|
|
11
|
+
* Captures the full desktop and writes it as a PNG to
|
|
12
|
+
* `<aidenHome>/screenshots/<timestamp>.png`. Returns the absolute path
|
|
13
|
+
* in `path` so a Telegram / Discord channel adapter can attach the
|
|
14
|
+
* file directly without a separate file_read round-trip.
|
|
15
|
+
*
|
|
16
|
+
* Privacy note (Phase v4.1.2-followup-3): this tool reads what is
|
|
17
|
+
* currently visible on the screen — anything in front of the user.
|
|
18
|
+
* The tool description says so explicitly so users know what they're
|
|
19
|
+
* approving when the model invokes it.
|
|
20
|
+
*
|
|
21
|
+
* Implementation: PowerShell-only, no native dependency. Uses
|
|
22
|
+
* `System.Windows.Forms.Screen` + `System.Drawing.Bitmap` /
|
|
23
|
+
* `Graphics.CopyFromScreen()` — both ship with .NET on every modern
|
|
24
|
+
* Windows install. Cross-platform fallback returns a structured error.
|
|
25
|
+
*/
|
|
26
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
|
+
};
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.screenshotTool = void 0;
|
|
31
|
+
const node_fs_1 = require("node:fs");
|
|
32
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
33
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
34
|
+
/**
|
|
35
|
+
* Build the PowerShell capture script. The bitmap dimensions come from
|
|
36
|
+
* `Screen::PrimaryScreen.Bounds` so we get the actual primary-monitor
|
|
37
|
+
* resolution, not a hardcoded value. SaveAs PNG to keep losslessness
|
|
38
|
+
* — file size on a 4K screen is ~4-8 MB which is fine for chat.
|
|
39
|
+
*/
|
|
40
|
+
function buildScreenshotPs(outPath) {
|
|
41
|
+
const psQuoted = outPath.replace(/'/g, "''"); // PowerShell single-quote escape
|
|
42
|
+
return [
|
|
43
|
+
'Add-Type -AssemblyName System.Windows.Forms;',
|
|
44
|
+
'Add-Type -AssemblyName System.Drawing;',
|
|
45
|
+
'$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds;',
|
|
46
|
+
'$bitmap = New-Object System.Drawing.Bitmap $bounds.Width, $bounds.Height;',
|
|
47
|
+
'$gfx = [System.Drawing.Graphics]::FromImage($bitmap);',
|
|
48
|
+
'$gfx.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size);',
|
|
49
|
+
`$bitmap.Save('${psQuoted}', [System.Drawing.Imaging.ImageFormat]::Png);`,
|
|
50
|
+
'$gfx.Dispose(); $bitmap.Dispose();',
|
|
51
|
+
`Write-Output '${psQuoted}';`,
|
|
52
|
+
].join(' ');
|
|
53
|
+
}
|
|
54
|
+
exports.screenshotTool = {
|
|
55
|
+
schema: {
|
|
56
|
+
name: 'screenshot',
|
|
57
|
+
description: 'Capture the current primary-monitor desktop as a PNG. Returns the absolute path of the saved file. Reads whatever is currently visible on screen — privacy-sensitive; only invoke when the user explicitly asks for a screenshot or screen share. Windows-only in v4.1.2.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
category: 'read',
|
|
64
|
+
mutates: false,
|
|
65
|
+
toolset: 'system',
|
|
66
|
+
async execute(_args, ctx) {
|
|
67
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
68
|
+
return (0, _psHelpers_1.windowsOnlyError)('screenshot');
|
|
69
|
+
if (!ctx.paths) {
|
|
70
|
+
return { success: false, error: 'aiden paths not wired (test mode?)' };
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const dir = node_path_1.default.join(ctx.paths.root, 'screenshots');
|
|
74
|
+
await node_fs_1.promises.mkdir(dir, { recursive: true });
|
|
75
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
76
|
+
const outPath = node_path_1.default.join(dir, `${stamp}.png`);
|
|
77
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)(buildScreenshotPs(outPath), {
|
|
78
|
+
timeoutMs: 30000,
|
|
79
|
+
});
|
|
80
|
+
// Verify the file actually landed on disk — PowerShell can exit 0
|
|
81
|
+
// and have written nothing on an exotic display configuration.
|
|
82
|
+
try {
|
|
83
|
+
const stat = await node_fs_1.promises.stat(outPath);
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
path: outPath,
|
|
87
|
+
size: stat.size,
|
|
88
|
+
// For Telegram / Discord adapters — they can attach via path.
|
|
89
|
+
attachAs: 'image/png',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return {
|
|
94
|
+
success: false,
|
|
95
|
+
error: `screenshot script ran but file not found at ${outPath} (stdout=${stdout.trim().slice(0, 120)})`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: e instanceof Error ? e.message : String(e),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
* tools/v4/system/volumeSet.ts — `volume_set` tool.
|
|
10
|
+
*
|
|
11
|
+
* Set the Windows master-volume level to a percentage (0-100), or
|
|
12
|
+
* toggle mute. Uses the Shell.Application COM object's appCommand
|
|
13
|
+
* SendKeys for up/down nudges; for absolute level setting, talks to
|
|
14
|
+
* `IAudioEndpointVolume` via PowerShell inline C# (Add-Type) — no
|
|
15
|
+
* external binary (no nircmd, no soundvolumeview).
|
|
16
|
+
*
|
|
17
|
+
* The inline-C# approach is well-trodden Windows lore: the
|
|
18
|
+
* IAudioEndpointVolume COM interface is part of the Core Audio APIs
|
|
19
|
+
* available since Vista. We declare the interop types in PowerShell,
|
|
20
|
+
* invoke `SetMasterVolumeLevelScalar`, and clean up. Adds ~1-2s
|
|
21
|
+
* cold-start (PowerShell Add-Type compilation) — fine for an
|
|
22
|
+
* occasionally-invoked control.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.volumeSetTool = void 0;
|
|
26
|
+
const _psHelpers_1 = require("./_psHelpers");
|
|
27
|
+
const ADD_TYPE_AUDIO = `
|
|
28
|
+
Add-Type -TypeDefinition @'
|
|
29
|
+
using System;
|
|
30
|
+
using System.Runtime.InteropServices;
|
|
31
|
+
[Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
32
|
+
interface IAudioEndpointVolume {
|
|
33
|
+
int RegisterControlChangeNotify(IntPtr cb);
|
|
34
|
+
int UnregisterControlChangeNotify(IntPtr cb);
|
|
35
|
+
int GetChannelCount(out uint count);
|
|
36
|
+
int SetMasterVolumeLevel(float level, Guid ctx);
|
|
37
|
+
int SetMasterVolumeLevelScalar(float level, Guid ctx);
|
|
38
|
+
int GetMasterVolumeLevel(out float level);
|
|
39
|
+
int GetMasterVolumeLevelScalar(out float level);
|
|
40
|
+
int SetChannelVolumeLevel(uint ch, float level, Guid ctx);
|
|
41
|
+
int SetChannelVolumeLevelScalar(uint ch, float level, Guid ctx);
|
|
42
|
+
int GetChannelVolumeLevel(uint ch, out float level);
|
|
43
|
+
int GetChannelVolumeLevelScalar(uint ch, out float level);
|
|
44
|
+
int SetMute(bool mute, Guid ctx);
|
|
45
|
+
int GetMute(out bool mute);
|
|
46
|
+
int GetVolumeStepInfo(out uint step, out uint count);
|
|
47
|
+
int VolumeStepUp(Guid ctx);
|
|
48
|
+
int VolumeStepDown(Guid ctx);
|
|
49
|
+
int QueryHardwareSupport(out uint mask);
|
|
50
|
+
int GetVolumeRange(out float min, out float max, out float inc);
|
|
51
|
+
}
|
|
52
|
+
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
53
|
+
interface IMMDeviceEnumerator {
|
|
54
|
+
int NotImpl0();
|
|
55
|
+
int GetDefaultAudioEndpoint(int dataFlow, int role, out IMMDevice ep);
|
|
56
|
+
}
|
|
57
|
+
[Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
58
|
+
interface IMMDevice {
|
|
59
|
+
int Activate(ref Guid id, int clsCtx, IntPtr pa, [MarshalAs(UnmanagedType.IUnknown)] out object o);
|
|
60
|
+
}
|
|
61
|
+
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
|
|
62
|
+
class MMDeviceEnumeratorComObject { }
|
|
63
|
+
public static class Audio {
|
|
64
|
+
static IAudioEndpointVolume Vol() {
|
|
65
|
+
var enumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
|
|
66
|
+
IMMDevice dev = null;
|
|
67
|
+
Marshal.ThrowExceptionForHR(enumerator.GetDefaultAudioEndpoint(0, 1, out dev));
|
|
68
|
+
Guid epvid = typeof(IAudioEndpointVolume).GUID;
|
|
69
|
+
object o;
|
|
70
|
+
Marshal.ThrowExceptionForHR(dev.Activate(ref epvid, 7, IntPtr.Zero, out o));
|
|
71
|
+
return o as IAudioEndpointVolume;
|
|
72
|
+
}
|
|
73
|
+
public static float GetLevel() { float l; Marshal.ThrowExceptionForHR(Vol().GetMasterVolumeLevelScalar(out l)); return l; }
|
|
74
|
+
public static void SetLevel(float level) { Marshal.ThrowExceptionForHR(Vol().SetMasterVolumeLevelScalar(level, Guid.Empty)); }
|
|
75
|
+
public static bool GetMute() { bool m; Marshal.ThrowExceptionForHR(Vol().GetMute(out m)); return m; }
|
|
76
|
+
public static void SetMute(bool mute) { Marshal.ThrowExceptionForHR(Vol().SetMute(mute, Guid.Empty)); }
|
|
77
|
+
}
|
|
78
|
+
'@;
|
|
79
|
+
`;
|
|
80
|
+
function buildPs(action, percent) {
|
|
81
|
+
if (action === 'set' && typeof percent === 'number') {
|
|
82
|
+
const scalar = (Math.max(0, Math.min(100, percent)) / 100).toFixed(4);
|
|
83
|
+
return [
|
|
84
|
+
ADD_TYPE_AUDIO,
|
|
85
|
+
`[Audio]::SetLevel([float]${scalar});`,
|
|
86
|
+
`$level = [Audio]::GetLevel();`,
|
|
87
|
+
`Write-Output ([math]::Round($level * 100, 0));`,
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
if (action === 'mute') {
|
|
91
|
+
return [ADD_TYPE_AUDIO, `[Audio]::SetMute($true); Write-Output 'muted';`].join('\n');
|
|
92
|
+
}
|
|
93
|
+
if (action === 'unmute') {
|
|
94
|
+
return [ADD_TYPE_AUDIO, `[Audio]::SetMute($false); Write-Output 'unmuted';`].join('\n');
|
|
95
|
+
}
|
|
96
|
+
// toggle_mute
|
|
97
|
+
return [
|
|
98
|
+
ADD_TYPE_AUDIO,
|
|
99
|
+
`$cur = [Audio]::GetMute(); [Audio]::SetMute(-not $cur);`,
|
|
100
|
+
`Write-Output (if (-not $cur) {'muted'} else {'unmuted'});`,
|
|
101
|
+
].join('\n');
|
|
102
|
+
}
|
|
103
|
+
exports.volumeSetTool = {
|
|
104
|
+
schema: {
|
|
105
|
+
name: 'volume_set',
|
|
106
|
+
description: 'Set Windows master volume to a percentage (0-100), or mute / unmute / toggle mute. Operates on the default audio endpoint. Windows-only in v4.1.2.',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
action: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
enum: ['set', 'mute', 'unmute', 'toggle_mute'],
|
|
113
|
+
description: "'set' requires `percent`. 'mute' / 'unmute' force the state. 'toggle_mute' flips it.",
|
|
114
|
+
},
|
|
115
|
+
percent: {
|
|
116
|
+
type: 'number',
|
|
117
|
+
description: 'Target volume 0-100 (only used when action="set"). Values outside the range are clamped.',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
required: ['action'],
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
category: 'execute',
|
|
124
|
+
mutates: true,
|
|
125
|
+
toolset: 'system',
|
|
126
|
+
async execute(args, _ctx) {
|
|
127
|
+
if (!(0, _psHelpers_1.isWindows)())
|
|
128
|
+
return (0, _psHelpers_1.windowsOnlyError)('volume_set');
|
|
129
|
+
const action = args.action;
|
|
130
|
+
if (!['set', 'mute', 'unmute', 'toggle_mute'].includes(action)) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: `Unknown volume action: ${String(args.action)}. ` +
|
|
134
|
+
`Valid: set, mute, unmute, toggle_mute`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const percent = typeof args.percent === 'number' ? args.percent : undefined;
|
|
138
|
+
if (action === 'set' && percent === undefined) {
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
error: "action='set' requires a numeric `percent` (0-100).",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
const { stdout } = await (0, _psHelpers_1.runPowerShell)(buildPs(action, percent), {
|
|
146
|
+
timeoutMs: 10000,
|
|
147
|
+
});
|
|
148
|
+
return { success: true, action, result: stdout.trim() };
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: e instanceof Error ? e.message : String(e),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|