agent-office-studio 0.0.0-stage → 0.2.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 (67) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +21 -0
  3. package/README.md +85 -2
  4. package/bin/agentoffice.js +38 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/_commonjsHelpers-CqkleIqs.js +1 -0
  7. package/dist/assets/addon-fit-BwprjgOP.js +1 -0
  8. package/dist/assets/index-CozPk6R9.js +4457 -0
  9. package/dist/assets/index-i7sU9SN-.css +32 -0
  10. package/dist/assets/inter-cyrillic-ext-wght-normal-B2xhLi22.woff2 +0 -0
  11. package/dist/assets/inter-cyrillic-wght-normal-CMZtQduZ.woff2 +0 -0
  12. package/dist/assets/inter-greek-ext-wght-normal-CGAr0uHJ.woff2 +0 -0
  13. package/dist/assets/inter-greek-wght-normal-CaVNZxsx.woff2 +0 -0
  14. package/dist/assets/inter-latin-ext-wght-normal-CFHvXkgd.woff2 +0 -0
  15. package/dist/assets/inter-latin-wght-normal-C2S99t-D.woff2 +0 -0
  16. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  17. package/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  18. package/dist/assets/jetbrains-mono-cyrillic-400-normal-CIEtRijs.woff +0 -0
  19. package/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  20. package/dist/assets/jetbrains-mono-greek-400-normal-CNojz0t3.woff +0 -0
  21. package/dist/assets/jetbrains-mono-latin-400-normal-B11XCQ5g.woff +0 -0
  22. package/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  23. package/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  24. package/dist/assets/jetbrains-mono-latin-ext-400-normal-DHMOLxAi.woff +0 -0
  25. package/dist/assets/jetbrains-mono-vietnamese-400-normal-BeU3T8wf.woff +0 -0
  26. package/dist/assets/xterm-C7kOH91j.js +9 -0
  27. package/dist/icon-192.png +0 -0
  28. package/dist/icon-512.png +0 -0
  29. package/dist/icon.svg +9 -0
  30. package/dist/index.html +240 -0
  31. package/dist/manifest.webmanifest +16 -0
  32. package/dist/sw.js +39 -0
  33. package/docs/README.md +26 -0
  34. package/docs/claude-access.md +31 -0
  35. package/docs/configuration.md +53 -0
  36. package/docs/development.md +76 -0
  37. package/docs/getting-started.md +54 -0
  38. package/docs/guides/troubleshooting.md +99 -0
  39. package/docs/guides/updating.md +36 -0
  40. package/docs/guides/working-with-the-team.md +56 -0
  41. package/docs/guides/your-first-job.md +65 -0
  42. package/docs/how-it-works.md +80 -0
  43. package/docs/images/office.jpg +0 -0
  44. package/docs/jobs.md +54 -0
  45. package/docs/leaderboard.md +65 -0
  46. package/docs/packaging.md +49 -0
  47. package/docs/phone.md +28 -0
  48. package/docs/security.md +48 -0
  49. package/docs/terminal.md +46 -0
  50. package/docs/using-the-office.md +109 -0
  51. package/docs/voice.md +44 -0
  52. package/package.json +64 -4
  53. package/server/auth.js +89 -0
  54. package/server/banter.js +53 -0
  55. package/server/changes.js +72 -0
  56. package/server/chief.js +130 -0
  57. package/server/index.js +1952 -0
  58. package/server/leaderboard.js +190 -0
  59. package/server/office-mcp.js +101 -0
  60. package/server/procstat.js +85 -0
  61. package/server/standup.js +78 -0
  62. package/server/supervise.js +51 -0
  63. package/server/terminal.js +255 -0
  64. package/server/tools.js +52 -0
  65. package/server/usage.js +40 -0
  66. package/server/voice.js +172 -0
  67. package/shared/speakable.js +50 -0
Binary file
Binary file
package/dist/icon.svg ADDED
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <rect width="512" height="512" fill="#1d1b18"/>
3
+ <rect x="136" y="160" width="240" height="176" rx="32" fill="#fbf9f5"/>
4
+ <rect x="216" y="336" width="80" height="28" fill="#fbf9f5"/>
5
+ <rect x="180" y="356" width="152" height="24" rx="12" fill="#fbf9f5"/>
6
+ <circle cx="206" cy="248" r="16" fill="#1d1b18"/>
7
+ <circle cx="256" cy="248" r="16" fill="#1d1b18"/>
8
+ <circle cx="306" cy="248" r="16" fill="#1d1b18"/>
9
+ </svg>
@@ -0,0 +1,240 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
6
+ <meta name="theme-color" content="#fbf9f5" />
7
+ <meta name="mobile-web-app-capable" content="yes" />
8
+ <meta name="apple-mobile-web-app-capable" content="yes" />
9
+ <meta name="apple-mobile-web-app-status-bar-style" content="default" />
10
+ <meta name="apple-mobile-web-app-title" content="Office" />
11
+ <link rel="manifest" href="/manifest.webmanifest" />
12
+ <link rel="icon" href="/icon.svg" type="image/svg+xml" />
13
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
14
+ <script>
15
+ // Before first paint, so a saved theme never flashes the default. theme.js owns it after that.
16
+ (function () {
17
+ try {
18
+ var t = localStorage.getItem('office.theme') || 'paper';
19
+ var m = localStorage.getItem('office.mode') || 'auto';
20
+ var d = m === 'dark' || (m === 'auto' && matchMedia('(prefers-color-scheme: dark)').matches);
21
+ var r = document.documentElement;
22
+ r.dataset.theme = t;
23
+ r.dataset.mode = d ? 'dark' : 'light';
24
+ } catch (e) {}
25
+ })();
26
+ </script>
27
+ <title>Agent Office</title>
28
+ <script type="module" crossorigin src="/assets/index-CozPk6R9.js"></script>
29
+ <link rel="stylesheet" crossorigin href="/assets/index-i7sU9SN-.css">
30
+ </head>
31
+ <body>
32
+ <div class="app" id="app" data-m="office">
33
+ <aside class="side">
34
+ <header class="brand">
35
+ <div class="title">
36
+ <h1>Agent Office</h1>
37
+ <span class="title-end">
38
+ <span class="lv" id="lv">Lv 1</span>
39
+ <button type="button" class="theme-btn" id="lbBtn" aria-label="Leaderboard" aria-haspopup="true" aria-expanded="false" title="Leaderboard">
40
+ <svg viewBox="0 0 24 24"><path d="M8 4h8v5a4 4 0 0 1-8 0z"/><path d="M8 6H5v1a3 3 0 0 0 3 3"/><path d="M16 6h3v1a3 3 0 0 1-3 3"/><path d="M12 13v4"/><path d="M8.5 20h7"/></svg>
41
+ </button>
42
+ <button type="button" class="theme-btn" id="acctBtn" aria-label="Claude access" aria-haspopup="true" aria-expanded="false" title="Claude access">
43
+ <svg viewBox="0 0 24 24"><circle cx="8" cy="15" r="4"/><path d="M10.8 12.2 20 3"/><path d="m17 6 3 3"/><path d="m14.5 8.5 2 2"/></svg>
44
+ </button>
45
+ <button type="button" class="theme-btn" id="themeBtn" aria-label="Appearance" aria-haspopup="true" aria-expanded="false" title="Appearance">
46
+ <svg viewBox="0 0 24 24"><path d="M12 3a9 9 0 1 0 0 18c1.4 0 2-.9 2-1.8 0-.9-.6-1.3-.6-2.2 0-1 .8-1.6 1.8-1.6H17a4 4 0 0 0 4-4C21 6.7 17 3 12 3z"/><circle cx="7.5" cy="11" r="1"/><circle cx="10" cy="7" r="1"/><circle cx="15" cy="7.5" r="1"/></svg>
47
+ </button>
48
+ </span>
49
+ <div class="theme-pop" id="themePop" role="dialog" aria-label="Appearance" hidden></div>
50
+ <div class="theme-pop lb-pop" id="lbPop" role="dialog" aria-label="Leaderboard" hidden></div>
51
+ <div class="theme-pop acct-pop" id="acctPop" role="dialog" aria-label="Claude access" hidden></div>
52
+ </div>
53
+ <div class="level">
54
+ <div class="xpbar"><i id="xpbar"></i></div>
55
+ <span class="xp" id="xptext">0 / 100 XP</span>
56
+ </div>
57
+ <div class="usage" id="usage" aria-label="Plan usage">
58
+ <div class="meter" id="useSession">
59
+ <span class="k">Session</span>
60
+ <span class="v"></span>
61
+ <div class="bar"><i></i></div>
62
+ <span class="r"></span>
63
+ </div>
64
+ <div class="meter" id="useWeek">
65
+ <span class="k">Week</span>
66
+ <span class="v"></span>
67
+ <div class="bar"><i></i></div>
68
+ <span class="r"></span>
69
+ </div>
70
+ <p class="u-err" id="useErr" hidden></p>
71
+ </div>
72
+ <div class="usage" id="load" aria-label="What the agents are using on this machine" hidden>
73
+ <div class="meter" id="loadCpu">
74
+ <span class="k">CPU</span>
75
+ <span class="v"></span>
76
+ <div class="bar"><i></i></div>
77
+ <span class="r"></span>
78
+ </div>
79
+ <div class="meter" id="loadMem">
80
+ <span class="k">Memory</span>
81
+ <span class="v"></span>
82
+ <div class="bar"><i></i></div>
83
+ <span class="r"></span>
84
+ </div>
85
+ </div>
86
+ </header>
87
+ <nav class="tabs">
88
+ <button type="button" data-tab="chat" class="on">Chat</button>
89
+ <button type="button" data-tab="jobs">Jobs <span id="jobCount"></span></button>
90
+ <button type="button" data-tab="projects">Projects</button>
91
+ <button type="button" data-tab="team">Team</button>
92
+ </nav>
93
+ <section class="pane" id="chatPane">
94
+ <div class="channels" id="channels" role="tablist" aria-label="Channels"></div>
95
+ <div class="msgs" id="msgs"></div>
96
+ <div class="typing" id="typing" hidden><span></span><span></span><span></span> Chief is reading</div>
97
+ <form class="composer" id="composer">
98
+ <div class="ac" id="ac" hidden></div>
99
+ <div class="tray" id="tray" hidden></div>
100
+ <div class="rec" id="rec" hidden><i></i><span id="recText">Recording</span><button type="button" id="recStop" class="link">Stop</button><button type="button" id="recDrop" class="link">Discard</button></div>
101
+ <textarea id="input" rows="1" placeholder="Message the office · @agent #project" aria-label="Message the office"></textarea>
102
+ <div class="controls">
103
+ <button type="button" class="mic" id="mic" title="Record a voice note (hold ⌥M)" aria-label="Record a voice note">
104
+ <svg viewBox="0 0 24 24"><path d="M12 3a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V6a3 3 0 0 1 3-3z"/><path d="M5 11a7 7 0 0 0 14 0"/><path d="M12 18v3"/></svg>
105
+ </button>
106
+ <button type="button" class="attach" id="attach" title="Attach files or images (or paste / drop them)" aria-label="Attach files">
107
+ <svg viewBox="0 0 24 24"><path d="M21 11.5l-8.6 8.6a5.5 5.5 0 0 1-7.8-7.8l8.6-8.6a3.7 3.7 0 0 1 5.2 5.2l-8.6 8.6a1.8 1.8 0 0 1-2.6-2.6l7.9-7.9"/></svg>
108
+ </button>
109
+ <input type="file" id="file" multiple hidden />
110
+ <select id="mode" aria-label="Clearance for new work">
111
+ <option value="plan">Plan only</option>
112
+ <option value="edit" selected>Edit files</option>
113
+ <option value="auto">Full autonomy</option>
114
+ </select>
115
+ <select id="model" aria-label="Model for new work">
116
+ <option value="">Chief picks</option>
117
+ <optgroup label="Fable">
118
+ <option value="claude-fable-5-1">Fable 5.1</option>
119
+ <option value="claude-fable-5">Fable 5</option>
120
+ </optgroup>
121
+ <optgroup label="Opus">
122
+ <option value="claude-opus-5-5">Opus 5.5</option>
123
+ <option value="claude-opus-5">Opus 5</option>
124
+ <option value="claude-opus-4-8">Opus 4.8</option>
125
+ <option value="claude-opus-4-7">Opus 4.7</option>
126
+ <option value="claude-opus-4-6">Opus 4.6</option>
127
+ </optgroup>
128
+ <optgroup label="Sonnet">
129
+ <option value="claude-sonnet-5-5">Sonnet 5.5</option>
130
+ <option value="claude-sonnet-5">Sonnet 5</option>
131
+ <option value="claude-sonnet-4-6">Sonnet 4.6</option>
132
+ </optgroup>
133
+ <optgroup label="Haiku">
134
+ <option value="claude-haiku-5-5">Haiku 5.5</option>
135
+ <option value="claude-haiku-4-5">Haiku 4.5</option>
136
+ </optgroup>
137
+ </select>
138
+ <button class="primary small" type="submit">Send</button>
139
+ </div>
140
+ </form>
141
+ </section>
142
+ <section class="pane" id="threadPane" hidden>
143
+ <header class="thread-head">
144
+ <button type="button" class="link" id="threadBack">← Chat</button>
145
+ <b id="threadWhere">Thread</b>
146
+ </header>
147
+ <div class="msgs" id="threadMsgs"></div>
148
+ <form class="composer" id="threadComposer">
149
+ <textarea id="threadInput" rows="1" placeholder="Reply in thread" aria-label="Reply in thread"></textarea>
150
+ <div class="controls">
151
+ <button class="primary small" type="submit">Reply</button>
152
+ </div>
153
+ </form>
154
+ </section>
155
+ <section class="pane" id="teamPane" hidden>
156
+ <div class="team" id="team"></div>
157
+ </section>
158
+ <section class="pane" id="projectsPane" hidden>
159
+ <div class="projects" id="projects"></div>
160
+ </section>
161
+ <section class="pane" id="jobsPane" hidden>
162
+ <div class="jobs" id="jobs"></div>
163
+ <button class="ghost" id="clear" hidden>Clear finished</button>
164
+ </section>
165
+ </aside>
166
+
167
+ <main class="view" id="view">
168
+ <div id="stage"></div>
169
+ <div id="labels"></div>
170
+
171
+ <div class="hud">
172
+ <div class="chip away" id="away" hidden></div>
173
+ <div class="chip" id="stats"></div>
174
+ <div class="chip tools">
175
+ <button type="button" id="turnLeft" title="Turn left (Q)" aria-label="Turn left">
176
+ <svg viewBox="0 0 24 24"><path d="M9 7H4V2"/><path d="M4.6 7A9 9 0 1 1 3 13"/></svg>
177
+ </button>
178
+ <button type="button" id="turnRight" title="Turn right (E)" aria-label="Turn right">
179
+ <svg viewBox="0 0 24 24"><path d="M15 7h5V2"/><path d="M19.4 7A9 9 0 1 0 21 13"/></svg>
180
+ </button>
181
+ <button type="button" id="recenter" title="Recenter (R)" aria-label="Recenter">
182
+ <svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>
183
+ </button>
184
+ <button type="button" id="standup" title="Call a standup" aria-label="Call a standup">
185
+ <svg viewBox="0 0 24 24"><circle cx="12" cy="5" r="2"/><circle cx="5" cy="15" r="2"/><circle cx="19" cy="15" r="2"/><path d="M8.5 20.5a7.5 7.5 0 0 0 7 0M3.5 10.5a8.5 8.5 0 0 1 4.5-5M20.5 10.5a8.5 8.5 0 0 0-4.5-5"/></svg>
186
+ </button>
187
+ <button type="button" id="termToggle" title="Terminal (⌃`)" aria-label="Terminal">
188
+ <svg viewBox="0 0 24 24"><rect x="2.5" y="4" width="19" height="16" rx="2.5"/><path d="M7 9.5l2.6 2.5L7 14.5"/><path d="M12.6 15h4.6"/></svg>
189
+ </button>
190
+ <span class="sep"></span>
191
+ <button type="button" class="sound" id="sound" aria-label="Sound on" title="Sound on"></button>
192
+ <button type="button" class="sound" id="speak" aria-label="Read replies aloud" title="Read replies aloud"></button>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="banner" id="banner"></div>
197
+ <div class="toast" id="toast"></div>
198
+ <p class="hint">Drag to move · Right-drag or Q / E to turn · Scroll to zoom · Double-click to go there</p>
199
+
200
+ <section class="term" id="term" hidden aria-label="Terminal">
201
+ <div class="term-grip" id="termGrip" role="separator" aria-orientation="horizontal" aria-label="Resize the terminal" tabindex="0"></div>
202
+ <header class="term-bar">
203
+ <div class="term-tabs" id="termTabs" role="tablist" aria-label="Terminal sessions"></div>
204
+ <select id="termFolder" aria-label="Folder for the next terminal"></select>
205
+ <button type="button" class="term-icon" id="termNew" title="New terminal" aria-label="New terminal">
206
+ <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
207
+ </button>
208
+ <span class="term-fill"></span>
209
+ <span class="term-note" id="termNote"></span>
210
+ <button type="button" class="term-icon" id="termHide" title="Hide the terminal (⌃`)" aria-label="Hide the terminal">
211
+ <svg viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>
212
+ </button>
213
+ </header>
214
+ <div class="term-body" id="termBody"></div>
215
+ <div class="term-off" id="termOff" hidden></div>
216
+ </section>
217
+ </main>
218
+
219
+ <aside class="detail" id="detail" hidden></aside>
220
+
221
+ <nav class="mnav" id="mnav" aria-label="Sections">
222
+ <button type="button" data-m="office" class="on">
223
+ <svg viewBox="0 0 24 24"><path d="M3 10.5L12 4l9 6.5"/><path d="M5 9.5V20h14V9.5"/><path d="M10 20v-5h4v5"/></svg>Office
224
+ </button>
225
+ <button type="button" data-m="chat">
226
+ <svg viewBox="0 0 24 24"><path d="M4 5h16v11H9l-5 4z"/></svg>Chat<i class="dot" id="mChatDot" hidden></i>
227
+ </button>
228
+ <button type="button" data-m="jobs">
229
+ <svg viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h10"/></svg>Jobs<b id="mJobCount"></b>
230
+ </button>
231
+ <button type="button" data-m="projects">
232
+ <svg viewBox="0 0 24 24"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>Projects
233
+ </button>
234
+ <button type="button" data-m="team">
235
+ <svg viewBox="0 0 24 24"><circle cx="9" cy="8" r="3.2"/><path d="M3 20a6 6 0 0 1 12 0"/><circle cx="17" cy="9" r="2.4"/><path d="M17 14.5a4.6 4.6 0 0 1 4 5.5"/></svg>Team
236
+ </button>
237
+ </nav>
238
+ </div>
239
+ </body>
240
+ </html>
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "Agent Office",
3
+ "short_name": "Office",
4
+ "description": "Your office of agents, from anywhere.",
5
+ "start_url": "/",
6
+ "scope": "/",
7
+ "display": "standalone",
8
+ "orientation": "any",
9
+ "background_color": "#fbf9f5",
10
+ "theme_color": "#fbf9f5",
11
+ "icons": [
12
+ { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
13
+ { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
14
+ { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
15
+ ]
16
+ }
package/dist/sw.js ADDED
@@ -0,0 +1,39 @@
1
+ // Keeps the app shell openable when the connection drops. Live data (/api,
2
+ // the websocket) is never cached: stale office state is worse than none.
3
+ const SHELL = 'office-shell-v1';
4
+
5
+ self.addEventListener('install', () => self.skipWaiting());
6
+ self.addEventListener('activate', (e) => {
7
+ e.waitUntil(
8
+ caches.keys()
9
+ .then((keys) => Promise.all(keys.filter((k) => k !== SHELL).map((k) => caches.delete(k))))
10
+ .then(() => self.clients.claim()),
11
+ );
12
+ });
13
+
14
+ self.addEventListener('fetch', (e) => {
15
+ const req = e.request;
16
+ const url = new URL(req.url);
17
+ if (req.method !== 'GET' || url.origin !== self.location.origin || url.pathname.startsWith('/api')) return;
18
+
19
+ // Hashed build files never change: serve from cache, fill on first sight.
20
+ if (url.pathname.startsWith('/assets/')) {
21
+ e.respondWith(caches.open(SHELL).then(async (cache) => {
22
+ const hit = await cache.match(req);
23
+ if (hit) return hit;
24
+ const res = await fetch(req);
25
+ if (res.ok) cache.put(req, res.clone());
26
+ return res;
27
+ }));
28
+ return;
29
+ }
30
+
31
+ // The page itself: fresh when online, last copy when not.
32
+ if (req.mode === 'navigate') {
33
+ e.respondWith(
34
+ fetch(req)
35
+ .then((res) => { if (res.ok) caches.open(SHELL).then((c) => c.put('/', res.clone())); return res; })
36
+ .catch(() => caches.match('/')),
37
+ );
38
+ }
39
+ });
package/docs/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # Agent Office documentation
2
+
3
+ **Guides**
4
+ - [Your first job](guides/your-first-job.md) — from a fresh install to a change you've reviewed
5
+ - [Working with the team](guides/working-with-the-team.md) — briefs that work, clearance, channels, cost
6
+ - [Troubleshooting](guides/troubleshooting.md) — common problems and their fixes
7
+ - [Updating](guides/updating.md) — new versions without losing your office
8
+
9
+ **Reference**
10
+ - [Getting started](getting-started.md) — install, run, point it at your projects
11
+ - [Using the office](using-the-office.md) — the chat, channels, threads, standup, the break room
12
+ - [Jobs](jobs.md) — clearance, which model, what changed
13
+ - [The leaderboard](leaderboard.md) — an optional board of jobs, tokens and agent time
14
+
15
+ **Setting it up**
16
+ - [Claude access](claude-access.md) — Claude Code login or API key, and the plan meters
17
+ - [Configuration](configuration.md) — every setting
18
+ - [Talking out loud](voice.md) — speech in and voices out, all on this machine
19
+ - [The terminal](terminal.md) — a real shell under the office
20
+ - [On your phone](phone.md) — install it like an app, reach it over Tailscale
21
+
22
+ **Under the hood**
23
+ - [How the office works](how-it-works.md) — jobs, agents' office tools, where things are kept
24
+ - [Security](security.md) — who can reach it and what jobs may do
25
+ - [Development](development.md) — dev server, shipping, testing safely
26
+ - [Packaging roadmap](packaging.md) — from a clone to an install
@@ -0,0 +1,31 @@
1
+ # Claude access
2
+
3
+ The key button beside Appearance chooses how the office signs in to Claude:
4
+
5
+ - **Claude Code login** — the login Claude Code keeps on this machine, so jobs
6
+ run on your Claude plan and the plan meters show how much of it is used.
7
+ - **API key** — an Anthropic API key, billed to that API account. Paste it
8
+ once; it's checked with Anthropic, kept in `data/auth.json` (readable only by
9
+ you) and never sent back to the browser. A key already in
10
+ `ANTHROPIC_API_KEY` is used until you paste another. The plan meters hide,
11
+ because there's no plan.
12
+
13
+ The choice applies to every `claude` the office starts: jobs, the chief,
14
+ standup and break-room talk. With the login chosen, a stray
15
+ `ANTHROPIC_API_KEY` in your shell is kept away from them, since `claude` would
16
+ otherwise prefer it. Jobs already running finish the way they started. The
17
+ in-app terminal is your own shell and never gets the office's key.
18
+
19
+ ## Plan usage
20
+
21
+ Under the level bar, two meters show how much of your Claude plan is used:
22
+ **This session** (the 5-hour window) and **This week**, each with when it
23
+ resets and a countdown. They turn amber at 70% and red at 90%. The server reads
24
+ the same numbers as Claude Code's `/usage`, using the login Claude Code keeps
25
+ on this machine (the macOS keychain, `~/.claude/.credentials.json`, or
26
+ `CLAUDE_CODE_OAUTH_TOKEN`). It checks every two minutes and shortly after each
27
+ turn finishes.
28
+
29
+ The meters only apply to the Claude Code login. They use the same endpoint
30
+ Claude Code does, which isn't a published API, so if it stops answering the
31
+ meters say so and the rest of the office carries on.
@@ -0,0 +1,53 @@
1
+ # Configuration
2
+
3
+ Everything is set with environment variables when the server starts, e.g.
4
+ `PORT=4400 CHIEF_MODEL=haiku npm start`. In-app choices — Claude access, the
5
+ voice toggle, team profiles, theme — are made in the office and remembered.
6
+
7
+ ## The office
8
+
9
+ | Env | Default | |
10
+ |---|---|---|
11
+ | `OFFICE_ROOT` | the folder above this one; with `agentoffice`, the folder you run it from | its subfolders are the projects |
12
+ | `OFFICE_DATA` | `data/` here; with `agentoffice`, `~/.agent-office` | where the office keeps its state |
13
+ | `PORT` | `4317` | |
14
+ | `OFFICE_ALLOWED_HOSTS` | none | extra hostnames to accept, comma-separated; see [On your phone](phone.md) |
15
+
16
+ ## Claude
17
+
18
+ | Env | Default | |
19
+ |---|---|---|
20
+ | `CLAUDE_BIN` | `claude` | the Claude Code binary |
21
+ | `ANTHROPIC_API_KEY` | none | used when Claude access is set to *API key* and none was pasted; see [Claude access](claude-access.md) |
22
+ | `CLAUDE_CODE_OAUTH_TOKEN` | none | a Claude Code login token, for the login mode and the plan meters |
23
+ | `CHIEF_MODEL` | `sonnet` | the chief of staff, and the standup write-up |
24
+ | `AGENT_COMPACT_PCT` | `60` | how full an agent's context gets before Claude Code compacts it |
25
+
26
+ ## Standup and the break room
27
+
28
+ | Env | Default | |
29
+ |---|---|---|
30
+ | `STANDUP_AT` | `09:30` | local time, or `off` |
31
+ | `STANDUP_WEEKENDS` | unset | `1` holds standups on weekends too |
32
+ | `BANTER` | on | `off` keeps break-room talk to the canned lines |
33
+ | `BANTER_MODEL` | `haiku` | the model that writes break-room talk |
34
+ | `BANTER_GAP_SECONDS` | `120` | at most one written conversation per this many seconds |
35
+
36
+ ## Voice
37
+
38
+ | Env | Default | |
39
+ |---|---|---|
40
+ | `WHISPER_BIN` | `whisper-cli` | |
41
+ | `WHISPER_MODEL` | the best model in `data/whisper` | point it at a bigger one (`ggml-small.en.bin`) to trade speed for accuracy |
42
+ | `PIPER_BIN` | `data/piper/venv/bin/python` | the voices live in `data/piper/voices` |
43
+ | `FFMPEG_BIN` | `ffmpeg` | |
44
+
45
+ ## Terminal
46
+
47
+ | Env | Default | |
48
+ |---|---|---|
49
+ | `OFFICE_LEADERBOARD_URL` | unset | address of a leaderboard service; see [The leaderboard](leaderboard.md) |
50
+ | `OFFICE_TERMINAL` | unset | `1` opens the in-app terminal |
51
+ | `OFFICE_TERMINAL_SHELL` | your `$SHELL` | |
52
+ | `OFFICE_TERMINAL_MAX` | `4` | sessions at once |
53
+ | `OFFICE_TERMINAL_IDLE_MIN` | `30` | `0` never closes an idle shell |
@@ -0,0 +1,76 @@
1
+ # Development
2
+
3
+ ```bash
4
+ npm install
5
+ npm run dev # server with --watch on :4317, Vite with hot reload on :5317
6
+ ```
7
+
8
+ Use <http://localhost:5317> while developing; Vite proxies `/api` and `/ws`
9
+ to the server.
10
+
11
+ ## Checking a change
12
+
13
+ ```bash
14
+ npm run build
15
+ npm test
16
+ ```
17
+
18
+ `npm test` is a smoke test: it starts the office from a temporary copy with
19
+ its own data, against a stand-in `claude`, and checks that it serves the app,
20
+ refuses other sites, runs a job with the right clearance and records what
21
+ changed. It costs no tokens and can't touch an office you're using. CI runs
22
+ it on macOS and Linux for every push and pull request.
23
+
24
+ ## Shipping a web change
25
+
26
+ `npm run build` writes `dist/`, which the server reads from disk on every
27
+ request. A web-only change goes live with a build and a reload — no server
28
+ restart.
29
+
30
+ ## Shipping a server change
31
+
32
+ Server changes need a restart. `npm start` runs the server under a small
33
+ supervisor (`server/supervise.js`) so the office can restart itself:
34
+
35
+ - **You:** type `/restart` in the chat (`/restart now` skips the wait).
36
+ - **An agent:** calls its `restart_office` tool, then ends its turn. It gets a
37
+ message when the office is back, so it can check its change.
38
+
39
+ A restart waits until nobody is mid-turn (up to 10 minutes, then it goes
40
+ anyway), exits with code 75, and the supervisor starts a fresh server. Open
41
+ jobs resume on their sessions. Before exiting, the server checks that its own
42
+ code parses and loads, and refuses if it doesn't, because a broken server would
43
+ leave nothing running to fix it. If the server crashes three times in a row
44
+ within seconds of starting, the supervisor gives up.
45
+
46
+ A server started with plain `node server/index.js`, or with `npm run dev`, has
47
+ no supervisor and refuses to restart itself. Agents must never kill the server
48
+ by signal: its shutdown ends their own process along with everyone else's.
49
+
50
+ ## Testing without touching the live office
51
+
52
+ A second server pointed at the same `data/` resumes the live office's jobs.
53
+ Test from a copy instead, on another port, with its own data and a scratch
54
+ project folder:
55
+
56
+ ```bash
57
+ rsync -a --exclude data --exclude dist --exclude .git --exclude node_modules ./ /tmp/office-test/
58
+ ln -s "$PWD/node_modules" /tmp/office-test/node_modules
59
+ cd /tmp/office-test && npx vite build
60
+ PORT=4398 OFFICE_ROOT=/tmp/office-test-projects CLAUDE_BIN=/path/to/stub \
61
+ STANDUP_AT=off BANTER=off node server/index.js
62
+ ```
63
+
64
+ `CLAUDE_BIN` can point at a stub that records its arguments and environment,
65
+ so you can check what a job was started with without spending tokens.
66
+ `launch/stub.mjs` is a fuller one that plays scripted jobs.
67
+
68
+ When you stop a test server, kill it by PID, not with `pkill -f`, which can't
69
+ tell it from the live one.
70
+
71
+ ## The launch video
72
+
73
+ `launch/` holds the tooling that recorded the launch video: `setup.sh` resets a
74
+ sandbox office on port 4399 under `/tmp/office-demo`, `stub.mjs` plays the
75
+ scripted agents, and the Python scripts drive and capture the browser. The
76
+ rendered video itself isn't kept in git.
@@ -0,0 +1,54 @@
1
+ # Getting started
2
+
3
+ ## What you need
4
+
5
+ - **Node.js 20 or newer.**
6
+ - **Claude Code**, installed and on your `PATH` as `claude`. Either sign in
7
+ once by running `claude` in a terminal, or have an Anthropic API key ready
8
+ (see [Claude access](claude-access.md)).
9
+ - **macOS or Linux.** Windows works under WSL.
10
+ - **git**, for the "what changed" card on each job.
11
+
12
+ ## Install and run
13
+
14
+ ```bash
15
+ git clone https://github.com/onuohasilver/agent-office.git
16
+ cd agent-office
17
+ npm install
18
+ npm start # builds, then serves on http://localhost:4317
19
+ ```
20
+
21
+ Open <http://localhost:4317>.
22
+
23
+ ## Where your projects are
24
+
25
+ The office's projects are the subfolders of one folder, `OFFICE_ROOT`. By
26
+ default that is the folder the office itself sits in, so a layout like this
27
+ works with no settings:
28
+
29
+ ```
30
+ ~/code/
31
+ ├── agent-office/
32
+ ├── storefront/ ← a project
33
+ └── payments-api/ ← a project
34
+ ```
35
+
36
+ Point it somewhere else with `OFFICE_ROOT=~/work npm start`. Name a project in
37
+ the chat with `#`, e.g. `#storefront`.
38
+
39
+ ## Optional extras
40
+
41
+ | Extra | What it adds | How |
42
+ |---|---|---|
43
+ | Voice | talk to the office, hear it answer | [Talking out loud](voice.md) |
44
+ | Terminal | a real shell in a drawer under the office | [The terminal](terminal.md) |
45
+ | Phone | the office on your phone, installed like an app | [On your phone](phone.md) |
46
+
47
+ ## Next
48
+
49
+ Run [your first job](guides/your-first-job.md).
50
+
51
+ ## Developing
52
+
53
+ `npm run dev` serves with hot reload on <http://localhost:5317>. See
54
+ [Development](development.md).