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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// A SECOND ACCOUNT GETS YOUR SKILLS, COMMANDS AND AGENTS.
|
|
2
|
+
//
|
|
3
|
+
// THE PROBLEM THIS EXISTS FOR, and it is one we ship rather than one a person
|
|
4
|
+
// causes. The Accounts page in Settings tells a person to add a second
|
|
5
|
+
// subscription by logging in with CLAUDE_CONFIG_DIR set to a brand new folder.
|
|
6
|
+
// Do exactly that and the folder holds a login and nothing else, because
|
|
7
|
+
// Claude Code reads a session's skills, commands and subagents out of whichever
|
|
8
|
+
// folder CLAUDE_CONFIG_DIR names. The supervisor then round-robins every spawn
|
|
9
|
+
// across the accounts, so from that moment half the person's workers run with
|
|
10
|
+
// none of their own tooling and nothing anywhere says so. Measured on the one
|
|
11
|
+
// machine where it had happened: 65 skills on the first login, 12 on the
|
|
12
|
+
// second, and 445 of 874 sessions had already run on the empty one.
|
|
13
|
+
//
|
|
14
|
+
// THE REPAIR IS THREE SYMLINKS, made the first time a worker spawns on an
|
|
15
|
+
// account that is missing them:
|
|
16
|
+
//
|
|
17
|
+
// <account folder>/skills -> ~/.claude/skills
|
|
18
|
+
// <account folder>/commands -> ~/.claude/commands
|
|
19
|
+
// <account folder>/agents -> ~/.claude/agents
|
|
20
|
+
//
|
|
21
|
+
// Links rather than copies, so a person edits a skill once and every account
|
|
22
|
+
// has it. Claude Code 2.1.236 follows them; that was verified on her machine
|
|
23
|
+
// rather than assumed.
|
|
24
|
+
//
|
|
25
|
+
// WHAT IS DELIBERATELY NOT IN THAT LIST. Plugins. They carry their own install
|
|
26
|
+
// records and two of the ones on this machine want their own login, so sharing
|
|
27
|
+
// that folder is a different and riskier change and it should be its own
|
|
28
|
+
// decision. Settings, credentials, history, projects and todos are per account
|
|
29
|
+
// on purpose: a shared `.credentials.json` or `settings.json` would be two
|
|
30
|
+
// subscriptions fighting over one file, which is the opposite of the point.
|
|
31
|
+
//
|
|
32
|
+
// WHAT THIS WILL NOT DO, in every case leaving the folder exactly as it found
|
|
33
|
+
// it: overwrite anything. If a name is already taken, by a real directory of
|
|
34
|
+
// their own, a file, or a link pointing somewhere else, it is left alone and
|
|
35
|
+
// reported as theirs. Someone who copied their commands across by hand keeps
|
|
36
|
+
// the copy.
|
|
37
|
+
|
|
38
|
+
import fs from 'node:fs';
|
|
39
|
+
import os from 'node:os';
|
|
40
|
+
import path from 'node:path';
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The three folders a session reads its own tooling out of, and the only
|
|
44
|
+
* three. See the note above for why `plugins` is not here. */
|
|
45
|
+
export const SHARED_DIRS = ['skills', 'commands', 'agents'];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Where the first login lives: the home folder Claude Code uses when nothing
|
|
49
|
+
* sets CLAUDE_CONFIG_DIR, which is the account every one of these links
|
|
50
|
+
* points back at. */
|
|
51
|
+
export function defaultClaudeHome(home = os.homedir()) {
|
|
52
|
+
return path.join(home, '.claude');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* A FOLDER FOR THE NEXT CLAUDE LOGIN, MADE BEFORE ANYTHING IS ASKED TO USE IT.
|
|
57
|
+
*
|
|
58
|
+
* The twin of `makeCodexHome`, and it exists for the same reason that one does:
|
|
59
|
+
* a second login is a second home folder, and the app should make it rather
|
|
60
|
+
* than print a line telling somebody to make it themselves.
|
|
61
|
+
*
|
|
62
|
+
* THE NAME IS COUNTED, NEVER GUESSED. `~/.claude-2` and up, skipping anything
|
|
63
|
+
* that exists, so pressing Add twice cannot hand the second login the first
|
|
64
|
+
* one's folder and sign over a working account. The `.claude-` shape is also
|
|
65
|
+
* exactly what main/account-discovery.mjs looks for, so a folder made here is
|
|
66
|
+
* found again on its own even if the config entry is later removed by hand.
|
|
67
|
+
*
|
|
68
|
+
* Claude Code will create a config directory it is pointed at, unlike
|
|
69
|
+
* codex-cli, so this is not strictly required to make the sign in work. It is
|
|
70
|
+
* still made here: the row for the new account appears the moment she presses,
|
|
71
|
+
* and a row for a folder that does not exist yet would be a screen describing
|
|
72
|
+
* something that is not there.
|
|
73
|
+
*
|
|
74
|
+
* @returns {{ home: string, profile: string }} `profile` is the word the fleet
|
|
75
|
+
* spawns on, which for every home but the first one IS the path.
|
|
76
|
+
*/
|
|
77
|
+
export function makeClaudeHome({ home = os.homedir(), mkdir = fs.mkdirSync, exists = fs.existsSync } = {}) {
|
|
78
|
+
for (let n = 2; n < 100; n += 1) {
|
|
79
|
+
const dir = path.join(home, `.claude-${n}`);
|
|
80
|
+
if (exists(dir)) continue;
|
|
81
|
+
mkdir(dir, { recursive: true });
|
|
82
|
+
return { home: dir, profile: dir };
|
|
83
|
+
}
|
|
84
|
+
throw new Error('There are already a lot of Claude folders in your home directory.');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The one line that signs a new Claude account in, for the terminal to run.
|
|
89
|
+
*
|
|
90
|
+
* `claude auth login` is their own sign-in command and this app has no
|
|
91
|
+
* business driving the browser half of it. The folder is spelled out in full
|
|
92
|
+
* rather than left as `~`, because `~` inside a variable assignment is not
|
|
93
|
+
* expanded by every shell the same way. */
|
|
94
|
+
export function claudeLoginCommand(home) {
|
|
95
|
+
return `CLAUDE_CONFIG_DIR=${home} claude auth login`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function realOrNull(p) {
|
|
99
|
+
try { return fs.realpathSync(p); } catch { return null; }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* WHAT IS ALREADY AT THIS NAME, without following the link, because a link is
|
|
104
|
+
* precisely the thing we must be able to see. Returns 'none' when the name is
|
|
105
|
+
* free, 'ours' when it is already the link this module would have made, and
|
|
106
|
+
* 'theirs' for anything else at all. */
|
|
107
|
+
function occupancy(target, source) {
|
|
108
|
+
let st;
|
|
109
|
+
try { st = fs.lstatSync(target); } catch { return 'none'; }
|
|
110
|
+
if (!st.isSymbolicLink()) return 'theirs';
|
|
111
|
+
const to = realOrNull(target);
|
|
112
|
+
const from = realOrNull(source);
|
|
113
|
+
return to && from && to === from ? 'ours' : 'theirs';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* MAKE THE LINKS FOR ONE ACCOUNT. `dir` is the folder that account's
|
|
118
|
+
* CLAUDE_CONFIG_DIR names.
|
|
119
|
+
*
|
|
120
|
+
* Returns what happened, in the four kinds a person could be told about:
|
|
121
|
+
* `linked` are the ones this call created, `already` were there and correct,
|
|
122
|
+
* `theirs` are names that were taken and were left untouched, and `missing`
|
|
123
|
+
* are folders the first login does not have either, which is not a fault and
|
|
124
|
+
* is why nothing is created for them.
|
|
125
|
+
*
|
|
126
|
+
* `skipped` is set instead when there was nothing to do at all, and is one of:
|
|
127
|
+
* 'default' (the first login, which is the source), 'same' (an account folder
|
|
128
|
+
* that IS the first login by another path), or 'no-dir' (a folder that does
|
|
129
|
+
* not exist, which means the account is misconfigured and making a home for it
|
|
130
|
+
* would only hide that). */
|
|
131
|
+
export function linkAccountTooling(dir, { home = os.homedir(), source = null } = {}) {
|
|
132
|
+
const src = source ?? defaultClaudeHome(home);
|
|
133
|
+
const out = { dir, linked: [], already: [], theirs: [], missing: [], skipped: null };
|
|
134
|
+
if (!dir || dir === 'default') return { ...out, skipped: 'default' };
|
|
135
|
+
|
|
136
|
+
const real = realOrNull(dir);
|
|
137
|
+
if (!real) return { ...out, skipped: 'no-dir' };
|
|
138
|
+
try { if (!fs.statSync(real).isDirectory()) return { ...out, skipped: 'no-dir' }; } catch { return { ...out, skipped: 'no-dir' }; }
|
|
139
|
+
// An account pointed at ~/.claude by a second path is the first login. Linking
|
|
140
|
+
// a folder into itself would make skills/skills/skills and there is a real way
|
|
141
|
+
// to arrive here: a symlinked home, or `authProfiles: ["~/.claude"]`.
|
|
142
|
+
if (real === realOrNull(src)) return { ...out, skipped: 'same' };
|
|
143
|
+
|
|
144
|
+
for (const name of SHARED_DIRS) {
|
|
145
|
+
const from = path.join(src, name);
|
|
146
|
+
if (!realOrNull(from)) { out.missing.push(name); continue; }
|
|
147
|
+
const to = path.join(real, name);
|
|
148
|
+
const held = occupancy(to, from);
|
|
149
|
+
if (held === 'ours') { out.already.push(name); continue; }
|
|
150
|
+
if (held === 'theirs') { out.theirs.push(name); continue; }
|
|
151
|
+
try {
|
|
152
|
+
fs.symlinkSync(from, to, 'dir');
|
|
153
|
+
out.linked.push(name);
|
|
154
|
+
} catch {
|
|
155
|
+
// A read-only folder, a race with another Agentbox, a permission the app
|
|
156
|
+
// does not have. The account still runs; it just runs the way it did
|
|
157
|
+
// before this module existed, so this is reported and never thrown.
|
|
158
|
+
out.theirs.push(name);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* ONE LINE FOR THE TRACE, in the words she would use, or null when nothing
|
|
166
|
+
* changed and there is therefore nothing to say. Silence is the ordinary case:
|
|
167
|
+
* after the first spawn on an account every later one finds the links already
|
|
168
|
+
* made. */
|
|
169
|
+
export function toolingLine(result) {
|
|
170
|
+
if (!result || !result.linked.length) return null;
|
|
171
|
+
const where = path.basename(result.dir);
|
|
172
|
+
return `Gave the ${where} account your ${list(result.linked)}.`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function list(names) {
|
|
176
|
+
if (names.length === 1) return names[0];
|
|
177
|
+
return `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* ------------------------- WHOSE LOGIN IS IN THERE ------------------------ */
|
|
181
|
+
// WHICH CLAUDE ACCOUNT A FOLDER IS ACTUALLY SIGNED IN AS.
|
|
182
|
+
//
|
|
183
|
+
// The Accounts page named its two rows `default` and `.claude-second`, which
|
|
184
|
+
// are folders, not people. So there was no way to tell from the screen which
|
|
185
|
+
// subscription either row was running on, and on 2026-08-29 that cost her the
|
|
186
|
+
// second one. The page said the second account was signed out. She did exactly
|
|
187
|
+
// what it told her to do, ran the command and typed /login at 1:47pm, and the
|
|
188
|
+
// browser was already signed in as her personal account, so the login handed
|
|
189
|
+
// that one back. Her work account, work@example.com, had been in that folder
|
|
190
|
+
// since at least 08-23 and was gone by 1:50pm, replaced by the same account the
|
|
191
|
+
// first row was already using. Measured off the config backups the CLI itself
|
|
192
|
+
// writes. Nothing on the page changed when it happened, because nothing on the
|
|
193
|
+
// page had ever said whose account it was, which is also exactly what she
|
|
194
|
+
// reported: "Idk which is logged in nothing changed in settings".
|
|
195
|
+
//
|
|
196
|
+
// One line of the answer, read from the file Claude Code keeps its own account
|
|
197
|
+
// in. Null when the folder holds no login, which reads on the page as the one
|
|
198
|
+
// honest thing we can say about it.
|
|
199
|
+
const identityCache = new Map();
|
|
200
|
+
|
|
201
|
+
export function accountIdentity(dir, { home = os.homedir() } = {}) {
|
|
202
|
+
const file = path.join(dir || defaultClaudeHome(home), '.claude.json');
|
|
203
|
+
// Cached on the file's own mtime rather than on a timer. That file is ~120 KB
|
|
204
|
+
// on this machine and the settings page re-reads on every poll, so parsing it
|
|
205
|
+
// each time would be real work for an answer that changes about twice a
|
|
206
|
+
// month; keying on mtime means a /login is still seen the moment it lands.
|
|
207
|
+
let mtime = 0;
|
|
208
|
+
try { mtime = fs.statSync(file).mtimeMs; } catch { return null; }
|
|
209
|
+
const hit = identityCache.get(file);
|
|
210
|
+
if (hit && hit.mtime === mtime) return hit.value;
|
|
211
|
+
|
|
212
|
+
let value = null;
|
|
213
|
+
try {
|
|
214
|
+
const acct = JSON.parse(fs.readFileSync(file, 'utf8'))?.oauthAccount;
|
|
215
|
+
if (acct?.emailAddress) {
|
|
216
|
+
value = { email: acct.emailAddress, accountUuid: acct.accountUuid ?? null };
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
// A half-written file, or one Claude Code has changed the shape of. The
|
|
220
|
+
// page falls back to naming the folder, which is what it did before this
|
|
221
|
+
// existed, so a bad read costs the extra sentence and nothing else.
|
|
222
|
+
}
|
|
223
|
+
identityCache.set(file, { mtime, value });
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* THE SENTENCE FOR TWO ROWS THAT TURNED OUT TO BE ONE ACCOUNT. It only ever
|
|
229
|
+
* speaks on a machine that already has two profile folders, so it describes a
|
|
230
|
+
* setup rather than suggesting one.
|
|
231
|
+
*
|
|
232
|
+
* The first fault is that it is an instruction she cannot carry out from this
|
|
233
|
+
* screen and, worse, one that only means anything to somebody who already has
|
|
234
|
+
* a second subscription. Anybody else reads it as Agentbox telling them to go
|
|
235
|
+
* and get one, which is the thing her standing rule forbids. So this sentence
|
|
236
|
+
* instructs nobody now. It says what is true and stops.
|
|
237
|
+
*
|
|
238
|
+
* The second is her point about email, and it is a fair one, so it is worth
|
|
239
|
+
* writing down plainly: THE CHECK IS NOT ON THE EMAIL. It is on `accountUuid`,
|
|
240
|
+
* the id Claude Code stores for the subscription itself, so what it catches is
|
|
241
|
+
* two folders holding one subscription, not two folders showing one address.
|
|
242
|
+
*
|
|
243
|
+
* AND IT NO LONGER CLAIMS THEY ADD NO CAPACITY, because that is not what the
|
|
244
|
+
* app does. `_capacity` in main/supervisor.mjs is `maxConcurrentSessions *
|
|
245
|
+
* liveProfiles.length`, which counts FOLDERS. So a duplicated machine is told
|
|
246
|
+
* it can run double and really does start double, all of it against one
|
|
247
|
+
* subscription. The old sentence described what we intended; this one describes
|
|
248
|
+
* what happens, and says the number above is too high rather than telling her
|
|
249
|
+
* to go and change something.
|
|
250
|
+
* Null when the two really are different subscriptions. */
|
|
251
|
+
export function duplicateAccountNote(identities) {
|
|
252
|
+
const seen = new Map();
|
|
253
|
+
for (const id of identities) {
|
|
254
|
+
if (!id?.accountUuid) continue;
|
|
255
|
+
seen.set(id.accountUuid, (seen.get(id.accountUuid) ?? 0) + 1);
|
|
256
|
+
}
|
|
257
|
+
const dupes = [...seen.values()].filter((n) => n > 1).length;
|
|
258
|
+
if (!dupes) return null;
|
|
259
|
+
return 'Both of these are signed in to the same Claude account.'
|
|
260
|
+
+ ' That is one subscription rather than two, so the number above is higher than it should be.';
|
|
261
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Ephemeral, local status from matched tool start/end events. A persisted trace
|
|
2
|
+
// is history and cannot prove a command is still running. Nothing here executes
|
|
3
|
+
// commands, records reasoning, or changes worker permissions.
|
|
4
|
+
import { plainCommand, shortPath } from '../shared/work-lines.mjs';
|
|
5
|
+
const clean = value => typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
6
|
+
function add(session, id, label, detail) {
|
|
7
|
+
if (!id || !label) return false;
|
|
8
|
+
const pending = session.activeTools ??= new Map();
|
|
9
|
+
if (pending.has(id)) return false;
|
|
10
|
+
pending.set(id, { id, label: label.length > 240 ? `${label.slice(0, 237)}…` : label, detail: String(detail ?? ''), startedAt: Date.now() });
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
function finish(session, id) { return session.activeTools?.delete(id) ?? false; }
|
|
14
|
+
function clear(session) { const changed = !!session.activeTools?.size; session.activeTools?.clear(); return changed; }
|
|
15
|
+
export function currentActivity(session) { return [...(session.activeTools?.values() ?? [])]; }
|
|
16
|
+
function toolLabel(name, input) {
|
|
17
|
+
const command = clean(input.command);
|
|
18
|
+
const file = clean(input.file_path ?? input.path);
|
|
19
|
+
if (name === 'Bash') return ['Running ' + (plainCommand(command) || 'command'), input.command ?? ''];
|
|
20
|
+
if (['Read','Write','Edit','MultiEdit'].includes(name)) return [`${name === 'Read' ? 'Reading' : name === 'Write' ? 'Writing' : 'Editing'} ${shortPath(file) || 'file'}`, file];
|
|
21
|
+
if (['Grep','Glob','WebSearch'].includes(name)) return [`Searching ${clean(input.pattern ?? input.query) || 'files'}`, input.pattern ?? input.query ?? ''];
|
|
22
|
+
if (name === 'WebFetch') return ['Reading web page', input.url ?? ''];
|
|
23
|
+
const friendly = clean(name).split('__').pop().replace(/_/g, ' ');
|
|
24
|
+
return [`Using ${friendly || 'tool'}`, clean(input.description ?? input.title)];
|
|
25
|
+
}
|
|
26
|
+
export function claudeActivity(session, line) {
|
|
27
|
+
try {
|
|
28
|
+
const obj = JSON.parse(line);
|
|
29
|
+
if (obj.parent_tool_use_id) return false;
|
|
30
|
+
if (obj.type === 'result') return clear(session);
|
|
31
|
+
let changed = false;
|
|
32
|
+
for (const part of Array.isArray(obj.message?.content) ? obj.message.content : []) {
|
|
33
|
+
if (obj.type === 'assistant' && part.type === 'tool_use') {
|
|
34
|
+
const [label, detail] = toolLabel(part.name, part.input ?? {});
|
|
35
|
+
changed = add(session, part.id, label, detail) || changed;
|
|
36
|
+
}
|
|
37
|
+
if (obj.type === 'user' && part.type === 'tool_result') changed = finish(session, part.tool_use_id) || changed;
|
|
38
|
+
}
|
|
39
|
+
return changed;
|
|
40
|
+
} catch { return false; }
|
|
41
|
+
}
|
|
42
|
+
export function codexActivity(session, method, params) {
|
|
43
|
+
if (method === 'turn/completed' || method === 'turn/failed') return clear(session);
|
|
44
|
+
const item = params?.item;
|
|
45
|
+
if (!item?.id) return false;
|
|
46
|
+
if (method === 'item/completed') return finish(session, item.id);
|
|
47
|
+
if (method !== 'item/started') return false;
|
|
48
|
+
if (item.status && !['inProgress', 'in_progress', 'running'].includes(item.status)) return false;
|
|
49
|
+
switch (item.type) {
|
|
50
|
+
case 'commandExecution': return add(session, item.id, `Running ${plainCommand(clean(item.command)) || 'command'}`, item.command);
|
|
51
|
+
case 'fileChange': return add(session, item.id, 'Editing files', (Array.isArray(item.changes) ? item.changes : []).map(c => c?.path ?? '').join('\n'));
|
|
52
|
+
case 'mcpToolCall': return add(session, item.id, `Using ${clean(item.tool).replace(/_/g, ' ') || 'tool'}`, `${item.server ?? ''} · ${item.tool ?? ''}`);
|
|
53
|
+
case 'webSearch': return add(session, item.id, 'Searching the web', item.query ?? '');
|
|
54
|
+
default: return false;
|
|
55
|
+
}
|
|
56
|
+
}
|