@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,37 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createCleanupPlan, buildCleanupScript } from './uninstall.ts';
|
|
4
|
+
|
|
5
|
+
describe('uninstall helpers', () => {
|
|
6
|
+
test('includes managed repo installs under ~/.jarvis', () => {
|
|
7
|
+
const plan = createCleanupPlan(join(process.env.HOME ?? '/tmp', '.jarvis', 'daemon'));
|
|
8
|
+
expect(plan.removablePaths).toContain(join(process.env.HOME ?? '/tmp', '.jarvis'));
|
|
9
|
+
expect(plan.removablePaths).toContain(join(process.env.HOME ?? '/tmp', '.jarvis', 'daemon'));
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('includes bun global installs', () => {
|
|
13
|
+
const plan = createCleanupPlan(join(process.env.HOME ?? '/tmp', '.bun', 'install', 'global', 'node_modules', '@usejarvis', 'brain'));
|
|
14
|
+
expect(plan.removablePaths.some((path) => path.includes(join('.bun', 'install', 'global')))).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('does not remove arbitrary source checkouts', () => {
|
|
18
|
+
const plan = createCleanupPlan('/work/projects/jarvis');
|
|
19
|
+
expect(plan.removablePaths).toContain(join(process.env.HOME ?? '/tmp', '.jarvis'));
|
|
20
|
+
expect(plan.removablePaths).not.toContain('/work/projects/jarvis');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('cleanup script includes package uninstall and wrapper cleanup', () => {
|
|
24
|
+
const script = buildCleanupScript({
|
|
25
|
+
dataDir: '/tmp/.jarvis',
|
|
26
|
+
packageRoot: '/tmp/.jarvis/daemon',
|
|
27
|
+
removablePaths: ['/tmp/.jarvis/daemon', '/tmp/.jarvis'],
|
|
28
|
+
cliWrapperPaths: ['/tmp/bin/jarvis'],
|
|
29
|
+
bunPath: '/usr/bin/bun',
|
|
30
|
+
autostartInstalled: false,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(script).toContain("@usejarvis/brain");
|
|
34
|
+
expect(script).toContain('/tmp/bin/jarvis');
|
|
35
|
+
expect(script).toContain('/usr/bin/bun');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { closeRL, ask, askYesNo, c } from './helpers.ts';
|
|
6
|
+
import { isLocked, releaseLock } from '../daemon/pid.ts';
|
|
7
|
+
import { getAutostartName, isAutostartInstalled, uninstallAutostart } from './autostart.ts';
|
|
8
|
+
|
|
9
|
+
const PACKAGE_ROOT = join(import.meta.dir, '..', '..');
|
|
10
|
+
const JARVIS_HOME = join(homedir(), '.jarvis');
|
|
11
|
+
const GLOBAL_BUN_ROOT = join(homedir(), '.bun', 'install', 'global');
|
|
12
|
+
const CLI_WRAPPER_CANDIDATES = [
|
|
13
|
+
join(homedir(), '.bun', 'bin', 'jarvis'),
|
|
14
|
+
join(homedir(), '.bun', 'bin', 'jarvis.cmd'),
|
|
15
|
+
join(homedir(), '.bun', 'bin', 'jarvis.ps1'),
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
type CleanupPlan = {
|
|
19
|
+
dataDir: string;
|
|
20
|
+
packageRoot: string;
|
|
21
|
+
removablePaths: string[];
|
|
22
|
+
cliWrapperPaths: string[];
|
|
23
|
+
bunPath: string;
|
|
24
|
+
autostartInstalled: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function normalizePath(path: string): string {
|
|
28
|
+
const resolved = resolve(path);
|
|
29
|
+
return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isWithinPath(candidate: string, parent: string): boolean {
|
|
33
|
+
const normalizedCandidate = normalizePath(candidate);
|
|
34
|
+
const normalizedParent = normalizePath(parent);
|
|
35
|
+
return normalizedCandidate === normalizedParent || normalizedCandidate.startsWith(`${normalizedParent}${process.platform === 'win32' ? '\\' : '/'}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function uniqueSortedPaths(paths: string[]): string[] {
|
|
39
|
+
const unique = Array.from(new Set(paths.map((path) => resolve(path))));
|
|
40
|
+
return unique.sort((left, right) => right.length - left.length);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function createCleanupPlan(packageRoot = PACKAGE_ROOT): CleanupPlan {
|
|
44
|
+
const resolvedPackageRoot = resolve(packageRoot);
|
|
45
|
+
const removablePaths = [JARVIS_HOME];
|
|
46
|
+
|
|
47
|
+
if (isWithinPath(resolvedPackageRoot, GLOBAL_BUN_ROOT) || isWithinPath(resolvedPackageRoot, JARVIS_HOME)) {
|
|
48
|
+
removablePaths.push(resolvedPackageRoot);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
dataDir: JARVIS_HOME,
|
|
53
|
+
packageRoot: resolvedPackageRoot,
|
|
54
|
+
removablePaths: uniqueSortedPaths(removablePaths),
|
|
55
|
+
cliWrapperPaths: CLI_WRAPPER_CANDIDATES.filter((path) => existsSync(path)),
|
|
56
|
+
bunPath: Bun.which('bun') ?? 'bun',
|
|
57
|
+
autostartInstalled: isAutostartInstalled(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildCleanupScript(plan: CleanupPlan): string {
|
|
62
|
+
const payload = JSON.stringify({
|
|
63
|
+
removablePaths: plan.removablePaths,
|
|
64
|
+
cliWrapperPaths: plan.cliWrapperPaths,
|
|
65
|
+
bunPath: plan.bunPath,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return `import { rmSync, unlinkSync } from 'node:fs';
|
|
69
|
+
import { spawnSync } from 'node:child_process';
|
|
70
|
+
import { setTimeout as sleep } from 'node:timers/promises';
|
|
71
|
+
|
|
72
|
+
const payload = ${payload};
|
|
73
|
+
|
|
74
|
+
function removePath(path) {
|
|
75
|
+
try {
|
|
76
|
+
rmSync(path, { recursive: true, force: true });
|
|
77
|
+
} catch {}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function unlinkPath(path) {
|
|
81
|
+
try {
|
|
82
|
+
unlinkSync(path);
|
|
83
|
+
} catch {
|
|
84
|
+
removePath(path);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
await sleep(1500);
|
|
89
|
+
|
|
90
|
+
for (const wrapperPath of payload.cliWrapperPaths) {
|
|
91
|
+
unlinkPath(wrapperPath);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
spawnSync(payload.bunPath, ['uninstall', '-g', '@usejarvis/brain'], {
|
|
96
|
+
stdio: 'ignore',
|
|
97
|
+
env: { ...process.env },
|
|
98
|
+
});
|
|
99
|
+
} catch {}
|
|
100
|
+
|
|
101
|
+
for (const target of payload.removablePaths) {
|
|
102
|
+
removePath(target);
|
|
103
|
+
}
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function stopDaemonIfRunning(): Promise<void> {
|
|
108
|
+
const pid = isLocked();
|
|
109
|
+
if (!pid) return;
|
|
110
|
+
|
|
111
|
+
console.log(c.dim(`Stopping daemon (PID ${pid})...`));
|
|
112
|
+
try {
|
|
113
|
+
process.kill(pid, 'SIGTERM');
|
|
114
|
+
|
|
115
|
+
let alive = true;
|
|
116
|
+
for (let i = 0; i < 10; i += 1) {
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
118
|
+
try {
|
|
119
|
+
process.kill(pid, 0);
|
|
120
|
+
} catch {
|
|
121
|
+
alive = false;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (alive) {
|
|
127
|
+
try {
|
|
128
|
+
process.kill(pid, 'SIGKILL');
|
|
129
|
+
} catch {}
|
|
130
|
+
}
|
|
131
|
+
} catch {}
|
|
132
|
+
|
|
133
|
+
releaseLock();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function scheduleCleanup(plan: CleanupPlan): Promise<void> {
|
|
137
|
+
const tempDir = mkdtempSync(join(tmpdir(), 'jarvis-uninstall-'));
|
|
138
|
+
const scriptPath = join(tempDir, 'cleanup.mjs');
|
|
139
|
+
writeFileSync(scriptPath, buildCleanupScript(plan), 'utf-8');
|
|
140
|
+
|
|
141
|
+
const child = spawn(plan.bunPath, [scriptPath], {
|
|
142
|
+
detached: true,
|
|
143
|
+
stdio: 'ignore',
|
|
144
|
+
env: { ...process.env },
|
|
145
|
+
});
|
|
146
|
+
child.unref();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function runUninstallWizard(packageRoot = PACKAGE_ROOT): Promise<void> {
|
|
150
|
+
const plan = createCleanupPlan(packageRoot);
|
|
151
|
+
|
|
152
|
+
console.log(c.red('\nJARVIS Uninstall Wizard\n'));
|
|
153
|
+
console.log('This will remove JARVIS from this machine.');
|
|
154
|
+
console.log(c.dim('\nPlanned removal:'));
|
|
155
|
+
console.log(c.dim(` • Data directory: ${plan.dataDir}`));
|
|
156
|
+
|
|
157
|
+
if (plan.autostartInstalled) {
|
|
158
|
+
console.log(c.dim(` • Autostart: ${getAutostartName()}`));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (plan.cliWrapperPaths.length > 0) {
|
|
162
|
+
for (const wrapperPath of plan.cliWrapperPaths) {
|
|
163
|
+
console.log(c.dim(` • CLI wrapper: ${wrapperPath}`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const target of plan.removablePaths) {
|
|
168
|
+
if (target !== plan.dataDir) {
|
|
169
|
+
console.log(c.dim(` • Managed install: ${target}`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(c.dim('\nSidecars are separate installs and will not be removed.\n'));
|
|
174
|
+
|
|
175
|
+
const proceed = await askYesNo('Continue with complete uninstall?', false);
|
|
176
|
+
if (!proceed) {
|
|
177
|
+
console.log(c.yellow('\nUninstall cancelled.'));
|
|
178
|
+
closeRL();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const confirmation = await ask('Type UNINSTALL to confirm');
|
|
183
|
+
if (confirmation !== 'UNINSTALL') {
|
|
184
|
+
console.log(c.yellow('\nConfirmation did not match. Uninstall cancelled.'));
|
|
185
|
+
closeRL();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
closeRL();
|
|
190
|
+
|
|
191
|
+
await stopDaemonIfRunning();
|
|
192
|
+
|
|
193
|
+
if (plan.autostartInstalled) {
|
|
194
|
+
console.log(c.dim(`Removing ${getAutostartName()}...`));
|
|
195
|
+
await uninstallAutostart();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
await scheduleCleanup(plan);
|
|
199
|
+
|
|
200
|
+
console.log(c.green('\nJARVIS uninstall scheduled.'));
|
|
201
|
+
console.log(c.dim('Background cleanup will finish in a moment and remove the CLI wrapper.'));
|
|
202
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# J.A.R.V.I.S. Communication Layer
|
|
2
|
+
|
|
3
|
+
The communication layer provides multi-channel messaging, WebSocket-based real-time communication, LLM response streaming, and voice I/O capabilities.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
comms/
|
|
9
|
+
├── websocket.ts # Local WebSocket server using Bun.serve()
|
|
10
|
+
├── streaming.ts # LLM stream relay to WebSocket clients
|
|
11
|
+
├── voice.ts # STT/TTS provider interfaces and stubs
|
|
12
|
+
├── channels/ # Multi-platform messaging adapters
|
|
13
|
+
│ ├── telegram.ts # ✅ Telegram Bot API (fully functional)
|
|
14
|
+
│ ├── whatsapp.ts # 🚧 WhatsApp Business API (stub)
|
|
15
|
+
│ ├── discord.ts # 🚧 Discord Bot (stub)
|
|
16
|
+
│ └── signal.ts # 🚧 Signal CLI (stub)
|
|
17
|
+
└── index.ts # ChannelManager and exports
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Core Components
|
|
21
|
+
|
|
22
|
+
### 1. WebSocket Server
|
|
23
|
+
|
|
24
|
+
Real-time bidirectional communication with web clients.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { WebSocketServer } from './comms';
|
|
28
|
+
|
|
29
|
+
const wsServer = new WebSocketServer(3142);
|
|
30
|
+
|
|
31
|
+
wsServer.setHandler({
|
|
32
|
+
async onMessage(msg) {
|
|
33
|
+
console.log('Received:', msg.type, msg.payload);
|
|
34
|
+
return { type: 'status', payload: 'Acknowledged', timestamp: Date.now() };
|
|
35
|
+
},
|
|
36
|
+
onConnect() {
|
|
37
|
+
console.log('Client connected');
|
|
38
|
+
},
|
|
39
|
+
onDisconnect() {
|
|
40
|
+
console.log('Client disconnected');
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
wsServer.start();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Endpoints:**
|
|
48
|
+
- `ws://localhost:3142/ws` - WebSocket connection
|
|
49
|
+
- `http://localhost:3142/health` - Health check
|
|
50
|
+
- `http://localhost:3142/` - Server info
|
|
51
|
+
|
|
52
|
+
**Message Types:**
|
|
53
|
+
```typescript
|
|
54
|
+
type WSMessage = {
|
|
55
|
+
type: 'chat' | 'command' | 'status' | 'stream' | 'error';
|
|
56
|
+
payload: unknown;
|
|
57
|
+
id?: string;
|
|
58
|
+
timestamp: number;
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Stream Relay
|
|
63
|
+
|
|
64
|
+
Relays LLM streaming responses to connected WebSocket clients in real-time.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { StreamRelay, WebSocketServer } from './comms';
|
|
68
|
+
|
|
69
|
+
const wsServer = new WebSocketServer();
|
|
70
|
+
wsServer.start();
|
|
71
|
+
|
|
72
|
+
const relay = new StreamRelay(wsServer);
|
|
73
|
+
|
|
74
|
+
// Relay LLM stream to all connected clients
|
|
75
|
+
const stream = llmProvider.generateStream(prompt);
|
|
76
|
+
const fullResponse = await relay.relayStream(stream, 'request-123');
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Clients receive incremental updates:
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"type": "stream",
|
|
83
|
+
"payload": {
|
|
84
|
+
"text": "chunk of text",
|
|
85
|
+
"requestId": "request-123",
|
|
86
|
+
"accumulated": "full text so far"
|
|
87
|
+
},
|
|
88
|
+
"timestamp": 1708645200000
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 3. Channel Manager
|
|
93
|
+
|
|
94
|
+
Unified interface for managing multiple messaging platforms.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { ChannelManager, TelegramAdapter } from './comms';
|
|
98
|
+
|
|
99
|
+
const manager = new ChannelManager();
|
|
100
|
+
|
|
101
|
+
// Register Telegram
|
|
102
|
+
const telegram = new TelegramAdapter(process.env.TELEGRAM_BOT_TOKEN!);
|
|
103
|
+
manager.register(telegram);
|
|
104
|
+
|
|
105
|
+
// Set unified message handler
|
|
106
|
+
manager.setHandler(async (message) => {
|
|
107
|
+
console.log(`[${message.channel}] ${message.from}: ${message.text}`);
|
|
108
|
+
return `Received: ${message.text}`;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Connect all channels
|
|
112
|
+
await manager.connectAll();
|
|
113
|
+
|
|
114
|
+
// Check status
|
|
115
|
+
console.log(manager.getStatus()); // { telegram: true }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 4. Telegram Adapter (Functional)
|
|
119
|
+
|
|
120
|
+
Full implementation using Telegram Bot API with long polling.
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { TelegramAdapter } from './comms/channels/telegram';
|
|
124
|
+
|
|
125
|
+
const telegram = new TelegramAdapter(process.env.TELEGRAM_BOT_TOKEN!);
|
|
126
|
+
|
|
127
|
+
telegram.onMessage(async (message) => {
|
|
128
|
+
console.log(`Message from ${message.from}: ${message.text}`);
|
|
129
|
+
return `Echo: ${message.text}`;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await telegram.connect();
|
|
133
|
+
|
|
134
|
+
// Send message
|
|
135
|
+
await telegram.sendMessage('CHAT_ID', 'Hello from J.A.R.V.I.S.!');
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Setup:**
|
|
139
|
+
1. Create bot via [@BotFather](https://t.me/botfather)
|
|
140
|
+
2. Get bot token
|
|
141
|
+
3. Set `TELEGRAM_BOT_TOKEN` environment variable
|
|
142
|
+
|
|
143
|
+
### 5. Voice I/O (Stubs)
|
|
144
|
+
|
|
145
|
+
Speech-to-Text and Text-to-Speech provider interfaces.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { WhisperSTT, LocalTTS } from './comms';
|
|
149
|
+
|
|
150
|
+
// STT - requires whisper.cpp setup
|
|
151
|
+
const stt = new WhisperSTT('http://localhost:8080');
|
|
152
|
+
// const text = await stt.transcribe(audioBuffer);
|
|
153
|
+
|
|
154
|
+
// TTS - requires ElevenLabs API or local TTS
|
|
155
|
+
const tts = new LocalTTS({ provider: 'elevenlabs', apiKey: '...' });
|
|
156
|
+
// const audio = await tts.synthesize('Hello world');
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Message Flow
|
|
160
|
+
|
|
161
|
+
### Incoming Message Flow
|
|
162
|
+
```
|
|
163
|
+
Telegram/Discord/etc
|
|
164
|
+
↓
|
|
165
|
+
ChannelAdapter.onMessage()
|
|
166
|
+
↓
|
|
167
|
+
ChannelHandler (unified)
|
|
168
|
+
↓
|
|
169
|
+
Business logic (in daemon)
|
|
170
|
+
↓
|
|
171
|
+
WebSocketServer.broadcast()
|
|
172
|
+
↓
|
|
173
|
+
All connected web clients
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### LLM Streaming Flow
|
|
177
|
+
```
|
|
178
|
+
LLM Provider stream
|
|
179
|
+
↓
|
|
180
|
+
StreamRelay.relayStream()
|
|
181
|
+
↓
|
|
182
|
+
WebSocketServer.broadcast()
|
|
183
|
+
↓
|
|
184
|
+
Real-time updates to clients
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Running Examples
|
|
188
|
+
|
|
189
|
+
### Start WebSocket Server Only
|
|
190
|
+
```bash
|
|
191
|
+
bun run src/comms/example.ts
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### With Telegram Integration
|
|
195
|
+
```bash
|
|
196
|
+
export TELEGRAM_BOT_TOKEN="your_bot_token"
|
|
197
|
+
bun run src/comms/example.ts
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Run Tests
|
|
201
|
+
```bash
|
|
202
|
+
bun test src/comms/websocket.test.ts
|
|
203
|
+
bun test src/comms/channels.test.ts
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Environment Variables
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Required for Telegram
|
|
210
|
+
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
|
|
211
|
+
|
|
212
|
+
# Future integrations
|
|
213
|
+
WHATSAPP_PHONE_ID=...
|
|
214
|
+
WHATSAPP_ACCESS_TOKEN=...
|
|
215
|
+
DISCORD_BOT_TOKEN=...
|
|
216
|
+
SIGNAL_PHONE=+1234567890
|
|
217
|
+
ELEVENLABS_API_KEY=...
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Channel Adapter Interface
|
|
221
|
+
|
|
222
|
+
All channel adapters implement this interface:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
interface ChannelAdapter {
|
|
226
|
+
name: string;
|
|
227
|
+
connect(): Promise<void>;
|
|
228
|
+
disconnect(): Promise<void>;
|
|
229
|
+
sendMessage(to: string, text: string): Promise<void>;
|
|
230
|
+
onMessage(handler: ChannelHandler): void;
|
|
231
|
+
isConnected(): boolean;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
type ChannelMessage = {
|
|
235
|
+
id: string;
|
|
236
|
+
channel: string;
|
|
237
|
+
from: string;
|
|
238
|
+
text: string;
|
|
239
|
+
timestamp: number;
|
|
240
|
+
metadata: Record<string, unknown>;
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
type ChannelHandler = (message: ChannelMessage) => Promise<string>;
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Future Channel Implementations
|
|
247
|
+
|
|
248
|
+
### WhatsApp
|
|
249
|
+
- Requires WhatsApp Business API setup
|
|
250
|
+
- Webhook-based architecture
|
|
251
|
+
- See: https://developers.facebook.com/docs/whatsapp/cloud-api
|
|
252
|
+
|
|
253
|
+
### Discord
|
|
254
|
+
- Use Discord Gateway WebSocket or discord.js
|
|
255
|
+
- Support slash commands
|
|
256
|
+
- See: https://discord.com/developers/docs
|
|
257
|
+
|
|
258
|
+
### Signal
|
|
259
|
+
- Requires signal-cli in daemon mode
|
|
260
|
+
- Local installation needed
|
|
261
|
+
- See: https://github.com/AsamK/signal-cli
|
|
262
|
+
|
|
263
|
+
## Integration with J.A.R.V.I.S. Daemon
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// In daemon/index.ts
|
|
267
|
+
import { WebSocketServer, ChannelManager, TelegramAdapter, StreamRelay } from './comms';
|
|
268
|
+
import { LLMRouter } from './llm';
|
|
269
|
+
|
|
270
|
+
const wsServer = new WebSocketServer();
|
|
271
|
+
const channels = new ChannelManager();
|
|
272
|
+
const streamRelay = new StreamRelay(wsServer);
|
|
273
|
+
const llmRouter = new LLMRouter();
|
|
274
|
+
|
|
275
|
+
// Unified message handler
|
|
276
|
+
channels.setHandler(async (message) => {
|
|
277
|
+
// Route to appropriate LLM provider
|
|
278
|
+
const stream = await llmRouter.route(message.text, { stream: true });
|
|
279
|
+
|
|
280
|
+
// Relay stream to WebSocket clients
|
|
281
|
+
const response = await streamRelay.relayStream(stream, message.id);
|
|
282
|
+
|
|
283
|
+
// Return response to original channel
|
|
284
|
+
return response;
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Start everything
|
|
288
|
+
wsServer.start();
|
|
289
|
+
if (process.env.TELEGRAM_BOT_TOKEN) {
|
|
290
|
+
channels.register(new TelegramAdapter(process.env.TELEGRAM_BOT_TOKEN));
|
|
291
|
+
}
|
|
292
|
+
await channels.connectAll();
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Performance Considerations
|
|
296
|
+
|
|
297
|
+
- **WebSocket**: Bun's native WebSocket support is extremely fast
|
|
298
|
+
- **Telegram Polling**: Uses long polling (30s timeout) to minimize requests
|
|
299
|
+
- **Stream Relay**: Zero-copy broadcast to multiple clients
|
|
300
|
+
- **Memory**: Each WebSocket connection uses ~1KB of memory
|
|
301
|
+
|
|
302
|
+
## Security Notes
|
|
303
|
+
|
|
304
|
+
- **WebSocket**: Currently no authentication - add JWT or session tokens for production
|
|
305
|
+
- **Channel Tokens**: Store in environment variables, never commit
|
|
306
|
+
- **Rate Limiting**: Implement on message handlers to prevent abuse
|
|
307
|
+
- **Input Validation**: Always sanitize user input before processing
|
|
308
|
+
|
|
309
|
+
## Troubleshooting
|
|
310
|
+
|
|
311
|
+
### WebSocket Connection Failed
|
|
312
|
+
- Check port 3142 is not in use: `lsof -i :3142`
|
|
313
|
+
- Verify firewall allows connections
|
|
314
|
+
- Check CORS if connecting from browser
|
|
315
|
+
|
|
316
|
+
### Telegram Not Receiving Messages
|
|
317
|
+
- Verify bot token is correct
|
|
318
|
+
- Check bot is not blocked
|
|
319
|
+
- Ensure polling is active: check logs for "Starting polling"
|
|
320
|
+
- Test with `/health` endpoint
|
|
321
|
+
|
|
322
|
+
### Stream Not Broadcasting
|
|
323
|
+
- Verify WebSocket clients are connected: check `/health`
|
|
324
|
+
- Ensure LLM stream implements AsyncIterable<LLMStreamEvent>
|
|
325
|
+
- Check client WebSocket listeners are set up
|
|
326
|
+
|
|
327
|
+
## API Reference
|
|
328
|
+
|
|
329
|
+
See inline TypeScript documentation in each module for detailed API reference.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<!--
|
|
5
|
+
Auth error page — if ?token= is inside the hash (e.g. /#/settings?token=xxx),
|
|
6
|
+
move it into the query string so the server can set the cookie via Set-Cookie + 302.
|
|
7
|
+
-->
|
|
8
|
+
<script>
|
|
9
|
+
(function () {
|
|
10
|
+
var h = location.hash;
|
|
11
|
+
var qi = h.indexOf('?');
|
|
12
|
+
if (qi !== -1) {
|
|
13
|
+
var hp = new URLSearchParams(h.slice(qi));
|
|
14
|
+
var t = hp.get('token');
|
|
15
|
+
if (t) {
|
|
16
|
+
hp.delete('token');
|
|
17
|
+
var cleanHash = h.slice(0, qi);
|
|
18
|
+
var remaining = hp.toString();
|
|
19
|
+
if (remaining) cleanHash += '?' + remaining;
|
|
20
|
+
location.replace(location.pathname + '?token=' + encodeURIComponent(t) + cleanHash);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
</script>
|
|
25
|
+
<title>Unauthorized</title>
|
|
26
|
+
<style>
|
|
27
|
+
body {
|
|
28
|
+
font-family: system-ui;
|
|
29
|
+
display: flex;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
align-items: center;
|
|
32
|
+
height: 100vh;
|
|
33
|
+
margin: 0;
|
|
34
|
+
background: #111;
|
|
35
|
+
color: #ccc;
|
|
36
|
+
}
|
|
37
|
+
.box { text-align: center }
|
|
38
|
+
h1 { font-size: 4rem; margin: 0; color: #666 }
|
|
39
|
+
p { margin-top: 1rem }
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<div class="box">
|
|
44
|
+
<h1>401</h1>
|
|
45
|
+
<p>Access requires a valid token.</p>
|
|
46
|
+
</div>
|
|
47
|
+
</body>
|
|
48
|
+
</html>
|