@shykaruu/jarvis-brain 0.4.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.
- package/LICENSE +153 -0
- package/README.md +428 -0
- package/bin/jarvis.ts +449 -0
- package/package.json +79 -0
- package/roles/activity-observer.yaml +60 -0
- package/roles/ceo-founder.yaml +144 -0
- package/roles/chief-of-staff.yaml +158 -0
- package/roles/dev-lead.yaml +182 -0
- package/roles/executive-assistant.yaml +77 -0
- package/roles/marketing-director.yaml +168 -0
- package/roles/personal-assistant.yaml +266 -0
- package/roles/research-specialist.yaml +60 -0
- package/roles/specialists/content-writer.yaml +53 -0
- package/roles/specialists/customer-support.yaml +57 -0
- package/roles/specialists/data-analyst.yaml +57 -0
- package/roles/specialists/financial-analyst.yaml +56 -0
- package/roles/specialists/hr-specialist.yaml +55 -0
- package/roles/specialists/legal-advisor.yaml +58 -0
- package/roles/specialists/marketing-strategist.yaml +56 -0
- package/roles/specialists/project-coordinator.yaml +55 -0
- package/roles/specialists/research-analyst.yaml +58 -0
- package/roles/specialists/software-engineer.yaml +57 -0
- package/roles/specialists/system-administrator.yaml +57 -0
- package/roles/system-admin.yaml +76 -0
- package/scripts/ensure-bun.cjs +16 -0
- package/src/actions/README.md +421 -0
- package/src/actions/app-control/desktop-controller.test.ts +26 -0
- package/src/actions/app-control/desktop-controller.ts +438 -0
- package/src/actions/app-control/interface.ts +64 -0
- package/src/actions/app-control/linux.ts +273 -0
- package/src/actions/app-control/macos.ts +54 -0
- package/src/actions/app-control/sidecar-launcher.test.ts +23 -0
- package/src/actions/app-control/sidecar-launcher.ts +286 -0
- package/src/actions/app-control/windows.ts +44 -0
- package/src/actions/browser/cdp.ts +138 -0
- package/src/actions/browser/chrome-launcher.ts +261 -0
- package/src/actions/browser/session.ts +506 -0
- package/src/actions/browser/stealth.ts +49 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/terminal/executor.ts +157 -0
- package/src/actions/terminal/wsl-bridge.ts +126 -0
- package/src/actions/test.ts +93 -0
- package/src/actions/tools/agents.ts +363 -0
- package/src/actions/tools/builtin.ts +950 -0
- package/src/actions/tools/commitments.ts +192 -0
- package/src/actions/tools/content.ts +217 -0
- package/src/actions/tools/delegate.ts +147 -0
- package/src/actions/tools/desktop.test.ts +55 -0
- package/src/actions/tools/desktop.ts +305 -0
- package/src/actions/tools/documents.ts +169 -0
- package/src/actions/tools/goals.ts +376 -0
- package/src/actions/tools/local-tools-guard.ts +31 -0
- package/src/actions/tools/registry.ts +173 -0
- package/src/actions/tools/research.ts +111 -0
- package/src/actions/tools/sidecar-list.ts +57 -0
- package/src/actions/tools/sidecar-route.ts +105 -0
- package/src/actions/tools/workflows.ts +216 -0
- package/src/agents/agent.ts +132 -0
- package/src/agents/delegation.ts +107 -0
- package/src/agents/hierarchy.ts +113 -0
- package/src/agents/index.ts +19 -0
- package/src/agents/messaging.ts +125 -0
- package/src/agents/orchestrator.ts +592 -0
- package/src/agents/role-discovery.ts +61 -0
- package/src/agents/sub-agent-runner.ts +309 -0
- package/src/agents/task-manager.ts +151 -0
- package/src/authority/approval-delivery.ts +59 -0
- package/src/authority/approval.ts +196 -0
- package/src/authority/audit.ts +158 -0
- package/src/authority/authority.test.ts +519 -0
- package/src/authority/deferred-executor.ts +103 -0
- package/src/authority/emergency.ts +66 -0
- package/src/authority/engine.ts +301 -0
- package/src/authority/index.ts +12 -0
- package/src/authority/learning.ts +111 -0
- package/src/authority/tool-action-map.ts +74 -0
- package/src/awareness/analytics.ts +466 -0
- package/src/awareness/awareness.test.ts +332 -0
- package/src/awareness/capture-engine.ts +305 -0
- package/src/awareness/context-graph.ts +130 -0
- package/src/awareness/context-tracker.ts +349 -0
- package/src/awareness/index.ts +25 -0
- package/src/awareness/intelligence.ts +321 -0
- package/src/awareness/ocr-engine.ts +88 -0
- package/src/awareness/service.ts +528 -0
- package/src/awareness/struggle-detector.ts +342 -0
- package/src/awareness/suggestion-engine.ts +476 -0
- package/src/awareness/types.ts +201 -0
- package/src/cli/autostart.ts +417 -0
- package/src/cli/deps.ts +449 -0
- package/src/cli/doctor.ts +238 -0
- package/src/cli/helpers.ts +401 -0
- package/src/cli/onboard.ts +827 -0
- package/src/cli/uninstall.test.ts +37 -0
- package/src/cli/uninstall.ts +202 -0
- package/src/comms/README.md +329 -0
- package/src/comms/auth-error.html +48 -0
- package/src/comms/channels/discord.ts +228 -0
- package/src/comms/channels/signal.ts +56 -0
- package/src/comms/channels/telegram.ts +316 -0
- package/src/comms/channels/whatsapp.ts +60 -0
- package/src/comms/channels.test.ts +173 -0
- package/src/comms/dashboard-auth.ts +75 -0
- package/src/comms/desktop-notify.ts +114 -0
- package/src/comms/example.ts +129 -0
- package/src/comms/index.ts +129 -0
- package/src/comms/streaming.ts +149 -0
- package/src/comms/voice.test.ts +504 -0
- package/src/comms/voice.ts +341 -0
- package/src/comms/websocket.test.ts +409 -0
- package/src/comms/websocket.ts +669 -0
- package/src/config/README.md +389 -0
- package/src/config/index.ts +6 -0
- package/src/config/loader.test.ts +183 -0
- package/src/config/loader.ts +148 -0
- package/src/config/types.ts +293 -0
- package/src/daemon/README.md +232 -0
- package/src/daemon/agent-service-interface.ts +9 -0
- package/src/daemon/agent-service.ts +667 -0
- package/src/daemon/api-routes.ts +3067 -0
- package/src/daemon/background-agent-service.ts +396 -0
- package/src/daemon/background-agent.test.ts +78 -0
- package/src/daemon/channel-service.ts +201 -0
- package/src/daemon/commitment-executor.ts +297 -0
- package/src/daemon/dashboard-auth.test.ts +170 -0
- package/src/daemon/event-classifier.ts +239 -0
- package/src/daemon/event-coalescer.ts +123 -0
- package/src/daemon/event-reactor.ts +214 -0
- package/src/daemon/flock.c +7 -0
- package/src/daemon/health.ts +220 -0
- package/src/daemon/index.ts +1070 -0
- package/src/daemon/llm-settings.test.ts +78 -0
- package/src/daemon/llm-settings.ts +450 -0
- package/src/daemon/observer-service.ts +150 -0
- package/src/daemon/pid.test.ts +283 -0
- package/src/daemon/pid.ts +224 -0
- package/src/daemon/research-queue.ts +155 -0
- package/src/daemon/services.ts +175 -0
- package/src/daemon/ws-service.ts +926 -0
- package/src/global.d.ts +4 -0
- package/src/goals/accountability.ts +240 -0
- package/src/goals/awareness-bridge.ts +185 -0
- package/src/goals/estimator.ts +185 -0
- package/src/goals/events.ts +28 -0
- package/src/goals/goals.test.ts +400 -0
- package/src/goals/integration.test.ts +329 -0
- package/src/goals/nl-builder.test.ts +220 -0
- package/src/goals/nl-builder.ts +256 -0
- package/src/goals/rhythm.test.ts +177 -0
- package/src/goals/rhythm.ts +275 -0
- package/src/goals/service.test.ts +135 -0
- package/src/goals/service.ts +407 -0
- package/src/goals/types.ts +106 -0
- package/src/goals/workflow-bridge.ts +96 -0
- package/src/integrations/google-api.ts +134 -0
- package/src/integrations/google-auth.ts +175 -0
- package/src/llm/README.md +291 -0
- package/src/llm/anthropic.ts +400 -0
- package/src/llm/gemini.ts +380 -0
- package/src/llm/groq.ts +406 -0
- package/src/llm/history.ts +147 -0
- package/src/llm/index.ts +21 -0
- package/src/llm/manager.ts +226 -0
- package/src/llm/ollama.ts +316 -0
- package/src/llm/openai.ts +411 -0
- package/src/llm/openrouter.ts +390 -0
- package/src/llm/provider.test.ts +487 -0
- package/src/llm/provider.ts +61 -0
- package/src/llm/test.ts +88 -0
- package/src/observers/README.md +278 -0
- package/src/observers/calendar.ts +113 -0
- package/src/observers/clipboard.ts +136 -0
- package/src/observers/email.ts +109 -0
- package/src/observers/example.ts +58 -0
- package/src/observers/file-watcher.ts +124 -0
- package/src/observers/index.ts +159 -0
- package/src/observers/notifications.ts +197 -0
- package/src/observers/observers.test.ts +203 -0
- package/src/observers/processes.ts +225 -0
- package/src/personality/README.md +61 -0
- package/src/personality/adapter.ts +196 -0
- package/src/personality/index.ts +20 -0
- package/src/personality/learner.ts +209 -0
- package/src/personality/model.ts +132 -0
- package/src/personality/personality.test.ts +236 -0
- package/src/roles/README.md +252 -0
- package/src/roles/authority.ts +120 -0
- package/src/roles/example-usage.ts +198 -0
- package/src/roles/index.ts +42 -0
- package/src/roles/loader.ts +143 -0
- package/src/roles/prompt-builder.ts +218 -0
- package/src/roles/test-multi.ts +102 -0
- package/src/roles/test-role.yaml +77 -0
- package/src/roles/test-utils.ts +93 -0
- package/src/roles/test.ts +106 -0
- package/src/roles/tool-guide.ts +195 -0
- package/src/roles/types.ts +36 -0
- package/src/roles/utils.ts +200 -0
- package/src/scripts/google-setup.ts +168 -0
- package/src/sidecar/connection.ts +179 -0
- package/src/sidecar/index.ts +6 -0
- package/src/sidecar/manager.ts +542 -0
- package/src/sidecar/protocol.ts +85 -0
- package/src/sidecar/rpc.ts +161 -0
- package/src/sidecar/scheduler.ts +136 -0
- package/src/sidecar/types.ts +112 -0
- package/src/sidecar/validator.ts +144 -0
- package/src/sites/builder-tools.ts +215 -0
- package/src/sites/dev-server-manager.ts +286 -0
- package/src/sites/fixtures/security-test-site/.jarvis-project.json +6 -0
- package/src/sites/fixtures/security-test-site/Makefile +15 -0
- package/src/sites/fixtures/security-test-site/README.md +18 -0
- package/src/sites/fixtures/security-test-site/index.html +12 -0
- package/src/sites/fixtures/security-test-site/index.ts +16 -0
- package/src/sites/fixtures/security-test-site/package.json +13 -0
- package/src/sites/fixtures/security-test-site/src/app.tsx +780 -0
- package/src/sites/fixtures/security-test-site/tsconfig.json +10 -0
- package/src/sites/git-manager.ts +240 -0
- package/src/sites/github-manager.ts +355 -0
- package/src/sites/index.ts +25 -0
- package/src/sites/project-manager.ts +389 -0
- package/src/sites/proxy.ts +133 -0
- package/src/sites/service.ts +136 -0
- package/src/sites/templates.ts +169 -0
- package/src/sites/types.ts +89 -0
- package/src/user/profile-followup.test.ts +84 -0
- package/src/user/profile-followup.ts +185 -0
- package/src/user/profile.ts +224 -0
- package/src/vault/README.md +110 -0
- package/src/vault/awareness.ts +341 -0
- package/src/vault/commitments.ts +299 -0
- package/src/vault/content-pipeline.ts +270 -0
- package/src/vault/conversations.ts +173 -0
- package/src/vault/dashboard-sessions.ts +44 -0
- package/src/vault/documents.ts +130 -0
- package/src/vault/entities.ts +185 -0
- package/src/vault/extractor.test.ts +356 -0
- package/src/vault/extractor.ts +345 -0
- package/src/vault/facts.ts +190 -0
- package/src/vault/goals.ts +477 -0
- package/src/vault/index.ts +87 -0
- package/src/vault/keychain.ts +99 -0
- package/src/vault/observations.ts +115 -0
- package/src/vault/relationships.ts +178 -0
- package/src/vault/retrieval.test.ts +139 -0
- package/src/vault/retrieval.ts +258 -0
- package/src/vault/schema.ts +709 -0
- package/src/vault/settings.ts +38 -0
- package/src/vault/user-profile.test.ts +113 -0
- package/src/vault/user-profile.ts +176 -0
- package/src/vault/vectors.ts +92 -0
- package/src/vault/webapp-template-seeds.ts +116 -0
- package/src/vault/webapp-templates.ts +244 -0
- package/src/vault/workflows.ts +403 -0
- package/src/workflows/auto-suggest.ts +290 -0
- package/src/workflows/engine.ts +366 -0
- package/src/workflows/events.ts +24 -0
- package/src/workflows/executor.ts +207 -0
- package/src/workflows/nl-builder.ts +198 -0
- package/src/workflows/nodes/actions/agent-task.ts +73 -0
- package/src/workflows/nodes/actions/calendar-action.ts +85 -0
- package/src/workflows/nodes/actions/code-execution.ts +73 -0
- package/src/workflows/nodes/actions/discord.ts +77 -0
- package/src/workflows/nodes/actions/file-write.ts +73 -0
- package/src/workflows/nodes/actions/gmail.ts +69 -0
- package/src/workflows/nodes/actions/http-request.ts +117 -0
- package/src/workflows/nodes/actions/notification.ts +85 -0
- package/src/workflows/nodes/actions/run-tool.ts +55 -0
- package/src/workflows/nodes/actions/send-message.ts +82 -0
- package/src/workflows/nodes/actions/shell-command.ts +76 -0
- package/src/workflows/nodes/actions/telegram.ts +60 -0
- package/src/workflows/nodes/builtin.ts +119 -0
- package/src/workflows/nodes/error/error-handler.ts +37 -0
- package/src/workflows/nodes/error/fallback.ts +47 -0
- package/src/workflows/nodes/error/retry.ts +82 -0
- package/src/workflows/nodes/logic/delay.ts +42 -0
- package/src/workflows/nodes/logic/if-else.ts +41 -0
- package/src/workflows/nodes/logic/loop.ts +90 -0
- package/src/workflows/nodes/logic/merge.ts +38 -0
- package/src/workflows/nodes/logic/race.ts +40 -0
- package/src/workflows/nodes/logic/switch.ts +59 -0
- package/src/workflows/nodes/logic/template-render.ts +53 -0
- package/src/workflows/nodes/logic/variable-get.ts +37 -0
- package/src/workflows/nodes/logic/variable-set.ts +59 -0
- package/src/workflows/nodes/registry.ts +99 -0
- package/src/workflows/nodes/transform/aggregate.ts +99 -0
- package/src/workflows/nodes/transform/csv-parse.ts +70 -0
- package/src/workflows/nodes/transform/json-parse.ts +63 -0
- package/src/workflows/nodes/transform/map-filter.ts +84 -0
- package/src/workflows/nodes/transform/regex-match.ts +89 -0
- package/src/workflows/nodes/triggers/calendar.ts +33 -0
- package/src/workflows/nodes/triggers/clipboard.ts +32 -0
- package/src/workflows/nodes/triggers/cron.ts +40 -0
- package/src/workflows/nodes/triggers/email.ts +40 -0
- package/src/workflows/nodes/triggers/file-change.ts +45 -0
- package/src/workflows/nodes/triggers/git.ts +46 -0
- package/src/workflows/nodes/triggers/manual.ts +23 -0
- package/src/workflows/nodes/triggers/poll.ts +81 -0
- package/src/workflows/nodes/triggers/process.ts +44 -0
- package/src/workflows/nodes/triggers/screen-event.ts +37 -0
- package/src/workflows/nodes/triggers/webhook.ts +39 -0
- package/src/workflows/safe-eval.ts +139 -0
- package/src/workflows/template.ts +118 -0
- package/src/workflows/triggers/cron.ts +311 -0
- package/src/workflows/triggers/manager.ts +285 -0
- package/src/workflows/triggers/observer-bridge.ts +172 -0
- package/src/workflows/triggers/poller.ts +201 -0
- package/src/workflows/triggers/screen-condition.ts +218 -0
- package/src/workflows/triggers/triggers.test.ts +740 -0
- package/src/workflows/triggers/webhook.ts +191 -0
- package/src/workflows/types.ts +133 -0
- package/src/workflows/variables.ts +72 -0
- package/src/workflows/workflows.test.ts +383 -0
- package/src/workflows/yaml.ts +104 -0
- package/ui/dist/index-3gr23jt9.js +112614 -0
- package/ui/dist/index-9vmj8127.css +14239 -0
- package/ui/dist/index-hy9pc1gm.js +112873 -0
- package/ui/dist/index-j2ep5d1w.js +112374 -0
- package/ui/dist/index-jt00vjqs.js +112858 -0
- package/ui/dist/index-k9ymx5qb.js +112374 -0
- package/ui/dist/index.html +16 -0
- package/ui/public/audio/pcm-capture-processor.js +11 -0
- package/ui/public/openwakeword/models/embedding_model.onnx +0 -0
- package/ui/public/openwakeword/models/hey_jarvis_v0.1.onnx +0 -0
- package/ui/public/openwakeword/models/melspectrogram.onnx +0 -0
- package/ui/public/openwakeword/models/silero_vad.onnx +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.mjs +106 -0
- package/ui/public/ort/ort-wasm-simd-threaded.jsep.wasm +0 -0
- package/ui/public/ort/ort-wasm-simd-threaded.mjs +59 -0
- package/ui/public/ort/ort-wasm-simd-threaded.wasm +0 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site Builder — LLM Tools
|
|
3
|
+
*
|
|
4
|
+
* Tools available to the LLM when working in the context of a site builder project.
|
|
5
|
+
* These are scoped to the active project directory.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ToolDefinition } from '../actions/tools/registry.ts';
|
|
9
|
+
import type { ProjectManager } from './project-manager.ts';
|
|
10
|
+
import type { GitManager } from './git-manager.ts';
|
|
11
|
+
import type { GitHubManager } from './github-manager.ts';
|
|
12
|
+
|
|
13
|
+
/** Block patterns for long-running dev servers that conflict with the managed server */
|
|
14
|
+
const BLOCKED_SERVER_PATTERNS = /\b(make\s+dev|bun\s+--hot|vite\s*$|next\s+dev|npm\s+run\s+dev|yarn\s+dev)\b/i;
|
|
15
|
+
|
|
16
|
+
/** Env keys that must never leak to subprocesses */
|
|
17
|
+
const SECRET_ENV_PATTERNS = [
|
|
18
|
+
/api[_-]?key/i, /secret/i, /token/i, /password/i, /credential/i,
|
|
19
|
+
/^JARVIS_API_KEY$/, /^JARVIS_AUTH_TOKEN$/, /^JARVIS_OPENAI_KEY$/,
|
|
20
|
+
/^JARVIS_OPENROUTER_KEY$/, /^ANTHROPIC_API_KEY$/, /^OPENAI_API_KEY$/,
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
/** Build a sanitized env with secrets stripped */
|
|
24
|
+
function sanitizedEnv(): Record<string, string> {
|
|
25
|
+
const env: Record<string, string> = {};
|
|
26
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
27
|
+
if (value === undefined) continue;
|
|
28
|
+
if (SECRET_ENV_PATTERNS.some(p => p.test(key))) continue;
|
|
29
|
+
env[key] = value;
|
|
30
|
+
}
|
|
31
|
+
return env;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createSiteBuilderTools(
|
|
35
|
+
projectManager: ProjectManager,
|
|
36
|
+
gitManager: GitManager,
|
|
37
|
+
githubManager?: GitHubManager,
|
|
38
|
+
): ToolDefinition[] {
|
|
39
|
+
return [
|
|
40
|
+
{
|
|
41
|
+
name: 'site_read_file',
|
|
42
|
+
description: 'Read a file from the current site builder project. Returns the file content as text.',
|
|
43
|
+
category: 'site-builder',
|
|
44
|
+
parameters: {
|
|
45
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
46
|
+
path: { type: 'string', description: 'Relative path to the file (e.g., "src/App.tsx")', required: true },
|
|
47
|
+
},
|
|
48
|
+
execute: async (params) => {
|
|
49
|
+
try {
|
|
50
|
+
const content = await projectManager.readFile(params.project_id as string, params.path as string);
|
|
51
|
+
return content;
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'site_write_file',
|
|
59
|
+
description: 'Write content to a file in the current site builder project. Creates parent directories if needed.',
|
|
60
|
+
category: 'site-builder',
|
|
61
|
+
parameters: {
|
|
62
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
63
|
+
path: { type: 'string', description: 'Relative path to the file (e.g., "src/App.tsx")', required: true },
|
|
64
|
+
content: { type: 'string', description: 'The full file content to write', required: true },
|
|
65
|
+
},
|
|
66
|
+
execute: async (params) => {
|
|
67
|
+
try {
|
|
68
|
+
await projectManager.writeFile(params.project_id as string, params.path as string, params.content as string);
|
|
69
|
+
return `File written: ${params.path}`;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'site_delete_file',
|
|
77
|
+
description: 'Delete a file from the current site builder project.',
|
|
78
|
+
category: 'site-builder',
|
|
79
|
+
parameters: {
|
|
80
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
81
|
+
path: { type: 'string', description: 'Relative path to the file to delete', required: true },
|
|
82
|
+
},
|
|
83
|
+
execute: async (params) => {
|
|
84
|
+
try {
|
|
85
|
+
await projectManager.deleteFile(params.project_id as string, params.path as string);
|
|
86
|
+
return `File deleted: ${params.path}`;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'site_list_files',
|
|
94
|
+
description: 'List the file tree of the current site builder project. Returns the directory structure.',
|
|
95
|
+
category: 'site-builder',
|
|
96
|
+
parameters: {
|
|
97
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
98
|
+
},
|
|
99
|
+
execute: async (params) => {
|
|
100
|
+
try {
|
|
101
|
+
const tree = projectManager.getFileTree(params.project_id as string);
|
|
102
|
+
return JSON.stringify(tree, null, 2);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'site_run_command',
|
|
110
|
+
description: 'Run a shell command in the project directory. Use for installing packages, building, running one-off scripts, etc. Has a 30-second timeout. Do NOT use this to start dev servers — use the dashboard Start button or /api/sites/projects/:id/start instead.',
|
|
111
|
+
category: 'site-builder',
|
|
112
|
+
parameters: {
|
|
113
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
114
|
+
command: { type: 'string', description: 'The command to run (e.g., "bun add react-router"). Do NOT run long-lived servers here.', required: true },
|
|
115
|
+
},
|
|
116
|
+
execute: async (params) => {
|
|
117
|
+
const projectPath = projectManager.getProjectPath(params.project_id as string);
|
|
118
|
+
if (!projectPath) return 'Error: Project not found';
|
|
119
|
+
|
|
120
|
+
const cmd = (params.command as string).trim();
|
|
121
|
+
|
|
122
|
+
// Block commands that start long-running dev servers (conflicts with managed server)
|
|
123
|
+
if (BLOCKED_SERVER_PATTERNS.test(cmd)) {
|
|
124
|
+
return 'Error: Do not start dev servers with site_run_command. The dev server is managed automatically — use the Start button in the Sites page or POST /api/sites/projects/:id/start instead.';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const proc = Bun.spawn(['sh', '-c', cmd], {
|
|
129
|
+
cwd: projectPath,
|
|
130
|
+
stdout: 'pipe',
|
|
131
|
+
stderr: 'pipe',
|
|
132
|
+
env: sanitizedEnv(),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// 30-second timeout to prevent hanging
|
|
136
|
+
const result = await Promise.race([
|
|
137
|
+
(async () => {
|
|
138
|
+
const [stdout, stderr] = await Promise.all([
|
|
139
|
+
new Response(proc.stdout).text(),
|
|
140
|
+
new Response(proc.stderr).text(),
|
|
141
|
+
]);
|
|
142
|
+
const exitCode = await proc.exited;
|
|
143
|
+
|
|
144
|
+
let output = '';
|
|
145
|
+
if (stdout.trim()) output += stdout.trim();
|
|
146
|
+
if (stderr.trim()) output += (output ? '\n' : '') + stderr.trim();
|
|
147
|
+
if (exitCode !== 0) output += `\n(exit code: ${exitCode})`;
|
|
148
|
+
return output || '(no output)';
|
|
149
|
+
})(),
|
|
150
|
+
new Promise<string>((resolve) => {
|
|
151
|
+
setTimeout(() => {
|
|
152
|
+
try { proc.kill(); } catch { /* ignore */ }
|
|
153
|
+
resolve('Error: Command timed out after 30 seconds. If you were trying to start a dev server, use the Sites page Start button instead.');
|
|
154
|
+
}, 30_000);
|
|
155
|
+
}),
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
return result;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'site_git_commit',
|
|
166
|
+
description: 'Stage all changes and commit in the site builder project.',
|
|
167
|
+
category: 'site-builder',
|
|
168
|
+
parameters: {
|
|
169
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
170
|
+
message: { type: 'string', description: 'Commit message', required: true },
|
|
171
|
+
},
|
|
172
|
+
execute: async (params) => {
|
|
173
|
+
const projectPath = projectManager.getProjectPath(params.project_id as string);
|
|
174
|
+
if (!projectPath) return 'Error: Project not found';
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const commit = await gitManager.autoCommit(projectPath, params.message as string);
|
|
178
|
+
if (!commit) return 'Nothing to commit — working tree clean';
|
|
179
|
+
return `Committed: ${commit.shortHash} ${commit.message}`;
|
|
180
|
+
} catch (err) {
|
|
181
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
...(githubManager ? [{
|
|
186
|
+
name: 'site_github_push',
|
|
187
|
+
description: 'Push the current site builder project to GitHub. The project must already be connected to a GitHub repository (via the Git panel). Commits all pending changes before pushing.',
|
|
188
|
+
category: 'site-builder',
|
|
189
|
+
parameters: {
|
|
190
|
+
project_id: { type: 'string', description: 'The project ID', required: true },
|
|
191
|
+
commit_message: { type: 'string', description: 'Optional commit message for any uncommitted changes. If omitted, uncommitted changes are not committed before pushing.', required: false },
|
|
192
|
+
},
|
|
193
|
+
execute: async (params) => {
|
|
194
|
+
const projectPath = projectManager.getProjectPath(params.project_id as string);
|
|
195
|
+
if (!projectPath) return 'Error: Project not found';
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
// Optionally commit pending changes first
|
|
199
|
+
if (params.commit_message) {
|
|
200
|
+
const commit = await gitManager.autoCommit(projectPath, params.commit_message as string);
|
|
201
|
+
if (commit) {
|
|
202
|
+
// continue to push
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const result = await githubManager.push(projectPath);
|
|
207
|
+
if (!result.success) return `Error: ${result.error}`;
|
|
208
|
+
return 'Pushed to GitHub successfully';
|
|
209
|
+
} catch (err) {
|
|
210
|
+
return `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
} as ToolDefinition] : []),
|
|
214
|
+
];
|
|
215
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site Builder — Dev Server Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages spawned `make dev` processes for project previews.
|
|
5
|
+
* Tracks PIDs for cleanup, manages port allocation, health checks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Subprocess } from 'bun';
|
|
9
|
+
import type { SiteBuilderConfig } from './types.ts';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
|
|
13
|
+
type RunningServer = {
|
|
14
|
+
proc: Subprocess;
|
|
15
|
+
port: number;
|
|
16
|
+
projectId: string;
|
|
17
|
+
projectPath: string;
|
|
18
|
+
logs: string[];
|
|
19
|
+
startedAt: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const MAX_LOG_LINES = 500;
|
|
23
|
+
const READY_POLL_INTERVAL = 500;
|
|
24
|
+
const READY_TIMEOUT = 30_000;
|
|
25
|
+
|
|
26
|
+
export class DevServerManager {
|
|
27
|
+
private servers = new Map<string, RunningServer>();
|
|
28
|
+
private allocatedPorts = new Set<number>();
|
|
29
|
+
private portStart: number;
|
|
30
|
+
private portEnd: number;
|
|
31
|
+
private maxConcurrent: number;
|
|
32
|
+
private pidFilePath: string;
|
|
33
|
+
|
|
34
|
+
constructor(config: SiteBuilderConfig) {
|
|
35
|
+
this.portStart = config.port_range_start;
|
|
36
|
+
this.portEnd = config.port_range_end;
|
|
37
|
+
this.maxConcurrent = config.max_concurrent_servers;
|
|
38
|
+
this.pidFilePath = join(config.projects_dir.replace(/^~/, homedir()), '.running-pids.json');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Start a dev server for a project.
|
|
43
|
+
*/
|
|
44
|
+
async start(projectId: string, projectPath: string): Promise<{ port: number; pid: number }> {
|
|
45
|
+
// Already running?
|
|
46
|
+
if (this.servers.has(projectId)) {
|
|
47
|
+
const existing = this.servers.get(projectId)!;
|
|
48
|
+
return { port: existing.port, pid: existing.proc.pid };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check concurrency limit
|
|
52
|
+
if (this.servers.size >= this.maxConcurrent) {
|
|
53
|
+
throw new Error(`Max concurrent servers reached (${this.maxConcurrent}). Stop another project first.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const port = await this.allocatePort();
|
|
57
|
+
|
|
58
|
+
const proc = Bun.spawn(['make', 'dev'], {
|
|
59
|
+
cwd: projectPath,
|
|
60
|
+
stdout: 'pipe',
|
|
61
|
+
stderr: 'pipe',
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
PORT: String(port),
|
|
65
|
+
// Enforce loopback bind — prevent dev servers from listening on 0.0.0.0
|
|
66
|
+
HOST: '127.0.0.1',
|
|
67
|
+
// Dev servers (Vite, Next.js) fail or behave unexpectedly under NODE_ENV=production
|
|
68
|
+
NODE_ENV: 'development',
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const server: RunningServer = {
|
|
73
|
+
proc,
|
|
74
|
+
port,
|
|
75
|
+
projectId,
|
|
76
|
+
projectPath,
|
|
77
|
+
logs: [],
|
|
78
|
+
startedAt: Date.now(),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
this.servers.set(projectId, server);
|
|
82
|
+
|
|
83
|
+
// Pipe stdout/stderr to log buffer
|
|
84
|
+
this.pipeToLogs(server, proc.stdout, 'stdout');
|
|
85
|
+
this.pipeToLogs(server, proc.stderr, 'stderr');
|
|
86
|
+
|
|
87
|
+
// Handle process exit
|
|
88
|
+
proc.exited.then(code => {
|
|
89
|
+
if (this.servers.get(projectId) === server) {
|
|
90
|
+
this.servers.delete(projectId);
|
|
91
|
+
this.releasePort(port);
|
|
92
|
+
console.log(`[SiteBuilder] Dev server for "${projectId}" exited with code ${code}`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Save PIDs for crash recovery
|
|
97
|
+
this.savePids();
|
|
98
|
+
|
|
99
|
+
console.log(`[SiteBuilder] Starting dev server for "${projectId}" on port ${port} (pid ${proc.pid})`);
|
|
100
|
+
|
|
101
|
+
return { port, pid: proc.pid };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stop a running dev server.
|
|
106
|
+
*/
|
|
107
|
+
async stop(projectId: string): Promise<void> {
|
|
108
|
+
const server = this.servers.get(projectId);
|
|
109
|
+
if (!server) return;
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
server.proc.kill();
|
|
113
|
+
// Give it a moment to exit gracefully
|
|
114
|
+
await Promise.race([
|
|
115
|
+
server.proc.exited,
|
|
116
|
+
new Promise(resolve => setTimeout(resolve, 3000)),
|
|
117
|
+
]);
|
|
118
|
+
} catch { /* process may already be dead */ }
|
|
119
|
+
|
|
120
|
+
this.servers.delete(projectId);
|
|
121
|
+
this.releasePort(server.port);
|
|
122
|
+
this.savePids();
|
|
123
|
+
|
|
124
|
+
console.log(`[SiteBuilder] Stopped dev server for "${projectId}"`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Stop all running dev servers.
|
|
129
|
+
*/
|
|
130
|
+
async stopAll(): Promise<void> {
|
|
131
|
+
const ids = Array.from(this.servers.keys());
|
|
132
|
+
await Promise.all(ids.map(id => this.stop(id)));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if a dev server is running.
|
|
137
|
+
*/
|
|
138
|
+
isRunning(projectId: string): boolean {
|
|
139
|
+
return this.servers.has(projectId);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the port for a running project.
|
|
144
|
+
*/
|
|
145
|
+
getPort(projectId: string): number | null {
|
|
146
|
+
return this.servers.get(projectId)?.port ?? null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get recent logs for a project's dev server.
|
|
151
|
+
*/
|
|
152
|
+
getLogs(projectId: string, limit: number = 100): string[] {
|
|
153
|
+
const server = this.servers.get(projectId);
|
|
154
|
+
if (!server) return [];
|
|
155
|
+
return server.logs.slice(-limit);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Wait for the dev server to respond on its port.
|
|
160
|
+
*/
|
|
161
|
+
async waitForReady(port: number, timeoutMs: number = READY_TIMEOUT): Promise<boolean> {
|
|
162
|
+
const start = Date.now();
|
|
163
|
+
while (Date.now() - start < timeoutMs) {
|
|
164
|
+
try {
|
|
165
|
+
const resp = await fetch(`http://127.0.0.1:${port}/`, {
|
|
166
|
+
signal: AbortSignal.timeout(2000),
|
|
167
|
+
});
|
|
168
|
+
if (resp.status < 500) return true;
|
|
169
|
+
} catch { /* server not ready yet */ }
|
|
170
|
+
await Bun.sleep(READY_POLL_INTERVAL);
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get info about all running servers.
|
|
177
|
+
*/
|
|
178
|
+
getRunningServers(): Array<{ projectId: string; port: number; pid: number; startedAt: number }> {
|
|
179
|
+
return Array.from(this.servers.values()).map(s => ({
|
|
180
|
+
projectId: s.projectId,
|
|
181
|
+
port: s.port,
|
|
182
|
+
pid: s.proc.pid,
|
|
183
|
+
startedAt: s.startedAt,
|
|
184
|
+
}));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Port Management ──
|
|
188
|
+
|
|
189
|
+
private async allocatePort(): Promise<number> {
|
|
190
|
+
for (let port = this.portStart; port <= this.portEnd; port++) {
|
|
191
|
+
if (!this.allocatedPorts.has(port)) {
|
|
192
|
+
// Verify the port is actually free on the system
|
|
193
|
+
const available = await this.isPortAvailable(port);
|
|
194
|
+
if (available) {
|
|
195
|
+
this.allocatedPorts.add(port);
|
|
196
|
+
return port;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
throw new Error(`No available ports in range ${this.portStart}-${this.portEnd}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Probe whether a port is free by attempting a TCP connect */
|
|
204
|
+
private isPortAvailable(port: number): Promise<boolean> {
|
|
205
|
+
return new Promise((resolve) => {
|
|
206
|
+
const { createServer } = require('node:net');
|
|
207
|
+
const server = createServer();
|
|
208
|
+
server.once('error', () => resolve(false));
|
|
209
|
+
server.once('listening', () => { server.close(() => resolve(true)); });
|
|
210
|
+
server.listen(port, '127.0.0.1');
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private releasePort(port: number): void {
|
|
215
|
+
this.allocatedPorts.delete(port);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── Log Piping ──
|
|
219
|
+
|
|
220
|
+
private async pipeToLogs(server: RunningServer, stream: ReadableStream<Uint8Array> | null, label: string): Promise<void> {
|
|
221
|
+
if (!stream) return;
|
|
222
|
+
const reader = stream.getReader();
|
|
223
|
+
const decoder = new TextDecoder();
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
while (true) {
|
|
227
|
+
const { done, value } = await reader.read();
|
|
228
|
+
if (done) break;
|
|
229
|
+
|
|
230
|
+
const text = decoder.decode(value, { stream: true });
|
|
231
|
+
const lines = text.split('\n').filter(l => l.trim());
|
|
232
|
+
for (const line of lines) {
|
|
233
|
+
server.logs.push(`[${label}] ${line}`);
|
|
234
|
+
if (server.logs.length > MAX_LOG_LINES) {
|
|
235
|
+
server.logs.shift();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch { /* stream closed */ }
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ── PID Persistence ──
|
|
243
|
+
|
|
244
|
+
private savePids(): void {
|
|
245
|
+
const pids: Record<string, { pid: number; port: number; projectPath: string }> = {};
|
|
246
|
+
for (const [id, server] of this.servers) {
|
|
247
|
+
pids[id] = { pid: server.proc.pid, port: server.port, projectPath: server.projectPath };
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
Bun.write(this.pidFilePath, JSON.stringify(pids, null, 2));
|
|
251
|
+
} catch { /* best effort */ }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Kill any orphaned processes from a previous crash.
|
|
256
|
+
*/
|
|
257
|
+
async cleanupOrphans(): Promise<void> {
|
|
258
|
+
try {
|
|
259
|
+
const file = Bun.file(this.pidFilePath);
|
|
260
|
+
if (!await file.exists()) return;
|
|
261
|
+
|
|
262
|
+
const { readFileSync, unlinkSync } = await import('node:fs');
|
|
263
|
+
|
|
264
|
+
const pids = JSON.parse(await file.text()) as Record<string, { pid: number; port: number }>;
|
|
265
|
+
for (const [id, { pid }] of Object.entries(pids)) {
|
|
266
|
+
try {
|
|
267
|
+
// Verify PID belongs to a Jarvis-spawned process before killing
|
|
268
|
+
const cmdline = readFileSync(`/proc/${pid}/cmdline`, 'utf-8');
|
|
269
|
+
const isMakeDev = cmdline.includes('make') && cmdline.includes('dev');
|
|
270
|
+
const isBunHot = cmdline.includes('bun') && cmdline.includes('--hot');
|
|
271
|
+
const isVite = cmdline.includes('vite');
|
|
272
|
+
const isNext = cmdline.includes('next');
|
|
273
|
+
if (isMakeDev || isBunHot || isVite || isNext) {
|
|
274
|
+
process.kill(pid, 'SIGTERM');
|
|
275
|
+
console.log(`[SiteBuilder] Killed orphaned process ${pid} for project "${id}"`);
|
|
276
|
+
} else {
|
|
277
|
+
console.log(`[SiteBuilder] Skipped PID ${pid} — not a recognized dev server process`);
|
|
278
|
+
}
|
|
279
|
+
} catch { /* process already gone or /proc not available */ }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Clean up the PID file
|
|
283
|
+
try { unlinkSync(this.pidFilePath); } catch { /* ignore */ }
|
|
284
|
+
} catch { /* no PID file or parse error */ }
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Security Test Site (Fixture)
|
|
2
|
+
|
|
3
|
+
Browser-based attack suite for the site builder proxy. Tests iframe sandbox,
|
|
4
|
+
same-origin isolation, WebSocket origin checks, and capability restrictions.
|
|
5
|
+
|
|
6
|
+
## Usage
|
|
7
|
+
|
|
8
|
+
Copy to the Jarvis projects directory, install, and open in the Sites page:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
cp -r src/sites/fixtures/security-test-site ~/.jarvis/projects/security-test
|
|
12
|
+
cd ~/.jarvis/projects/security-test
|
|
13
|
+
bun install
|
|
14
|
+
git init && git add -A && git commit -m "init"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then open the Jarvis dashboard, go to Sites, and start the `security-test` project.
|
|
18
|
+
All 28 tests should show **BLOCKED**.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Site Builder Security Tests</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./src/app.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import index from "./index.html";
|
|
2
|
+
|
|
3
|
+
const port = parseInt(process.env.BUN_PORT || "3000", 10);
|
|
4
|
+
|
|
5
|
+
Bun.serve({
|
|
6
|
+
port,
|
|
7
|
+
routes: {
|
|
8
|
+
"/": index,
|
|
9
|
+
},
|
|
10
|
+
development: {
|
|
11
|
+
hmr: true,
|
|
12
|
+
console: true,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
console.log(`Security test site running on http://localhost:${port}`);
|