agentbox-app 0.1.6
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 +674 -0
- package/README.md +176 -0
- package/bin/agentbox.mjs +103 -0
- package/briefs/adhd-mode.md +5 -0
- package/briefs/message-rules.md +95 -0
- package/briefs/worker.md +230 -0
- package/briefs/writing-rules.md +336 -0
- package/main/account-discovery.mjs +145 -0
- package/main/account-tooling.mjs +261 -0
- package/main/agent-activity.mjs +56 -0
- package/main/agent-files.mjs +422 -0
- package/main/agent-schedule.mjs +254 -0
- package/main/agent-sessions.mjs +435 -0
- package/main/agents.mjs +1095 -0
- package/main/analytics.mjs +206 -0
- package/main/answer-modes.mjs +206 -0
- package/main/approval-prompt-server.mjs +160 -0
- package/main/approvals.mjs +487 -0
- package/main/artifact-path.mjs +144 -0
- package/main/claude-bin.mjs +311 -0
- package/main/claude-input.mjs +117 -0
- package/main/claude-models.mjs +244 -0
- package/main/claude-plan.mjs +148 -0
- package/main/claude-usage.mjs +149 -0
- package/main/code-change.mjs +383 -0
- package/main/code-file.mjs +116 -0
- package/main/codex-account.mjs +146 -0
- package/main/codex-app-server.mjs +796 -0
- package/main/codex-approvals.mjs +712 -0
- package/main/codex-bin.mjs +261 -0
- package/main/codex-compaction.mjs +49 -0
- package/main/codex-models.mjs +194 -0
- package/main/codex-session.mjs +797 -0
- package/main/codex-threads.mjs +268 -0
- package/main/codex-usage-file.mjs +192 -0
- package/main/codex-usage.mjs +149 -0
- package/main/codex-watch.mjs +35 -0
- package/main/codex.mjs +757 -0
- package/main/config.mjs +345 -0
- package/main/copy-selection.mjs +55 -0
- package/main/crash-report.mjs +158 -0
- package/main/demo.mjs +254 -0
- package/main/dev-window.mjs +103 -0
- package/main/doc-file.mjs +99 -0
- package/main/doc-scheme.mjs +126 -0
- package/main/first-run.mjs +190 -0
- package/main/folders.mjs +91 -0
- package/main/fresh-user.mjs +120 -0
- package/main/git-change.mjs +323 -0
- package/main/img-scheme.mjs +90 -0
- package/main/instruction-settings.mjs +205 -0
- package/main/ipc.mjs +1558 -0
- package/main/live-replies.mjs +77 -0
- package/main/machine.mjs +113 -0
- package/main/main.mjs +972 -0
- package/main/menu.mjs +177 -0
- package/main/notify.mjs +155 -0
- package/main/open-source-file.mjs +8 -0
- package/main/project-identity.mjs +149 -0
- package/main/provider-commands.mjs +32 -0
- package/main/rail-note.mjs +74 -0
- package/main/repeats.mjs +261 -0
- package/main/row-label.mjs +185 -0
- package/main/screen-detail.mjs +44 -0
- package/main/serve.mjs +262 -0
- package/main/settings.mjs +912 -0
- package/main/staleness.mjs +76 -0
- package/main/store/chats.mjs +188 -0
- package/main/store/home.mjs +383 -0
- package/main/store/migrate.mjs +40 -0
- package/main/store/paths.mjs +82 -0
- package/main/store/pending-writes.mjs +122 -0
- package/main/store/project-lock.mjs +200 -0
- package/main/store/project.mjs +523 -0
- package/main/store/queues.mjs +31 -0
- package/main/store/work-items.mjs +299 -0
- package/main/store/write-queue.mjs +14 -0
- package/main/store-modules.mjs +23 -0
- package/main/store.mjs +1269 -0
- package/main/supervisor.mjs +6875 -0
- package/main/task-commands.mjs +161 -0
- package/main/task-folders-offthread.mjs +84 -0
- package/main/task-folders-thread.mjs +13 -0
- package/main/task-folders.mjs +409 -0
- package/main/task-remote-control.mjs +65 -0
- package/main/task-terminals.mjs +169 -0
- package/main/terminal-access.mjs +6 -0
- package/main/updater.mjs +240 -0
- package/main/zoom-keys.mjs +54 -0
- package/main/zoom-readout.mjs +20 -0
- package/mcp/agentbox-mcp.sh +34 -0
- package/mcp/core/account.mjs +77 -0
- package/mcp/core/chat.mjs +61 -0
- package/mcp/core/documents.mjs +238 -0
- package/mcp/core/page.mjs +152 -0
- package/mcp/core/products.mjs +110 -0
- package/mcp/core/skills.mjs +126 -0
- package/mcp/core/work.mjs +377 -0
- package/mcp/stdio.mjs +70 -0
- package/mcp/tools.mjs +257 -0
- package/package.json +186 -0
- package/renderer/dist/assets/Geist-Variable-Bj2R_7yk.woff2 +0 -0
- package/renderer/dist/assets/GeistMono-Variable-Dispecij.woff2 +0 -0
- package/renderer/dist/assets/addon-fit-YJmn1quW.js +12 -0
- package/renderer/dist/assets/addon-serialize-CsFQTvP_.js +13 -0
- package/renderer/dist/assets/cel-dusk-CS5I7kJS.webp +0 -0
- package/renderer/dist/assets/cel-dusk-DUslqS6B.webp +0 -0
- package/renderer/dist/assets/cel-dusk-Jt_xVGxp.webp +0 -0
- package/renderer/dist/assets/cel-dusk-sm-BBkV-tWT.webp +0 -0
- package/renderer/dist/assets/frost-haze-CxPSOBEg.webp +0 -0
- package/renderer/dist/assets/frost-haze-sm-C_w21qgd.webp +0 -0
- package/renderer/dist/assets/gouache-valley-ChSu14-t.webp +0 -0
- package/renderer/dist/assets/gouache-valley-d9qasI6k.webp +0 -0
- package/renderer/dist/assets/gouache-valley-sm-CdLjT5Ec.webp +0 -0
- package/renderer/dist/assets/ice-BC8luINc.webp +0 -0
- package/renderer/dist/assets/ice-CP9PrKHo.webp +0 -0
- package/renderer/dist/assets/ice-river-D6ziOhLY.webp +0 -0
- package/renderer/dist/assets/ice-river-UVM2-JY_.webp +0 -0
- package/renderer/dist/assets/ice-river-bErJwYWW.webp +0 -0
- package/renderer/dist/assets/ice-river-sm-Bg1U-SIj.webp +0 -0
- package/renderer/dist/assets/ice-sm-CbVcg3_D.webp +0 -0
- package/renderer/dist/assets/index-CwiDv5bL.css +32 -0
- package/renderer/dist/assets/index-D9hCAVPl.js +684 -0
- package/renderer/dist/assets/lake-C0WaW-6E.webp +0 -0
- package/renderer/dist/assets/lake-CSKjLC5K.webp +0 -0
- package/renderer/dist/assets/lake-ETGcOb25.webp +0 -0
- package/renderer/dist/assets/lake-harbour-BCQRbrHb.webp +0 -0
- package/renderer/dist/assets/lake-harbour-B_wfXTmE.webp +0 -0
- package/renderer/dist/assets/lake-harbour-Te1pEvBh.webp +0 -0
- package/renderer/dist/assets/lake-harbour-sm-Cme_ShEg.webp +0 -0
- package/renderer/dist/assets/lake-sm-CbG72Hbv.webp +0 -0
- package/renderer/dist/assets/lino-coast-CUZHcucI.webp +0 -0
- package/renderer/dist/assets/lino-coast-DP4J7cgd.webp +0 -0
- package/renderer/dist/assets/lino-coast-kOIAffr_.webp +0 -0
- package/renderer/dist/assets/lino-coast-sm-C1Mdbgit.webp +0 -0
- package/renderer/dist/assets/moon-pines-BskgnnXR.webp +0 -0
- package/renderer/dist/assets/moon-pines-CFSIIwt0.webp +0 -0
- package/renderer/dist/assets/moon-pines-PNhdOYww.webp +0 -0
- package/renderer/dist/assets/moon-pines-sm-C6K1NomK.webp +0 -0
- package/renderer/dist/assets/orbit-rings-2XV36Ch3.webp +0 -0
- package/renderer/dist/assets/orbit-rings-C4NrcYGQ.webp +0 -0
- package/renderer/dist/assets/orbit-rings-DGLk07dT.webp +0 -0
- package/renderer/dist/assets/orbit-rings-sm-Dky3YdvL.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-C6f7xnWw.webp +0 -0
- package/renderer/dist/assets/peach-haze-2-sm-Dj_Ez_Sb.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-DQjqMROg.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-Dw71H_bc.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-kOZfm-GM.webp +0 -0
- package/renderer/dist/assets/pixel-harbour-sm-Dturjuky.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C296JVMO.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-C4oMRDSx.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-CqVmQ4eh.webp +0 -0
- package/renderer/dist/assets/pixel-orbit-sm-CHagC_nO.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-C6LYu6uE.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-CIUCEESs.webp +0 -0
- package/renderer/dist/assets/pixel-ridge-sm-BnE0F_w-.webp +0 -0
- package/renderer/dist/assets/riso-hills-7heKVtry.webp +0 -0
- package/renderer/dist/assets/riso-hills-CIjAL3J6.webp +0 -0
- package/renderer/dist/assets/riso-hills-sm-DHsnbCfP.webp +0 -0
- package/renderer/dist/assets/slate-haze-BPWdkNsw.webp +0 -0
- package/renderer/dist/assets/slate-haze-sm-B0Cb5a2W.webp +0 -0
- package/renderer/dist/assets/source-sans-3-cyrillic-ext-wght-normal-DzyfIafT.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-cyrillic-wght-normal-BMDVbyM7.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-greek-ext-wght-normal-BWSLJLk6.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-greek-wght-normal-C9H9m1vD.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-latin-ext-wght-normal-C8iNium2.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-latin-wght-normal-BqRLTx4X.woff2 +0 -0
- package/renderer/dist/assets/source-sans-3-vietnamese-wght-normal-C1uRvKPU.woff2 +0 -0
- package/renderer/dist/assets/valley-haze-BDHPqoJy.webp +0 -0
- package/renderer/dist/assets/valley-haze-sm-CTyjmPAG.webp +0 -0
- package/renderer/dist/assets/vector-dunes-DzzWLdKo.webp +0 -0
- package/renderer/dist/assets/vector-dunes-Yr9NBQUy.webp +0 -0
- package/renderer/dist/assets/vector-dunes-sm-D8MYY6ms.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BCGKN4u4.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-BF-jk5MC.webp +0 -0
- package/renderer/dist/assets/watercolour-mist-sm-Ds1xzTw9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-B1um67Z9.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-DKQdYs32.webp +0 -0
- package/renderer/dist/assets/woodblock-sea-sm-BJzNq8xh.webp +0 -0
- package/renderer/dist/assets/xterm-BT7tBf8A.js +27 -0
- package/renderer/dist/emulator/audio-rules.mjs +74 -0
- package/renderer/dist/emulator/data/compression/README.md +11 -0
- package/renderer/dist/emulator/data/compression/extract7z.js +1 -0
- package/renderer/dist/emulator/data/compression/extractzip.js +1 -0
- package/renderer/dist/emulator/data/compression/libunrar.js +519 -0
- package/renderer/dist/emulator/data/compression/libunrar.wasm +0 -0
- package/renderer/dist/emulator/data/cores/README.md +22 -0
- package/renderer/dist/emulator/data/cores/mgba-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-legacy-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-thread-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/mgba-wasm.data +0 -0
- package/renderer/dist/emulator/data/cores/package.json +65 -0
- package/renderer/dist/emulator/data/cores/reports/mgba.json +1 -0
- package/renderer/dist/emulator/data/emulator.css +1614 -0
- package/renderer/dist/emulator/data/loader.js +170 -0
- package/renderer/dist/emulator/data/localization/README.md +63 -0
- package/renderer/dist/emulator/data/localization/af-FR.json +339 -0
- package/renderer/dist/emulator/data/localization/ar-AR.json +301 -0
- package/renderer/dist/emulator/data/localization/ben-BEN.json +301 -0
- package/renderer/dist/emulator/data/localization/de-GER.json +301 -0
- package/renderer/dist/emulator/data/localization/el-GR.json +301 -0
- package/renderer/dist/emulator/data/localization/en-US.json +339 -0
- package/renderer/dist/emulator/data/localization/es-ES.json +310 -0
- package/renderer/dist/emulator/data/localization/fa-AF.json +310 -0
- package/renderer/dist/emulator/data/localization/hi-HI.json +301 -0
- package/renderer/dist/emulator/data/localization/it-IT.json +302 -0
- package/renderer/dist/emulator/data/localization/ja-JA.json +301 -0
- package/renderer/dist/emulator/data/localization/jv-JV.json +301 -0
- package/renderer/dist/emulator/data/localization/ko-KO.json +336 -0
- package/renderer/dist/emulator/data/localization/pt-BR.json +361 -0
- package/renderer/dist/emulator/data/localization/retroarch.json +617 -0
- package/renderer/dist/emulator/data/localization/ro-RO.json +310 -0
- package/renderer/dist/emulator/data/localization/ru-RU.json +301 -0
- package/renderer/dist/emulator/data/localization/tr-TR.json +310 -0
- package/renderer/dist/emulator/data/localization/vi-VN.json +302 -0
- package/renderer/dist/emulator/data/localization/zh-CN.json +351 -0
- package/renderer/dist/emulator/data/src/GameManager.js +464 -0
- package/renderer/dist/emulator/data/src/compression.js +142 -0
- package/renderer/dist/emulator/data/src/emulator.js +6158 -0
- package/renderer/dist/emulator/data/src/gamepad.js +167 -0
- package/renderer/dist/emulator/data/src/nipplejs.js +1 -0
- package/renderer/dist/emulator/data/src/shaders.js +236 -0
- package/renderer/dist/emulator/data/src/socket.io.min.js +7 -0
- package/renderer/dist/emulator/data/src/storage.js +125 -0
- package/renderer/dist/emulator/data/version.json +5 -0
- package/renderer/dist/emulator/host.js +299 -0
- package/renderer/dist/emulator/index.html +28 -0
- package/renderer/dist/emulator/provenance.json +6 -0
- package/renderer/dist/fixtures/onboard/designs/w-n13/the-first-row.html +1 -0
- package/renderer/dist/index.html +13 -0
- package/scripts/after-install.mjs +59 -0
- package/scripts/approval-server.sh +43 -0
- package/scripts/before-publish.mjs +133 -0
- package/shared/agent-import.d.mts +13 -0
- package/shared/agent-import.mjs +252 -0
- package/shared/agents.d.mts +190 -0
- package/shared/agents.mjs +937 -0
- package/shared/analytics-events.mjs +63 -0
- package/shared/answers.d.mts +12 -0
- package/shared/answers.mjs +80 -0
- package/shared/artifact-keys.d.mts +28 -0
- package/shared/artifact-keys.mjs +140 -0
- package/shared/bridge-map.mjs +128 -0
- package/shared/claude-commands.d.mts +20 -0
- package/shared/claude-commands.generated.mjs +149 -0
- package/shared/claude-commands.mjs +131 -0
- package/shared/claude-models.generated.d.mts +15 -0
- package/shared/claude-models.generated.mjs +26 -0
- package/shared/claude-usage.d.mts +16 -0
- package/shared/claude-usage.mjs +172 -0
- package/shared/codex-commands.d.mts +4 -0
- package/shared/codex-commands.mjs +17 -0
- package/shared/codex-import.d.mts +38 -0
- package/shared/codex-import.mjs +232 -0
- package/shared/codex-modes.mjs +159 -0
- package/shared/codex-usage.mjs +180 -0
- package/shared/contracts.mjs +412 -0
- package/shared/crash-scrub.mjs +182 -0
- package/shared/dashboard.mjs +481 -0
- package/shared/dead-run-trace.mjs +127 -0
- package/shared/demo-world.mjs +415 -0
- package/shared/effort-levels.d.mts +15 -0
- package/shared/effort-levels.mjs +76 -0
- package/shared/engines.d.mts +62 -0
- package/shared/engines.mjs +389 -0
- package/shared/first-run-examples.mjs +60 -0
- package/shared/first-run-line.mjs +149 -0
- package/shared/first-run-practice.d.mts +32 -0
- package/shared/first-run-practice.mjs +254 -0
- package/shared/first-run-shape.mjs +100 -0
- package/shared/fresh-user-home.mjs +189 -0
- package/shared/hook-failure.mjs +22 -0
- package/shared/install-id.mjs +39 -0
- package/shared/instruction-defaults.json +5 -0
- package/shared/library-folder.mjs +28 -0
- package/shared/notify-rules.mjs +120 -0
- package/shared/product-name.d.mts +6 -0
- package/shared/product-name.mjs +144 -0
- package/shared/project-folder-check.d.mts +22 -0
- package/shared/project-folder-check.mjs +152 -0
- package/shared/provider-commands.d.mts +8 -0
- package/shared/provider-commands.mjs +58 -0
- package/shared/rank.d.mts +19 -0
- package/shared/rank.mjs +146 -0
- package/shared/recovery.mjs +26 -0
- package/shared/referenced-files.d.mts +11 -0
- package/shared/referenced-files.mjs +122 -0
- package/shared/repeats.d.mts +45 -0
- package/shared/repeats.mjs +193 -0
- package/shared/schemes.mjs +33 -0
- package/shared/settings-terminal.d.mts +1 -0
- package/shared/settings-terminal.mjs +14 -0
- package/shared/side-build.mjs +57 -0
- package/shared/spawn-trouble.d.mts +8 -0
- package/shared/spawn-trouble.mjs +599 -0
- package/shared/terminal-state.d.mts +1 -0
- package/shared/terminal-state.mjs +58 -0
- package/shared/usage.d.mts +53 -0
- package/shared/usage.mjs +319 -0
- package/shared/work-items.mjs +561 -0
- package/shared/work-lines.mjs +360 -0
- package/worker-permissions.json +6 -0
package/main/serve.mjs
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// THE SECOND FRONT DOOR: the same app, answering a browser tab.
|
|
2
|
+
//
|
|
3
|
+
// Agentbox is two halves. The screen, and the half that starts `claude -p`,
|
|
4
|
+
// reads the store, watches sessions and opens terminals. The second half is
|
|
5
|
+
// 27,000 lines of ordinary Node and has never cared what is drawing.
|
|
6
|
+
//
|
|
7
|
+
// Until now the only way to reach it was Electron's private channel between a
|
|
8
|
+
// window and the app, which a browser tab cannot knock on. This is the other
|
|
9
|
+
// door: the same `registerIpc` surface, the same 78 channels, reachable at
|
|
10
|
+
// http://127.0.0.1:<port> so an ordinary tab can use it.
|
|
11
|
+
//
|
|
12
|
+
// WHAT THIS IS NOT. It is not hosting and it is not the internet. It binds to
|
|
13
|
+
// the loopback address, which is the machine's name for itself, so nothing off
|
|
14
|
+
// this computer can reach it. The token below is what stops another PROGRAM on
|
|
15
|
+
// the same machine driving somebody's agents: a browser will happily let any
|
|
16
|
+
// page make a request to localhost, so the port alone is not a door key.
|
|
17
|
+
//
|
|
18
|
+
// HOW IT REUSES RATHER THAN REPEATS. `registerIpc` takes its three Electron
|
|
19
|
+
// things as an argument, so this file hands it plain Node stand-ins: an
|
|
20
|
+
// `ipcMain` that files each handler in a Map instead of wiring it to a window,
|
|
21
|
+
// an `app` with the two flags and the quit hook it reads, and a `dialog` that
|
|
22
|
+
// says "not here" until the browser folder picker exists. Every channel is then
|
|
23
|
+
// answered by the code the desktop app answers with.
|
|
24
|
+
|
|
25
|
+
import http from 'node:http';
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
import crypto from 'node:crypto';
|
|
29
|
+
import { fileURLToPath } from 'node:url';
|
|
30
|
+
|
|
31
|
+
import { loadConfig } from './config.mjs';
|
|
32
|
+
import { Store } from './store.mjs';
|
|
33
|
+
import { Supervisor } from './supervisor.mjs';
|
|
34
|
+
import { registerIpc } from './ipc.mjs';
|
|
35
|
+
|
|
36
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const repoRoot = path.dirname(here);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The stand-in for Electron's `ipcMain`.
|
|
41
|
+
*
|
|
42
|
+
* It files handlers rather than wiring them, so the http layer can look one up
|
|
43
|
+
* by name. `on` and `handle` are the same thing here: Electron's `on` is the
|
|
44
|
+
* fire-and-forget half, and over http a caller who does not want the answer
|
|
45
|
+
* can simply ignore it.
|
|
46
|
+
*/
|
|
47
|
+
export function collectingIpcMain() {
|
|
48
|
+
const handlers = new Map();
|
|
49
|
+
return {
|
|
50
|
+
handlers,
|
|
51
|
+
handle(channel, fn) { handlers.set(channel, fn); },
|
|
52
|
+
on(channel, fn) { handlers.set(channel, fn); },
|
|
53
|
+
removeHandler(channel) { handlers.delete(channel); },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The stand-in for the window.
|
|
59
|
+
*
|
|
60
|
+
* `registerIpc` pushes to the screen through `window.webContents.send`, seven
|
|
61
|
+
* channels of it. Here each push goes to every open listener instead, which is
|
|
62
|
+
* what the websocket half will hand to the tab.
|
|
63
|
+
*/
|
|
64
|
+
export function broadcastingWindow() {
|
|
65
|
+
const listeners = new Set();
|
|
66
|
+
return {
|
|
67
|
+
listeners,
|
|
68
|
+
isDestroyed: () => false,
|
|
69
|
+
webContents: {
|
|
70
|
+
send(channel, ...args) {
|
|
71
|
+
for (const send of listeners) {
|
|
72
|
+
try { send({ channel, args }); } catch { /* a closed tab is not an error */ }
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The stand-in for `app` and `dialog`.
|
|
81
|
+
*
|
|
82
|
+
* `app` is read for two flags and one quit hook. `isPackaged` is false because
|
|
83
|
+
* this is being run from a checkout or from npm, never from a signed bundle,
|
|
84
|
+
* and the screens that read it use it to decide whether to offer to open a
|
|
85
|
+
* second copy of a packaged app.
|
|
86
|
+
*
|
|
87
|
+
* `dialog` says there is no dialog, and that is all it says. It does NOT mean
|
|
88
|
+
* a tab cannot choose a folder: this process reads the disk perfectly well and
|
|
89
|
+
* `zero:list-folders` walks it, so the screen draws its own picker and comes
|
|
90
|
+
* back with a real absolute path. `noDialog` is the flag that tells it to.
|
|
91
|
+
* See main/folders.mjs.
|
|
92
|
+
*/
|
|
93
|
+
export function nodeHost({ onQuit = () => {} } = {}) {
|
|
94
|
+
const quitHooks = [];
|
|
95
|
+
return {
|
|
96
|
+
ipcMain: collectingIpcMain(),
|
|
97
|
+
app: {
|
|
98
|
+
isPackaged: false,
|
|
99
|
+
getVersion: () => readVersion(),
|
|
100
|
+
on(event, fn) { if (event === 'before-quit') quitHooks.push(fn); },
|
|
101
|
+
quit() { for (const fn of quitHooks) { try { fn({ preventDefault() {} }); } catch {} } onQuit(); },
|
|
102
|
+
},
|
|
103
|
+
dialog: {
|
|
104
|
+
showOpenDialog: async () => ({ canceled: true, noDialog: true, filePaths: [] }),
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readVersion() {
|
|
110
|
+
try {
|
|
111
|
+
return JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')).version ?? '0.0.0';
|
|
112
|
+
} catch {
|
|
113
|
+
return '0.0.0';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Boot the doing half, with no Electron anywhere.
|
|
119
|
+
*
|
|
120
|
+
* Six lines, and they are the same six `main/main.mjs` runs. `analytics` and
|
|
121
|
+
* `updater` are left at the defaults `registerIpc` already has for them: one
|
|
122
|
+
* sends nothing and the other reports that there is nothing to update, which
|
|
123
|
+
* is the truth for a copy run out of npm.
|
|
124
|
+
*/
|
|
125
|
+
export async function bootHeadless({ dataDir = repoRoot, appDir = repoRoot, userDir = dataDir } = {}) {
|
|
126
|
+
const config = loadConfig(dataDir);
|
|
127
|
+
const store = await new Store(config).init();
|
|
128
|
+
const supervisor = new Supervisor(config, store, appDir, dataDir, userDir);
|
|
129
|
+
const window = broadcastingWindow();
|
|
130
|
+
const host = nodeHost();
|
|
131
|
+
const ipc = registerIpc({ store, supervisor, config, window, host });
|
|
132
|
+
return { config, store, supervisor, window, host, ipc, channels: host.ipcMain.handlers };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** A token in the url, because the port alone is not a door key. */
|
|
136
|
+
export const newToken = () => crypto.randomBytes(24).toString('base64url');
|
|
137
|
+
|
|
138
|
+
const MIME = {
|
|
139
|
+
'.html': 'text/html; charset=utf-8',
|
|
140
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
141
|
+
'.css': 'text/css; charset=utf-8',
|
|
142
|
+
'.json': 'application/json; charset=utf-8',
|
|
143
|
+
'.svg': 'image/svg+xml',
|
|
144
|
+
'.png': 'image/png',
|
|
145
|
+
'.webp': 'image/webp',
|
|
146
|
+
'.woff2': 'font/woff2',
|
|
147
|
+
'.map': 'application/json; charset=utf-8',
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Serve one built renderer and one channel endpoint.
|
|
152
|
+
*
|
|
153
|
+
* `POST /api/<channel>` with a json body calls the handler `registerIpc` filed
|
|
154
|
+
* under that name and answers with whatever it returned. That is the whole
|
|
155
|
+
* request half: 78 channels, one route.
|
|
156
|
+
*
|
|
157
|
+
* `listeners` is the Set off `broadcastingWindow`, which is where the pushes
|
|
158
|
+
* going the other way are waiting for somewhere to go.
|
|
159
|
+
*/
|
|
160
|
+
export function createServer({ channels, token, listeners = new Set(), dist = path.join(repoRoot, 'renderer', 'dist') }) {
|
|
161
|
+
return http.createServer(async (req, res) => {
|
|
162
|
+
const url = new URL(req.url, 'http://127.0.0.1');
|
|
163
|
+
|
|
164
|
+
// The token rides in a header on api calls and in the query on the first
|
|
165
|
+
// page load, because a browser cannot put a header on a navigation.
|
|
166
|
+
const given = req.headers['x-agentbox-token'] || url.searchParams.get('token');
|
|
167
|
+
if (given !== token) {
|
|
168
|
+
res.writeHead(403, { 'content-type': 'text/plain' });
|
|
169
|
+
res.end('Wrong or missing token. Use the url the terminal printed.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// The phone line left open. Seven channels talk the other way, main to
|
|
174
|
+
// screen, and every one of them is one-way, so this is an event stream
|
|
175
|
+
// rather than a websocket: the same job with no dependency to add.
|
|
176
|
+
if (url.pathname === '/events') {
|
|
177
|
+
res.writeHead(200, {
|
|
178
|
+
'content-type': 'text/event-stream',
|
|
179
|
+
'cache-control': 'no-cache',
|
|
180
|
+
connection: 'keep-alive',
|
|
181
|
+
});
|
|
182
|
+
res.write('retry: 1000\n\n');
|
|
183
|
+
const send = (message) => res.write(`data: ${JSON.stringify(message)}\n\n`);
|
|
184
|
+
listeners.add(send);
|
|
185
|
+
// A proxy or a sleeping laptop will drop a silent connection, and the
|
|
186
|
+
// screen only finds out when a push goes missing. A comment every twenty
|
|
187
|
+
// seconds keeps it honest and costs nothing.
|
|
188
|
+
const beat = setInterval(() => res.write(': beat\n\n'), 20_000);
|
|
189
|
+
req.on('close', () => { clearInterval(beat); listeners.delete(send); });
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (url.pathname.startsWith('/api/')) {
|
|
194
|
+
const channel = decodeURIComponent(url.pathname.slice('/api/'.length));
|
|
195
|
+
const fn = channels.get(channel);
|
|
196
|
+
if (!fn) {
|
|
197
|
+
res.writeHead(404, { 'content-type': 'application/json' });
|
|
198
|
+
res.end(JSON.stringify({ error: `No channel named ${channel}` }));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
let payload;
|
|
202
|
+
try {
|
|
203
|
+
const body = await readBody(req);
|
|
204
|
+
payload = body ? JSON.parse(body) : undefined;
|
|
205
|
+
} catch {
|
|
206
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
207
|
+
res.end(JSON.stringify({ error: 'Body was not json' }));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
// The first argument is Electron's event object, which the handlers
|
|
212
|
+
// either ignore or read a sender off. Nothing here has a sender.
|
|
213
|
+
const value = await fn({ sender: null }, payload);
|
|
214
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
215
|
+
res.end(JSON.stringify(value ?? null));
|
|
216
|
+
} catch (err) {
|
|
217
|
+
res.writeHead(500, { 'content-type': 'application/json' });
|
|
218
|
+
res.end(JSON.stringify({ error: String(err?.message ?? err) }));
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Anything else is the built screen. An unknown path is index.html, because
|
|
224
|
+
// the screen routes itself.
|
|
225
|
+
const rel = url.pathname === '/' ? '/index.html' : url.pathname;
|
|
226
|
+
const file = path.join(dist, path.normalize(rel).replace(/^(\.\.[/\\])+/, ''));
|
|
227
|
+
if (!file.startsWith(dist)) {
|
|
228
|
+
res.writeHead(403).end('No');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
fs.readFile(file, (err, buf) => {
|
|
232
|
+
if (err) {
|
|
233
|
+
fs.readFile(path.join(dist, 'index.html'), (err2, index) => {
|
|
234
|
+
if (err2) {
|
|
235
|
+
res.writeHead(404, { 'content-type': 'text/plain' });
|
|
236
|
+
res.end(`No built screen at ${dist}. Run: npm run build`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
res.writeHead(200, { 'content-type': MIME['.html'] });
|
|
240
|
+
res.end(index);
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
res.writeHead(200, { 'content-type': MIME[path.extname(file)] ?? 'application/octet-stream' });
|
|
245
|
+
res.end(buf);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function readBody(req) {
|
|
251
|
+
return new Promise((resolve, reject) => {
|
|
252
|
+
let body = '';
|
|
253
|
+
req.on('data', (c) => {
|
|
254
|
+
body += c;
|
|
255
|
+
// A screen never posts anything large; the uploads go through their own
|
|
256
|
+
// channels. This is here so a stuck client cannot eat the process.
|
|
257
|
+
if (body.length > 8 * 1024 * 1024) reject(new Error('Body too large'));
|
|
258
|
+
});
|
|
259
|
+
req.on('end', () => resolve(body));
|
|
260
|
+
req.on('error', reject);
|
|
261
|
+
});
|
|
262
|
+
}
|