@usejarvis/brain 0.1.0

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 (266) hide show
  1. package/LICENSE +153 -0
  2. package/README.md +278 -0
  3. package/bin/jarvis.ts +413 -0
  4. package/package.json +74 -0
  5. package/scripts/ensure-bun.cjs +8 -0
  6. package/src/actions/README.md +421 -0
  7. package/src/actions/app-control/desktop-controller.test.ts +26 -0
  8. package/src/actions/app-control/desktop-controller.ts +438 -0
  9. package/src/actions/app-control/interface.ts +64 -0
  10. package/src/actions/app-control/linux.ts +273 -0
  11. package/src/actions/app-control/macos.ts +54 -0
  12. package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
  13. package/src/actions/app-control/sidecar-launcher.ts +286 -0
  14. package/src/actions/app-control/windows.ts +44 -0
  15. package/src/actions/browser/cdp.ts +138 -0
  16. package/src/actions/browser/chrome-launcher.ts +252 -0
  17. package/src/actions/browser/session.ts +437 -0
  18. package/src/actions/browser/stealth.ts +49 -0
  19. package/src/actions/index.ts +20 -0
  20. package/src/actions/terminal/executor.ts +157 -0
  21. package/src/actions/terminal/wsl-bridge.ts +126 -0
  22. package/src/actions/test.ts +93 -0
  23. package/src/actions/tools/agents.ts +321 -0
  24. package/src/actions/tools/builtin.ts +846 -0
  25. package/src/actions/tools/commitments.ts +192 -0
  26. package/src/actions/tools/content.ts +217 -0
  27. package/src/actions/tools/delegate.ts +147 -0
  28. package/src/actions/tools/desktop.test.ts +55 -0
  29. package/src/actions/tools/desktop.ts +305 -0
  30. package/src/actions/tools/goals.ts +376 -0
  31. package/src/actions/tools/local-tools-guard.ts +20 -0
  32. package/src/actions/tools/registry.ts +171 -0
  33. package/src/actions/tools/research.ts +111 -0
  34. package/src/actions/tools/sidecar-list.ts +57 -0
  35. package/src/actions/tools/sidecar-route.ts +105 -0
  36. package/src/actions/tools/workflows.ts +216 -0
  37. package/src/agents/agent.ts +132 -0
  38. package/src/agents/delegation.ts +107 -0
  39. package/src/agents/hierarchy.ts +113 -0
  40. package/src/agents/index.ts +19 -0
  41. package/src/agents/messaging.ts +125 -0
  42. package/src/agents/orchestrator.ts +576 -0
  43. package/src/agents/role-discovery.ts +61 -0
  44. package/src/agents/sub-agent-runner.ts +307 -0
  45. package/src/agents/task-manager.ts +151 -0
  46. package/src/authority/approval-delivery.ts +59 -0
  47. package/src/authority/approval.ts +196 -0
  48. package/src/authority/audit.ts +158 -0
  49. package/src/authority/authority.test.ts +519 -0
  50. package/src/authority/deferred-executor.ts +103 -0
  51. package/src/authority/emergency.ts +66 -0
  52. package/src/authority/engine.ts +297 -0
  53. package/src/authority/index.ts +12 -0
  54. package/src/authority/learning.ts +111 -0
  55. package/src/authority/tool-action-map.ts +74 -0
  56. package/src/awareness/analytics.ts +466 -0
  57. package/src/awareness/awareness.test.ts +332 -0
  58. package/src/awareness/capture-engine.ts +305 -0
  59. package/src/awareness/context-graph.ts +130 -0
  60. package/src/awareness/context-tracker.ts +349 -0
  61. package/src/awareness/index.ts +25 -0
  62. package/src/awareness/intelligence.ts +321 -0
  63. package/src/awareness/ocr-engine.ts +88 -0
  64. package/src/awareness/service.ts +528 -0
  65. package/src/awareness/struggle-detector.ts +342 -0
  66. package/src/awareness/suggestion-engine.ts +476 -0
  67. package/src/awareness/types.ts +201 -0
  68. package/src/cli/autostart.ts +241 -0
  69. package/src/cli/deps.ts +449 -0
  70. package/src/cli/doctor.ts +230 -0
  71. package/src/cli/helpers.ts +401 -0
  72. package/src/cli/onboard.ts +580 -0
  73. package/src/comms/README.md +329 -0
  74. package/src/comms/auth-error.html +48 -0
  75. package/src/comms/channels/discord.ts +228 -0
  76. package/src/comms/channels/signal.ts +56 -0
  77. package/src/comms/channels/telegram.ts +316 -0
  78. package/src/comms/channels/whatsapp.ts +60 -0
  79. package/src/comms/channels.test.ts +173 -0
  80. package/src/comms/desktop-notify.ts +114 -0
  81. package/src/comms/example.ts +129 -0
  82. package/src/comms/index.ts +129 -0
  83. package/src/comms/streaming.ts +142 -0
  84. package/src/comms/voice.test.ts +152 -0
  85. package/src/comms/voice.ts +291 -0
  86. package/src/comms/websocket.test.ts +409 -0
  87. package/src/comms/websocket.ts +473 -0
  88. package/src/config/README.md +387 -0
  89. package/src/config/index.ts +6 -0
  90. package/src/config/loader.test.ts +137 -0
  91. package/src/config/loader.ts +142 -0
  92. package/src/config/types.ts +260 -0
  93. package/src/daemon/README.md +232 -0
  94. package/src/daemon/agent-service-interface.ts +9 -0
  95. package/src/daemon/agent-service.ts +600 -0
  96. package/src/daemon/api-routes.ts +2119 -0
  97. package/src/daemon/background-agent-service.ts +396 -0
  98. package/src/daemon/background-agent.test.ts +78 -0
  99. package/src/daemon/channel-service.ts +201 -0
  100. package/src/daemon/commitment-executor.ts +297 -0
  101. package/src/daemon/event-classifier.ts +239 -0
  102. package/src/daemon/event-coalescer.ts +123 -0
  103. package/src/daemon/event-reactor.ts +214 -0
  104. package/src/daemon/health.ts +220 -0
  105. package/src/daemon/index.ts +1004 -0
  106. package/src/daemon/llm-settings.ts +316 -0
  107. package/src/daemon/observer-service.ts +150 -0
  108. package/src/daemon/pid.ts +98 -0
  109. package/src/daemon/research-queue.ts +155 -0
  110. package/src/daemon/services.ts +175 -0
  111. package/src/daemon/ws-service.ts +788 -0
  112. package/src/goals/accountability.ts +240 -0
  113. package/src/goals/awareness-bridge.ts +185 -0
  114. package/src/goals/estimator.ts +185 -0
  115. package/src/goals/events.ts +28 -0
  116. package/src/goals/goals.test.ts +400 -0
  117. package/src/goals/integration.test.ts +329 -0
  118. package/src/goals/nl-builder.test.ts +220 -0
  119. package/src/goals/nl-builder.ts +256 -0
  120. package/src/goals/rhythm.test.ts +177 -0
  121. package/src/goals/rhythm.ts +275 -0
  122. package/src/goals/service.test.ts +135 -0
  123. package/src/goals/service.ts +348 -0
  124. package/src/goals/types.ts +106 -0
  125. package/src/goals/workflow-bridge.ts +96 -0
  126. package/src/integrations/google-api.ts +134 -0
  127. package/src/integrations/google-auth.ts +175 -0
  128. package/src/llm/README.md +291 -0
  129. package/src/llm/anthropic.ts +386 -0
  130. package/src/llm/gemini.ts +371 -0
  131. package/src/llm/index.ts +19 -0
  132. package/src/llm/manager.ts +153 -0
  133. package/src/llm/ollama.ts +307 -0
  134. package/src/llm/openai.ts +350 -0
  135. package/src/llm/provider.test.ts +231 -0
  136. package/src/llm/provider.ts +60 -0
  137. package/src/llm/test.ts +87 -0
  138. package/src/observers/README.md +278 -0
  139. package/src/observers/calendar.ts +113 -0
  140. package/src/observers/clipboard.ts +136 -0
  141. package/src/observers/email.ts +109 -0
  142. package/src/observers/example.ts +58 -0
  143. package/src/observers/file-watcher.ts +124 -0
  144. package/src/observers/index.ts +159 -0
  145. package/src/observers/notifications.ts +197 -0
  146. package/src/observers/observers.test.ts +203 -0
  147. package/src/observers/processes.ts +225 -0
  148. package/src/personality/README.md +61 -0
  149. package/src/personality/adapter.ts +196 -0
  150. package/src/personality/index.ts +20 -0
  151. package/src/personality/learner.ts +209 -0
  152. package/src/personality/model.ts +132 -0
  153. package/src/personality/personality.test.ts +236 -0
  154. package/src/roles/README.md +252 -0
  155. package/src/roles/authority.ts +119 -0
  156. package/src/roles/example-usage.ts +198 -0
  157. package/src/roles/index.ts +42 -0
  158. package/src/roles/loader.ts +143 -0
  159. package/src/roles/prompt-builder.ts +194 -0
  160. package/src/roles/test-multi.ts +102 -0
  161. package/src/roles/test-role.yaml +77 -0
  162. package/src/roles/test-utils.ts +93 -0
  163. package/src/roles/test.ts +106 -0
  164. package/src/roles/tool-guide.ts +190 -0
  165. package/src/roles/types.ts +36 -0
  166. package/src/roles/utils.ts +200 -0
  167. package/src/scripts/google-setup.ts +168 -0
  168. package/src/sidecar/connection.ts +179 -0
  169. package/src/sidecar/index.ts +6 -0
  170. package/src/sidecar/manager.ts +542 -0
  171. package/src/sidecar/protocol.ts +85 -0
  172. package/src/sidecar/rpc.ts +161 -0
  173. package/src/sidecar/scheduler.ts +136 -0
  174. package/src/sidecar/types.ts +112 -0
  175. package/src/sidecar/validator.ts +144 -0
  176. package/src/vault/README.md +110 -0
  177. package/src/vault/awareness.ts +341 -0
  178. package/src/vault/commitments.ts +299 -0
  179. package/src/vault/content-pipeline.ts +260 -0
  180. package/src/vault/conversations.ts +173 -0
  181. package/src/vault/entities.ts +180 -0
  182. package/src/vault/extractor.test.ts +356 -0
  183. package/src/vault/extractor.ts +345 -0
  184. package/src/vault/facts.ts +190 -0
  185. package/src/vault/goals.ts +477 -0
  186. package/src/vault/index.ts +87 -0
  187. package/src/vault/keychain.ts +99 -0
  188. package/src/vault/observations.ts +115 -0
  189. package/src/vault/relationships.ts +178 -0
  190. package/src/vault/retrieval.test.ts +126 -0
  191. package/src/vault/retrieval.ts +227 -0
  192. package/src/vault/schema.ts +658 -0
  193. package/src/vault/settings.ts +38 -0
  194. package/src/vault/vectors.ts +92 -0
  195. package/src/vault/workflows.ts +403 -0
  196. package/src/workflows/auto-suggest.ts +290 -0
  197. package/src/workflows/engine.ts +366 -0
  198. package/src/workflows/events.ts +24 -0
  199. package/src/workflows/executor.ts +207 -0
  200. package/src/workflows/nl-builder.ts +198 -0
  201. package/src/workflows/nodes/actions/agent-task.ts +73 -0
  202. package/src/workflows/nodes/actions/calendar-action.ts +85 -0
  203. package/src/workflows/nodes/actions/code-execution.ts +73 -0
  204. package/src/workflows/nodes/actions/discord.ts +77 -0
  205. package/src/workflows/nodes/actions/file-write.ts +73 -0
  206. package/src/workflows/nodes/actions/gmail.ts +69 -0
  207. package/src/workflows/nodes/actions/http-request.ts +117 -0
  208. package/src/workflows/nodes/actions/notification.ts +85 -0
  209. package/src/workflows/nodes/actions/run-tool.ts +55 -0
  210. package/src/workflows/nodes/actions/send-message.ts +82 -0
  211. package/src/workflows/nodes/actions/shell-command.ts +76 -0
  212. package/src/workflows/nodes/actions/telegram.ts +60 -0
  213. package/src/workflows/nodes/builtin.ts +119 -0
  214. package/src/workflows/nodes/error/error-handler.ts +37 -0
  215. package/src/workflows/nodes/error/fallback.ts +47 -0
  216. package/src/workflows/nodes/error/retry.ts +82 -0
  217. package/src/workflows/nodes/logic/delay.ts +42 -0
  218. package/src/workflows/nodes/logic/if-else.ts +41 -0
  219. package/src/workflows/nodes/logic/loop.ts +90 -0
  220. package/src/workflows/nodes/logic/merge.ts +38 -0
  221. package/src/workflows/nodes/logic/race.ts +40 -0
  222. package/src/workflows/nodes/logic/switch.ts +59 -0
  223. package/src/workflows/nodes/logic/template-render.ts +53 -0
  224. package/src/workflows/nodes/logic/variable-get.ts +37 -0
  225. package/src/workflows/nodes/logic/variable-set.ts +59 -0
  226. package/src/workflows/nodes/registry.ts +99 -0
  227. package/src/workflows/nodes/transform/aggregate.ts +99 -0
  228. package/src/workflows/nodes/transform/csv-parse.ts +70 -0
  229. package/src/workflows/nodes/transform/json-parse.ts +63 -0
  230. package/src/workflows/nodes/transform/map-filter.ts +84 -0
  231. package/src/workflows/nodes/transform/regex-match.ts +89 -0
  232. package/src/workflows/nodes/triggers/calendar.ts +33 -0
  233. package/src/workflows/nodes/triggers/clipboard.ts +32 -0
  234. package/src/workflows/nodes/triggers/cron.ts +40 -0
  235. package/src/workflows/nodes/triggers/email.ts +40 -0
  236. package/src/workflows/nodes/triggers/file-change.ts +45 -0
  237. package/src/workflows/nodes/triggers/git.ts +46 -0
  238. package/src/workflows/nodes/triggers/manual.ts +23 -0
  239. package/src/workflows/nodes/triggers/poll.ts +81 -0
  240. package/src/workflows/nodes/triggers/process.ts +44 -0
  241. package/src/workflows/nodes/triggers/screen-event.ts +37 -0
  242. package/src/workflows/nodes/triggers/webhook.ts +39 -0
  243. package/src/workflows/safe-eval.ts +139 -0
  244. package/src/workflows/template.ts +118 -0
  245. package/src/workflows/triggers/cron.ts +311 -0
  246. package/src/workflows/triggers/manager.ts +285 -0
  247. package/src/workflows/triggers/observer-bridge.ts +172 -0
  248. package/src/workflows/triggers/poller.ts +201 -0
  249. package/src/workflows/triggers/screen-condition.ts +218 -0
  250. package/src/workflows/triggers/triggers.test.ts +740 -0
  251. package/src/workflows/triggers/webhook.ts +191 -0
  252. package/src/workflows/types.ts +133 -0
  253. package/src/workflows/variables.ts +72 -0
  254. package/src/workflows/workflows.test.ts +383 -0
  255. package/src/workflows/yaml.ts +104 -0
  256. package/ui/dist/index-j75njzc1.css +1199 -0
  257. package/ui/dist/index-p2zh407q.js +80603 -0
  258. package/ui/dist/index.html +13 -0
  259. package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
  260. package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
  261. package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
  262. package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
  263. package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
  264. package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
  265. package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
  266. package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
@@ -0,0 +1,77 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const discordAction: NodeDefinition = {
4
+ type: 'action.discord',
5
+ label: 'Send Discord Message',
6
+ description: 'Send a message to a Discord channel via a webhook or bot token.',
7
+ category: 'action',
8
+ icon: '🎮',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ channel_id: {
12
+ type: 'string',
13
+ label: 'Channel ID or Webhook URL',
14
+ description: 'Discord channel ID (requires bot token) or full webhook URL.',
15
+ required: true,
16
+ placeholder: '1234567890123456789',
17
+ },
18
+ message: {
19
+ type: 'template',
20
+ label: 'Message',
21
+ description: 'Message content. Supports template expressions and Discord markdown.',
22
+ required: true,
23
+ placeholder: '**Alert**: {{data.message}}',
24
+ },
25
+ },
26
+ inputs: ['default'],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ const channelId = String(config.channel_id ?? '');
30
+ const message = String(config.message ?? '');
31
+
32
+ ctx.logger.info(`Sending Discord message to channel ${channelId}`);
33
+
34
+ let success = false;
35
+ let note = '';
36
+
37
+ // If channelId looks like a webhook URL, use it directly
38
+ if (channelId.startsWith('https://discord.com/api/webhooks/') || channelId.startsWith('https://discordapp.com/api/webhooks/')) {
39
+ try {
40
+ const resp = await fetch(channelId, {
41
+ method: 'POST',
42
+ headers: { 'Content-Type': 'application/json' },
43
+ body: JSON.stringify({ content: message }),
44
+ signal: ctx.abortSignal,
45
+ });
46
+ success = resp.ok;
47
+ if (!resp.ok) {
48
+ note = `Discord webhook returned ${resp.status}`;
49
+ ctx.logger.warn(note);
50
+ }
51
+ } catch (err) {
52
+ throw new Error(`Discord webhook send failed: ${err instanceof Error ? err.message : String(err)}`);
53
+ }
54
+ } else if (ctx.toolRegistry.has('send_discord')) {
55
+ try {
56
+ await ctx.toolRegistry.execute('send_discord', { channel_id: channelId, content: message });
57
+ success = true;
58
+ } catch (err) {
59
+ throw new Error(`Discord send failed: ${err instanceof Error ? err.message : String(err)}`);
60
+ }
61
+ } else {
62
+ note = 'Discord integration not configured — message not sent';
63
+ ctx.logger.warn(note);
64
+ }
65
+
66
+ return {
67
+ data: {
68
+ ...input.data,
69
+ discord_sent: success,
70
+ channel_id: channelId,
71
+ message,
72
+ note: note || undefined,
73
+ sentAt: Date.now(),
74
+ },
75
+ };
76
+ },
77
+ };
@@ -0,0 +1,73 @@
1
+ import { writeFileSync, appendFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import type { NodeDefinition } from '../registry.ts';
4
+
5
+ export const fileWriteAction: NodeDefinition = {
6
+ type: 'action.file_write',
7
+ label: 'File Write',
8
+ description: 'Write or append content to a file on disk.',
9
+ category: 'action',
10
+ icon: '💾',
11
+ color: '#3b82f6',
12
+ configSchema: {
13
+ path: {
14
+ type: 'template',
15
+ label: 'File Path',
16
+ description: 'Absolute or relative path to the file. Supports template expressions.',
17
+ required: true,
18
+ placeholder: '/tmp/output.txt',
19
+ },
20
+ content: {
21
+ type: 'template',
22
+ label: 'Content',
23
+ description: 'Content to write. Supports template expressions.',
24
+ required: true,
25
+ placeholder: '{{data.result}}',
26
+ },
27
+ mode: {
28
+ type: 'select',
29
+ label: 'Mode',
30
+ description: 'Whether to overwrite the file or append to it.',
31
+ required: true,
32
+ default: 'write',
33
+ options: [
34
+ { label: 'Write (overwrite)', value: 'write' },
35
+ { label: 'Append', value: 'append' },
36
+ ],
37
+ },
38
+ },
39
+ inputs: ['default'],
40
+ outputs: ['default'],
41
+ execute: async (input, config, ctx) => {
42
+ const filePath = String(config.path ?? '');
43
+ if (!filePath) throw new Error('path is required');
44
+
45
+ const content = String(config.content ?? '');
46
+ const mode = String(config.mode ?? 'write');
47
+
48
+ ctx.logger.info(`File ${mode}: ${filePath}`);
49
+
50
+ // Ensure parent directory exists
51
+ try {
52
+ mkdirSync(dirname(filePath), { recursive: true });
53
+ } catch {
54
+ // ignore — may already exist
55
+ }
56
+
57
+ if (mode === 'append') {
58
+ appendFileSync(filePath, content, 'utf8');
59
+ } else {
60
+ writeFileSync(filePath, content, 'utf8');
61
+ }
62
+
63
+ return {
64
+ data: {
65
+ ...input.data,
66
+ path: filePath,
67
+ mode,
68
+ bytesWritten: Buffer.byteLength(content, 'utf8'),
69
+ writtenAt: Date.now(),
70
+ },
71
+ };
72
+ },
73
+ };
@@ -0,0 +1,69 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const gmailAction: NodeDefinition = {
4
+ type: 'action.gmail',
5
+ label: 'Send Gmail',
6
+ description: 'Send an email via Gmail using the Google API integration.',
7
+ category: 'action',
8
+ icon: '📨',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ to: {
12
+ type: 'template',
13
+ label: 'To',
14
+ description: 'Recipient email address. Supports template expressions.',
15
+ required: true,
16
+ placeholder: 'user@example.com',
17
+ },
18
+ subject: {
19
+ type: 'template',
20
+ label: 'Subject',
21
+ description: 'Email subject. Supports template expressions.',
22
+ required: true,
23
+ placeholder: 'Report from JARVIS',
24
+ },
25
+ body: {
26
+ type: 'template',
27
+ label: 'Body',
28
+ description: 'Email body (plain text or HTML). Supports template expressions.',
29
+ required: true,
30
+ placeholder: 'Here is your report:\n\n{{data.content}}',
31
+ },
32
+ },
33
+ inputs: ['default'],
34
+ outputs: ['default'],
35
+ execute: async (input, config, ctx) => {
36
+ const to = String(config.to ?? '');
37
+ const subject = String(config.subject ?? '');
38
+ const body = String(config.body ?? '');
39
+
40
+ ctx.logger.info(`Sending Gmail to ${to}: ${subject}`);
41
+
42
+ // Delegate to the send_email tool if registered, otherwise placeholder
43
+ let success = false;
44
+ let note = '';
45
+ if (ctx.toolRegistry.has('send_email')) {
46
+ try {
47
+ await ctx.toolRegistry.execute('send_email', { to, subject, body });
48
+ success = true;
49
+ } catch (err) {
50
+ throw new Error(`Gmail send failed: ${err instanceof Error ? err.message : String(err)}`);
51
+ }
52
+ } else {
53
+ note = 'Google API integration not configured — email not sent';
54
+ ctx.logger.warn(note);
55
+ success = false;
56
+ }
57
+
58
+ return {
59
+ data: {
60
+ ...input.data,
61
+ gmail_sent: success,
62
+ to,
63
+ subject,
64
+ note: note || undefined,
65
+ sentAt: Date.now(),
66
+ },
67
+ };
68
+ },
69
+ };
@@ -0,0 +1,117 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const httpRequestAction: NodeDefinition = {
4
+ type: 'action.http_request',
5
+ label: 'HTTP Request',
6
+ description: 'Make an outbound HTTP request and return the response.',
7
+ category: 'action',
8
+ icon: '🌐',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ url: {
12
+ type: 'template',
13
+ label: 'URL',
14
+ description: 'Target URL. Supports template expressions.',
15
+ required: true,
16
+ placeholder: 'https://api.example.com/endpoint',
17
+ },
18
+ method: {
19
+ type: 'select',
20
+ label: 'Method',
21
+ description: 'HTTP method.',
22
+ required: true,
23
+ default: 'GET',
24
+ options: [
25
+ { label: 'GET', value: 'GET' },
26
+ { label: 'POST', value: 'POST' },
27
+ { label: 'PUT', value: 'PUT' },
28
+ { label: 'PATCH', value: 'PATCH' },
29
+ { label: 'DELETE', value: 'DELETE' },
30
+ ],
31
+ },
32
+ headers: {
33
+ type: 'json',
34
+ label: 'Headers',
35
+ description: 'JSON object of request headers.',
36
+ required: false,
37
+ default: {},
38
+ },
39
+ body: {
40
+ type: 'template',
41
+ label: 'Body',
42
+ description: 'Request body. Supports template expressions. Leave empty for GET/DELETE.',
43
+ required: false,
44
+ placeholder: '{"key": "{{data.value}}"}',
45
+ },
46
+ },
47
+ inputs: ['default'],
48
+ outputs: ['default'],
49
+ execute: async (input, config, ctx) => {
50
+ const url = String(config.url ?? '');
51
+ if (!url) throw new Error('url is required');
52
+
53
+ const method = String(config.method ?? 'GET');
54
+ const body = config.body ? String(config.body) : undefined;
55
+
56
+ // Build headers
57
+ let headers: Record<string, string> = {};
58
+ if (config.headers && typeof config.headers === 'object' && !Array.isArray(config.headers)) {
59
+ headers = Object.fromEntries(
60
+ Object.entries(config.headers as Record<string, unknown>).map(([k, v]) => [k, String(v)])
61
+ );
62
+ } else if (typeof config.headers === 'string' && config.headers) {
63
+ try {
64
+ const parsed = JSON.parse(config.headers);
65
+ headers = Object.fromEntries(
66
+ Object.entries(parsed as Record<string, unknown>).map(([k, v]) => [k, String(v)])
67
+ );
68
+ } catch {
69
+ ctx.logger.warn('Could not parse headers JSON — using empty headers');
70
+ }
71
+ }
72
+
73
+ ctx.logger.info(`HTTP ${method} ${url}`);
74
+
75
+ const fetchOptions: RequestInit = {
76
+ method,
77
+ headers,
78
+ signal: ctx.abortSignal,
79
+ };
80
+
81
+ if (body && !['GET', 'HEAD', 'DELETE'].includes(method)) {
82
+ fetchOptions.body = body;
83
+ if (!headers['Content-Type'] && !headers['content-type']) {
84
+ (fetchOptions.headers as Record<string, string>)['Content-Type'] = 'application/json';
85
+ }
86
+ }
87
+
88
+ const response = await fetch(url, fetchOptions);
89
+ const statusCode = response.status;
90
+ const responseHeaders: Record<string, string> = {};
91
+ response.headers.forEach((value, key) => { responseHeaders[key] = value; });
92
+
93
+ const responseText = await response.text();
94
+ let responseBody: unknown = responseText;
95
+ try {
96
+ responseBody = JSON.parse(responseText);
97
+ } catch {
98
+ // keep as text
99
+ }
100
+
101
+ if (!response.ok) {
102
+ ctx.logger.warn(`HTTP ${method} ${url} returned ${statusCode}`);
103
+ }
104
+
105
+ return {
106
+ data: {
107
+ ...input.data,
108
+ status: statusCode,
109
+ ok: response.ok,
110
+ headers: responseHeaders,
111
+ body: responseBody,
112
+ url,
113
+ method,
114
+ },
115
+ };
116
+ },
117
+ };
@@ -0,0 +1,85 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const notificationAction: NodeDefinition = {
4
+ type: 'action.notification',
5
+ label: 'Desktop Notification',
6
+ description: 'Send a desktop notification via the OS notification system.',
7
+ category: 'action',
8
+ icon: '🔔',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ title: {
12
+ type: 'template',
13
+ label: 'Title',
14
+ description: 'Notification title. Supports template expressions.',
15
+ required: true,
16
+ placeholder: 'JARVIS Alert',
17
+ },
18
+ body: {
19
+ type: 'template',
20
+ label: 'Body',
21
+ description: 'Notification body text. Supports template expressions.',
22
+ required: true,
23
+ placeholder: 'Something happened: {{data.message}}',
24
+ },
25
+ urgency: {
26
+ type: 'select',
27
+ label: 'Urgency',
28
+ description: 'Notification urgency level.',
29
+ required: true,
30
+ default: 'normal',
31
+ options: [
32
+ { label: 'Low', value: 'low' },
33
+ { label: 'Normal', value: 'normal' },
34
+ { label: 'High', value: 'high' },
35
+ ],
36
+ },
37
+ },
38
+ inputs: ['default'],
39
+ outputs: ['default'],
40
+ execute: async (input, config, ctx) => {
41
+ const title = String(config.title ?? 'JARVIS');
42
+ const body = String(config.body ?? '');
43
+ const urgency = String(config.urgency ?? 'normal');
44
+
45
+ ctx.logger.info(`Sending notification: [${urgency}] ${title}`);
46
+
47
+ // Broadcast to dashboard via WebSocket
48
+ if (ctx.broadcast) {
49
+ ctx.broadcast('workflow_message', {
50
+ channel: 'notification',
51
+ message: body ? `**${title}**: ${body}` : title,
52
+ title,
53
+ body,
54
+ urgency,
55
+ executionId: ctx.executionId,
56
+ workflowId: ctx.workflowId,
57
+ });
58
+ }
59
+
60
+ // Also try notify-send on Linux/WSLg for desktop notification
61
+ let sent = false;
62
+ try {
63
+ const urgencyFlag = urgency === 'high' ? 'critical' : urgency === 'low' ? 'low' : 'normal';
64
+ const proc = Bun.spawn(
65
+ ['notify-send', '-u', urgencyFlag, '--', title, body],
66
+ { stdout: 'pipe', stderr: 'pipe' }
67
+ );
68
+ await proc.exited;
69
+ sent = proc.exitCode === 0;
70
+ } catch {
71
+ // notify-send not available — dashboard broadcast is the primary channel
72
+ }
73
+
74
+ return {
75
+ data: {
76
+ ...input.data,
77
+ notification_sent: sent,
78
+ title,
79
+ body,
80
+ urgency,
81
+ sentAt: Date.now(),
82
+ },
83
+ };
84
+ },
85
+ };
@@ -0,0 +1,55 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const runToolAction: NodeDefinition = {
4
+ type: 'action.run_tool',
5
+ label: 'Run Tool',
6
+ description: 'Execute any registered JARVIS tool by name.',
7
+ category: 'action',
8
+ icon: '🔧',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ tool_name: {
12
+ type: 'string',
13
+ label: 'Tool Name',
14
+ description: 'Name of the registered tool to execute.',
15
+ required: true,
16
+ placeholder: 'web_search',
17
+ },
18
+ arguments: {
19
+ type: 'json',
20
+ label: 'Arguments',
21
+ description: 'JSON object of arguments to pass to the tool.',
22
+ required: false,
23
+ default: {},
24
+ },
25
+ },
26
+ inputs: ['default'],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ const toolName = String(config.tool_name ?? '');
30
+ if (!toolName) throw new Error('tool_name is required');
31
+
32
+ // Resolve arguments — they may contain template values already resolved upstream
33
+ let resolvedArgs: Record<string, unknown> = {};
34
+ if (config.arguments && typeof config.arguments === 'object' && !Array.isArray(config.arguments)) {
35
+ resolvedArgs = config.arguments as Record<string, unknown>;
36
+ } else if (typeof config.arguments === 'string') {
37
+ try {
38
+ resolvedArgs = JSON.parse(config.arguments);
39
+ } catch {
40
+ throw new Error(`arguments must be a valid JSON object, got: ${config.arguments}`);
41
+ }
42
+ }
43
+
44
+ ctx.logger.info(`Running tool: ${toolName}`);
45
+ const result = await ctx.toolRegistry.execute(toolName, resolvedArgs);
46
+
47
+ return {
48
+ data: {
49
+ ...input.data,
50
+ tool_name: toolName,
51
+ tool_result: result,
52
+ },
53
+ };
54
+ },
55
+ };
@@ -0,0 +1,82 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const sendMessageAction: NodeDefinition = {
4
+ type: 'action.send_message',
5
+ label: 'Send Message',
6
+ description: 'Send a message to a channel: dashboard, Telegram, or Discord.',
7
+ category: 'action',
8
+ icon: '💬',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ channel: {
12
+ type: 'select',
13
+ label: 'Channel',
14
+ description: 'Destination channel for the message.',
15
+ required: true,
16
+ default: 'dashboard',
17
+ options: [
18
+ { label: 'Dashboard', value: 'dashboard' },
19
+ { label: 'Telegram', value: 'telegram' },
20
+ { label: 'Discord', value: 'discord' },
21
+ ],
22
+ },
23
+ message: {
24
+ type: 'template',
25
+ label: 'Message',
26
+ description: 'Message content. Supports template expressions.',
27
+ required: true,
28
+ placeholder: 'Hello from JARVIS! Result: {{data.result}}',
29
+ },
30
+ },
31
+ inputs: ['default'],
32
+ outputs: ['default'],
33
+ execute: async (input, config, ctx) => {
34
+ const channel = String(config.channel ?? 'dashboard');
35
+ const message = String(config.message ?? '');
36
+ ctx.logger.info(`Sending message to ${channel}: ${message.slice(0, 100)}`);
37
+
38
+ let success = false;
39
+
40
+ if (channel === 'dashboard') {
41
+ // Broadcast directly to connected dashboard clients via WebSocket
42
+ if (ctx.broadcast) {
43
+ ctx.broadcast('workflow_message', {
44
+ channel: 'dashboard',
45
+ message,
46
+ executionId: ctx.executionId,
47
+ workflowId: ctx.workflowId,
48
+ });
49
+ success = true;
50
+ } else {
51
+ ctx.logger.warn('No broadcast function available — message logged only');
52
+ }
53
+ } else {
54
+ // Route to Telegram/Discord via tool registry
55
+ const toolName = channel === 'telegram'
56
+ ? 'send_telegram'
57
+ : 'send_discord';
58
+
59
+ try {
60
+ if (ctx.toolRegistry.has(toolName)) {
61
+ await ctx.toolRegistry.execute(toolName, { message });
62
+ success = true;
63
+ } else {
64
+ ctx.logger.warn(`Tool '${toolName}' not available — message logged only`);
65
+ }
66
+ } catch (err) {
67
+ ctx.logger.error(`Failed to send message: ${err instanceof Error ? err.message : String(err)}`);
68
+ throw err;
69
+ }
70
+ }
71
+
72
+ return {
73
+ data: {
74
+ ...input.data,
75
+ sent: success,
76
+ channel,
77
+ message,
78
+ sentAt: Date.now(),
79
+ },
80
+ };
81
+ },
82
+ };
@@ -0,0 +1,76 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const shellCommandAction: NodeDefinition = {
4
+ type: 'action.shell_command',
5
+ label: 'Shell Command',
6
+ description: 'Execute a shell command and capture its output.',
7
+ category: 'action',
8
+ icon: '💻',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ command: {
12
+ type: 'template',
13
+ label: 'Command',
14
+ description: 'Shell command to run. Supports template expressions.',
15
+ required: true,
16
+ placeholder: 'echo "Hello {{data.name}}"',
17
+ },
18
+ timeout_ms: {
19
+ type: 'number',
20
+ label: 'Timeout (ms)',
21
+ description: 'Maximum execution time before the process is killed.',
22
+ required: false,
23
+ default: 30000,
24
+ },
25
+ },
26
+ inputs: ['default'],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ const command = String(config.command ?? '');
30
+ if (!command) throw new Error('command is required');
31
+ const timeoutMs = typeof config.timeout_ms === 'number' ? config.timeout_ms : 30000;
32
+
33
+ ctx.logger.info(`Shell command: ${command.slice(0, 120)}`);
34
+
35
+ // Use Bun.spawn with /bin/sh -c to support full shell syntax
36
+ const proc = Bun.spawn(['/bin/sh', '-c', command], {
37
+ stdout: 'pipe',
38
+ stderr: 'pipe',
39
+ });
40
+
41
+ // Race between process exit and timeout (with proper cleanup)
42
+ let timer: ReturnType<typeof setTimeout> | undefined;
43
+ const timeoutPromise = new Promise<never>((_, reject) => {
44
+ timer = setTimeout(() => reject(new Error(`Shell command timed out after ${timeoutMs}ms`)), timeoutMs);
45
+ });
46
+
47
+ let exitCode: number | null = null;
48
+ try {
49
+ exitCode = await Promise.race([proc.exited, timeoutPromise]);
50
+ } catch (err) {
51
+ proc.kill();
52
+ throw err;
53
+ } finally {
54
+ clearTimeout(timer);
55
+ }
56
+
57
+ const stdout = await new Response(proc.stdout).text();
58
+ const stderr = await new Response(proc.stderr).text();
59
+
60
+ const success = exitCode === 0;
61
+ if (!success) {
62
+ ctx.logger.warn(`Shell command exited with code ${exitCode}: ${stderr.slice(0, 200)}`);
63
+ }
64
+
65
+ return {
66
+ data: {
67
+ ...input.data,
68
+ exit_code: exitCode,
69
+ stdout: stdout.trim(),
70
+ stderr: stderr.trim(),
71
+ success,
72
+ command,
73
+ },
74
+ };
75
+ },
76
+ };
@@ -0,0 +1,60 @@
1
+ import type { NodeDefinition } from '../registry.ts';
2
+
3
+ export const telegramAction: NodeDefinition = {
4
+ type: 'action.telegram',
5
+ label: 'Send Telegram Message',
6
+ description: 'Send a message to a Telegram chat via the Bot API.',
7
+ category: 'action',
8
+ icon: '✈️',
9
+ color: '#3b82f6',
10
+ configSchema: {
11
+ chat_id: {
12
+ type: 'string',
13
+ label: 'Chat ID',
14
+ description: 'Telegram chat ID or username to send the message to.',
15
+ required: true,
16
+ placeholder: '-1001234567890',
17
+ },
18
+ message: {
19
+ type: 'template',
20
+ label: 'Message',
21
+ description: 'Message text. Supports template expressions and Markdown.',
22
+ required: true,
23
+ placeholder: 'Alert: {{data.message}}',
24
+ },
25
+ },
26
+ inputs: ['default'],
27
+ outputs: ['default'],
28
+ execute: async (input, config, ctx) => {
29
+ const chatId = String(config.chat_id ?? '');
30
+ const message = String(config.message ?? '');
31
+
32
+ ctx.logger.info(`Sending Telegram message to ${chatId}`);
33
+
34
+ // Delegate to telegram tool if registered
35
+ let success = false;
36
+ let note = '';
37
+ if (ctx.toolRegistry.has('send_telegram')) {
38
+ try {
39
+ await ctx.toolRegistry.execute('send_telegram', { chat_id: chatId, text: message });
40
+ success = true;
41
+ } catch (err) {
42
+ throw new Error(`Telegram send failed: ${err instanceof Error ? err.message : String(err)}`);
43
+ }
44
+ } else {
45
+ note = 'Telegram tool not registered — message not sent';
46
+ ctx.logger.warn(note);
47
+ }
48
+
49
+ return {
50
+ data: {
51
+ ...input.data,
52
+ telegram_sent: success,
53
+ chat_id: chatId,
54
+ message,
55
+ note: note || undefined,
56
+ sentAt: Date.now(),
57
+ },
58
+ };
59
+ },
60
+ };