mulmoterminal 9.1.0 → 9.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/README.md +2 -1
  2. package/common/appActions.ts +1 -0
  3. package/common/confetti.ts +50 -0
  4. package/common/keymap.ts +1 -0
  5. package/common/playfulEffects.ts +1 -1
  6. package/common/tokenRotation.ts +103 -0
  7. package/dist/assets/{abnfDiagram-O67JEVCF-Cql7eDy4.js → abnfDiagram-YUKMZFIV-SkyTmgW4.js} +1 -1
  8. package/dist/assets/architecture-WOLXFQ4H-CM95IIgB.js +1 -0
  9. package/dist/assets/{architectureDiagram-NJMV4G6O-BPwX4Kdv.js → architectureDiagram-47P4ROYG-ByS1TeDn.js} +3 -3
  10. package/dist/assets/{blockDiagram-BEXU5L5S-CfKfvGbN.js → blockDiagram-E7TT5TSB-B63Mpb7u.js} +3 -3
  11. package/dist/assets/{c4Diagram-YGBWAQC7-BxkigRnJ.js → c4Diagram-CRA5TL53-COpls4b7.js} +2 -2
  12. package/dist/assets/channel-DoAEsayh.js +1 -0
  13. package/dist/assets/chunk-2BW5OAIV-CERsR7O1.js +10 -0
  14. package/dist/assets/chunk-3YJQHVM4-C-RkS8_W.js +2 -0
  15. package/dist/assets/{chunk-7PRAP22T-C14mAIqt.js → chunk-4EA7E6EY-DljY5Vea.js} +1 -1
  16. package/dist/assets/{chunk-GWA4HPMP-CB4GBCTJ.js → chunk-7TKQ45FW-BHujPREU.js} +1 -1
  17. package/dist/assets/{chunk-GTNCS2PH-C-P8WS3W.js → chunk-AW2ZBBNX-dzHLAvxJ.js} +3 -3
  18. package/dist/assets/{chunk-5DYCD2WN-DAiWE_L-.js → chunk-DBDB3WZW-DC4yHCqL.js} +1 -1
  19. package/dist/assets/{chunk-MBY4JIJT-IceNmBgh.js → chunk-E2ZNV5FY-KLIXtwwn.js} +3 -3
  20. package/dist/assets/chunk-EU5HNXII-Tj5v-IRx.js +213 -0
  21. package/dist/assets/chunk-FVRAUYC3-DdVUZ61w.js +2 -0
  22. package/dist/assets/{chunk-742MDFTN-0z7Oj4QR.js → chunk-HJ2JQQFS-CjEREIc6.js} +1 -1
  23. package/dist/assets/{chunk-NETBCI7D-BSYUBzfP.js → chunk-HTAEGDNF-DXbImTGg.js} +1 -1
  24. package/dist/assets/{chunk-WEXAMYUT-DWwNhWea.js → chunk-KQW6MTUR-CsxYVqP9.js} +1 -1
  25. package/dist/assets/chunk-NGNAAXSQ-DqoPQk1a.js +136 -0
  26. package/dist/assets/{chunk-UA2S7LBM-CefRaTtZ.js → chunk-NTY3LDVX-CM3aWKgJ.js} +1 -1
  27. package/dist/assets/chunk-VPRB5NB3-Dc2uf9TY.js +127 -0
  28. package/dist/assets/{chunk-7INBJB4K-Cbz_RcWe.js → chunk-XC4XBNZT-CH1Gjc2H.js} +10 -10
  29. package/dist/assets/{classDiagram-v2-NBCMYWYE-DvblB8tx.js → classDiagram-v2-K4WV4PDN-Qf_583Bq.js} +3 -3
  30. package/dist/assets/cynefin-EF2NZ3EQ-D5NGnJyz.js +1 -0
  31. package/dist/assets/cynefinDiagram-3GCD6N5R-B1c5B1hi.js +62 -0
  32. package/dist/assets/dagre-W4DXFKR2-CFUKRvsS.js +4 -0
  33. package/dist/assets/{diagram-22UHCM2B-CBr8zyf2.js → diagram-2UJZ2QOL-CgwAnu_N.js} +7 -7
  34. package/dist/assets/{diagram-3UASUU5V-CPh2LwGn.js → diagram-OVF4WLC6-DJfRPYa-.js} +11 -11
  35. package/dist/assets/{diagram-CDSNMT55-9xay63d0.js → diagram-PFPMY2P6-NKbYv6LB.js} +3 -3
  36. package/dist/assets/{diagram-MLGK6HIB-B1gXSuka.js → diagram-UMYRVEAY-CEWBeTVA.js} +1 -1
  37. package/dist/assets/diagram-YEKJPTXX-BauLDfmp.js +41 -0
  38. package/dist/assets/diagram-ZIFT7M5P-C8eZFcrJ.js +3 -0
  39. package/dist/assets/{dist-DiGRwfdQ.js → dist-5qUL9E-O.js} +1 -1
  40. package/dist/assets/{dist-BvxNDT5q.js → dist-BexzXBQp.js} +1 -1
  41. package/dist/assets/{dist-COSExo-X.js → dist-CG1_4YKv.js} +1 -1
  42. package/dist/assets/{dist-CRU2Hx9e.js → dist-Cqkp1P6V.js} +1 -1
  43. package/dist/assets/{dist-B6NNAnlb.js → dist-SZQE7Hxo.js} +1 -1
  44. package/dist/assets/{ebnfDiagram-ZINNZB2B-zw7fYT4W.js → ebnfDiagram-VR2GEFS7-BJX7_1tc.js} +1 -1
  45. package/dist/assets/elk-IJKZMXRS-oB1ckXbA.js +27 -0
  46. package/dist/assets/{erDiagram-OPXOYQCR-D3ybTKMV.js → erDiagram-O2IAPWRE-nZimRIPA.js} +2 -2
  47. package/dist/assets/eventmodeling-K75KTNOO-l9iWQ9RK.js +1 -0
  48. package/dist/assets/flowDiagram-OXPTDLAJ-CJJjs3vD.js +1 -0
  49. package/dist/assets/ganttDiagram-R7TSEDQI-CyMLs7TU.js +292 -0
  50. package/dist/assets/gitGraph-VSP46ZUC-BEpJvye-.js +1 -0
  51. package/dist/assets/gitGraphDiagram-XJZIOB7I-ByePDxBx.js +106 -0
  52. package/dist/assets/graphlib-DS17s2tU.js +1 -0
  53. package/dist/assets/{index-ywZ0KgS0.js → index-BXvDinV4.js} +379 -379
  54. package/dist/assets/index-pILcO9oj.css +1 -0
  55. package/dist/assets/info-OHQRW6UA-BEh0sGQl.js +1 -0
  56. package/dist/assets/{infoDiagram-VRGFBTTK-CIahd7m7.js → infoDiagram-5W2HQ5XZ-CoRqQAQS.js} +1 -1
  57. package/dist/assets/{ishikawaDiagram-OU5B5YK6-_pJwf5z0.js → ishikawaDiagram-K3B6WC7H-CFOPrQ_I.js} +2 -2
  58. package/dist/assets/{journeyDiagram-ZHPQQLJL-DVu1r_Sp.js → journeyDiagram-COXZFDF6-gzUbBOK5.js} +3 -3
  59. package/dist/assets/{kanban-definition-PNTS6WVX-Bvf-1KYk.js → kanban-definition-P3RFRI5V-DUQhCT4u.js} +9 -9
  60. package/dist/assets/{line-B8A3a3kh.js → line-D3rU2hl4.js} +1 -1
  61. package/dist/assets/{manifold-B4xOJLzD.js → manifold-BJaPjNyo.js} +1 -1
  62. package/dist/assets/{marp-xhjNh7vr.js → marp-Cg8ZzZOC.js} +1 -1
  63. package/dist/assets/{mermaid-parser.core-DaS4aeEe.js → mermaid-parser.core-DIqrGlMr.js} +6 -6
  64. package/dist/assets/mermaid.core-Dx2cEWgm.js +44 -0
  65. package/dist/assets/{mindmap-definition-NLK3R4M7-CpbXJFTL.js → mindmap-definition-LZFPQGKD-BM-Q6ddb.js} +24 -24
  66. package/dist/assets/packet-JDAUHWVQ-Exh4d5oj.js +1 -0
  67. package/dist/assets/{pegDiagram-GJSIUBJH-Dbc2tsyn.js → pegDiagram-BGZESJAR-HqoWZ9UC.js} +1 -1
  68. package/dist/assets/pie-XZMESJXO-sNNkz3IM.js +1 -0
  69. package/dist/assets/pieDiagram-CAPJLFHJ-R_gRc3es.js +39 -0
  70. package/dist/assets/{quadrantDiagram-O4NWA36T-D-DOXsw7.js → quadrantDiagram-GDMTTRAM-CxwbHNOW.js} +3 -3
  71. package/dist/assets/radar-ABXABTNO-DmV2rgcW.js +1 -0
  72. package/dist/assets/railroad-I3PHUGI6-B5psHt4W.js +1 -0
  73. package/dist/assets/railroad-abnf-LNEFI6M7-B6y_99cJ.js +1 -0
  74. package/dist/assets/railroad-ebnf-DWYD2UWJ-y-Kd7gZ3.js +1 -0
  75. package/dist/assets/railroad-peg-JR7BVNR7-CxfusapB.js +1 -0
  76. package/dist/assets/{railroadDiagram-XR7U4H2S-D3kqADTa.js → railroadDiagram-SM67HX2B--Of4jUTW.js} +1 -1
  77. package/dist/assets/{requirementDiagram-PLB6GJNP-YpGYYUq8.js → requirementDiagram-X7JNWC4A-PUmP8iBy.js} +4 -4
  78. package/dist/assets/sankeyDiagram-UM26HJRW-QOUjE5vE.js +40 -0
  79. package/dist/assets/sequenceDiagram-ZO4K6R2Y-B_NhAqQo.js +169 -0
  80. package/dist/assets/{stateDiagram-v2-GCMORJYK-D9jE6vCT.js → stateDiagram-v2-TBUQTH76-DLae5vuD.js} +2 -2
  81. package/dist/assets/{swimlanes-2SLR337P-Bw9cn0PT.js → swimlanes-N4OXWK64-OwABGfpU.js} +1 -1
  82. package/dist/assets/swimlanesDiagram-K3J5GTZL-rwxwvgbV.js +8 -0
  83. package/dist/assets/{timeline-definition-EJHVYXUP-vzEfdjnB.js → timeline-definition-YOQAKGHF-CgSC2U-i.js} +4 -4
  84. package/dist/assets/treeView-D4JQ5SDB--wgcnlHV.js +1 -0
  85. package/dist/assets/treemap-SAJKECNS-C6AM9Gjr.js +1 -0
  86. package/dist/assets/usecaseDiagram-VIAY4XPW-BROLO6TX.js +328 -0
  87. package/dist/assets/{vennDiagram-UO4OBE2U-C6RtvXya.js → vennDiagram-BLWOH2XV-DyBDCNhd.js} +3 -3
  88. package/dist/assets/wardley-7MLQ67FV-Bn1Ug7mB.js +1 -0
  89. package/dist/assets/{wardleyDiagram-VNRHLVJA-DeqTsydI.js → wardleyDiagram-YMQ3BMBF-Dv2yg39H.js} +3 -3
  90. package/dist/assets/xychartDiagram-TAQBALBS-C48R0SI0.js +7 -0
  91. package/dist/index.html +2 -2
  92. package/docs/guide/en/v9.2.0.md +113 -0
  93. package/docs/guide/en/v9.3.0.md +41 -0
  94. package/docs/guide/ja/v9.2.0.md +106 -0
  95. package/docs/guide/ja/v9.3.0.md +39 -0
  96. package/package.json +8 -8
  97. package/server/agents/account-rate-limits.ts +57 -30
  98. package/server/agents/probe-stall.spec.ts +27 -0
  99. package/server/agents/probe-stall.ts +12 -2
  100. package/server/agents/rate-limit-probe.spec.ts +36 -0
  101. package/server/agents/rate-limit-probe.ts +5 -1
  102. package/server/agents/rate-limit-routes.ts +4 -1
  103. package/server/agents/rate-limit-service.ts +66 -20
  104. package/server/agents/token-assignment.ts +98 -0
  105. package/server/agents/token-choice.ts +96 -0
  106. package/server/agents/token-rotation-service.ts +110 -0
  107. package/server/agents/token-secret.ts +87 -0
  108. package/server/config/app-config.ts +15 -0
  109. package/server/config/config-routes.ts +6 -0
  110. package/server/index.ts +38 -4
  111. package/server/routes/app-routes.ts +6 -0
  112. package/server/routes/hook-routes.ts +17 -4
  113. package/server/session/activity-hook.ts +3 -0
  114. package/server/session/hook-settings.ts +3 -0
  115. package/server/session/limit-rotation.ts +78 -0
  116. package/server/session/rotation-notice.ts +29 -0
  117. package/server/session/session-credential.ts +35 -0
  118. package/server/session/spawn-claude.ts +64 -7
  119. package/server/session/spawn-deps.ts +6 -0
  120. package/server/session/token-session-log.ts +34 -0
  121. package/server/session/token-sessions.ts +52 -0
  122. package/server/skills/mulmoterminal-config/SKILL.md +20 -2
  123. package/server/skills/mulmoterminal-header/SKILL.md +1 -1
  124. package/server/skills/mulmoterminal-keys/SKILL.md +1 -0
  125. package/server/skills/mulmoterminal-model/SKILL.md +58 -0
  126. package/dist/assets/_basePickBy-QJXUw_pr.js +0 -1
  127. package/dist/assets/_baseUniq-DR4vVjyC.js +0 -1
  128. package/dist/assets/architecture-7GRP2DOG-CZIYYJ4J.js +0 -1
  129. package/dist/assets/channel-C7PAQOe4.js +0 -1
  130. package/dist/assets/chunk-3FUC2YCW-HE1nZO4E.js +0 -2
  131. package/dist/assets/chunk-7M6MHVWA-D0fHotJI.js +0 -213
  132. package/dist/assets/chunk-FOHPRMQF-DzxwRta7.js +0 -161
  133. package/dist/assets/chunk-O7XYJQB3-BWsd-NGX.js +0 -126
  134. package/dist/assets/chunk-Z7XXMR3K-AxGELBoY.js +0 -10
  135. package/dist/assets/chunk-ZIGJFQKS-BjGm6Q_-.js +0 -2
  136. package/dist/assets/cynefin-OW5HDTMX-DtUMK6KD.js +0 -1
  137. package/dist/assets/cynefinDiagram-VND7K2PF-CAwbB6dF.js +0 -62
  138. package/dist/assets/dagre-6A5THRUB-D5XInuQL.js +0 -4
  139. package/dist/assets/diagram-ATOU4E4O-CZtXRSJ1.js +0 -3
  140. package/dist/assets/diagram-MPIPVDR6-D2cdkgqI.js +0 -41
  141. package/dist/assets/elk-276RUBZZ-BnbzbO70.js +0 -27
  142. package/dist/assets/eventmodeling-NTZA5JFV-Du3-gAJl.js +0 -1
  143. package/dist/assets/flowDiagram-KWPJA3E3-DM38I9KM.js +0 -1
  144. package/dist/assets/ganttDiagram-FUAMR5RP-B9IuJaJ7.js +0 -292
  145. package/dist/assets/gitGraph-4MIJSDKK-CACnw5Rn.js +0 -1
  146. package/dist/assets/gitGraphDiagram-X574FWY7-mJeRIXgv.js +0 -106
  147. package/dist/assets/graphlib-C9Tl7QrK.js +0 -1
  148. package/dist/assets/index-B-X7-YRY.css +0 -1
  149. package/dist/assets/info-A6RAGUB7-B_99gUG6.js +0 -1
  150. package/dist/assets/mermaid.core-CnYkvp8n.js +0 -44
  151. package/dist/assets/packet-AYTQ26CC-jcWTTmks.js +0 -1
  152. package/dist/assets/pie-WAS4IAKB-CizFfv6i.js +0 -1
  153. package/dist/assets/pieDiagram-5QR66LMP-DXX3IcV9.js +0 -39
  154. package/dist/assets/radar-RG4KPBEZ-DVaylYAV.js +0 -1
  155. package/dist/assets/railroad-74A4TZTK-DEszSi6n.js +0 -1
  156. package/dist/assets/railroad-abnf-HS5TGJTU-BvKljvCm.js +0 -1
  157. package/dist/assets/railroad-ebnf-LZEXJU2U-C0PANGU1.js +0 -1
  158. package/dist/assets/railroad-peg-WCYAUIDC-iA0I8YHa.js +0 -1
  159. package/dist/assets/sankeyDiagram-IPEJSGJF-R4KL4_Zi.js +0 -40
  160. package/dist/assets/sequenceDiagram-PO4LG4MO-tIIojIJD.js +0 -169
  161. package/dist/assets/swimlanesDiagram-TC7HE7FX-BzC92HZb.js +0 -8
  162. package/dist/assets/treeView-Q6P3EWNA-Lt-Je8zH.js +0 -1
  163. package/dist/assets/treemap-WGGIJYW6-BY_7_NMi.js +0 -1
  164. package/dist/assets/usecaseDiagram-POWQR4AR-Dgsia1SG.js +0 -328
  165. package/dist/assets/wardley-WFR3VGLG-B2gP_do0.js +0 -1
  166. package/dist/assets/xychartDiagram-PMCCYNJV-D2Sngf-8.js +0 -7
package/README.md CHANGED
@@ -805,6 +805,7 @@ The Settings modal (the gear button) persists per-user UI choices to `~/.mulmote
805
805
  | `launchers` | `{ label, command }` entries offered in a grid cell's launcher besides the agents — any interactive command. A plain shell needs no entry: the Agent Picker's **Shell** option opens `$SHELL` unconfigured. |
806
806
  | `customAgents` | `{ id, label, agent, command }` entries offered in the **Agent Picker** — your own way of starting Claude Code (`ollama launch claude --model … --`, a wrapper script). Unlike a launcher, Claude Code's own argv is **appended** to `command`, so the cell is a real session: resume, cost, context, GUI tools. `agent` says which agent's arguments to append and is required (`"claude"` is the only value today); `command` must stop taking arguments where Claude Code's begin — hence the trailing `--` above. Up to 8. |
807
807
  | `accounts` | **Beta** ([setup guide](docs/guide/en/accounts.md)). `{ id, label, agent, home }` entries — a second login for Claude Code (`agent: "claude"`) or Codex (`"codex"`), kept in its own config directory `home` (absolute or `~/…`). Picked per cell in the launch form's **ACCOUNT** select (the cell's header then names it); a session started on one runs with `CLAUDE_CONFIG_DIR` / `CODEX_HOME` set to it, and is bound to that account for good: resume, cost, titles and history are read from there, session lists show every account's rows, and the toolbar shows each account's 5h / 7d usage beside the default's. An account pointed at `~/.claude` / `~/.codex` itself is treated as the default login: no variable, no gauge of its own. A cell with no account gets no such variable added (whatever MulmoTerminal's own environment carries is inherited as before), so configuring none changes nothing. Up to 8. |
808
+ | `tokenRotation` | **Beta** ([setup guide](docs/guide/en/token-rotation.md)). `{ enabled, includeDefaultLogin, tokens: [{ id, label, email?, keychain \| file }] }` — several Claude subscriptions behind the one `~/.claude`. Each new plain Claude process starts on the subscription whose 7-day window has the most room per hour until its reset; a session whose subscription is at 98% moves to another at the end of its turn, and one that hits its limit moves at once — the cell reconnects and resumes the same conversation. A token is named by where it is kept (a keychain item, or a `chmod 600` file), never written here. Provider, custom-agent and account cells are never rotated. Up to 8. |
808
809
  | `quickCommands` | `{ label, text, agents? }` phrases the **phone** offers as chips on a session's terminal view. Tapping one puts `text` in the input box; it is not sent until you press send. `agents` scopes a chip to session kinds — any of `SESSION_AGENTS` (`"claude"`, `"codex"`, `"antigravity"`, `"grok"`, `"muse"`, `"copilot"`, `"cursor"`, `"shell"`); omit it to offer the chip everywhere. Empty by default. |
809
810
  | `userMcpServers` | `{ id, url }` HTTP MCP servers merged into the `--mcp-config` of the **Claude** sessions that carry the full GUI MCP (codex is handed the GUI server alone, `codexGuiMcpServers`) — a cell whose working directory is the **workspace**, and a session the server starts itself (the phone, a scheduled task) unless it asks for a grid cell's shape, as an issue's seed session does (`issueSpawnOptions`). A cell in a project directory does not get this merge; the MCP config the user wrote is read either way. Takes effect on the next session. |
810
811
  | `buttons` | Header action buttons — see [Header buttons](#header-buttons). Omit to keep the defaults; set to replace them. |
@@ -851,7 +852,7 @@ takes effect; it costs a resume and asks nothing first. `"terminal-new-here"` op
851
852
  on the cell's directory, and `"pane-files"` / `"pane-prompts"` / … / `"terminal-timeline"` /
852
853
  `"terminal-talk"` / `"terminal-park"` / `"terminal-copy-code"` / `"terminal-insert-path"` / `"terminal-reveal"` /
853
854
  `"terminal-voice"` / `"terminal-diff"` / `"terminal-note"` do what the cell's menus and buttons do, and `"screen-*"` / `"settings-open"` /
854
- `"sound-toggle"` / `"view-toggle"` / `"order-*"` / `"page-next"` / `"page-prev"` do what the toolbar and the page tabs do; the old name `"restart"` still works). An `open` button targets one of `url` / `reveal` (OS file manager) / `files`
855
+ `"sound-toggle"` / `"confetti"` / `"view-toggle"` / `"order-*"` / `"page-next"` / `"page-prev"` do what the toolbar and the page tabs do; the old name `"restart"` still works). An `open` button targets one of `url` / `reveal` (OS file manager) / `files`
855
856
  (in-app explorer) / `view` (a built-in overlay) / `terminal` (a dir → a new cell running `$SHELL`,
856
857
  opened next to the current one) / `pr: true` (open the current branch's PR — the button is hidden when
857
858
  there's no open PR) / `pickFile: true` (OS file dialog → insert the path).
@@ -23,6 +23,7 @@ export const APP_ACTIONS = [
23
23
  ...SCREEN_ACTIONS,
24
24
  "settings-open",
25
25
  "sound-toggle",
26
+ "confetti",
26
27
  "view-toggle",
27
28
  ...ORDER_ACTIONS,
28
29
  "page-next",
@@ -0,0 +1,50 @@
1
+ // The `confetti` setting: which kinds of celebration exist, and which app events set one off.
2
+ // Both sides decide from it — the server keeps it in the config, the browser draws.
3
+ import { isRecord } from "./isRecord.js";
4
+
5
+ export const CONFETTI_STYLES = ["cracker", "fireworks", "sakura", "rain", "balloons"] as const;
6
+ export type ConfettiStyle = (typeof CONFETTI_STYLES)[number];
7
+ export const isConfettiStyle = (value: unknown): value is ConfettiStyle => CONFETTI_STYLES.some((style) => style === value);
8
+
9
+ export const CONFETTI_EVENTS = ["pr-merged", "turn-finished", "command-done"] as const;
10
+ export type ConfettiEvent = (typeof CONFETTI_EVENTS)[number];
11
+ export const isConfettiEvent = (value: unknown): value is ConfettiEvent => CONFETTI_EVENTS.some((event) => event === value);
12
+
13
+ export interface Confetti {
14
+ /** What a celebration may be; each one mixes a few picked at random from these. */
15
+ styles: readonly ConfettiStyle[];
16
+ /** Events that set one off by themselves. The keyboard shortcut and the palette work regardless. */
17
+ events: readonly ConfettiEvent[];
18
+ }
19
+
20
+ export const CONFETTI_DEFAULT: Confetti = { styles: CONFETTI_STYLES, events: [] };
21
+
22
+ const onlyKnown = <T>(input: unknown, isKnown: (value: unknown) => value is T): T[] => (Array.isArray(input) ? [...new Set(input.filter(isKnown))] : []);
23
+
24
+ /** Anything unrecognised is "unconfigured". An empty or all-unknown `styles` falls back to every
25
+ * style rather than to none: a celebration that can draw nothing is a shortcut that silently
26
+ * does nothing. `events` has no such fallback — empty is how it is switched off. */
27
+ export function sanitizeConfetti(input: unknown): Confetti {
28
+ if (!isRecord(input)) return CONFETTI_DEFAULT;
29
+ const styles = onlyKnown(input.styles, isConfettiStyle);
30
+ return { styles: styles.length > 0 ? styles : CONFETTI_STYLES, events: onlyKnown(input.events, isConfettiEvent) };
31
+ }
32
+
33
+ /** How many styles one celebration mixes, when the list has that many. */
34
+ export const CONFETTI_MIX_COUNT = 3;
35
+
36
+ /** `count` different styles at random from `styles` (all of them when there are fewer);
37
+ * `random` is injected so a spec can walk every choice. */
38
+ export function pickConfettiMix(styles: readonly ConfettiStyle[], count: number, random: () => number): ConfettiStyle[] {
39
+ const pool = [...(styles.length > 0 ? styles : CONFETTI_STYLES)];
40
+ return Array.from(
41
+ { length: Math.min(count, pool.length) },
42
+ () => pool.splice(Math.min(pool.length - 1, Math.floor(random() * pool.length)), 1)[0] ?? "cracker",
43
+ );
44
+ }
45
+
46
+ /** One style at random from `styles`; `random` is injected so a spec can walk every choice. */
47
+ export function pickConfettiStyle(styles: readonly ConfettiStyle[], random: () => number): ConfettiStyle {
48
+ const pool = styles.length > 0 ? styles : CONFETTI_STYLES;
49
+ return pool[Math.min(pool.length - 1, Math.floor(random() * pool.length))] ?? "cracker";
50
+ }
package/common/keymap.ts CHANGED
@@ -64,6 +64,7 @@ export const KEYMAP_ACTIONS = [
64
64
  "screen-skills",
65
65
  "settings-open",
66
66
  "sound-toggle",
67
+ "confetti",
67
68
  "view-toggle",
68
69
  "order-auto",
69
70
  "order-manual",
@@ -3,7 +3,7 @@
3
3
  // the picture and draws the level.
4
4
  import { isRecord } from "./isRecord.js";
5
5
 
6
- export const HEAT_PATTERNS = ["bomb", "volcano", "kettle", "rocket", "dynamite", "balloon", "skull"] as const;
6
+ export const HEAT_PATTERNS = ["bomb", "volcano", "kettle", "rocket", "dynamite", "balloon", "skull", "sumo"] as const;
7
7
  export type HeatPattern = (typeof HEAT_PATTERNS)[number];
8
8
  export const isHeatPattern = (value: unknown): value is HeatPattern => HEAT_PATTERNS.some((pattern) => pattern === value);
9
9
 
@@ -0,0 +1,103 @@
1
+ // TOKEN ROTATION: several Claude subscriptions behind ONE config home (#2919).
2
+ //
3
+ // Not an account (common/agentAccounts.ts). An account is a second home, so its conversations stay
4
+ // in it; a rotation token is only a credential, handed to a session as CLAUDE_CODE_OAUTH_TOKEN, so
5
+ // every conversation stays in the default home and can be resumed on any token. Measured: the
6
+ // token outranks the `/login` credential and its statusLine reports that subscription's own windows.
7
+ //
8
+ // The secret is never in config.json. An entry names WHERE it is — a keychain item or a file — and
9
+ // the server reads it per spawn.
10
+ import { isRecord } from "./isRecord.js";
11
+
12
+ export interface RotationToken {
13
+ /** Stable slug. It keys the per-session record and the usage meter. */
14
+ id: string;
15
+ label: string;
16
+ /** The subscription's sign-in address, written by the user and shown beside the usage — the token
17
+ * says nothing about whose it is that this server can read. */
18
+ email?: string;
19
+ /** macOS keychain item: `security find-generic-password -a <keychainAccount> -s <keychain> -w`. */
20
+ keychain?: string;
21
+ /** Defaults to KEYCHAIN_ACCOUNT_DEFAULT. */
22
+ keychainAccount?: string;
23
+ /** A file holding the token, absolute or `~/`-relative. */
24
+ file?: string;
25
+ }
26
+
27
+ export interface TokenRotation {
28
+ enabled: boolean;
29
+ /** Whether the `/login` credential takes part as one more candidate. */
30
+ includeDefaultLogin: boolean;
31
+ tokens: RotationToken[];
32
+ }
33
+
34
+ export const KEYCHAIN_ACCOUNT_DEFAULT = "mulmoterminal";
35
+
36
+ /** The id the `/login` credential is recorded under. Not a valid token id, so it cannot collide. */
37
+ export const DEFAULT_LOGIN_ID = "@default";
38
+
39
+ export const ROTATION_TOKEN_ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
40
+ export const ROTATION_TOKEN_LABEL_MAX = 24;
41
+ export const ROTATION_TOKEN_REF_MAX = 500;
42
+ export const ROTATION_TOKENS_MAX = 8;
43
+ export const ROTATION_TOKEN_EMAIL_MAX = 254;
44
+
45
+ /** What a terminal line calls the `/login` credential. */
46
+ export const DEFAULT_LOGIN_LABEL = "the /login account";
47
+
48
+ /** What a terminal line calls a credential: its label and address, the `/login` one by name, and a
49
+ * token gone from the config by its id. */
50
+ export function rotationLoginLabel(rotation: TokenRotation, tokenId: string): string {
51
+ if (tokenId === DEFAULT_LOGIN_ID) return DEFAULT_LOGIN_LABEL;
52
+ const token = rotation.tokens.find((candidate) => candidate.id === tokenId);
53
+ if (!token) return tokenId;
54
+ return token.email ? `${token.label} (${token.email})` : token.label;
55
+ }
56
+
57
+ export const TOKEN_ROTATION_OFF: TokenRotation = { enabled: false, includeDefaultLogin: true, tokens: [] };
58
+
59
+ export const isRotationTokenId = (value: unknown): value is string => typeof value === "string" && ROTATION_TOKEN_ID_RE.test(value);
60
+
61
+ const isTokenFile = (value: unknown): value is string =>
62
+ typeof value === "string" && (value.startsWith("/") || value.startsWith("~/") || /^[A-Za-z]:[\\/]/.test(value));
63
+
64
+ const optionalEmail = (value: unknown): string | undefined => {
65
+ if (typeof value !== "string") return undefined;
66
+ const trimmed = value.trim();
67
+ return trimmed.length <= ROTATION_TOKEN_EMAIL_MAX && /^[^\s@]+@[^\s@]+$/.test(trimmed) ? trimmed : undefined;
68
+ };
69
+
70
+ const optionalRef = (value: unknown): string | undefined => {
71
+ if (typeof value !== "string") return undefined;
72
+ const trimmed = value.trim();
73
+ return trimmed && trimmed.length <= ROTATION_TOKEN_REF_MAX ? trimmed : undefined;
74
+ };
75
+
76
+ /** One config row as a RotationToken, or null. Exactly one of `keychain` / `file` must be set. */
77
+ export function rotationTokenFrom(row: unknown): RotationToken | null {
78
+ if (!isRecord(row) || typeof row.id !== "string" || typeof row.label !== "string") return null;
79
+ const id = row.id.trim();
80
+ const label = row.label.trim().slice(0, ROTATION_TOKEN_LABEL_MAX);
81
+ const keychain = optionalRef(row.keychain);
82
+ const file = optionalRef(row.file);
83
+ if (!isRotationTokenId(id) || !label) return null;
84
+ const email = optionalEmail(row.email);
85
+ const named = email ? { id, label, email } : { id, label };
86
+ if (keychain !== undefined && file === undefined) {
87
+ const keychainAccount = optionalRef(row.keychainAccount);
88
+ return keychainAccount ? { ...named, keychain, keychainAccount } : { ...named, keychain };
89
+ }
90
+ return file !== undefined && keychain === undefined && isTokenFile(file) ? { ...named, file } : null;
91
+ }
92
+
93
+ /** The `tokenRotation` key read back, with every malformed or duplicate row dropped. */
94
+ export function sanitizeTokenRotation(input: unknown): TokenRotation {
95
+ if (!isRecord(input)) return TOKEN_ROTATION_OFF;
96
+ const rows = Array.isArray(input.tokens) ? input.tokens : [];
97
+ const tokens = rows
98
+ .map(rotationTokenFrom)
99
+ .filter((token): token is RotationToken => token !== null)
100
+ .filter((token, index, all) => all.findIndex((other) => other.id === token.id) === index)
101
+ .slice(0, ROTATION_TOKENS_MAX);
102
+ return { enabled: input.enabled === true, includeDefaultLogin: input.includeDefaultLogin !== false, tokens };
103
+ }
@@ -1 +1 @@
1
- import{m as e,t}from"./mermaid-parser.core-DaS4aeEe.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-CllyRBe0.js";import"./chunk-O7XYJQB3-BWsd-NGX.js";import{n as i,r as a,t as o}from"./chunk-GTNCS2PH-C-P8WS3W.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-CnYkvp8n.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
1
+ import{m as e,t}from"./mermaid-parser.core-DIqrGlMr.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-CllyRBe0.js";import"./chunk-VPRB5NB3-Dc2uf9TY.js";import{n as i,r as a,t as o}from"./chunk-AW2ZBBNX-dzHLAvxJ.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-Dx2cEWgm.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
@@ -0,0 +1 @@
1
+ import"./chunk-NGNAAXSQ-DqoPQk1a.js";import{M as e}from"./mermaid-parser.core-DIqrGlMr.js";export{e as createArchitectureServices};