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.
Files changed (55) hide show
  1. package/README.md +78 -26
  2. package/dist/cli/v4/aidenCLI.js +159 -9
  3. package/dist/cli/v4/callbacks.js +5 -2
  4. package/dist/cli/v4/chatSession.js +525 -15
  5. package/dist/cli/v4/commands/auth.js +6 -3
  6. package/dist/cli/v4/commands/help.js +4 -0
  7. package/dist/cli/v4/commands/index.js +10 -1
  8. package/dist/cli/v4/commands/reloadSoul.js +37 -0
  9. package/dist/cli/v4/commands/update.js +102 -0
  10. package/dist/cli/v4/defaultSoul.js +68 -2
  11. package/dist/cli/v4/display.js +28 -10
  12. package/dist/cli/v4/doctor.js +112 -0
  13. package/dist/cli/v4/doctorLiveness.js +65 -10
  14. package/dist/cli/v4/promotionPrompt.js +202 -0
  15. package/dist/cli/v4/providerBootSelector.js +144 -0
  16. package/dist/cli/v4/sessionSummaryGate.js +66 -0
  17. package/dist/cli/v4/toolPreview.js +139 -0
  18. package/dist/core/v4/aidenAgent.js +91 -29
  19. package/dist/core/v4/capabilities.js +89 -0
  20. package/dist/core/v4/contextCompressor.js +25 -8
  21. package/dist/core/v4/distillationIndex.js +167 -0
  22. package/dist/core/v4/distillationStore.js +98 -0
  23. package/dist/core/v4/logger/logger.js +40 -9
  24. package/dist/core/v4/promotionCandidates.js +234 -0
  25. package/dist/core/v4/promptBuilder.js +145 -1
  26. package/dist/core/v4/sessionDistiller.js +405 -0
  27. package/dist/core/v4/skillMining/skillMiner.js +43 -6
  28. package/dist/core/v4/skillOutcomeTracker.js +323 -0
  29. package/dist/core/v4/subsystemHealth.js +143 -0
  30. package/dist/core/v4/update/executeInstall.js +233 -0
  31. package/dist/core/version.js +1 -1
  32. package/dist/moat/memoryGuard.js +111 -0
  33. package/dist/moat/skillTeacher.js +14 -5
  34. package/dist/providers/v4/chatCompletionsAdapter.js +9 -0
  35. package/dist/providers/v4/errors.js +20 -4
  36. package/dist/providers/v4/modelDefaults.js +65 -0
  37. package/dist/providers/v4/registry.js +9 -2
  38. package/dist/providers/v4/runtimeResolver.js +6 -0
  39. package/dist/tools/v4/index.js +57 -1
  40. package/dist/tools/v4/memory/memoryRemove.js +57 -2
  41. package/dist/tools/v4/memory/sessionSummary.js +151 -0
  42. package/dist/tools/v4/sessions/recallSession.js +163 -0
  43. package/dist/tools/v4/sessions/sessionSearch.js +5 -1
  44. package/dist/tools/v4/system/_psHelpers.js +55 -0
  45. package/dist/tools/v4/system/aidenSelfUpdate.js +162 -0
  46. package/dist/tools/v4/system/appClose.js +79 -0
  47. package/dist/tools/v4/system/appLaunch.js +92 -0
  48. package/dist/tools/v4/system/clipboardRead.js +54 -0
  49. package/dist/tools/v4/system/clipboardWrite.js +84 -0
  50. package/dist/tools/v4/system/mediaKey.js +78 -0
  51. package/dist/tools/v4/system/osProcessList.js +99 -0
  52. package/dist/tools/v4/system/screenshot.js +106 -0
  53. package/dist/tools/v4/system/volumeSet.js +157 -0
  54. package/package.json +4 -1
  55. 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
+ };