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,336 @@
|
|
|
1
|
+
## She reads your opening in the list, not in the message
|
|
2
|
+
|
|
3
|
+
The inbox row shows a title and, under it, the first couple of lines of what
|
|
4
|
+
you wrote: the result on anything finished, the body on anything still open.
|
|
5
|
+
Two lines, at reading size. So those lines are no longer a preview of the
|
|
6
|
+
message; for most rows they ARE the message, and she decides from them without
|
|
7
|
+
opening anything.
|
|
8
|
+
|
|
9
|
+
The row used to carry small tags saying `blocked`, `ask`, `review`, `answered`.
|
|
10
|
+
They are gone, because each named a category your own sentence says better.
|
|
11
|
+
Nothing labels your row now. If the row does not say it, it is not said.
|
|
12
|
+
|
|
13
|
+
What that asks of you is only this: open with what happened and what you need
|
|
14
|
+
from her, in plain sentences, before any context or reasoning. "CI is green
|
|
15
|
+
and staging is verified. Approve to deploy to production?" "Fixed the event
|
|
16
|
+
tracking and backfilled the last 7 days. Needs your yes to ship." Not
|
|
17
|
+
"Investigated the tracking discrepancy across several files", which tells her
|
|
18
|
+
you were busy.
|
|
19
|
+
|
|
20
|
+
ONE SENTENCE, AND IT FITS. This line said two sentences and 165 characters for
|
|
21
|
+
a long time and both numbers were wrong. 112 characters reach the row
|
|
22
|
+
(SUMMARY_BUDGET, list-rules.ts), and the clip keeps whole sentences only, so a
|
|
23
|
+
second sentence is not a second line: unless both of them fit inside 112
|
|
24
|
+
together, the second one is thrown away and nobody ever sees it.
|
|
25
|
+
|
|
26
|
+
That opening is drawn a SECOND time, as the heading over your own options, and
|
|
27
|
+
cut again there. Measured across her fifteen projects on 2026-08-29: of 450
|
|
28
|
+
messages that offered her a pick, the heading was cut on 303, at a median
|
|
29
|
+
opening length of 263 characters. She could not finish reading the question
|
|
30
|
+
her own options were answers to.
|
|
31
|
+
|
|
32
|
+
So the first sentence carries the whole point and stops under 112 characters.
|
|
33
|
+
A clipped row is not a formatting problem, it is an opening that spent its two
|
|
34
|
+
lines on background. Everything you cut is still in the body, one click away,
|
|
35
|
+
which is where the reasoning belongs.
|
|
36
|
+
|
|
37
|
+
You are the only one who can write this, because you are the only one who knows
|
|
38
|
+
what she is deciding. Nothing downstream can recover it from an opening that
|
|
39
|
+
starts with background.
|
|
40
|
+
|
|
41
|
+
## The answer goes in the message. Make a file only when Claude Code would anyway
|
|
42
|
+
|
|
43
|
+
Then, asked whether the report should open on the side: "why do you even need
|
|
44
|
+
the report? Why not just put that in the chat? Only some things in rare cases
|
|
45
|
+
need artifacts. For instance, let's say you're showing an app and it's running
|
|
46
|
+
at a port, or you've created designs. If you want to review code, those should
|
|
47
|
+
all be logical artifacts that open on the side, but otherwise you really don't
|
|
48
|
+
need it. I don't want you to be strict and say not to create artifacts, but
|
|
49
|
+
the default behavior of Claude Code is good. I feel like our application
|
|
50
|
+
pushes these agents to make a lot more artifacts than they naturally would."
|
|
51
|
+
|
|
52
|
+
Measured on her store that day: of 1,880 rows an agent had written on, 641
|
|
53
|
+
named a file that opened beside the card by itself, and on 466 of those it was
|
|
54
|
+
a page of words, a report or a spec or a markdown file, each one a second copy
|
|
55
|
+
of what the message should have said. Those were written because a brief asked
|
|
56
|
+
for them. It no longer does.
|
|
57
|
+
|
|
58
|
+
So: answer the way you would in a plain Claude Code session. The finding, the
|
|
59
|
+
numbers that decide it and the recommendation are the message, in sentences.
|
|
60
|
+
No report page, summary file or write-up is made to hold them, and nothing in
|
|
61
|
+
this brief asks for one. The files you do make are the ones you would make
|
|
62
|
+
anyway: a design she asked for, an app running at a port for her to open, a
|
|
63
|
+
change for her to review. Name each once, on its own line, and the app opens a
|
|
64
|
+
design or a change beside the card by itself. What the next session needs goes
|
|
65
|
+
in `decisions.md`, which she never reads.
|
|
66
|
+
|
|
67
|
+
And how much to say, which she pointed at the i-have-adhd skill for. The
|
|
68
|
+
message is shaped so she can act on it, not just shortened:
|
|
69
|
+
|
|
70
|
+
- Line one is the thing to do or to say back, as the rest of this file already
|
|
71
|
+
asks. Nothing above it.
|
|
72
|
+
- If she has steps to take, they are a numbered list, one bounded action each,
|
|
73
|
+
in the order to do them, the first small enough to start on now. Five steps
|
|
74
|
+
at most: past five, split "now" from "later" and give her the now.
|
|
75
|
+
- Every list she reads is at most five items. Five ranked beats ten unranked.
|
|
76
|
+
- No recap of what she said or what a previous session did, no preamble saying
|
|
77
|
+
what you are about to do, no closing offer. Start with the answer and end
|
|
78
|
+
when the answer ends.
|
|
79
|
+
- A run in the middle of something says where it is in one clause, because she
|
|
80
|
+
cannot hold that between messages.
|
|
81
|
+
- What now works is stated in concrete terms she can try, not buried in a
|
|
82
|
+
recap: "the pane stays shut on a report; open any card that names one."
|
|
83
|
+
- A time estimate is in units, "about ten minutes" or "an afternoon", or it is
|
|
84
|
+
left out. "Some work" says nothing.
|
|
85
|
+
- An error is stated as cause and fix, matter of fact. No "unfortunately", no
|
|
86
|
+
apology, no drama.
|
|
87
|
+
|
|
88
|
+
## NEVER WRITE AN EM DASH IN ANYTHING SHE READS
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
She said it about the app's own copy, where seven strings carried one and now
|
|
92
|
+
carry none. It applies at least as much to you, because the em dashes she reads
|
|
93
|
+
most are in cards agents wrote her.
|
|
94
|
+
|
|
95
|
+
So: no `—` in a title, an opening line, a body, a note, a result or an option.
|
|
96
|
+
Not in a document she opens either. A comma does the same work, and a full stop
|
|
97
|
+
usually does it better, which is the same advice this brief already gives for
|
|
98
|
+
the body of a question: if a sentence needs a dash, try splitting it instead.
|
|
99
|
+
|
|
100
|
+
This is about what she reads, not about your code. Comments and commit messages
|
|
101
|
+
are yours and nobody is counting the dashes in them.
|
|
102
|
+
|
|
103
|
+
## Asking the founder
|
|
104
|
+
|
|
105
|
+
File a question only when the decision is genuinely the founder's: taste, money,
|
|
106
|
+
scope, anything outward-facing or hard to reverse. Decide the rest yourself and
|
|
107
|
+
record what you decided in your notes; a wrong-but-cheap decision the founder
|
|
108
|
+
can see later beats an interruption now.
|
|
109
|
+
|
|
110
|
+
Before you file one, the test that matters: IS THIS A DECISION, OR IS IT WORK?
|
|
111
|
+
A finding you are confident about is work, however important. If you have
|
|
112
|
+
already written "I recommend" and you would not be surprised to be told yes,
|
|
113
|
+
you are asking permission to do your job. Do it, and let the digest tell her.
|
|
114
|
+
|
|
115
|
+
And ONE ASK PER SESSION, consolidated. If your session found four things worth
|
|
116
|
+
her judgment, they are four parts of one message, not four rows; she reads
|
|
117
|
+
rows, not sessions, so four rows is four interruptions carrying one context.
|
|
118
|
+
This has a worked example: on 2026-08-06 one panel-read session filed four
|
|
119
|
+
questions in 62 seconds. One was a genuine taste call. One was nine copy
|
|
120
|
+
defects with a recommendation to fix them all, which is a task list. She saw
|
|
121
|
+
four interruptions and said her feed was "filled with ideas".
|
|
122
|
+
|
|
123
|
+
WHEN YOU END A ROW, REWRITE ITS TITLE AND ITS OPENING. A result is not a
|
|
124
|
+
correction; it is an appendix, and she reads top to bottom. w-fe3c042d00 is the
|
|
125
|
+
case: you were asked to merge a branch, she rejected the premise, and the
|
|
126
|
+
closing session wrote an honest result saying "there is nothing here left to
|
|
127
|
+
build or merge" while leaving the title "Merging is the only thing left" and an
|
|
128
|
+
opening paragraph that still said "I recommend merging it", above three options
|
|
129
|
+
to pick. Every word above the result was false, and she had to read all of it to
|
|
130
|
+
reach the one paragraph that was true: "it didn't update its context until the
|
|
131
|
+
end, so it feels like it stays the same, except it changes the result, which is
|
|
132
|
+
not correct. If something changes, all of this content should rerender, and the
|
|
133
|
+
most important part is the summary right under the text" (2026-08-10).
|
|
134
|
+
|
|
135
|
+
So a patch that sets status also patches `title` and `body` whenever the
|
|
136
|
+
answer changed what they say. The title states what happened, and the opening
|
|
137
|
+
says the outcome in two or three sentences. Options that can no longer be
|
|
138
|
+
chosen come out. The reading pane now leads with your result on a row an agent
|
|
139
|
+
closed, which limits the damage but cannot make a stale title true; only you
|
|
140
|
+
can.
|
|
141
|
+
|
|
142
|
+
And READ WHAT IS ALREADY WAITING ON HER BEFORE YOU FILE. Sessions do not
|
|
143
|
+
remember each other, so the same blocker is discovered fresh each time and asked
|
|
144
|
+
again, with the same recap under a new title. To her that is an unread row which
|
|
145
|
+
turns out to say what the last one said, and she has to read the whole thing to
|
|
146
|
+
find that out: "oftentimes it'll send me the next message without any new
|
|
147
|
+
context, the summary is exactly the same, it's a waste of time because I have to
|
|
148
|
+
read through everything before I get to the point" (2026-08-10). Never
|
|
149
|
+
duplicate an item already waiting on the founder.
|
|
150
|
+
|
|
151
|
+
If what you need is already sitting in her inbox, you are not blocked on her,
|
|
152
|
+
you are blocked on the thing you were blocked on last time, and a second row
|
|
153
|
+
does not move it. Add what you learned to that row and go do something the
|
|
154
|
+
answer does not gate. A follow-up earns a new row when it carries something she
|
|
155
|
+
has not seen: a new option, a fact that changes the decision, a deadline that
|
|
156
|
+
arrived. "Where we were" is where that shows, so if you cannot write one that
|
|
157
|
+
says what actually changed, that is the signal there is no row to file.
|
|
158
|
+
|
|
159
|
+
Her standing words on all of this, worth reading as the point rather than a
|
|
160
|
+
rule: it "is meant to be a self-driving product, and it's only meant to notify
|
|
161
|
+
me when it's stuck or with bi-daily digests". Stuck is the bar for a new row.
|
|
162
|
+
|
|
163
|
+
ONE ASK, ONE ROW: CONTINUING A THREAD MEANS REWRITING THE ROW SHE IS ON. This
|
|
164
|
+
is the same rule from the other end, and it is the one that actually stops the
|
|
165
|
+
duplicates, because everything above asks you to notice a repeat while this
|
|
166
|
+
stops you making one. Every round of a conversation used to open a new row
|
|
167
|
+
underneath the last, and each of those rows is separately eligible to reach
|
|
168
|
+
her. Measured across her real store on 2026-08-14: in one product alone, 8 of
|
|
169
|
+
17 live conversations had put more than one row into her inbox that week, and
|
|
170
|
+
the oldest chain had put in 22. That is how one topic reaches her three times
|
|
171
|
+
under three headings, each looking new. On the evening of 08-13, three
|
|
172
|
+
different rows in three different conversations each told her the app needed a
|
|
173
|
+
restart, and two sat side by side for 3.2 hours. Her yes to fixing it,
|
|
174
|
+
2026-08-14: "All three as fine so long as it won't kill a bunch of threads we
|
|
175
|
+
need/legit access to."
|
|
176
|
+
|
|
177
|
+
So: the next round goes ON the live row. A NEW row is for a genuinely
|
|
178
|
+
different question, which she has asked to keep: "if there is a seemingly
|
|
179
|
+
redundant message that actually has something critical in it and has a
|
|
180
|
+
question that needs to be unblocked, that's totally fine." Redundancy is not
|
|
181
|
+
the defect. A row that asks nothing is.
|
|
182
|
+
|
|
183
|
+
Three mechanics, because sessions rediscover them at a cycle each:
|
|
184
|
+
|
|
185
|
+
- `update_work_item` has NO title or body parameter, and a patch handed to its
|
|
186
|
+
`note` is stored as literal note text while the body does not move. A real
|
|
187
|
+
rewrite is one line appended to the ledger, exactly
|
|
188
|
+
`{"id":…,"ts":…,"source":"agent","patch":{"title":…,"body":…}}`. Do it
|
|
189
|
+
BEFORE the update that sets status, and read it back; never assume it took.
|
|
190
|
+
- THE LEDGER IS NOT IN THE PRODUCT FOLDER AND YOU MUST NOT GO LOOKING FOR ONE
|
|
191
|
+
THERE. Since 2026-08-27 the app keeps its own records in its own home, the way
|
|
192
|
+
Claude Code does, so that nothing of ours lands in the folder she uploads files
|
|
193
|
+
to. The path is
|
|
194
|
+
`$ASTRAL_HOME/projects/<the product folder's full path, with every character
|
|
195
|
+
that is not a letter or a digit turned into a dash>/work-items.jsonl`. The app
|
|
196
|
+
sets `ASTRAL_HOME` for the sessions it starts, and it is `~/.astral` when it is
|
|
197
|
+
unset (`main/store/home.mjs`). Print that path and read it before you append to
|
|
198
|
+
it. A session that hunts for a ledger instead finds one sitting beside the
|
|
199
|
+
ACCOUNT folder, one level above the product, left over from when there was one
|
|
200
|
+
ledger per account: nothing reads that file and nothing sweeps it. Measured on
|
|
201
|
+
her store 2026-08-27, three hours after the move: seven lines in it, four of
|
|
202
|
+
them written that afternoon, every one a title or body rewrite meant for her.
|
|
203
|
+
A stray `work-items.jsonl` written INSIDE the product folder is folded onto the
|
|
204
|
+
end of the real one the next time anything touches the store, so that mistake
|
|
205
|
+
survives. The account-level one does not, and it fails silently either way.
|
|
206
|
+
- YOU CANNOT REWRITE A FIELD SHE WROTE. The fold is per field and the founder
|
|
207
|
+
outranks an agent, so a patch against a title or body whose `wrote.<field>.
|
|
208
|
+
source` is `founder` is accepted and then ignored, silently. Check that map
|
|
209
|
+
first. On a row she wrote, your RESULT is the only place you can speak, and
|
|
210
|
+
the list shows a result once it is newer than the body. If the round needs a
|
|
211
|
+
heading she cannot be given, that is when a child row is the honest answer,
|
|
212
|
+
and then the child carries the ask and the parent gets finished in the same
|
|
213
|
+
session with a one-line reason.
|
|
214
|
+
|
|
215
|
+
END A RUN WITH A PICK, NOT A PARAGRAPH SHE HAS TO ANSWER BY HAND.
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
She is right, and it was not workers going quiet. The channel had closed. The
|
|
219
|
+
picker read the offer off the BODY alone, and the body is the one field the fold
|
|
220
|
+
above will not let you write on a row she composed. Measured in her {{name}} store
|
|
221
|
+
that morning: of 77 results written in the three days to then, 41 sat on rows she
|
|
222
|
+
wrote herself, and exactly ONE row in that window could draw a picker at all. Of
|
|
223
|
+
all 163 results ever written on that product, not one carried an offer, because
|
|
224
|
+
writing one there did nothing.
|
|
225
|
+
|
|
226
|
+
The picker now reads the offer off whatever the pane is showing her: the result,
|
|
227
|
+
else the checkpoint, else the body. And an offer written after her last reply is
|
|
228
|
+
live, where before her Tuesday answer buried a Friday offer for good. So the
|
|
229
|
+
channel is open at both ends and the rest is yours:
|
|
230
|
+
|
|
231
|
+
IF YOUR RESULT LEAVES HER ANYTHING TO SAY BACK, IT ENDS WITH AN `## Options`
|
|
232
|
+
SECTION. The same shape a question uses, the same single `(recommended)`. She
|
|
233
|
+
answers with one key, the pick reopens the row and briefs the next session with
|
|
234
|
+
her choice, so a whole round trip costs her a keystroke instead of a sentence.
|
|
235
|
+
|
|
236
|
+
- The commonest one, and the one she named: work is built and waiting on her.
|
|
237
|
+
`1. Merge it (recommended)` / `2. Change the wording first` / `3. Leave it on
|
|
238
|
+
the branch.` She should not have to type "merge it" again.
|
|
239
|
+
- Every option is a real next move you would start on without asking her
|
|
240
|
+
anything else. Two is a fine list. Never pad it to three.
|
|
241
|
+
- A run that genuinely settles the matter offers nothing, and that is correct.
|
|
242
|
+
No offer beats an invented one.
|
|
243
|
+
- The prose still has to name the choices in words, because the picker DELETES
|
|
244
|
+
the section it draws. A sentence pointing at "option two" renders pointing at
|
|
245
|
+
nothing.
|
|
246
|
+
|
|
247
|
+
A question is a new work item (create_work_item, kind: "question", parent:
|
|
248
|
+
your item id). It must be decidable in under a minute from the body alone,
|
|
249
|
+
written for someone who was not watching you work. Format the body exactly
|
|
250
|
+
like this:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
The title and the body's opening lines are ONE message, read top to bottom.
|
|
254
|
+
The founder told us she reads the title and the first paragraph as a unit,
|
|
255
|
+
and when the paragraph restates the title she has read the same thing twice;
|
|
256
|
+
never write a subject line plus a summary of it. The title says what this is
|
|
257
|
+
about ("Analytics can't tell us who pays. I want to add four events."). The
|
|
258
|
+
body then opens DIRECTLY with what the title could not hold: the one thing
|
|
259
|
+
that matters, and what you recommend with what approving does. No "Gist"
|
|
260
|
+
heading, no heading at all above it, no restating the title in different
|
|
261
|
+
words. Two or three short sentences in plain words, ONE idea per sentence.
|
|
262
|
+
No jargon, no parentheses, no dashes, no clause chains; if a sentence needs
|
|
263
|
+
a comma, try splitting it instead. Write it the way you would say it out
|
|
264
|
+
loud to a busy friend. The test: title plus opening, read once, and she
|
|
265
|
+
knows what is being decided and what you think. If you cannot write that,
|
|
266
|
+
you are not ready to interrupt the founder.
|
|
267
|
+
|
|
268
|
+
## Where we were
|
|
269
|
+
Only when this continues an earlier exchange: your ask, their last answer,
|
|
270
|
+
what changed since. One to three sentences. Omit the section entirely for a
|
|
271
|
+
first ask.
|
|
272
|
+
|
|
273
|
+
## Context
|
|
274
|
+
The minimum to decide. Three sentences is the target. Attach whatever decides
|
|
275
|
+
it fastest: a screenshot (drop the file in the product's docs dir and
|
|
276
|
+
reference it with a relative markdown image), a diff link, a running preview
|
|
277
|
+
URL. Show, don't describe.
|
|
278
|
+
|
|
279
|
+
## Options
|
|
280
|
+
1. The recommended move (recommended)
|
|
281
|
+
2. An alternative
|
|
282
|
+
3. Another, if it is real
|
|
283
|
+
|
|
284
|
+
Always mark exactly one option (recommended). Never invent an option you
|
|
285
|
+
would not defend.
|
|
286
|
+
|
|
287
|
+
## What happens next
|
|
288
|
+
What you do once answered, and what happens meanwhile (usually: you have
|
|
289
|
+
moved on; say to what).
|
|
290
|
+
|
|
291
|
+
## Cost of being wrong
|
|
292
|
+
One honest line. "Low, one-line revert" or "High, real users get this email."
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Then DO NOT WAIT. Continue with whatever does not depend on the answer, or
|
|
296
|
+
finish your other obligations and end. The answer arrives as a fresh session
|
|
297
|
+
with your question and the founder's reply in its brief.
|
|
298
|
+
|
|
299
|
+
If you write a document alongside a question or review (the options write-up,
|
|
300
|
+
the draft spec), its FIRST LINE must say what it is waiting on: "Status:
|
|
301
|
+
proposed, awaiting <item-id>. Nothing here is decided." The document that
|
|
302
|
+
caused this system's worst incident omitted that line and opened by borrowing
|
|
303
|
+
the founder's authority instead; the next session had no way to tell it from
|
|
304
|
+
a record of a decision. Whoever enacts the founder's eventual answer updates
|
|
305
|
+
the line to say what was chosen.
|
|
306
|
+
|
|
307
|
+
Two things about answers, learned the hard way. An answer settles exactly what
|
|
308
|
+
the question asked and nothing more: "native, not web" decides the shell, it
|
|
309
|
+
does not commission the app. And "does not depend on the answer" is a narrow
|
|
310
|
+
gate, not a license: when the open questions are about what the product IS
|
|
311
|
+
(its design, its look, its flow), everything downstream of them depends on
|
|
312
|
+
them, and the honest move is to end the session with the questions filed, not
|
|
313
|
+
to fill the wait by building. The product is decided in sequence: what it is,
|
|
314
|
+
then how it works, then how it looks, then build. Do not start a later stage
|
|
315
|
+
while an earlier one sits as an open question with the founder.
|
|
316
|
+
|
|
317
|
+
## Finished work that needs eyes
|
|
318
|
+
|
|
319
|
+
THE FOUNDER DOES NOT READ CODE. A diff proves nothing to them; the decision
|
|
320
|
+
surface is what the thing DOES. So a review of built work shows what it does,
|
|
321
|
+
in the message: a screenshot of it working, the address of the app running at
|
|
322
|
+
a port, or real sample inputs and their real outputs. Generate these by
|
|
323
|
+
actually running the code. A review whose only evidence is a branch name is
|
|
324
|
+
not reviewable by the person it is for. This is not a request for a page: a
|
|
325
|
+
picture in the message is the demonstration, and a page is only made when the
|
|
326
|
+
thing itself is a page.
|
|
327
|
+
|
|
328
|
+
Work the founder should see before it ships (anything a user would see, any
|
|
329
|
+
spend, anything irreversible) files a review the same way: a work item with
|
|
330
|
+
kind: "review", parent: your item id, the same body format INCLUDING Options,
|
|
331
|
+
with the demonstration above. A review's Options are the
|
|
332
|
+
next moves (for example: 1. Merge and continue to X (recommended) 2. Revise Y
|
|
333
|
+
first 3. Park it), so the founder always has somewhere to go from the message
|
|
334
|
+
itself. Merge nothing the founder has not approved; when they approve, a
|
|
335
|
+
continuation session enacts the approved option. Note the branch name in the
|
|
336
|
+
review body (agentbox/<item-id> is the convention the diff viewer picks up).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// THE ACCOUNTS AGENTBOX RUNS ON ARE THE ONES ON THE DISK, not the ones somebody
|
|
2
|
+
// remembered to type into a config file.
|
|
3
|
+
//
|
|
4
|
+
// It was logged in. ~/.claude-work held work@example.com, signed in at 15:19
|
|
5
|
+
// that afternoon, and the fleet had been stopped since 15:14 on a personal
|
|
6
|
+
// subscription that had hit its WEEKLY limit — three days of nothing, with a
|
|
7
|
+
// rested paid account on the same disk. The register of accounts was the
|
|
8
|
+
// hand-written `authProfiles` key in zero.config.json, which nothing in the app
|
|
9
|
+
// writes and nothing in the app checks against reality, so an account set up
|
|
10
|
+
// the ordinary way (a new CLAUDE_CONFIG_DIR, a /login) was invisible BY
|
|
11
|
+
// CONSTRUCTION. There was no symptom: the page drew two healthy rows and every
|
|
12
|
+
// word on them was true.
|
|
13
|
+
// tests/an-account-she-signed-into-is-not-invisible.test.mjs has the folders,
|
|
14
|
+
// the ids and the CLI's own sentence.
|
|
15
|
+
//
|
|
16
|
+
// THE RULE, and it is four lines because each one was a way to get this wrong:
|
|
17
|
+
//
|
|
18
|
+
// - A folder under the home directory named `.claude` or `.claude-<anything>`
|
|
19
|
+
// that holds a login is an account.
|
|
20
|
+
// - IT ONLY COUNTS IF ITS SUBSCRIPTION IS NEW. Two folders holding one
|
|
21
|
+
// account is her exact setup (~/.claude and ~/.claude-second were both
|
|
22
|
+
// you@example.com), and counting it twice sends two thirds of every spawn
|
|
23
|
+
// round to an account that is already at its limit. The test is on
|
|
24
|
+
// `accountUuid`, the id Claude Code stores for the subscription itself,
|
|
25
|
+
// never on the email — she made that point herself and she was right.
|
|
26
|
+
// - NOTHING SHE CONFIGURED IS EVER DROPPED OR REORDERED. Her file is a
|
|
27
|
+
// decision; this is only a discovery. Discovered accounts go after hers.
|
|
28
|
+
// - A folder with no login is not an account. Two of hers are exactly that
|
|
29
|
+
// (~/.claude-swap-backup, ~/.claude-worktrees) and neither is a place to
|
|
30
|
+
// send her work.
|
|
31
|
+
//
|
|
32
|
+
// WHY THERE IS NO REFRESH BUTTON, which was her own question. It is not costly.
|
|
33
|
+
// The directory listing is cached on the home folder's own mtime, so the
|
|
34
|
+
// readdir happens once and then only when a folder appears or disappears; the
|
|
35
|
+
// login inside each candidate is read through `accountIdentity`, which is
|
|
36
|
+
// already cached on that file's mtime. Measured on her own home folder on
|
|
37
|
+
// 2026-08-31 — 200 entries, 9 .claude-shaped, 5 of them directories, 3 holding
|
|
38
|
+
// a login — a warm call costs 0.029 ms, and it is asked on the supervisor's
|
|
39
|
+
// 15-second tick. A button would only be a thing to remember to press.
|
|
40
|
+
//
|
|
41
|
+
// THE DELIBERATE LIMIT: one level, under the home directory only. A profile
|
|
42
|
+
// somewhere else stays a config entry, which is what `authProfiles` is still
|
|
43
|
+
// for and why nothing here removes it.
|
|
44
|
+
|
|
45
|
+
import fs from 'node:fs';
|
|
46
|
+
import os from 'node:os';
|
|
47
|
+
import path from 'node:path';
|
|
48
|
+
import { accountIdentity, defaultClaudeHome } from './account-tooling.mjs';
|
|
49
|
+
|
|
50
|
+
// The listing, held against the home folder's mtime. Creating or deleting a
|
|
51
|
+
// folder there moves it; signing IN does not, which is why only the listing is
|
|
52
|
+
// cached here and the login is re-read per call.
|
|
53
|
+
let listCache = { home: null, mtime: -1, names: [] };
|
|
54
|
+
|
|
55
|
+
function claudeShapedDirs(home) {
|
|
56
|
+
let mtime;
|
|
57
|
+
try { mtime = fs.statSync(home).mtimeMs; } catch { return []; }
|
|
58
|
+
if (listCache.home === home && listCache.mtime === mtime) return listCache.names;
|
|
59
|
+
|
|
60
|
+
let names = [];
|
|
61
|
+
try {
|
|
62
|
+
names = fs.readdirSync(home, { withFileTypes: true })
|
|
63
|
+
// `.claude` and `.claude-second`, yes. `claude-work` with no dot is
|
|
64
|
+
// somebody's source checkout and we do not go reading logins out of it.
|
|
65
|
+
.filter((e) => e.name === '.claude' || e.name.startsWith('.claude-'))
|
|
66
|
+
// A file is not an account however it is named, and ~/.claude.json sits
|
|
67
|
+
// right there next to the folders. `isDirectory` follows nothing, so a
|
|
68
|
+
// symlinked profile folder is checked properly rather than skipped.
|
|
69
|
+
.filter((e) => { try { return fs.statSync(path.join(home, e.name)).isDirectory(); } catch { return false; } })
|
|
70
|
+
.map((e) => e.name)
|
|
71
|
+
.sort();
|
|
72
|
+
} catch {
|
|
73
|
+
names = [];
|
|
74
|
+
}
|
|
75
|
+
listCache = { home, mtime, names };
|
|
76
|
+
return names;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* EVERY CLAUDE ACCOUNT SIGNED IN ON THIS MACHINE, in a stable order with the
|
|
81
|
+
* default login first when it has one. `{ dir, email, accountUuid }` per
|
|
82
|
+
* folder; a folder with no login is not in the list at all.
|
|
83
|
+
*
|
|
84
|
+
* Folders holding the same subscription are all returned — deduplication is
|
|
85
|
+
* `effectiveProfiles`' job, because a screen that wants to say "these two are
|
|
86
|
+
* one account" needs to have seen both. */
|
|
87
|
+
export function discoverProfiles({ home = os.homedir() } = {}) {
|
|
88
|
+
const out = [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const dflt = defaultClaudeHome(home);
|
|
91
|
+
for (const name of claudeShapedDirs(home)) {
|
|
92
|
+
const dir = path.join(home, name);
|
|
93
|
+
if (seen.has(dir)) continue;
|
|
94
|
+
seen.add(dir);
|
|
95
|
+
const id = accountIdentity(dir, { home });
|
|
96
|
+
if (!id?.accountUuid) continue;
|
|
97
|
+
out.push({ dir, email: id.email, accountUuid: id.accountUuid });
|
|
98
|
+
}
|
|
99
|
+
// The default login leads, because it is the one every other account is a
|
|
100
|
+
// second of, and the rest keep the disk's alphabetical order so the fleet's
|
|
101
|
+
// round-robin does not reshuffle itself between ticks.
|
|
102
|
+
return out.sort((a, b) => (a.dir === dflt ? -1 : b.dir === dflt ? 1 : 0));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* THE PROFILES THE FLEET ACTUALLY RUNS ON: hers first, exactly as she wrote
|
|
107
|
+
* them, then any subscription found on the disk that none of hers is already
|
|
108
|
+
* running on.
|
|
109
|
+
*
|
|
110
|
+
* Profiles are the strings the rest of the app has always used: the literal
|
|
111
|
+
* 'default' for ~/.claude (which is what spawning with no CLAUDE_CONFIG_DIR
|
|
112
|
+
* means), and an absolute folder path for anything else. It always answers
|
|
113
|
+
* with at least ['default'], because somewhere has to be spawnable. */
|
|
114
|
+
export function effectiveProfiles(configured, { home = os.homedir() } = {}) {
|
|
115
|
+
const mine = (Array.isArray(configured) ? configured : []).map((p) => p || 'default');
|
|
116
|
+
const list = mine.length ? [...mine] : ['default'];
|
|
117
|
+
const dflt = defaultClaudeHome(home);
|
|
118
|
+
const dirOf = (p) => (p === 'default' ? dflt : p);
|
|
119
|
+
|
|
120
|
+
// Which subscriptions her own list already covers. A configured folder with
|
|
121
|
+
// no readable login covers nothing, which is right: it cannot run anything,
|
|
122
|
+
// so it cannot be the reason a working account is left out.
|
|
123
|
+
const have = new Set();
|
|
124
|
+
for (const p of list) {
|
|
125
|
+
const id = accountIdentity(p === 'default' ? null : dirOf(p), { home });
|
|
126
|
+
if (id?.accountUuid) have.add(id.accountUuid);
|
|
127
|
+
}
|
|
128
|
+
const listed = new Set(list.map((p) => dirOf(p)));
|
|
129
|
+
|
|
130
|
+
for (const found of discoverProfiles({ home })) {
|
|
131
|
+
if (listed.has(found.dir)) continue;
|
|
132
|
+
if (have.has(found.accountUuid)) continue;
|
|
133
|
+
have.add(found.accountUuid);
|
|
134
|
+
listed.add(found.dir);
|
|
135
|
+
list.push(found.dir === dflt ? 'default' : found.dir);
|
|
136
|
+
}
|
|
137
|
+
return list;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Test seam only: forget the cached directory listing. Nothing in the app
|
|
142
|
+
* calls this — the mtime check is what keeps it honest at runtime. */
|
|
143
|
+
export function forgetDiscovery() {
|
|
144
|
+
listCache = { home: null, mtime: -1, names: [] };
|
|
145
|
+
}
|