agent-office-studio 0.3.0 → 0.5.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/CHANGELOG.md +40 -0
- package/README.md +0 -1
- package/dist/assets/index-Cdivbl1d.css +1 -0
- package/dist/assets/{index-DgXF8Kqx.js → index-CfqwI0KM.js} +266 -261
- package/dist/index.html +26 -30
- package/docs/README.md +0 -1
- package/docs/claude-access.md +3 -4
- package/docs/configuration.md +4 -5
- package/docs/development.md +16 -0
- package/docs/getting-started.md +0 -1
- package/docs/guides/troubleshooting.md +0 -10
- package/docs/guides/updating.md +24 -20
- package/docs/how-it-works.md +0 -2
- package/docs/jobs.md +21 -0
- package/docs/linked-offices.md +1 -1
- package/docs/packaging.md +3 -4
- package/docs/phone.md +2 -3
- package/docs/security.md +3 -9
- package/docs/using-the-office.md +1 -1
- package/package.json +8 -13
- package/server/chief.js +2 -1
- package/server/icons.js +82 -0
- package/server/index.js +197 -52
- package/server/office-mcp.js +5 -0
- package/server/settings.js +85 -0
- package/dist/assets/_commonjsHelpers-CqkleIqs.js +0 -1
- package/dist/assets/addon-fit-BwprjgOP.js +0 -1
- package/dist/assets/index-D5IzEWKU.css +0 -32
- package/dist/assets/xterm-C7kOH91j.js +0 -9
- package/docs/terminal.md +0 -46
- package/server/terminal.js +0 -255
package/dist/index.html
CHANGED
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
} catch (e) {}
|
|
25
25
|
})();
|
|
26
26
|
</script>
|
|
27
|
-
<title>
|
|
28
|
-
<script type="module" crossorigin src="/assets/index-
|
|
29
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
27
|
+
<title>Office</title>
|
|
28
|
+
<script type="module" crossorigin src="/assets/index-CfqwI0KM.js"></script>
|
|
29
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Cdivbl1d.css">
|
|
30
30
|
</head>
|
|
31
31
|
<body>
|
|
32
32
|
<div class="app" id="app" data-m="office">
|
|
@@ -34,9 +34,12 @@
|
|
|
34
34
|
<div class="side-grip" id="side-grip" role="separator" aria-orientation="vertical" aria-label="Resize the side panel" tabindex="0"></div>
|
|
35
35
|
<header class="brand">
|
|
36
36
|
<div class="title">
|
|
37
|
-
<
|
|
37
|
+
<span class="title-start">
|
|
38
|
+
<span class="lv" role="img" id="lv" aria-label="Level 1"><svg viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/></svg><b id="lvNum">1</b></span>
|
|
39
|
+
<h1 id="officeName">Your Office</h1>
|
|
40
|
+
<button type="button" class="fold" id="fold" aria-label="Show usage" aria-expanded="false" aria-controls="gaugesBox" title="Show usage"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
|
|
41
|
+
</span>
|
|
38
42
|
<span class="title-end">
|
|
39
|
-
<span class="lv" id="lv">Lv 1</span>
|
|
40
43
|
<button type="button" class="theme-btn" id="lbBtn" aria-label="Leaderboard" aria-haspopup="true" aria-expanded="false" title="Leaderboard">
|
|
41
44
|
<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>
|
|
42
45
|
</button>
|
|
@@ -46,6 +49,9 @@
|
|
|
46
49
|
<button type="button" class="theme-btn" id="acctBtn" aria-label="Claude access" aria-haspopup="true" aria-expanded="false" title="Claude access">
|
|
47
50
|
<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>
|
|
48
51
|
</button>
|
|
52
|
+
<button type="button" class="theme-btn" id="setBtn" aria-label="Settings" aria-haspopup="dialog" aria-expanded="false" title="Settings">
|
|
53
|
+
<svg viewBox="0 0 24 24"><path d="M4 7h9M17 7h3M4 17h3M11 17h9"/><circle cx="15" cy="7" r="2"/><circle cx="9" cy="17" r="2"/></svg>
|
|
54
|
+
</button>
|
|
49
55
|
<button type="button" class="theme-btn" id="themeBtn" aria-label="Appearance" aria-haspopup="true" aria-expanded="false" title="Appearance">
|
|
50
56
|
<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>
|
|
51
57
|
</button>
|
|
@@ -59,17 +65,18 @@
|
|
|
59
65
|
<div class="xpbar"><i id="xpbar"></i></div>
|
|
60
66
|
<span class="xp" id="xptext">0 / 100 XP</span>
|
|
61
67
|
</div>
|
|
68
|
+
<div class="gauges" id="gaugesBox" hidden>
|
|
62
69
|
<div class="usage" id="usage" aria-label="Plan usage">
|
|
63
70
|
<div class="meter" id="useSession">
|
|
64
71
|
<span class="k">Session</span>
|
|
65
72
|
<span class="v"></span>
|
|
66
|
-
<
|
|
73
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><path d="M18 13v5l3 2"/></g></svg>
|
|
67
74
|
<span class="r"></span>
|
|
68
75
|
</div>
|
|
69
76
|
<div class="meter" id="useWeek">
|
|
70
77
|
<span class="k">Week</span>
|
|
71
78
|
<span class="v"></span>
|
|
72
|
-
<
|
|
79
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><rect x="13" y="14" width="10" height="9" rx="1.5"/><path d="M13 17.5h10M16 12.5v2.5M20 12.5v2.5"/></g></svg>
|
|
73
80
|
<span class="r"></span>
|
|
74
81
|
</div>
|
|
75
82
|
<p class="u-err" id="useErr" hidden></p>
|
|
@@ -78,16 +85,17 @@
|
|
|
78
85
|
<div class="meter" id="loadCpu">
|
|
79
86
|
<span class="k">CPU</span>
|
|
80
87
|
<span class="v"></span>
|
|
81
|
-
<
|
|
88
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><rect x="14" y="14" width="8" height="8" rx="1.5"/><path d="M16.5 12v2M19.5 12v2M16.5 22v2M19.5 22v2M12 16.5h2M12 19.5h2M22 16.5h2M22 19.5h2"/></g></svg>
|
|
82
89
|
<span class="r"></span>
|
|
83
90
|
</div>
|
|
84
91
|
<div class="meter" id="loadMem">
|
|
85
92
|
<span class="k">Memory</span>
|
|
86
93
|
<span class="v"></span>
|
|
87
|
-
<
|
|
94
|
+
<svg class="bar" viewBox="0 0 36 36" aria-hidden="true"><circle class="trk" cx="18" cy="18" r="15"/><circle class="arc" cx="18" cy="18" r="15" pathLength="100"/><g class="ico"><rect x="12.5" y="14.5" width="11" height="7" rx="1"/><path d="M15.5 21.5v1.5M18 21.5v1.5M20.5 21.5v1.5M15.5 17h1M19.5 17h1"/></g></svg>
|
|
88
95
|
<span class="r"></span>
|
|
89
96
|
</div>
|
|
90
97
|
</div>
|
|
98
|
+
</div>
|
|
91
99
|
</header>
|
|
92
100
|
<nav class="tabs">
|
|
93
101
|
<button type="button" data-tab="chat" class="on">Chat</button>
|
|
@@ -158,9 +166,17 @@
|
|
|
158
166
|
</form>
|
|
159
167
|
</section>
|
|
160
168
|
<section class="pane" id="teamPane" hidden>
|
|
169
|
+
<div class="viewbar" role="group" aria-label="View as">
|
|
170
|
+
<button type="button" data-view="list" aria-pressed="true" aria-label="List" title="List"><svg viewBox="0 0 24 24"><path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01"/></svg></button>
|
|
171
|
+
<button type="button" data-view="icons" aria-pressed="false" aria-label="Icons" title="Icons"><svg viewBox="0 0 24 24"><rect x="4" y="4" width="6" height="6" rx="1.5"/><rect x="14" y="4" width="6" height="6" rx="1.5"/><rect x="4" y="14" width="6" height="6" rx="1.5"/><rect x="14" y="14" width="6" height="6" rx="1.5"/></svg></button>
|
|
172
|
+
</div>
|
|
161
173
|
<div class="team" id="team"></div>
|
|
162
174
|
</section>
|
|
163
175
|
<section class="pane" id="projectsPane" hidden>
|
|
176
|
+
<div class="viewbar" role="group" aria-label="View as">
|
|
177
|
+
<button type="button" data-view="list" aria-pressed="true" aria-label="List" title="List"><svg viewBox="0 0 24 24"><path d="M8 6h12M8 12h12M8 18h12M4 6h.01M4 12h.01M4 18h.01"/></svg></button>
|
|
178
|
+
<button type="button" data-view="icons" aria-pressed="false" aria-label="Icons" title="Icons"><svg viewBox="0 0 24 24"><rect x="4" y="4" width="6" height="6" rx="1.5"/><rect x="14" y="4" width="6" height="6" rx="1.5"/><rect x="4" y="14" width="6" height="6" rx="1.5"/><rect x="14" y="14" width="6" height="6" rx="1.5"/></svg></button>
|
|
179
|
+
</div>
|
|
164
180
|
<div class="projects" id="projects"></div>
|
|
165
181
|
</section>
|
|
166
182
|
<section class="pane" id="jobsPane" hidden>
|
|
@@ -189,9 +205,6 @@
|
|
|
189
205
|
<button type="button" id="standup" title="Call a standup" aria-label="Call a standup">
|
|
190
206
|
<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>
|
|
191
207
|
</button>
|
|
192
|
-
<button type="button" id="termToggle" title="Terminal (⌃`)" aria-label="Terminal">
|
|
193
|
-
<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>
|
|
194
|
-
</button>
|
|
195
208
|
<span class="sep"></span>
|
|
196
209
|
<button type="button" class="sound" id="sound" aria-label="Sound on" title="Sound on"></button>
|
|
197
210
|
<button type="button" class="sound" id="speak" aria-label="Read replies aloud" title="Read replies aloud"></button>
|
|
@@ -201,24 +214,6 @@
|
|
|
201
214
|
<div class="banner" id="banner"></div>
|
|
202
215
|
<div class="toast" id="toast"></div>
|
|
203
216
|
<p class="hint">Drag to move · Right-drag or Q / E to turn · Scroll to zoom · Double-click to go there</p>
|
|
204
|
-
|
|
205
|
-
<section class="term" id="term" hidden aria-label="Terminal">
|
|
206
|
-
<div class="term-grip" id="termGrip" role="separator" aria-orientation="horizontal" aria-label="Resize the terminal" tabindex="0"></div>
|
|
207
|
-
<header class="term-bar">
|
|
208
|
-
<div class="term-tabs" id="termTabs" role="tablist" aria-label="Terminal sessions"></div>
|
|
209
|
-
<select id="termFolder" aria-label="Folder for the next terminal"></select>
|
|
210
|
-
<button type="button" class="term-icon" id="termNew" title="New terminal" aria-label="New terminal">
|
|
211
|
-
<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
|
|
212
|
-
</button>
|
|
213
|
-
<span class="term-fill"></span>
|
|
214
|
-
<span class="term-note" id="termNote"></span>
|
|
215
|
-
<button type="button" class="term-icon" id="termHide" title="Hide the terminal (⌃`)" aria-label="Hide the terminal">
|
|
216
|
-
<svg viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>
|
|
217
|
-
</button>
|
|
218
|
-
</header>
|
|
219
|
-
<div class="term-body" id="termBody"></div>
|
|
220
|
-
<div class="term-off" id="termOff" hidden></div>
|
|
221
|
-
</section>
|
|
222
217
|
</main>
|
|
223
218
|
|
|
224
219
|
<aside class="detail" id="detail" hidden></aside>
|
|
@@ -241,5 +236,6 @@
|
|
|
241
236
|
</button>
|
|
242
237
|
</nav>
|
|
243
238
|
</div>
|
|
239
|
+
<dialog class="settings" id="settings" aria-label="Settings"></dialog>
|
|
244
240
|
</body>
|
|
245
241
|
</html>
|
package/docs/README.md
CHANGED
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
- [Claude access](claude-access.md) — Claude Code login or API key, and the plan meters
|
|
18
18
|
- [Configuration](configuration.md) — every setting
|
|
19
19
|
- [Talking out loud](voice.md) — speech in and voices out, all on this machine
|
|
20
|
-
- [The terminal](terminal.md) — a real shell under the office
|
|
21
20
|
- [On your phone](phone.md) — install it like an app, reach it over Tailscale
|
|
22
21
|
|
|
23
22
|
**Under the hood**
|
package/docs/claude-access.md
CHANGED
|
@@ -5,16 +5,15 @@ The key button beside Appearance chooses how the office signs in to Claude:
|
|
|
5
5
|
- **Claude Code login** — the login Claude Code keeps on this machine, so jobs
|
|
6
6
|
run on your Claude plan and the plan meters show how much of it is used.
|
|
7
7
|
- **API key** — an Anthropic API key, billed to that API account. Paste it
|
|
8
|
-
once; it's checked with Anthropic, kept in `
|
|
9
|
-
you) and never sent back to the browser. A key already in
|
|
8
|
+
once; it's checked with Anthropic, kept in `auth.json` in the data folder
|
|
9
|
+
(readable only by you) and never sent back to the browser. A key already in
|
|
10
10
|
`ANTHROPIC_API_KEY` is used until you paste another. The plan meters hide,
|
|
11
11
|
because there's no plan.
|
|
12
12
|
|
|
13
13
|
The choice applies to every `claude` the office starts: jobs, the chief,
|
|
14
14
|
standup and break-room talk. With the login chosen, a stray
|
|
15
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.
|
|
17
|
-
in-app terminal is your own shell and never gets the office's key.
|
|
16
|
+
otherwise prefer it. Jobs already running finish the way they started.
|
|
18
17
|
|
|
19
18
|
## Plan usage
|
|
20
19
|
|
package/docs/configuration.md
CHANGED
|
@@ -4,6 +4,8 @@ Everything is set with environment variables when the server starts, e.g.
|
|
|
4
4
|
`PORT=4400 CHIEF_MODEL=haiku npm start`. In-app choices — Claude access, the
|
|
5
5
|
voice toggle, team profiles, theme — are made in the office and remembered.
|
|
6
6
|
|
|
7
|
+
**Settings page.** Open the sliders icon in the office header to change the owner, token limit, compaction point, chief model, standup time and weekends, sharing of job titles, and the break-room talk without restarting. A value saved there wins over the environment variable below, which becomes the default; "Use the default" puts it back. Everything else here is environment-only.
|
|
8
|
+
|
|
7
9
|
## The office
|
|
8
10
|
|
|
9
11
|
| Env | Default | |
|
|
@@ -23,6 +25,7 @@ voice toggle, team profiles, theme — are made in the office and remembered.
|
|
|
23
25
|
| `CLAUDE_CODE_OAUTH_TOKEN` | none | a Claude Code login token, for the login mode and the plan meters |
|
|
24
26
|
| `CHIEF_MODEL` | `sonnet` | the chief of staff, and the standup write-up |
|
|
25
27
|
| `AGENT_COMPACT_PCT` | `60` | how full an agent's context gets before Claude Code compacts it |
|
|
28
|
+
| `AGENT_CLEAR_TOKENS` | `150000` | context size past which an agent's next message nudges it to `clear_context` and start fresh; `0` turns the nudge off; the limit set in the office (Claude access menu) overrides it |
|
|
26
29
|
|
|
27
30
|
## Standup and the break room
|
|
28
31
|
|
|
@@ -43,12 +46,8 @@ voice toggle, team profiles, theme — are made in the office and remembered.
|
|
|
43
46
|
| `PIPER_BIN` | `data/piper/venv/bin/python` | the voices live in `data/piper/voices` |
|
|
44
47
|
| `FFMPEG_BIN` | `ffmpeg` | |
|
|
45
48
|
|
|
46
|
-
##
|
|
49
|
+
## Leaderboard
|
|
47
50
|
|
|
48
51
|
| Env | Default | |
|
|
49
52
|
|---|---|---|
|
|
50
53
|
| `OFFICE_LEADERBOARD_URL` | the hosted board | address of a different leaderboard service; see [The leaderboard](leaderboard.md) |
|
|
51
|
-
| `OFFICE_TERMINAL` | unset | `1` opens the in-app terminal |
|
|
52
|
-
| `OFFICE_TERMINAL_SHELL` | your `$SHELL` | |
|
|
53
|
-
| `OFFICE_TERMINAL_MAX` | `4` | sessions at once |
|
|
54
|
-
| `OFFICE_TERMINAL_IDLE_MIN` | `30` | `0` never closes an idle shell |
|
package/docs/development.md
CHANGED
|
@@ -68,6 +68,22 @@ so you can check what a job was started with without spending tokens.
|
|
|
68
68
|
When you stop a test server, kill it by PID, not with `pkill -f`, which can't
|
|
69
69
|
tell it from the live one.
|
|
70
70
|
|
|
71
|
+
## Releasing
|
|
72
|
+
|
|
73
|
+
1. Move the changelog's *Unreleased* entries under a new version heading,
|
|
74
|
+
`## 0.2.0 — <date>`.
|
|
75
|
+
2. Set the same version in `package.json` (`npm version 0.2.0 --no-git-tag-version`).
|
|
76
|
+
3. Commit, then tag and push: `git tag v0.2.0 && git push origin master v0.2.0`.
|
|
77
|
+
|
|
78
|
+
The *Release* workflow checks the tag matches `package.json`, runs the build,
|
|
79
|
+
the smoke test and `scripts/pack-smoke.sh` (which installs the packed package
|
|
80
|
+
the way a user would), publishes to npm with provenance, and creates the
|
|
81
|
+
GitHub release from that changelog section. It needs an npm access token in
|
|
82
|
+
the repository secret `NPM_TOKEN`.
|
|
83
|
+
|
|
84
|
+
To try the package locally without publishing: `sh scripts/pack-smoke.sh`, or
|
|
85
|
+
`npm pack` and `npm install -g ./agent-office-studio-*.tgz`.
|
|
86
|
+
|
|
71
87
|
## The launch video
|
|
72
88
|
|
|
73
89
|
`launch/` holds the tooling that recorded the launch video: `setup.sh` resets a
|
package/docs/getting-started.md
CHANGED
|
@@ -44,7 +44,6 @@ the chat with `#`, e.g. `#storefront`.
|
|
|
44
44
|
| Extra | What it adds | How |
|
|
45
45
|
|---|---|---|
|
|
46
46
|
| Voice | talk to the office, hear it answer | [Talking out loud](voice.md) |
|
|
47
|
-
| Terminal | a real shell in a drawer under the office | [The terminal](terminal.md) |
|
|
48
47
|
| Phone | the office on your phone, installed like an app | [On your phone](phone.md) |
|
|
49
48
|
|
|
50
49
|
## Next
|
|
@@ -86,16 +86,6 @@ browser's built-in voices.
|
|
|
86
86
|
Browsers only allow it on `localhost` or HTTPS. Open the office at
|
|
87
87
|
`http://localhost:4317`, not an IP address.
|
|
88
88
|
|
|
89
|
-
## The terminal won't open
|
|
90
|
-
|
|
91
|
-
**There's no terminal button, or it says the terminal is off.**
|
|
92
|
-
It's off by default. Start the office with `OFFICE_TERMINAL=1 npm start`.
|
|
93
|
-
|
|
94
|
-
**"node-pty is not installed."**
|
|
95
|
-
It's a native module and needs build tools. On macOS:
|
|
96
|
-
`xcode-select --install`, then `npm install`. On Linux: install
|
|
97
|
-
`build-essential` and `python3`, then `npm install`.
|
|
98
|
-
|
|
99
89
|
## On a phone
|
|
100
90
|
|
|
101
91
|
**"Local only." (HTTP 403)**
|
package/docs/guides/updating.md
CHANGED
|
@@ -1,36 +1,40 @@
|
|
|
1
1
|
# Updating
|
|
2
2
|
|
|
3
|
-
Your office
|
|
4
|
-
|
|
3
|
+
Your office — jobs, chat, channels, XP, team profiles, Claude access — lives
|
|
4
|
+
in the data folder (`~/.agent-office`), which updates never touch.
|
|
5
5
|
|
|
6
|
-
##
|
|
6
|
+
## The installed command
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
|
|
10
|
-
git pull
|
|
11
|
-
npm install
|
|
12
|
-
npm run build
|
|
9
|
+
npm install -g agent-office-studio@latest
|
|
13
10
|
```
|
|
14
11
|
|
|
15
|
-
Then stop the office (Ctrl-C
|
|
16
|
-
|
|
12
|
+
Then stop the office (Ctrl-C) and start it again. Running jobs stop cleanly
|
|
13
|
+
and resume when it comes back.
|
|
14
|
+
|
|
15
|
+
Read the [changelog](../../CHANGELOG.md) for what changed and anything you
|
|
16
|
+
need to do. `agent-office --version` says what you're on.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
need to do.
|
|
18
|
+
### A particular version
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g agent-office-studio@0.2.0
|
|
22
|
+
```
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
## A clone
|
|
24
25
|
|
|
25
26
|
```bash
|
|
26
|
-
|
|
27
|
-
git
|
|
28
|
-
npm install
|
|
27
|
+
cd agent-office
|
|
28
|
+
git pull
|
|
29
|
+
npm install
|
|
30
|
+
npm run build
|
|
29
31
|
```
|
|
30
32
|
|
|
33
|
+
Then restart it with `npm start`. To stay on a release, check out its tag
|
|
34
|
+
(`git checkout v0.2.0`) instead of pulling. A clone's data is in `data/`.
|
|
35
|
+
|
|
31
36
|
## If something breaks
|
|
32
37
|
|
|
33
|
-
-
|
|
34
|
-
the
|
|
35
|
-
|
|
36
|
-
the office, check out the previous tag, restore `data/`, build and start.
|
|
38
|
+
- **Keep a way back:** copy the data folder before updating. Going back is
|
|
39
|
+
then: stop the office, install the previous version, restore the folder,
|
|
40
|
+
start.
|
package/docs/how-it-works.md
CHANGED
|
@@ -52,7 +52,6 @@ If the server restarts mid-job, jobs with a session resume where they left off.
|
|
|
52
52
|
| `data/auth.json` | how Claude is signed in to, and the API key if you gave one (mode `0600`) |
|
|
53
53
|
| `data/uploads/` | attachments and voice notes |
|
|
54
54
|
| `data/whisper/`, `data/piper/` | speech models, if you installed them |
|
|
55
|
-
| `data/terminal.log` | when terminal sessions opened and closed |
|
|
56
55
|
|
|
57
56
|
`data/` is git-ignored. State is written shortly after every change, and the server stops its jobs cleanly on Ctrl-C so they can resume.
|
|
58
57
|
|
|
@@ -70,7 +69,6 @@ If the server restarts mid-job, jobs with a session resume where they left off.
|
|
|
70
69
|
| `leaderboard/` | the leaderboard service (a Cloudflare Worker) |
|
|
71
70
|
| `server/changes.js` | the "what changed" snapshots and diffs |
|
|
72
71
|
| `server/voice.js` | whisper.cpp transcription and Piper voices |
|
|
73
|
-
| `server/terminal.js` | the in-app terminal |
|
|
74
72
|
| `server/procstat.js` | agents' CPU and memory |
|
|
75
73
|
| `web/src/main.js` | browser entry: socket, state, wiring |
|
|
76
74
|
| `web/src/office.js`, `figure.js`, `agent.js` | the 3D office and the people in it |
|
package/docs/jobs.md
CHANGED
|
@@ -33,6 +33,27 @@ never moves on its own.
|
|
|
33
33
|
The drawer shows each job's model and how much context its last turn carried,
|
|
34
34
|
so a session that has grown expensive is visible rather than guessed at.
|
|
35
35
|
|
|
36
|
+
An agent can also clear its own context. Its `clear_context` tool takes notes
|
|
37
|
+
to itself (what is done, what is left, what it learned); when it ends its
|
|
38
|
+
turn the office closes the session and starts the job again fresh, carrying the
|
|
39
|
+
brief, those notes and any message that arrived meanwhile. The history is what
|
|
40
|
+
costs tokens on every turn, so this is the way to shed it at a natural break.
|
|
41
|
+
Once a session's context passes `AGENT_CLEAR_TOKENS` (150,000 by default, `0` to
|
|
42
|
+
turn it off), the next message the agent receives carries a one-time nudge to do
|
|
43
|
+
this. Clearing is still the agent's call. The log records each clear, and it is
|
|
44
|
+
capped at five per job.
|
|
45
|
+
|
|
46
|
+
The boss can also just say "clear context" in chat, for everyone or for named
|
|
47
|
+
agents. The chief of staff does it directly: a working agent restarts fresh when
|
|
48
|
+
its current turn ends (carrying its last reply as notes), and a finished job
|
|
49
|
+
forgets its session so the next reply starts clean.
|
|
50
|
+
|
|
51
|
+
Neither kind of clear loses track of the work. The office keeps a short note on
|
|
52
|
+
the job (`lastClear`: the agent's last report, or the brief if it had not
|
|
53
|
+
reported) and shows it in the job's log. A reply to a cleared job carries the
|
|
54
|
+
note and the brief into the fresh session, and any new job the agent takes
|
|
55
|
+
mentions what they were last on, so "what were you doing?" still has an answer.
|
|
56
|
+
|
|
36
57
|
## What changed
|
|
37
58
|
|
|
38
59
|
When a job starts, the office snapshots the project's working tree; when it
|
package/docs/linked-offices.md
CHANGED
|
@@ -47,7 +47,7 @@ account. Pick **Invite a guest** and choose:
|
|
|
47
47
|
|
|
48
48
|
A guest can start up to three jobs at a time, reply to them, stop them, and see
|
|
49
49
|
their logs and changes. They can't see your other jobs, chat or channels, and
|
|
50
|
-
they can't
|
|
50
|
+
they can't restart the office, or change staff or settings.
|
|
51
51
|
Their jobs show `from @handle` in your job list, and you can stop any of them.
|
|
52
52
|
Agents working for a guest can't message or hand work to your other agents.
|
|
53
53
|
They also don't get your MCP servers or claude.ai connectors (mail, Slack,
|
package/docs/packaging.md
CHANGED
|
@@ -16,7 +16,7 @@ the package.
|
|
|
16
16
|
|---|---|---|---|
|
|
17
17
|
| **npm CLI** (`npx agent-office`) | Best: the audience already has Node and Claude Code | ~1 week | data and project root must move out of the package |
|
|
18
18
|
| **Homebrew tap** | Good extra for Mac users | ~1 day on top of npm | another channel to release to |
|
|
19
|
-
| **Desktop app** (Electron) | Friendlier, but still needs the `claude` CLI | 3–5 weeks | signing and notarisation, auto-update
|
|
19
|
+
| **Desktop app** (Electron) | Friendlier, but still needs the `claude` CLI | 3–5 weeks | signing and notarisation, auto-update |
|
|
20
20
|
| **Docker** | Poor for a local tool | ~1 week | agents edit *host* repos: bind mounts, file ownership, git/SSH keys, login inside the container |
|
|
21
21
|
| **Hosted** | A different product | months | accounts, a sandbox per user, repos via GitHub, a database, API billing; the local-only security model goes |
|
|
22
22
|
|
|
@@ -34,14 +34,13 @@ if users ask for one; leave hosted until the local version has users.
|
|
|
34
34
|
`OFFICE_DATA` override, migrating an existing `<app>/data` on first run.
|
|
35
35
|
- [ ] **Project root:** default to the current directory, `--root <dir>`,
|
|
36
36
|
remembered after the first run.
|
|
37
|
-
- [ ] **`bin/agent-office.js`:** `--port`, `--root`, `--open
|
|
37
|
+
- [ ] **`bin/agent-office.js`:** `--port`, `--root`, `--open`;
|
|
38
38
|
pick another port if 4317 is taken; open the browser.
|
|
39
39
|
- [ ] **Preflight:** `claude` on `PATH`, recent enough, and signed in or given a
|
|
40
40
|
key — one clear fix for each problem.
|
|
41
41
|
- [ ] **Package:** publish a prebuilt `dist/`; move the web-only dependencies
|
|
42
|
-
(`three`, `marked`,
|
|
42
|
+
(`three`, `marked`, fonts) to devDependencies so the runtime
|
|
43
43
|
needs only `ws`.
|
|
44
|
-
- [ ] **Terminal:** a `node-pty` build with prebuilt binaries, still optional.
|
|
45
44
|
- [ ] **Voice:** `agent-office voice setup` in place of the two npm scripts.
|
|
46
45
|
- [ ] **Plan meters:** hide quietly if the usage endpoint goes away.
|
|
47
46
|
- [ ] **Platforms:** CI on clean macOS and Ubuntu; Windows via WSL.
|
package/docs/phone.md
CHANGED
|
@@ -16,7 +16,7 @@ The supported way is [Tailscale](https://tailscale.com): put the office behind
|
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
tailscale serve --bg 4317
|
|
19
|
-
OFFICE_ALLOWED_HOSTS=my-mac.tail1234.ts.net
|
|
19
|
+
OFFICE_ALLOWED_HOSTS=my-mac.tail1234.ts.net agent-office
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`OFFICE_ALLOWED_HOSTS` takes exact hostnames, comma-separated. Every other
|
|
@@ -27,6 +27,5 @@ On the phone, open the tailnet address once with the office key on the end,
|
|
|
27
27
|
printed, or `agentoffice --link`). The phone keeps it after that.
|
|
28
28
|
|
|
29
29
|
**Read this first.** Whoever can open that address can run Claude in your
|
|
30
|
-
projects with full autonomy
|
|
31
|
-
this machine. Only expose it on a tailnet that is just you. Never put it on
|
|
30
|
+
projects with full autonomy. Only expose it on a tailnet that is just you. Never put it on
|
|
32
31
|
the open internet. See [Security](security.md).
|
package/docs/security.md
CHANGED
|
@@ -35,10 +35,10 @@ command you could.
|
|
|
35
35
|
`claude` it starts reads them. The plan meters read the login's access token
|
|
36
36
|
to ask for usage, and nothing else.
|
|
37
37
|
- With an **API key**, the key is checked with Anthropic, stored in
|
|
38
|
-
`
|
|
38
|
+
`auth.json` in the data folder with mode `0600`, passed only to the `claude` processes the
|
|
39
39
|
office starts, and never sent to the browser, which sees the last four
|
|
40
40
|
characters.
|
|
41
|
-
-
|
|
41
|
+
- The data folder is outside the package (`~/.agent-office`), or git-ignored `data/` in a clone.
|
|
42
42
|
|
|
43
43
|
## The leaderboard
|
|
44
44
|
|
|
@@ -64,7 +64,7 @@ enforced on your side and the guest's office is never trusted:
|
|
|
64
64
|
- only the projects you picked;
|
|
65
65
|
- at or below the clearance cap you set;
|
|
66
66
|
- within the tokens per day you set;
|
|
67
|
-
- no
|
|
67
|
+
- no settings, chat, or other people's jobs;
|
|
68
68
|
- their agents can't reach your other agents;
|
|
69
69
|
- their jobs don't get your MCP servers or claude.ai connectors, your
|
|
70
70
|
user-level Claude Code settings and hooks, or your Claude Code memory.
|
|
@@ -72,9 +72,3 @@ enforced on your side and the guest's office is never trusted:
|
|
|
72
72
|
Even *Edit files* can change scripts or hooks that run later, so invite only
|
|
73
73
|
people you would trust with those projects. Details in
|
|
74
74
|
[Linked offices](linked-offices.md).
|
|
75
|
-
|
|
76
|
-
## The terminal
|
|
77
|
-
|
|
78
|
-
Off unless `OFFICE_TERMINAL=1`. A session needs a single-use ticket from the
|
|
79
|
-
origin-checked API, no agent can open one, and keystrokes are never logged. It
|
|
80
|
-
is not a sandbox. Details in [The terminal](terminal.md).
|
package/docs/using-the-office.md
CHANGED
|
@@ -57,7 +57,7 @@ Drag to move, right-drag (or `Q` / `E`) to turn around what's in the middle
|
|
|
57
57
|
of the view, scroll to zoom toward the cursor, double-click to glide somewhere.
|
|
58
58
|
`W A S D` pan, `R` recenters. The walls nearest you drop down so you can always
|
|
59
59
|
see in. Chat and job details sit in their own columns and never cover the
|
|
60
|
-
office
|
|
60
|
+
office.
|
|
61
61
|
|
|
62
62
|
## Daily standup
|
|
63
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-office-studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A 3D office where the staff are Claude Code agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -49,19 +49,14 @@
|
|
|
49
49
|
"prepack": "vite build"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"
|
|
53
|
-
"@fontsource/jetbrains-mono": "5.1.1",
|
|
54
|
-
"@xterm/addon-fit": "0.10.0",
|
|
55
|
-
"@xterm/xterm": "5.5.0",
|
|
56
|
-
"marked": "^14.1.3",
|
|
57
|
-
"three": "0.169.0",
|
|
58
|
-
"ws": "8.18.0"
|
|
52
|
+
"ws": "8.22.0"
|
|
59
53
|
},
|
|
60
54
|
"devDependencies": {
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
55
|
+
"@fontsource-variable/inter": "5.3.0",
|
|
56
|
+
"@fontsource/jetbrains-mono": "5.3.0",
|
|
57
|
+
"concurrently": "10.0.6",
|
|
58
|
+
"marked": "^18.1.0",
|
|
59
|
+
"three": "0.186.1",
|
|
60
|
+
"vite": "6.4.3"
|
|
66
61
|
}
|
|
67
62
|
}
|
package/server/chief.js
CHANGED
|
@@ -24,7 +24,7 @@ export function canSee(files) {
|
|
|
24
24
|
const SCHEMA = {
|
|
25
25
|
type: 'object',
|
|
26
26
|
properties: {
|
|
27
|
-
action: { type: 'string', enum: ['followup', 'new', 'dismiss', 'recall', 'standup', 'ask', 'chat'] },
|
|
27
|
+
action: { type: 'string', enum: ['followup', 'new', 'dismiss', 'recall', 'clear', 'standup', 'ask', 'chat'] },
|
|
28
28
|
jobId: { type: 'string' },
|
|
29
29
|
agentId: { type: 'string' },
|
|
30
30
|
agentIds: { type: 'array', items: { type: 'string' } },
|
|
@@ -45,6 +45,7 @@ Actions:
|
|
|
45
45
|
- "new": a new piece of work. Give "repo" (exactly one name from Projects), "brief" (the task in the boss's own words, plus any context from recent chat that the agent needs; never add requirements), "agentId" (the tagged agent's id; otherwise a free agent whose role in Staff clearly fits the work; otherwise "" for whoever is free), and "model" (see Sizing the work). Never pick a busy or dismissed agent unless the boss tagged them.
|
|
46
46
|
- "dismiss": the boss wants agents sent home (dismissed, let go for now, off the floor). Give "agentIds": the ids of exactly who they mean, worked out from Staff (e.g. "everyone who's free except Mira" means every free agent but Mira). Only free agents can go; leave out anyone working.
|
|
47
47
|
- "recall": the boss wants dismissed agents back in. Give "agentIds" (every dismissed agent if they say "everyone").
|
|
48
|
+
- "clear": the boss wants agents to clear their context (start fresh, forget the conversation so far). Do it yourself; never hand it out as a job. Give "agentIds" for the agents they name, or leave it empty when they mean everyone.
|
|
48
49
|
- "standup": the boss wants the team to hold a standup now (e.g. "standup time", "let's do standup", "gather everyone for a status update").
|
|
49
50
|
- "ask": you can't tell which project new work belongs to, or the request is too unclear to start. Ask one short question.
|
|
50
51
|
- "chat": greetings, thanks, or questions about the office itself (who is doing what). Answer from the context given. If your answer is about one particular job, give its jobId too, so the boss gets a link to where that job's updates are collecting.
|
package/server/icons.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
// Finds a project's own app icon or favicon so the office can show it beside
|
|
5
|
+
// the project's name. Looks in the usual places, then a shallow scan.
|
|
6
|
+
|
|
7
|
+
const TYPES = { '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp' };
|
|
8
|
+
const SKIP = new Set(['node_modules', '.git', 'dist', 'build', '.next', '.expo', 'Pods', 'vendor', 'coverage', 'out', 'target', 'data', 'DerivedData']);
|
|
9
|
+
const NAME = /^(favicon|app-?icon|apple-touch-icon|icon|logo|ic_launcher)([-_.@x\d\w]*)\.(svg|png|ico|jpe?g|webp)$/i;
|
|
10
|
+
const MAX_BYTES = 1024 * 1024;
|
|
11
|
+
const MAX_DEPTH = 4;
|
|
12
|
+
const MAX_VISITS = 1500;
|
|
13
|
+
|
|
14
|
+
const score = (rel, size) => {
|
|
15
|
+
const parts = rel.split(path.sep);
|
|
16
|
+
const base = parts[parts.length - 1].toLowerCase();
|
|
17
|
+
const ext = path.extname(base);
|
|
18
|
+
let s = 0;
|
|
19
|
+
if (/^(app-?icon|icon)\./.test(base)) s += 40;
|
|
20
|
+
else if (/^apple-touch-icon/.test(base)) s += 36;
|
|
21
|
+
else if (/^favicon/.test(base)) s += 30;
|
|
22
|
+
else if (/^logo/.test(base)) s += 20;
|
|
23
|
+
else s += 10;
|
|
24
|
+
if (ext === '.svg') s += 12; else if (ext === '.png') s += 8; else if (ext === '.ico') s -= 4;
|
|
25
|
+
if (parts.some((p) => /^(public|assets|static|resources|app|icons?|images|web)$/i.test(p))) s += 6;
|
|
26
|
+
if (parts.some((p) => /^(test|tests|__tests__|docs?|examples?|fixtures?|storybook|screenshots?)$/i.test(p))) s -= 25;
|
|
27
|
+
s -= (parts.length - 1) * 3;
|
|
28
|
+
s += Math.min(size / 20000, 4); // among equals, the sharper one
|
|
29
|
+
return s;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function scan(root) {
|
|
33
|
+
const found = [];
|
|
34
|
+
let visits = 0;
|
|
35
|
+
const walk = (dir, depth) => {
|
|
36
|
+
let entries;
|
|
37
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
38
|
+
for (const e of entries) {
|
|
39
|
+
if (++visits > MAX_VISITS) return;
|
|
40
|
+
const full = path.join(dir, e.name);
|
|
41
|
+
if (e.isDirectory()) {
|
|
42
|
+
if (!SKIP.has(e.name) && !e.name.startsWith('.') && depth < MAX_DEPTH) {
|
|
43
|
+
// iOS keeps its icons in an appiconset; the biggest PNG is the one to use.
|
|
44
|
+
if (e.name === 'AppIcon.appiconset') {
|
|
45
|
+
try {
|
|
46
|
+
const pngs = fs.readdirSync(full).filter((f) => /\.png$/i.test(f)).map((f) => [f, fs.statSync(path.join(full, f)).size]).sort((a, b) => b[1] - a[1]);
|
|
47
|
+
if (pngs[0] && pngs[0][1] <= MAX_BYTES) found.push({ full: path.join(full, pngs[0][0]), score: 38 - depth * 3 });
|
|
48
|
+
} catch {}
|
|
49
|
+
} else walk(full, depth + 1);
|
|
50
|
+
}
|
|
51
|
+
} else if (e.isFile() && NAME.test(e.name)) {
|
|
52
|
+
try {
|
|
53
|
+
const size = fs.statSync(full).size;
|
|
54
|
+
if (size > 0 && size <= MAX_BYTES) found.push({ full, score: score(path.relative(root, full), size) });
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
walk(root, 0);
|
|
60
|
+
return found.sort((a, b) => b.score - a.score)[0]?.full || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cache = new Map(); // repo dir -> { at, file }
|
|
64
|
+
const TTL = 5 * 60 * 1000;
|
|
65
|
+
|
|
66
|
+
// Returns { file, type } for a project folder, or null when it has no icon.
|
|
67
|
+
export function findIcon(dir) {
|
|
68
|
+
const hit = cache.get(dir);
|
|
69
|
+
if (hit && Date.now() - hit.at < TTL) return hit.file;
|
|
70
|
+
let file = null;
|
|
71
|
+
try {
|
|
72
|
+
const found = scan(dir);
|
|
73
|
+
const real = found && fs.realpathSync(found);
|
|
74
|
+
// A symlink must not lead out of the project.
|
|
75
|
+
if (real && real.startsWith(fs.realpathSync(dir) + path.sep)) {
|
|
76
|
+
const type = TYPES[path.extname(real).toLowerCase()];
|
|
77
|
+
if (type) file = { file: real, type };
|
|
78
|
+
}
|
|
79
|
+
} catch {}
|
|
80
|
+
cache.set(dir, { at: Date.now(), file });
|
|
81
|
+
return file;
|
|
82
|
+
}
|