frayui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/claude-agent-broker.js +21641 -0
- package/dist/codex-app-server-daemon.js +354 -0
- package/dist/dev-child.js +40319 -0
- package/dist/frayui.js +8738 -0
- package/package.json +46 -0
- package/runtime/cc/scripts/fray/agent-bindings.mjs +287 -0
- package/runtime/cc/scripts/fray/agent-liveness.mjs +367 -0
- package/runtime/cc/scripts/fray/agent-status.mjs +178 -0
- package/runtime/cc/scripts/fray/config.mjs +982 -0
- package/runtime/cc/scripts/fray/decisions.mjs +97 -0
- package/runtime/cc/scripts/fray/index.mjs +699 -0
- package/runtime/cc/scripts/fray/notify-shared.mjs +90 -0
- package/runtime/cc/scripts/fray/notify.mjs +81 -0
- package/runtime/cc/scripts/fray/ownership.mjs +120 -0
- package/runtime/cc/scripts/fray/rest-detect.mjs +213 -0
- package/runtime/cc/scripts/fray/thread-excerpt.mjs +162 -0
- package/runtime/cc/scripts/fray/thread-update.mjs +285 -0
- package/runtime/cc-worker/.claude-plugin/plugin.json +10 -0
- package/runtime/cc-worker/DECISIONS.md +923 -0
- package/runtime/cc-worker/LICENSE +21 -0
- package/runtime/cc-worker/agents/fable-high.md +8 -0
- package/runtime/cc-worker/agents/fable-low.md +8 -0
- package/runtime/cc-worker/agents/fable-max.md +8 -0
- package/runtime/cc-worker/agents/fable-medium.md +8 -0
- package/runtime/cc-worker/agents/fable-xhigh.md +8 -0
- package/runtime/cc-worker/agents/haiku.md +7 -0
- package/runtime/cc-worker/agents/opus-high.md +8 -0
- package/runtime/cc-worker/agents/opus-low.md +8 -0
- package/runtime/cc-worker/agents/opus-max.md +8 -0
- package/runtime/cc-worker/agents/opus-medium.md +8 -0
- package/runtime/cc-worker/agents/opus-xhigh.md +8 -0
- package/runtime/cc-worker/agents/sonnet-high.md +8 -0
- package/runtime/cc-worker/agents/sonnet-low.md +8 -0
- package/runtime/cc-worker/agents/sonnet-max.md +8 -0
- package/runtime/cc-worker/agents/sonnet-medium.md +8 -0
- package/runtime/cc-worker/agents/sonnet-xhigh.md +8 -0
- package/runtime/cc-worker/bin/fray +17 -0
- package/runtime/cc-worker/bin/fray-mcp.mjs +248 -0
- package/runtime/cc-worker/bin/fray-update +18 -0
- package/runtime/cc-worker/hooks/agent-bind.mjs +40 -0
- package/runtime/cc-worker/hooks/agent-dispatch.mjs +74 -0
- package/runtime/cc-worker/hooks/bash-background.d.mts +6 -0
- package/runtime/cc-worker/hooks/bash-background.mjs +188 -0
- package/runtime/cc-worker/hooks/deny-ask.mjs +38 -0
- package/runtime/cc-worker/hooks/deny-plan.mjs +61 -0
- package/runtime/cc-worker/hooks/hooks.json +111 -0
- package/runtime/cc-worker/hooks/perm-policy.mjs +211 -0
- package/runtime/cc-worker/hooks/precompact-instructions.mjs +122 -0
- package/runtime/cc-worker/hooks/scratchpad-stop.mjs +125 -0
- package/runtime/cc-worker/hooks/scratchpad.mjs +419 -0
- package/runtime/cc-worker/hooks/session-seed.mjs +105 -0
- package/runtime/cc-worker/scripts/fray/agent-bindings.mjs +9 -0
- package/runtime/cc-worker/scripts/fray/config.mjs +12 -0
- package/runtime/cc-worker/skills/gh/SKILL.md +132 -0
- package/runtime/cc-worker/skills/gh/scripts/ci-watch.mjs +60 -0
- package/runtime/cc-worker/skills/gh/scripts/github-watch.mjs +130 -0
- package/runtime/cc-worker/skills/gh/scripts/review-watch.mjs +54 -0
- package/runtime/cc-worker/skills/handoff/SKILL.md +203 -0
- package/runtime/cc-worker/skills/waits/SKILL.md +83 -0
- package/web-dist/apple-touch-icon.png +0 -0
- package/web-dist/assets/TerminalPane-fMGrgB-a.js +7 -0
- package/web-dist/assets/abnfDiagram-VRR7QNED-BtlrDuYX.js +1 -0
- package/web-dist/assets/arc-BSyeo0Gb.js +1 -0
- package/web-dist/assets/architecture-TIHT7OUA-CuD8jpr-.js +1 -0
- package/web-dist/assets/architectureDiagram-ZJ3FMSHR-CdLQ71be.js +36 -0
- package/web-dist/assets/array-BifhSqXX.js +1 -0
- package/web-dist/assets/blockDiagram-677ZJIJ3-CBk2FHEj.js +132 -0
- package/web-dist/assets/c4Diagram-LMCZKHZV-mZ3HQ6WX.js +10 -0
- package/web-dist/assets/channel-C2eUWc74.js +1 -0
- package/web-dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/web-dist/assets/chunk-32BRIVSS-Daxvi7f5.js +1 -0
- package/web-dist/assets/chunk-52WLFC77-BGJoZvry.js +10 -0
- package/web-dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/web-dist/assets/chunk-7BUUIJ7U-Bb538aSH.js +1 -0
- package/web-dist/assets/chunk-C7G6YPKG-Ycd1yheh.js +1 -0
- package/web-dist/assets/chunk-EX3LRPZG-BPFhlsHp.js +231 -0
- package/web-dist/assets/chunk-FWX5IMBZ--xy8DTon.js +2 -0
- package/web-dist/assets/chunk-HOUHSVGY-Dq3zoygp.js +1 -0
- package/web-dist/assets/chunk-ICXQ74PX-FfKP-7yM.js +2 -0
- package/web-dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/web-dist/assets/chunk-KEIR6QF5-BfrZ3jm6.js +161 -0
- package/web-dist/assets/chunk-MOJQB5TN-BeiHnsLF.js +88 -0
- package/web-dist/assets/chunk-OGEWGWER-CjDU_9fC.js +1 -0
- package/web-dist/assets/chunk-PUDLZKDR-BO7tm5QX.js +156 -0
- package/web-dist/assets/chunk-Q4XR5HBZ-BPefovOg.js +70 -0
- package/web-dist/assets/chunk-RYQCIY6F-Cu_KplZW.js +1 -0
- package/web-dist/assets/chunk-V7JOEXUC-DWnebFHh.js +206 -0
- package/web-dist/assets/chunk-VAUOI2AC-BL6qWFhW.js +1 -0
- package/web-dist/assets/chunk-VR4S4FIN-Mfs__7L9.js +1 -0
- package/web-dist/assets/chunk-WYO6CB5R-Bf2IYbEU.js +127 -0
- package/web-dist/assets/chunk-XXDRQBXY-DYlTP5J-.js +1 -0
- package/web-dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/web-dist/assets/chunk-ZGVPDNZ5-CJ9ZHZgU.js +62 -0
- package/web-dist/assets/chunk-ZIRB5QZD-C6fEPe3t.js +32 -0
- package/web-dist/assets/classDiagram-OUVF2IWQ-GaOJNgfE.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-GaOJNgfE.js +1 -0
- package/web-dist/assets/cose-bilkent-JH36ORCC-BUIsLrGc.js +1 -0
- package/web-dist/assets/cynefin-VYW2F7L2-COSC0oNL.js +1 -0
- package/web-dist/assets/cynefinDiagram-TSTJHNR4-mInhJdg1.js +62 -0
- package/web-dist/assets/cytoscape.esm-B3I8pqwA.js +321 -0
- package/web-dist/assets/dagre-CXRCoUWR.js +1 -0
- package/web-dist/assets/dagre-VKFMJZFB-D-T5z05M.js +4 -0
- package/web-dist/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/web-dist/assets/diagram-FQU43EPY-HbMkSb3j.js +3 -0
- package/web-dist/assets/diagram-G47NLZAW-DBwZLcZh.js +24 -0
- package/web-dist/assets/diagram-NH7WQ7WH-DG_Mhays.js +24 -0
- package/web-dist/assets/diagram-OA4YK3LP-hH39gqpu.js +30 -0
- package/web-dist/assets/diagram-WEI45ONY-DhKZuwxW.js +41 -0
- package/web-dist/assets/dist-DoH_9pyS.js +1 -0
- package/web-dist/assets/ebnfDiagram-CCIWWBDH-B7BiO-NR.js +1 -0
- package/web-dist/assets/erDiagram-Q63AITRT-DHjG5RDQ.js +85 -0
- package/web-dist/assets/eventmodeling-45OFAUF4-C0eV8RBx.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-BbldRZUK.js +1 -0
- package/web-dist/assets/ganttDiagram-NO4QXBWP-BZ-98wTa.js +292 -0
- package/web-dist/assets/gitGraph-TEB2WS4Q-nF22R2jO.js +1 -0
- package/web-dist/assets/gitGraphDiagram-IHSO6WYX-CoYQQ22V.js +106 -0
- package/web-dist/assets/graphlib-B8gBHxth.js +1 -0
- package/web-dist/assets/index-BHzIN-tQ.js +357 -0
- package/web-dist/assets/index-Duiy4w7C.css +1 -0
- package/web-dist/assets/info-DKCQHKI2-Cy2BCbBW.js +1 -0
- package/web-dist/assets/infoDiagram-FWYZ7A6U-CmhR2R1x.js +2 -0
- package/web-dist/assets/init-D6jRqBbL.js +1 -0
- package/web-dist/assets/ishikawaDiagram-FXEZZL3T-CfG59afl.js +70 -0
- package/web-dist/assets/journeyDiagram-5HDEW3XC-BvFmG20q.js +139 -0
- package/web-dist/assets/kanban-definition-HUTT4EX6-BHGEecsY.js +89 -0
- package/web-dist/assets/katex-CddkPoXu.js +257 -0
- package/web-dist/assets/line-Ds4xvN3d.js +1 -0
- package/web-dist/assets/linear-z2V0wJk9.js +1 -0
- package/web-dist/assets/map-DsCK-0Cs.js +1 -0
- package/web-dist/assets/mermaid-parser.core-Gow4kgYJ.js +7 -0
- package/web-dist/assets/mermaid.core-B7Qc_kbm.js +11 -0
- package/web-dist/assets/mindmap-definition-LN4V7U3C-ayXKGcy_.js +96 -0
- package/web-dist/assets/ordinal-hYBb2elL.js +1 -0
- package/web-dist/assets/packet-7NZHBO7P-oLBdtkD8.js +1 -0
- package/web-dist/assets/path-BWPyau1x.js +1 -0
- package/web-dist/assets/pegDiagram-2B236MQR-DwJyc_1B.js +1 -0
- package/web-dist/assets/pie-RZYD4A2V-CXzad3fT.js +1 -0
- package/web-dist/assets/pieDiagram-ENE6RG2P-CZzgSGtM.js +39 -0
- package/web-dist/assets/quadrantDiagram-ABIIQ3AL--IPIp8yo.js +7 -0
- package/web-dist/assets/radar-I7S5WNFK-DldySvtZ.js +1 -0
- package/web-dist/assets/railroad-3IZDKUUU-CiWFZfXM.js +1 -0
- package/web-dist/assets/railroad-abnf-AHOZXSZD-bXMYUOCD.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-D11ZwZby.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-DD1AC5X-.js +1 -0
- package/web-dist/assets/railroadDiagram-RFXS5EU6-DcCMNGGb.js +1 -0
- package/web-dist/assets/requirementDiagram-TGXJPOKE-C-6j8urk.js +84 -0
- package/web-dist/assets/rolldown-runtime-Bh1tDfsg.js +1 -0
- package/web-dist/assets/rough.esm-CSKSodPl.js +1 -0
- package/web-dist/assets/sankeyDiagram-HTMAVEWB-CPtiOpdz.js +40 -0
- package/web-dist/assets/sequenceDiagram-DBY2YBRQ-B8FnQTdn.js +162 -0
- package/web-dist/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +1 -0
- package/web-dist/assets/src-C4XfhTaE.js +1 -0
- package/web-dist/assets/stateDiagram-2N3HPSRC-CxG9nX2P.js +1 -0
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-SGarOFIy.js +1 -0
- package/web-dist/assets/swimlanes-5IMT3BWC-D49-t0hV.js +2 -0
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-krF0bh4b.js +8 -0
- package/web-dist/assets/timeline-definition-FHXFAJF6-C9OEd9SE.js +120 -0
- package/web-dist/assets/treeView-QDETBFTQ-D1AKqVPa.js +1 -0
- package/web-dist/assets/treemap-6X3UGDF4-BRssB7hM.js +1 -0
- package/web-dist/assets/vennDiagram-L72KCM5P-CftkAbY0.js +34 -0
- package/web-dist/assets/wardley-OPB4EBWU-RLMgzRq4.js +1 -0
- package/web-dist/assets/wardleyDiagram-EHGQE667-BLytgXPS.js +78 -0
- package/web-dist/assets/xychartDiagram-FW5EYKEG-BYgK1Y3H.js +7 -0
- package/web-dist/favicon-16.png +0 -0
- package/web-dist/favicon-32.png +0 -0
- package/web-dist/favicon.svg +78 -0
- package/web-dist/icon-192.png +0 -0
- package/web-dist/icon-512.png +0 -0
- package/web-dist/icon-maskable-512.png +0 -0
- package/web-dist/index.html +33 -0
- package/web-dist/manifest.webmanifest +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Colin McDonnell
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# fray-ui
|
|
2
|
+
|
|
3
|
+
fray-ui is a workspace-scoped orchestration surface for [fray](https://github.com/colinhacks/fray): a localhost server plus a web
|
|
4
|
+
client (opened as a normal tab in your default browser) that shows a sidebar of your repo's `.fray/`
|
|
5
|
+
threads and, for the selected thread, a live embedded Claude Code terminal. You dispatch agents,
|
|
6
|
+
watch them work, follow up, and clear the "awaiting you" queue — one server per repo. The UI itself
|
|
7
|
+
has zero orchestration intelligence: all the judgment lives in the user-editable dispatch preamble
|
|
8
|
+
(Settings) and in the [`cc-worker`](https://github.com/colinhacks/fray/tree/main/cc-worker) plugin that every dispatched agent loads.
|
|
9
|
+
|
|
10
|
+
## Quickstart
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
cd /path/to/fray
|
|
14
|
+
nub install
|
|
15
|
+
nub run fray-dev:install # one-time: writes ~/.local/bin/fray-dev → this checkout's CLI source
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then, from any Git repository:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd /path/to/a/workspace
|
|
22
|
+
fray-dev # foreground immutable server + default-browser request; Ctrl-C stops it
|
|
23
|
+
fray-dev /path/to/other/repo # explicitly select a repository
|
|
24
|
+
fray-dev --app # opt in to the legacy dedicated app window
|
|
25
|
+
fray-dev --no-app # reuse/start and print its URL without opening a browser
|
|
26
|
+
fray-dev --status # workspace, source checkout, port, supervisor PID
|
|
27
|
+
fray-dev --stop # stop only the UI server; agent tmux sessions survive
|
|
28
|
+
fray-dev --foreground # compatibility spelling; Fray already runs in the foreground
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Production npm launcher
|
|
32
|
+
|
|
33
|
+
The development command above is deliberately source-backed. The intended production command is a
|
|
34
|
+
separate registry package: `npx frayui` will run the npm-resolved `frayui` package and never follow
|
|
35
|
+
this checkout. The current package metadata still uses the working name `fray`; migrating it to the
|
|
36
|
+
single-file `frayui` bundle is a separate release-packaging slice. When a published release is
|
|
37
|
+
available, **Update & Restart** will ask npm for a fresh immutable package execution cache, drain only
|
|
38
|
+
Fray's disposable HTTP control plane, and start that cache with the
|
|
39
|
+
same project identity, port, SQLite state, tmux socket, and provider sessions. It does not edit the
|
|
40
|
+
currently executing `npx` cache or replace an arbitrary global installation.
|
|
41
|
+
|
|
42
|
+
This repository has **not** published the package or verified ownership/availability of the unscoped
|
|
43
|
+
`frayui` npm name. The current workspace dependencies still need a maintainer-selected release closure (publish the
|
|
44
|
+
`@fray-ui/*` runtime packages, or build one audited bundled runtime tarball) before `npx frayui` can
|
|
45
|
+
be released; the npm publisher account and that packaging decision require maintainer action.
|
|
46
|
+
Until then, `fray-dev` is the supported command for this checkout.
|
|
47
|
+
|
|
48
|
+
`fray-dev` is source-backed only at launch: the installed shim contains an absolute, shell-safe pointer
|
|
49
|
+
to this checkout's `packages/cli/src/index.ts`, not a copied build. On each fresh launch after that
|
|
50
|
+
workspace's Fray supervisor has stopped, it selects a verified immutable artifact matching the current
|
|
51
|
+
source fingerprint, reuses an identical global artifact when one exists, or builds and promotes one
|
|
52
|
+
automatically. No manual `build`/`promote` step or repeated shim install is required. The running server
|
|
53
|
+
never watches this checkout and never runs HMR: edits made while it is running do nothing until you stop
|
|
54
|
+
Fray and run `fray-dev` again. SQLite state, persisted port, lock, legacy app profile, and tmux socket
|
|
55
|
+
are keyed by a stable checkout UUID. An ordinary/main worktree retains the repo's
|
|
56
|
+
`git config --local fray.id`; each linked Git worktree keeps a different UUID in its private Git
|
|
57
|
+
administrative directory. Canonical real paths make a checkout opened through a symlink reuse the
|
|
58
|
+
same instance, while sibling worktrees remain isolated even when their paths contain spaces.
|
|
59
|
+
Managed ordinary repositories use `tmux -L fray-repo-<full UUID>`; a durable migration record keeps
|
|
60
|
+
an attributable historical `fray-<8 chars>` server pinned until its sessions are gone, then switches
|
|
61
|
+
permanently without moving them. An explicit `FRAY_TMUX_SOCKET` is an unmanaged escape hatch and is
|
|
62
|
+
used verbatim—Fray does not inspect, migrate, or add ownership markers to that server.
|
|
63
|
+
|
|
64
|
+
`fray-dev` stays in the foreground while it serves its immutable snapshot; Ctrl-C stops only that
|
|
65
|
+
workspace's UI server. A second `fray-dev` verifies `/health` has both this UUID and exact canonical
|
|
66
|
+
worktree root, then asks the OS to open its URL in the configured default browser instead of starting
|
|
67
|
+
a competitor. New workspaces serialize only port allocation/startup, then run concurrently on isolated
|
|
68
|
+
ports and tmux sockets. `--port <n>` is an explicit new-launch request and fails clearly on a conflict.
|
|
69
|
+
Broken live supervisors are never silently replaced; inspect `fray-dev --status`, fix source, or use
|
|
70
|
+
`fray-dev --stop`.
|
|
71
|
+
|
|
72
|
+
If `~/.local/bin` is not on `PATH`, the installer prints the exact export to add. Use
|
|
73
|
+
`FRAY_BIN_DIR=/another/bin nub run fray-dev:install` to choose another directory; it refuses to overwrite
|
|
74
|
+
an unrelated `fray-dev` executable unless passed `--force`. `nub run fray-dev:check` verifies the owned
|
|
75
|
+
shim without changing it, and `nub run fray-dev:uninstall` removes only that owned shim. `fray-ui` remains
|
|
76
|
+
a package-bin alias for compatibility, but `fray-dev` is the canonical source-checkout command.
|
|
77
|
+
|
|
78
|
+
## Browser launch modes
|
|
79
|
+
|
|
80
|
+
The default `fray-dev` launch makes one standard OS request to open its localhost URL in the configured
|
|
81
|
+
default browser. The launcher waits for the OS handler to accept or reject that request; the browser
|
|
82
|
+
decides which window or process receives it. In particular, macOS may choose among running browser
|
|
83
|
+
instances that share one bundle identity. Fray does not scan, reuse, focus, or privately address
|
|
84
|
+
browser tabs. External links use native safe new-tab navigation and internal links stay in the local
|
|
85
|
+
browser context. `fray-dev --no-app` remains the print-only option.
|
|
86
|
+
|
|
87
|
+
`fray-dev --app` preserves the legacy dedicated/chromeless window as an explicit compatibility opt-in.
|
|
88
|
+
On macOS that window gets its **own Dock name ("Fray") and icon**. On the first opt-in app launch,
|
|
89
|
+
the launcher silently installs the Fray PWA into the project's browser profile
|
|
90
|
+
over the Chrome DevTools Protocol (`--remote-debugging-pipe` → `PWA.install` +
|
|
91
|
+
`PWA.changeAppUserSettings(displayMode: standalone)`; windowless, ~3-4s, once per machine). Chrome
|
|
92
|
+
then generates a real app-shim bundle at `~/Applications/Chrome Apps.localized/fray.app` — own
|
|
93
|
+
`CFBundleName`, icon rasterized from our web-app manifest — and every launch goes through that shim
|
|
94
|
+
(`open fray.app`), whose `app_mode_loader` hosts the window under its own bundle identity. Verified:
|
|
95
|
+
`lsappinfo` shows `LSDisplayName="fray"`, `Arch=ARM64`, and the Dock shows fray's own tile.
|
|
96
|
+
Subsequent launches detect the existing shim (~60ms) and skip the install.
|
|
97
|
+
|
|
98
|
+
Why it works this way (all verified empirically on Chrome 150 / macOS):
|
|
99
|
+
|
|
100
|
+
- A plain `--app=` window is owned by the Chrome browser process — the Dock shows "Google Chrome",
|
|
101
|
+
no launch flag changes it, and a hand-rolled `.app` that `exec`s Chrome loses its identity the
|
|
102
|
+
moment Chrome's Cocoa startup re-registers the process (and risks a Rosetta launch besides).
|
|
103
|
+
Chrome's generated app-shim is the only mechanism that yields an own Dock identity.
|
|
104
|
+
- The CDP `PWA.*` domain is only exposed on `--remote-debugging-pipe` connections (port-based
|
|
105
|
+
websocket clients lack `AllowUnsafeOperations`), and a CDP install defaults the app to
|
|
106
|
+
open-in-a-tab — `changeAppUserSettings(displayMode: "standalone")` is the required second half;
|
|
107
|
+
without it the browser answers the shim `kSuccessAndDisconnect` and windows stay Chrome-branded.
|
|
108
|
+
- Shim detection is stateless: the launcher scans shim `Info.plist`s for `CrAppModeShortcutURL` ==
|
|
109
|
+
the launch URL and `CrAppModeUserDataDir` under the project profile. (Chrome's generated app id is
|
|
110
|
+
not reproducible as a hash of the URL — don't try.)
|
|
111
|
+
|
|
112
|
+
Failure at any opt-in app step (profile busy, install error, no shim found) falls back silently to
|
|
113
|
+
the plain `--app` window. [`packages/web/public/favicon.svg`](https://github.com/colinhacks/fray/tree/main/packages/web/public/favicon.svg) is
|
|
114
|
+
the canonical icon artwork; `nub scripts/generate-icons.mjs` regenerates its six tracked PNG
|
|
115
|
+
derivatives, while `nub scripts/generate-icons.mjs --check` detects drift. On macOS,
|
|
116
|
+
`nub scripts/generate-icons.mjs --refresh-app-icons` also refreshes the ICNS resource in idle,
|
|
117
|
+
metadata-verified Fray PWA shims without launching Chrome; it stages and verifies each ad-hoc-signed
|
|
118
|
+
bundle before swapping it into place. The manifest lives in
|
|
119
|
+
[`packages/web/public/`](https://github.com/colinhacks/fray/tree/main/packages/web/public); the window title is set at runtime to
|
|
120
|
+
`fray · <owner/repo>` from the board snapshot.
|
|
121
|
+
|
|
122
|
+
*Windows/Linux follow-ups (not yet wired):* the launcher only brands the Dock on macOS. Windows would
|
|
123
|
+
set an `AppUserModelID` (+ icon) on a generated `.lnk`; Linux (X11) would pass `--class=fray` and ship
|
|
124
|
+
a `.desktop` file whose `StartupWMClass=fray` matches it. Both are documented TODOs.
|
|
125
|
+
|
|
126
|
+
## Architecture (10 lines)
|
|
127
|
+
|
|
128
|
+
1. Workspace-scoped: one server per repo, launched from the repo root, watching only that repo's `.fray/`.
|
|
129
|
+
2. `.fray/` thread files are the source of truth for thread status — the server imports fray's own
|
|
130
|
+
board parser (`cc/scripts/fray/*.mjs`), never re-implements it.
|
|
131
|
+
3. Session JSONL (`~/.claude/projects/<slug>/*.jsonl`) is telemetry only: liveness + previews, parsed defensively.
|
|
132
|
+
4. Agents are top-level processes in detached tmux sessions on the workspace's private, UUID-keyed socket.
|
|
133
|
+
5. The web terminal attaches per-viewer via node-pty (`tmux attach`); closing a tab kills only that attach client.
|
|
134
|
+
6. A single `/events` SSE channel pushes full board snapshots (no diff protocol) plus `notify` events.
|
|
135
|
+
7. RPC is a typed query/mutation layer over Hono at `/rpc`; the terminal is a WebSocket at `/term/:slug`.
|
|
136
|
+
8. `web` is React 19 + Vite + Tailwind v4 + valtio + TanStack Query + xterm.js.
|
|
137
|
+
9. UI state (unread, settings, session registry) lives in `~/.fray/projects/<id>/ui.db` (SQLite), never in `.fray/`.
|
|
138
|
+
10. Packages: `shared` (zod contract), `rpc`, `server`, `web`, `cli`. Read `ARCHITECTURE.md` before touching any.
|
|
139
|
+
|
|
140
|
+
## Settings & the dispatch preamble
|
|
141
|
+
|
|
142
|
+
Everything the UI deliberately does NOT know lives in **Settings** (gear icon): the
|
|
143
|
+
**dispatch preamble** is injected verbatim ahead of every dispatched agent's task, carrying the
|
|
144
|
+
orchestration wisdom (sub-agent discipline, git hygiene, the stop criterion). It ships from
|
|
145
|
+
[`DEFAULT_PREAMBLE.md`](https://github.com/colinhacks/fray/blob/main/packages/server/src/DEFAULT_PREAMBLE.md); edit it per project and use **Reset preamble to
|
|
146
|
+
default** to clear your overrides back to the shipped defaults. Settings also carry the default
|
|
147
|
+
model, effort, and a desktop-notifications toggle (which requests browser
|
|
148
|
+
permission and only fires when the Fray tab or window is hidden).
|
|
149
|
+
|
|
150
|
+
## `FRAY.md` — a repo's own worker norms
|
|
151
|
+
|
|
152
|
+
The dispatch preamble is per-**operator** state that lives in the UI's database. For norms that should
|
|
153
|
+
travel **with the repo** and be shared by the whole team, drop a **`FRAY.md`** at the project root.
|
|
154
|
+
When present, its contents are injected into every dispatched worker's system prompt (dispatch, adopt,
|
|
155
|
+
and resume, on both the Claude and Codex backends) under a header stating that they **override fray's
|
|
156
|
+
built-in worker defaults** wherever they conflict. Fray's worker contract is deliberately
|
|
157
|
+
defer-by-default: its engineering-process guidance (review depth, git/PR conventions, the quality bar)
|
|
158
|
+
is a default for when the project is silent, and `FRAY.md` is how a project speaks. Use it to say
|
|
159
|
+
things like "our gates are `pnpm check`", "skip the independent adversarial review on small UI diffs —
|
|
160
|
+
implementer self-review plus a browser screenshot is enough", or "push to a branch + PR, never merge
|
|
161
|
+
your own". The fray-mechanical contract (signal fences, scratchpad, the browser runtime gate) is not
|
|
162
|
+
overridable. Keep it concise — it's read fresh on every spawn and bounded to stay within the
|
|
163
|
+
system-prompt budget.
|
|
164
|
+
|
|
165
|
+
## The cc-worker plugin
|
|
166
|
+
|
|
167
|
+
Every agent fray-ui spawns loads the [`cc-worker`](https://github.com/colinhacks/fray/tree/main/cc-worker) Claude Code plugin
|
|
168
|
+
(`--plugin-dir`), which supplies the single-thread worker contract and hooks. Its hooks gate on the
|
|
169
|
+
`FRAY_UI_THREAD` env var, so passing it is harmless even in a non-fray repo. In the monorepo it is
|
|
170
|
+
found automatically as a sibling of `cc/`; a standalone install points at it with
|
|
171
|
+
`FRAY_WORKER_PLUGIN_DIR`.
|
|
172
|
+
|
|
173
|
+
## Troubleshooting
|
|
174
|
+
|
|
175
|
+
- **Terminal never attaches / `spawn-helper` errors.** node-pty prebuilds lose the exec bit when
|
|
176
|
+
npm/pnpm unpack them. The server package's `postinstall` re-chmods it; if it didn't run, do it
|
|
177
|
+
manually: `chmod +x node_modules/node-pty/prebuilds/*/spawn-helper`. PTY code cannot run inside a
|
|
178
|
+
sandboxed shell.
|
|
179
|
+
- **Escape hatch into a stuck agent.** The sessions live on the workspace socket recorded in
|
|
180
|
+
`~/.fray/projects/<id>/project-launch.owner` (or `tmux-socket-migration.json` while stopped).
|
|
181
|
+
Attach with `tmux -L <socket> attach -t fray-<slug>` and detach with `Ctrl-b d`.
|
|
182
|
+
- **Non-monorepo install** (fray-ui run against a repo outside this monorepo): set
|
|
183
|
+
`FRAY_SCRIPTS_DIR` to the installed plugin's `scripts/fray` directory (the board parser) and
|
|
184
|
+
`FRAY_WORKER_PLUGIN_DIR` to the `cc-worker` plugin directory.
|
|
185
|
+
- **Port already in use.** Omit `--port` to let `fray-dev` allocate and persist an isolated port. An
|
|
186
|
+
explicit occupied port is rejected; `fray-dev --status` shows the workspace's current owner.
|