coworking-agents 0.3.0 → 0.4.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 +15 -0
- package/README.md +74 -7
- package/ROADMAP.md +7 -1
- package/bin/coworking-agents.js +3 -0
- package/docs/achievements.png +0 -0
- package/docs/building.png +0 -0
- package/docs/camera.png +0 -0
- package/docs/christmas.png +0 -0
- package/docs/customise.png +0 -0
- package/docs/game-room.png +0 -0
- package/docs/halloween.png +0 -0
- package/docs/house-wall.png +0 -0
- package/docs/mobile.png +0 -0
- package/docs/office-day.png +0 -0
- package/docs/office-night.png +0 -0
- package/docs/panel.png +0 -0
- package/docs/paper.png +0 -0
- package/docs/servers.png +0 -0
- package/docs/talk.png +0 -0
- package/docs/top-bar.png +0 -0
- package/docs/trophy-room.png +0 -0
- package/docs/usage.png +0 -0
- package/docs/work-site.png +0 -0
- package/package.json +4 -2
- package/public/index.html +1 -0
- package/public/js/alerts.js +1 -0
- package/public/js/fun2.js +21 -12
- package/public/js/i18n.js +32 -2
- package/public/js/report.js +4 -1
- package/public/js/tasks.js +248 -0
- package/public/js/views.js +109 -58
- package/public/js/work.js +1 -1
- package/public/style.css +32 -0
- package/src/collect.js +3 -2
- package/src/demo.js +15 -2
- package/src/focus.js +16 -1
- package/src/mcp.js +43 -0
- package/src/server.js +24 -1
- package/src/sources/codex.js +5 -4
- package/src/tasks.js +263 -0
- package/src/update.js +10 -3
- package/src/usage.js +1 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0 — 2026-10-11
|
|
4
|
+
- Tasks: create a task, pick folder, agent and profile; it runs in the background (Claude Code or Codex), in its own worktree if you want
|
|
5
|
+
- Answer questions, approve blocked tools, review the diff and test results, conclude, hand off or retry — all from the office
|
|
6
|
+
- Queue with priority, dependencies, max parallel agents and automatic agent choice
|
|
7
|
+
- Cost ranking by task and by AI (day, week, month), budget per task, Save / More capacity / Recommended / manual model and effort
|
|
8
|
+
- Recommendations with the reason, warnings for loops, retries and budget
|
|
9
|
+
- Notification center and a releases board with the office admin
|
|
10
|
+
- Read-only MCP server: `coworking-agents mcp`
|
|
11
|
+
- Linux: send text to agents running in tmux
|
|
12
|
+
- Private mode hides repo names and titles in the report and usage ranking
|
|
13
|
+
|
|
14
|
+
## 0.3.1 — 2026-10-10
|
|
15
|
+
- Crisp Bug Catcher, redesigned rooftop and server room
|
package/README.md
CHANGED
|
@@ -1,23 +1,67 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="docs/logo.png" width="
|
|
2
|
+
<img src="docs/logo.png" width="112" alt="coworking-agents logo">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">coworking-agents</h1>
|
|
6
6
|
|
|
7
|
-
<p align="center"><b>A live pixel-art office for your AI coding agents.</b><br>Claude Code
|
|
7
|
+
<p align="center"><b>A live pixel-art office for your AI coding agents.</b><br>Every Claude Code and Codex session becomes a person at a desk.</p>
|
|
8
8
|
|
|
9
|
-
<p align="center"
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/coworking-agents"><img src="https://img.shields.io/npm/v/coworking-agents?color=e8804a" alt="npm version"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/coworking-agents"><img src="https://img.shields.io/npm/dm/coworking-agents?color=4f7fd9" alt="npm downloads"></a>
|
|
12
|
+
<img src="https://img.shields.io/badge/node-%E2%89%A518-5bbf6a" alt="Node 18+">
|
|
13
|
+
<img src="https://img.shields.io/badge/dependencies-0-5bbf6a" alt="zero dependencies">
|
|
14
|
+
<img src="https://img.shields.io/badge/runs-100%25%20local-c75fa8" alt="runs 100% local">
|
|
15
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-8a8494" alt="MIT license"></a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<a href="https://portfolio-delta-five-78.vercel.app/demos/coworking/index.html?lang=en"><b>▶ Try the live demo</b></a> ·
|
|
20
|
+
<a href="#quick-start">Quick start</a> ·
|
|
21
|
+
<a href="#see-it-in-action">See it in action</a> ·
|
|
22
|
+
<a href="#how-it-works">How it works</a> ·
|
|
23
|
+
<a href="#as-a-claude-code-plugin">Claude Code plugin</a>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center"><img src="docs/demo.webp" alt="The office at night: agents typing at their desks, toasts asking for you, the camera flying in to one agent and its side panel"></p>
|
|
27
|
+
|
|
28
|
+
At a glance you see who is working, who needs you (and what they are asking), whose turn it is, and which sessions are editing the same checkout, even across different AIs. Reply to an agent or jump to its terminal tab straight from the office.
|
|
29
|
+
|
|
30
|
+
## Try it in 10 seconds
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx coworking-agents --demo # a fake office with every state, no sessions needed
|
|
34
|
+
npx coworking-agents # your real sessions
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Or **[open the live demo](https://portfolio-delta-five-78.vercel.app/demos/coworking/index.html?lang=en)** in your browser: nothing to install. It runs the same `--demo` office (with no server and no AI calls), so you can click the agents, open the panels and walk around.
|
|
10
38
|
|
|
11
|
-
|
|
39
|
+
## See it in action
|
|
12
40
|
|
|
13
|
-
|
|
41
|
+
<p align="center"><img src="docs/tour.webp" alt="Tour: the office, the camera flying in to an agent with its side panel, talking to an agent that finished, the usage report, the server room and the rooftop"></p>
|
|
14
42
|
|
|
15
|
-
|
|
43
|
+
<p align="center"><sub>The office → click an agent (camera + side panel) → talk to one that is waiting → usage report → server room → rooftop.</sub></p>
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
```mermaid
|
|
48
|
+
flowchart LR
|
|
49
|
+
CC["Claude Code<br/>~/.claude sessions + transcripts"] --> S
|
|
50
|
+
CX["Codex<br/>~/.codex sessions"] --> S
|
|
51
|
+
OT["Gemini CLI, Aider, OpenCode…<br/>running processes"] --> S
|
|
52
|
+
S["Local Node server<br/>zero dependencies · 127.0.0.1"] -- "state of every session, every second (SSE)" --> O["The office in your browser<br/>pixel-art canvas"]
|
|
53
|
+
O -- "reply · answer · compact · go to terminal" --> T["Your Terminal / iTerm tab"]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
1. **Read, never write.** The server reads the files each AI already keeps on disk (the live-session registry and the transcripts). No hooks, nothing installed into the AIs, no model is ever called.
|
|
57
|
+
2. **One snapshot per second.** It works out what every session is doing (editing, in the terminal, thinking, delegating, waiting for you) and streams it to the page over Server-Sent Events.
|
|
58
|
+
3. **Drawn as an office.** One room per repository, one desk per session, the kitchen when it's your turn, the meeting room when it delegates, a red **!** when it needs you.
|
|
16
59
|
|
|
17
60
|
## Tour
|
|
18
61
|
|
|
19
62
|
| | |
|
|
20
63
|
|---|---|
|
|
64
|
+
|  | **The office.** One room per repository, a desk per session, a shared wing with the kitchen, the glass meeting room and the nap corner. |
|
|
21
65
|
|  | **Top bar.** One HUD with the counters and the usage meters; the counter below is where agents waiting on you sit (4 per office, **+** slides to the next). |
|
|
22
66
|
|  | **Talk.** Click a waiting agent: it says what it wants at its desk. Reply right there (typed into its Terminal/iTerm tab for Claude Code and Codex CLI) or jump to it. |
|
|
23
67
|
|  | **Camera.** Click an agent and the camera flies in to its desk with a spotlight; the side panel shows everything about the session. |
|
|
@@ -134,6 +178,27 @@ Agents away from their desks (kitchen, meeting room, nap corner) carry a name ta
|
|
|
134
178
|
- **Focus mode:** sounds and system notifications pause for 25 or 50 minutes.
|
|
135
179
|
- **Replay:** scrub or play back the last hour.
|
|
136
180
|
|
|
181
|
+
## Tasks: hand work to agents from the office
|
|
182
|
+
|
|
183
|
+
Open **Tasks** (☰ → report tabs) and describe the work, pick a folder, an agent (Claude Code, Codex or *Automatic*) and a profile. The agent runs in the background (`claude -p` / `codex exec`), in its own git worktree if you tick it. You never need the terminal:
|
|
184
|
+
|
|
185
|
+
- **Answer** its questions and **approve** tools it was not allowed to use; it resumes the same session.
|
|
186
|
+
- **Review** the result, the files changed, the diff and your test command's output, then **conclude**, **retry** or **hand off** to the other agent (with a summary of what was done).
|
|
187
|
+
- **Queue:** priority, "after task X" dependencies and how many agents run at once.
|
|
188
|
+
- **Cost:** per task (tokens in/out/cache, estimated USD, attempts), a ranking by task and by AI for today / 7 / 30 days, and an optional budget that pauses the task when reached.
|
|
189
|
+
- **Profiles and controls:** Economy, Balanced, Deep. *Save*, *More capacity*, *Apply recommendation* or pick model and effort by hand; changes apply from the next step and are logged. The recommendation is plain rules plus what similar past tasks taught (no AI call to choose).
|
|
190
|
+
- **Warnings:** the same action repeated without progress, two failed attempts, budget reached.
|
|
191
|
+
|
|
192
|
+
Only tasks created here can be controlled. Sessions found by monitoring stay read-only. Costs are estimates at API prices, not your bill or plan percentage. Tasks live in `~/.config/coworking-agents/tasks/`.
|
|
193
|
+
|
|
194
|
+
## Notices, releases and the office admin
|
|
195
|
+
|
|
196
|
+
The 🔔 bell collects agent requests, task updates, recommendations (*Apply*, *See why*, *Not now*), cost alerts and new versions, grouped so repeats don't pile up. **Releases** shows the installed version, the newest on npm, when it checked and what changed, with the update command to copy. The office admin waves a sign while a new version is unseen, keeps it until you run it, then celebrates.
|
|
197
|
+
|
|
198
|
+
## MCP (read-only)
|
|
199
|
+
|
|
200
|
+
`claude mcp add coworking -- npx coworking-agents mcp` gives other agents `list_agents`, `get_agent_status`, `get_conflicts`, `get_daily_report` and `list_tasks`. Nothing through MCP changes anything.
|
|
201
|
+
|
|
137
202
|
## Walk around (Gather-style)
|
|
138
203
|
|
|
139
204
|
You're in the office too, with a **YOU** tag. **WASD** or the arrow keys walk (hold **Shift** to run), a click on the floor walks you there through the doors, and walls stop you. Near an agent, a prop or a door, **Enter** talks, opens or enters. Turn it off in the ☰ menu.
|
|
@@ -209,7 +274,8 @@ Click the elevator (or press Enter next to it) to ride between floors, up to the
|
|
|
209
274
|
|---|---|---|---|
|
|
210
275
|
| See sessions (Claude Code, Codex, other CLIs) | ✓ | ✓ | ✓ |
|
|
211
276
|
| Go to terminal | exact tab (Terminal, iTerm2) | window (needs `xdotool` or `wmctrl`) | window |
|
|
212
|
-
| Reply / answer / compact from the office | ✓ | copy and go | copy and go |
|
|
277
|
+
| Reply / answer / compact from the office | ✓ | tmux, else copy and go | tmux, else copy and go |
|
|
278
|
+
| Tasks in the background | ✓ | ✓ | ✓ (needs the CLIs on PATH) |
|
|
213
279
|
|
|
214
280
|
Linux and Windows support is written against their process tools but has not been tested on real machines yet; reports welcome. See the [roadmap](https://github.com/ChaconLucas/coworking-agents/blob/main/ROADMAP.md) for what's waiting on testing and what's next.
|
|
215
281
|
|
|
@@ -288,6 +354,7 @@ module.exports = {
|
|
|
288
354
|
npm test # builds fake ~/.claude and ~/.codex folders and checks the collector and the server
|
|
289
355
|
npm run demo # the fake office with every state
|
|
290
356
|
npm run screenshots # regenerates docs/*.png from demo mode (needs agent-browser on PATH)
|
|
357
|
+
npm run video # re-records docs/demo.webp and docs/tour.webp (needs agent-browser and img2webp)
|
|
291
358
|
node bin/coworking-agents.js --json # one snapshot of the state, as JSON
|
|
292
359
|
```
|
|
293
360
|
|
package/ROADMAP.md
CHANGED
|
@@ -9,7 +9,7 @@ These are written or designed, but can't be verified without hardware or data we
|
|
|
9
9
|
- **Windows, real-machine test.** The process table (PowerShell/CIM), snapshot and server have never run on a real Windows box. Needed: someone running `npx coworking-agents` and `npx coworking-agents --json` there.
|
|
10
10
|
- **Windows CPU per process.** Other AI CLIs always show "idle" on Windows because CIM doesn't give CPU %. Needs a cheap measurement (e.g. two `Get-Process` samples) tested on a real machine.
|
|
11
11
|
- **Linux, real-machine test.** `ps`, `/proc/<pid>/cwd` and the "go to terminal" path (xdotool / wmctrl) were written blind.
|
|
12
|
-
- **Typing into a terminal on Linux and Windows.** Reply, answer and `/compact` are macOS only (AppleScript into Terminal/iTerm).
|
|
12
|
+
- **Typing into a terminal on Linux and Windows.** Reply, answer and `/compact` are macOS only (AppleScript into Terminal/iTerm). Agents inside tmux now get text through `tmux send-keys` on any OS (written, not yet tested on a real Linux box); other Linux terminals and Windows Terminal/conhost still need support.
|
|
13
13
|
- **Full readers for other AIs.** Gemini CLI, OpenCode, Aider, Cursor Agent, Amp, Goose, Qwen Code, Crush, Droid and Kimi are detected by process only (working/idle). Showing what they are doing (tool, file, question, tokens) needs their on-disk session format, read from real transcripts. Share a sample (redacted) to get one added.
|
|
14
14
|
|
|
15
15
|
## Next
|
|
@@ -24,3 +24,9 @@ Estimated cost · wait reminder · git state on the door · daily report as Mark
|
|
|
24
24
|
## Ideas
|
|
25
25
|
|
|
26
26
|
- **Seasonal decor** by date (subtle, off with a toggle).
|
|
27
|
+
|
|
28
|
+
## Tasks (0.4.0) — next steps
|
|
29
|
+
- Automatic assignment beyond "least busy agent"; per-profile instructions (Backend, Frontend, Reviewer).
|
|
30
|
+
- Mid-run permission prompts in the page (today: denied tools are listed after the step and you approve to resume).
|
|
31
|
+
- Compare similar tasks by tests, cost and retries to tune recommendations further; optional auto-apply.
|
|
32
|
+
- Semantic search over history only if plain text search stops being enough.
|
package/bin/coworking-agents.js
CHANGED
|
@@ -19,10 +19,13 @@ if (flag('-h') || flag('--help')) {
|
|
|
19
19
|
--private hide session titles, paths and file names (screen sharing)
|
|
20
20
|
--demo [N] show a fake office with every state and N agents (default 10)
|
|
21
21
|
--json print one snapshot and exit
|
|
22
|
+
mcp run a read-only MCP server on stdio (claude mcp add coworking -- npx coworking-agents mcp)
|
|
22
23
|
--no-update-check don't ask npm whether a newer version is out`);
|
|
23
24
|
process.exit(0);
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
if (args[0] === 'mcp') { require('../src/mcp').serve(); return; }
|
|
28
|
+
|
|
26
29
|
if (flag('--json')) {
|
|
27
30
|
const { snapshot } = require('../src/collect');
|
|
28
31
|
// write and let Node exit on its own: process.exit() cuts a big snapshot short when stdout is a pipe
|
package/docs/achievements.png
CHANGED
|
Binary file
|
package/docs/building.png
CHANGED
|
Binary file
|
package/docs/camera.png
CHANGED
|
Binary file
|
package/docs/christmas.png
CHANGED
|
Binary file
|
package/docs/customise.png
CHANGED
|
Binary file
|
package/docs/game-room.png
CHANGED
|
Binary file
|
package/docs/halloween.png
CHANGED
|
Binary file
|
package/docs/house-wall.png
CHANGED
|
Binary file
|
package/docs/mobile.png
CHANGED
|
Binary file
|
package/docs/office-day.png
CHANGED
|
Binary file
|
package/docs/office-night.png
CHANGED
|
Binary file
|
package/docs/panel.png
CHANGED
|
Binary file
|
package/docs/paper.png
CHANGED
|
Binary file
|
package/docs/servers.png
CHANGED
|
Binary file
|
package/docs/talk.png
CHANGED
|
Binary file
|
package/docs/top-bar.png
CHANGED
|
Binary file
|
package/docs/trophy-room.png
CHANGED
|
Binary file
|
package/docs/usage.png
CHANGED
|
Binary file
|
package/docs/work-site.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coworking-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A pixel-art coworking office for your AI coding agents (Claude Code, Codex, …): see who's working, who needs you, and who's editing the same checkout.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"coworking-agents": "bin/coworking-agents.js"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"public",
|
|
12
12
|
".claude-plugin",
|
|
13
13
|
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
14
15
|
"LICENSE",
|
|
15
16
|
"commands",
|
|
16
17
|
"skills",
|
|
@@ -25,7 +26,8 @@
|
|
|
25
26
|
"start": "node bin/coworking-agents.js",
|
|
26
27
|
"demo": "node bin/coworking-agents.js --demo",
|
|
27
28
|
"test": "node test/run.js",
|
|
28
|
-
"screenshots": "bash scripts/screenshots.sh"
|
|
29
|
+
"screenshots": "bash scripts/screenshots.sh",
|
|
30
|
+
"video": "node scripts/record-video.js"
|
|
29
31
|
},
|
|
30
32
|
"keywords": [
|
|
31
33
|
"ai",
|
package/public/index.html
CHANGED
package/public/js/alerts.js
CHANGED
|
@@ -61,6 +61,7 @@ function checkAlerts() {
|
|
|
61
61
|
if (firstAlertPass) continue; // already waiting when the page opened: the counters show it
|
|
62
62
|
const what = p.state === 'needs_you' ? A.waitAsk(mins) : A.waitApprove(mins);
|
|
63
63
|
toast(`<b>${esc(what)}</b>${esc(p.name)} · ${esc(p.title || '')}`, 'warn');
|
|
64
|
+
if (typeof pushNotice === 'function') pushNotice('agent:' + p.id, 'warn', p.name + ': ' + what, p.title || '', { agent: p.id });
|
|
64
65
|
notifyOS(what, p.name);
|
|
65
66
|
}
|
|
66
67
|
}
|
package/public/js/fun2.js
CHANGED
|
@@ -91,21 +91,30 @@ function openArcade() {
|
|
|
91
91
|
function drawArcadeTitle() {
|
|
92
92
|
const c = arcadeEl.querySelector('.arcade-cv'); if (!c) return;
|
|
93
93
|
const g = c.getContext('2d'); arcadeBg(g, performance.now());
|
|
94
|
-
g
|
|
95
|
-
g
|
|
94
|
+
gText(g, AW / 2, 46, 'BUG CATCHER', '#ffd84d', 3, true);
|
|
95
|
+
gText(g, AW / 2, 78, lang === 'pt' ? 'ESPACO OU ENTER' : 'SPACE OR ENTER', '#c0cbdc', 1, true);
|
|
96
96
|
drawBug(g, AW / 2 - 30, 100, false, 0); drawBug(g, AW / 2 - 4, 104, true, 1); drawBug(g, AW / 2 + 22, 100, false, 0);
|
|
97
97
|
}
|
|
98
|
+
// crisp text: the office's pixel font, scaled by whole pixels
|
|
99
|
+
function gText(g, x, y, str, c, k = 1, center = false) {
|
|
100
|
+
const txt = String(str).toUpperCase(); if (center) x -= Math.round(txt.length * 4 * k / 2);
|
|
101
|
+
g.fillStyle = c; let cx = Math.round(x);
|
|
102
|
+
for (const ch of txt) { const gl = PIXFONT[ch]; if (gl) for (let i = 0; i < 15; i++) if (gl[i] === '1') g.fillRect(cx + (i % 3) * k, Math.round(y) + ((i / 3) | 0) * k, k, k); cx += 4 * k; }
|
|
103
|
+
}
|
|
98
104
|
function arcadeBg(g, now) {
|
|
99
105
|
g.fillStyle = '#141826'; g.fillRect(0, 0, AW, AH);
|
|
100
|
-
g.fillStyle = '#1c2236'; for (let y = 0; y < AH; y += 8) { const sh = (y * 13) % 70; g.fillRect(6 + sh % 20, (y + now / 60) % AH, 30 + sh, 2); } // code scrolling behind
|
|
106
|
+
g.fillStyle = '#1c2236'; for (let y = 0; y < AH; y += 8) { const sh = (y * 13) % 70; g.fillRect(6 + sh % 20, Math.round((y + now / 60) % AH), 30 + sh, 2); } // code scrolling behind
|
|
101
107
|
g.fillStyle = '#2a2f45'; g.fillRect(0, AH - 8, AW, 8);
|
|
102
108
|
}
|
|
103
109
|
function drawBug(g, x, y, gold, f) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
g.fillStyle =
|
|
107
|
-
g.fillStyle = '#
|
|
108
|
-
|
|
110
|
+
x = Math.round(x); y = Math.round(y);
|
|
111
|
+
const body = gold ? '#ffd84d' : '#63c74d', dark = gold ? '#b07d2a' : '#2f7a3b', leg = '#c0cbdc';
|
|
112
|
+
g.fillStyle = leg; for (const [dx, dy] of [[-3, 3], [-3, 6], [11, 3], [11, 6]]) g.fillRect(x + dx + (f ? (dx < 0 ? -1 : 1) : 0), y + dy + (f ? 1 : 0), 4, 1); // legs wiggle
|
|
113
|
+
g.fillStyle = '#0b0a0e'; g.fillRect(x - 1, y - 1, 13, 11);
|
|
114
|
+
g.fillStyle = dark; g.fillRect(x, y, 11, 9); g.fillStyle = body; g.fillRect(x + 1, y + 1, 4, 7); g.fillRect(x + 6, y + 1, 4, 7); // shell, split down the middle
|
|
115
|
+
g.fillStyle = '#ffffff'; g.fillRect(x + 2, y + 2, 1, 2); g.fillRect(x + 7, y + 2, 1, 2);
|
|
116
|
+
g.fillStyle = '#3a4058'; g.fillRect(x + 2, y - 4, 7, 4); g.fillStyle = '#ffffff'; g.fillRect(x + 3, y - 3, 1, 1); g.fillRect(x + 7, y - 3, 1, 1); // head and eyes
|
|
117
|
+
g.fillStyle = leg; g.fillRect(x + 2, y - 7, 1, 3); g.fillRect(x + 8, y - 7, 1, 3); // antennae
|
|
109
118
|
}
|
|
110
119
|
function startArcade() {
|
|
111
120
|
const cv2 = arcadeEl.querySelector('.arcade-cv'); if (!cv2) return;
|
|
@@ -129,7 +138,7 @@ function tickArcade(now) {
|
|
|
129
138
|
} else if (b.y > AH - 8) { b.miss = true; G.lives--; G.combo = 0; }
|
|
130
139
|
}
|
|
131
140
|
G.bugs = G.bugs.filter(b => !b.hit && !b.miss);
|
|
132
|
-
for (const s of G.sparks) { s.x += s.vx * dt; s.y += s.vy * dt; s.vy += 80 * dt; s.life -= dt;
|
|
141
|
+
for (const s of G.sparks) { s.x += s.vx * dt; s.y += s.vy * dt; s.vy += 80 * dt; s.life -= dt; if (s.txt) gText(g, s.x, s.y - 6, s.txt, s.c); else { g.fillStyle = s.c; g.fillRect(Math.round(s.x), Math.round(s.y), 2, 2); } }
|
|
133
142
|
G.sparks = G.sparks.filter(s => s.life > 0);
|
|
134
143
|
// the laptop paddle
|
|
135
144
|
const px = Math.round(G.x), py = AH - 18;
|
|
@@ -137,14 +146,14 @@ function tickArcade(now) {
|
|
|
137
146
|
g.fillStyle = '#8b9bb4'; g.fillRect(px, py + 2, 28, 4); g.fillStyle = '#d97757'; g.fillRect(px + 11, py - 4, 6, 2);
|
|
138
147
|
// HUD: hearts, score, combo
|
|
139
148
|
for (let i = 0; i < 3; i++) { g.fillStyle = i < G.lives ? '#e43b44' : '#3a3448'; g.fillRect(6 + i * 10, 6, 3, 2); g.fillRect(10 + i * 10, 6, 3, 2); g.fillRect(6 + i * 10, 8, 7, 2); g.fillRect(7 + i * 10, 10, 5, 1); g.fillRect(8 + i * 10, 11, 3, 1); }
|
|
140
|
-
g
|
|
141
|
-
if (G.combo >= 5)
|
|
149
|
+
gText(g, AW - 6 - String(G.score).length * 8, 5, G.score, '#ffffff', 2);
|
|
150
|
+
if (G.combo >= 5) gText(g, AW / 2, 6, 'COMBO X2', '#ff6ec7', 1, true);
|
|
142
151
|
const sc = arcadeEl.querySelector('#arcade-score'); if (sc) sc.textContent = `${T.arcade.score}: ${G.score}`;
|
|
143
152
|
if (G.lives <= 0) {
|
|
144
153
|
const best = Number(store.get('arcadeBest', 0)) || 0;
|
|
145
154
|
if (G.score > best) { store.set('arcadeBest', G.score); toast(`<b>${esc(T.arcade.record)}</b>${G.score}`, 'ok'); const be = arcadeEl.querySelector('#arcade-best'); if (be) be.textContent = G.score; }
|
|
146
155
|
achRecord.arcadeBest = Math.max(achRecord.arcadeBest || 0, G.score); achBump('arcadeGames');
|
|
147
|
-
g.fillStyle = 'rgba(0,0,0,.
|
|
156
|
+
g.fillStyle = 'rgba(0,0,0,.7)'; g.fillRect(0, 56, AW, 46); gText(g, AW / 2, 64, 'GAME OVER', '#ffd84d', 2, true); gText(g, AW / 2, 84, `${G.score}`, '#ffffff', 2, true);
|
|
148
157
|
game2 = null; return;
|
|
149
158
|
}
|
|
150
159
|
requestAnimationFrame(tickArcade);
|
package/public/js/i18n.js
CHANGED
|
@@ -122,6 +122,21 @@ const I18N = {
|
|
|
122
122
|
scoreboard: { title: 'Placar da semana entre as IAs', hours: 'horas', actions: 'ações', tokens: 'tokens' },
|
|
123
123
|
share: { card: 'Cartão da semana', visit: 'Foto para visita', week: 'Minha semana no escritório', hours: 'horas de agentes trabalhando', actions: 'ações', trophies: 'níveis de conquista · lendas', saved: 'Cartão salvo', visitSaved: 'Foto para visita salva', visitNote: 'Sem nomes, branches nem o nome do escritório.' },
|
|
124
124
|
elevator: { title: 'Elevador', sub: 'Escolha o andar: terraço, escritório, sala de troféus ou servidores.', terrace: '🌇 Terraço', trophy: '🏆 Sala de troféus', servers: '🖧 Sala de servidores', office: '🏢 Escritório', floor: n => `🏢 Andar ${n}` },
|
|
125
|
+
tasks: { tab: n => n ? `Tarefas (${n})` : 'Tarefas', sub: 'Crie uma tarefa e um agente trabalha nela em segundo plano. Você responde, aprova e revisa por aqui, sem abrir o terminal. Só as tarefas criadas aqui podem ser controladas; as sessões abertas por fora seguem só monitoradas.',
|
|
126
|
+
newT: 'Nova tarefa', title: 'Título (opcional)', desc: 'O que fazer', folder: 'Pasta', agent: 'Agente', auto: 'Automático', profile: 'Perfil', prio: 'Prioridade', prios: ['', 'Baixa', 'Normal', 'Alta'],
|
|
127
|
+
budget: 'Orçamento (US$, 0 = sem limite)', test: 'Comando de teste (opcional)', wt: 'Worktree separada (isola as alterações)', perm: 'Permissões', perms: { acceptEdits: 'Pode editar arquivos', plan: 'Só planejar', default: 'Pedir para tudo' },
|
|
128
|
+
dep: 'Depois de', none: 'nenhuma', create: 'Criar tarefa', rec: 'Recomendar', recWhy: { default: 'tarefa comum de desenvolvimento', deep: 'parece investigação, arquitetura ou mudança ampla', eco: 'parece texto, documentação ou ajuste localizado', history: 'tarefas parecidas falharam ou precisaram de novas tentativas nesse perfil' },
|
|
129
|
+
profiles: { eco: 'Econômico', bal: 'Equilibrado', deep: 'Profundo' }, states: { pending: 'Na fila', running: 'Trabalhando', blocked: 'Espera você', done: 'Concluída', failed: 'Falhou', cancelled: 'Cancelada' },
|
|
130
|
+
reasons: { question: 'fez uma pergunta', review: 'pronta para revisar', approval: 'precisa de permissão', budget: 'chegou no orçamento', restart: 'o servidor reiniciou; responda para retomar', error: 'deu erro' },
|
|
131
|
+
reply: 'Responder', send: 'Enviar', approve: 'Aprovar e continuar', cancel: 'Cancelar', cancelSure: 'Cancelar a tarefa? O que o agente já alterou continua alterado.', conclude: 'Concluir', retry: 'Tentar de novo', handoff: 'Passar para outro agente', del: 'Apagar', diff: 'Ver diff',
|
|
132
|
+
save: 'Economizar', more: 'Aumentar capacidade', applyRec: 'Aplicar recomendação', pick: 'Escolher', model: 'Modelo', effort: 'Esforço', cliDefault: 'padrão do CLI', next: 'vale a partir da próxima etapa', now: 'aplicado', configLog: 'Mudanças de configuração',
|
|
133
|
+
cost: 'Custo estimado', costUnknown: 'custo desconhecido (modelo padrão do CLI)', tokens: 'tokens', attempts: n => `${n} tentativa(s)`, files: 'Arquivos alterados', tests: c => c === 0 ? '✓ testes passaram' : `✗ testes falharam (código ${c})`, denied: 'Ferramentas bloqueadas', events: 'Atividade', back: '← Tarefas',
|
|
134
|
+
parallel: 'Agentes ao mesmo tempo', ranking: 'Ranking de custo', byTask: 'Por tarefa', byAi: 'Por IA', period: { day: 'Hoje', week: '7 dias', month: '30 dias' }, perDone: 'por tarefa concluída', costNote: 'Estimativa a preço de API. Não é o valor cobrado nem a porcentagem do seu plano.',
|
|
135
|
+
empty: 'Nenhuma tarefa ainda.', search: 'Buscar no histórico', fail: { nodir: 'Pasta inválida (precisa estar dentro da sua pasta pessoal).', nodesc: 'Escreva o que fazer.', unsupported: 'Agente não suportado.', state: 'Não dá agora.', demo: 'Modo demo: nada roda.' },
|
|
136
|
+
warn: { loop: n => `Repetiu a mesma ação (${n}) várias vezes sem progresso.`, retry: 'Já teve duas tentativas sem sucesso. Considere aumentar o esforço.', budget: 'Chegou no orçamento e foi pausada.' } },
|
|
137
|
+
notices: { tab: n => n ? `Avisos (${n})` : 'Avisos', sub: 'Pedidos dos agentes, tarefas, recomendações, orçamento e novidades do app, num lugar só.', empty: 'Nenhum aviso.', apply: 'Aplicar', why: 'Ver motivo', notNow: 'Agora não', open: 'Abrir', readAll: 'Marcar tudo como lido', times: n => `×${n}`,
|
|
138
|
+
task: (t, r) => `Tarefa “${t}”: ${r}`, update: v => `Versão ${v} disponível` },
|
|
139
|
+
ver: { tab: 'Versões', sub: 'Versão instalada, a mais nova no npm e o que mudou.', installed: 'Instalada', latest: 'No npm', checked: 'Verificado', never: 'ainda não', upToDate: 'Você está na versão mais nova.', news: 'Ver novidades', copy: 'Copiar comando', copied: 'Copiado!', later: 'Lembrar depois', npx: 'Se você usa npx:', global: 'Se instalou global:', plugin: 'No plugin do Claude Code: /plugin update coworking-agents', note: 'Copiar o comando não atualiza nada sozinho: rode, reinicie e a versão nova aparece aqui.', admin: 'Admin do escritório', celebrate: v => `Versão ${v} no ar! 🎉` },
|
|
125
140
|
queue: { tab: n => n ? `Fila (${n})` : 'Fila', sub: 'Quem está esperando você, de quem espera há mais tempo para quem espera menos.', empty: 'Ninguém esperando você. 🎉', answer: 'Responder', reply: 'Próxima instrução' },
|
|
126
141
|
quick: ['Pode seguir.', 'Roda os testes.', 'Faz o commit e o push.', 'Explica o que você fez.', 'Continua de onde parou.', 'Revisa o código antes de seguir.'],
|
|
127
142
|
welcome: { title: a => `Bem-vindo de volta! Você ficou fora ${a}`, body: (d, a, l, need) => `Enquanto isso: ${d} terminaram, ${a} chegaram, ${l} saíram. ${need} esperando você agora.`, queue: 'Ver a fila' },
|
|
@@ -150,7 +165,7 @@ const I18N = {
|
|
|
150
165
|
clockIn: 'Relógio de ponto: quem chegou hoje', clockNone: 'Ninguém bateu o ponto hoje ainda.',
|
|
151
166
|
thoughts: ['será que esqueceram de mim?', 'hora do café?', 'terminei… alguém?', 'zzz… brincadeira, tô aqui', 'posso fazer mais uma coisa', 'que silêncio…', 'alguém viu meu grampeador?', 'revisando meus commits mentalmente', 'tô de olho no relógio', 'me chama que eu vou!'],
|
|
152
167
|
radio: { label: '♪ Rádio chiptune', on: 'Rádio ligado: a estação segue a IA que mais trabalha, o ritmo segue o escritório (clique para desligar)', off: 'Rádio desligado (clique para ligar)' },
|
|
153
|
-
menu: 'Menu', langName: 'Idioma: português (trocar para inglês)', menuItems: { launch: 'Nova sessão', deco: 'Decorar (mover itens)', walk: 'Andar pelo escritório', office: 'Personalizar escritório', photo: 'Foto do escritório', radio: 'Rádio', notify: 'Avisos do sistema', sound: 'Sons', focus: 'Modo foco', replay: 'Replay da última hora', search: 'Buscar agente', lang: 'English' },
|
|
168
|
+
menu: 'Menu', langName: 'Idioma: português (trocar para inglês)', menuItems: { launch: 'Nova sessão', deco: 'Decorar (mover itens)', walk: 'Andar pelo escritório', office: 'Personalizar escritório', photo: 'Foto do escritório', radio: 'Rádio', notify: 'Avisos do sistema', sound: 'Sons', focus: 'Modo foco', replay: 'Replay da última hora', search: 'Buscar agente', tasks: 'Tarefas', notices: 'Avisos', lang: 'English' },
|
|
154
169
|
focus: { off: 'Modo foco: silencia sons e notificações por 25 min', on: m => `Modo foco: ${m} min restantes (clique para estender ou desligar)`, ended: 'Modo foco terminou' },
|
|
155
170
|
feed: { title: 'Histórico', sub: 'O que aconteceu nas últimas 24 horas (guardado só neste navegador)', empty: 'Nada ainda. Chegadas, saídas, pedidos e entregas aparecem aqui.', clear: 'Limpar', thisHour: 'Nesta hora', kinds: { arrive: 'chegou', leave: 'saiu', done: 'terminou', needs_you: 'precisa de você', waiting: 'espera aprovação' } },
|
|
156
171
|
git: { dirty: n => `${n} arquivo${n > 1 ? 's' : ''} sem commit`, clean: 'tudo commitado', ahead: n => `${n} commit${n > 1 ? 's' : ''} para enviar`, behind: n => `${n} commit${n > 1 ? 's' : ''} para puxar`, label: 'Git' },
|
|
@@ -329,6 +344,21 @@ const I18N = {
|
|
|
329
344
|
scoreboard: { title: "This week's AI scoreboard", hours: 'hours', actions: 'actions', tokens: 'tokens' },
|
|
330
345
|
share: { card: 'Weekly card', visit: 'Visitor photo', week: 'My week at the office', hours: 'hours of agents at work', actions: 'actions', trophies: 'achievement tiers · legends', saved: 'Card saved', visitSaved: 'Visitor photo saved', visitNote: 'No names, branches or office name.' },
|
|
331
346
|
elevator: { title: 'Elevator', sub: 'Pick a floor: rooftop, office, trophy room or servers.', terrace: '🌇 Rooftop', trophy: '🏆 Trophy room', servers: '🖧 Server room', office: '🏢 Office', floor: n => `🏢 Floor ${n}` },
|
|
347
|
+
tasks: { tab: n => n ? `Tasks (${n})` : 'Tasks', sub: 'Create a task and an agent works on it in the background. You answer, approve and review here, no terminal needed. Only tasks created here can be controlled; sessions opened elsewhere stay monitor-only.',
|
|
348
|
+
newT: 'New task', title: 'Title (optional)', desc: 'What to do', folder: 'Folder', agent: 'Agent', auto: 'Automatic', profile: 'Profile', prio: 'Priority', prios: ['', 'Low', 'Normal', 'High'],
|
|
349
|
+
budget: 'Budget (USD, 0 = no limit)', test: 'Test command (optional)', wt: 'Separate worktree (isolates the changes)', perm: 'Permissions', perms: { acceptEdits: 'May edit files', plan: 'Plan only', default: 'Ask for everything' },
|
|
350
|
+
dep: 'After', none: 'none', create: 'Create task', rec: 'Recommend', recWhy: { default: 'regular development task', deep: 'looks like investigation, architecture or a wide change', eco: 'looks like text, docs or a small fix', history: 'similar tasks failed or needed retries at this profile' },
|
|
351
|
+
profiles: { eco: 'Economy', bal: 'Balanced', deep: 'Deep' }, states: { pending: 'Queued', running: 'Working', blocked: 'Waiting on you', done: 'Done', failed: 'Failed', cancelled: 'Cancelled' },
|
|
352
|
+
reasons: { question: 'asked a question', review: 'ready for review', approval: 'needs permission', budget: 'hit the budget', restart: 'the server restarted; reply to resume', error: 'errored' },
|
|
353
|
+
reply: 'Reply', send: 'Send', approve: 'Approve and continue', cancel: 'Cancel', cancelSure: 'Cancel the task? What the agent already changed stays changed.', conclude: 'Conclude', retry: 'Try again', handoff: 'Hand off to another agent', del: 'Delete', diff: 'See diff',
|
|
354
|
+
save: 'Save', more: 'More capacity', applyRec: 'Apply recommendation', pick: 'Choose', model: 'Model', effort: 'Effort', cliDefault: 'CLI default', next: 'applies from the next step', now: 'applied', configLog: 'Configuration changes',
|
|
355
|
+
cost: 'Estimated cost', costUnknown: 'cost unknown (CLI default model)', tokens: 'tokens', attempts: n => `${n} attempt(s)`, files: 'Files changed', tests: c => c === 0 ? '✓ tests passed' : `✗ tests failed (code ${c})`, denied: 'Blocked tools', events: 'Activity', back: '← Tasks',
|
|
356
|
+
parallel: 'Agents at once', ranking: 'Cost ranking', byTask: 'By task', byAi: 'By AI', period: { day: 'Today', week: '7 days', month: '30 days' }, perDone: 'per finished task', costNote: 'Estimate at API prices. Not what you are billed nor your plan percentage.',
|
|
357
|
+
empty: 'No tasks yet.', search: 'Search history', fail: { nodir: 'Invalid folder (must be inside your home folder).', nodesc: 'Write what to do.', unsupported: 'Agent not supported.', state: 'Not possible now.', demo: 'Demo mode: nothing runs.' },
|
|
358
|
+
warn: { loop: n => `Repeated the same action (${n}) several times without progress.`, retry: 'Two attempts failed already. Consider more effort.', budget: 'Hit the budget and was paused.' } },
|
|
359
|
+
notices: { tab: n => n ? `Notices (${n})` : 'Notices', sub: 'Agent requests, tasks, recommendations, budget and app news in one place.', empty: 'No notices.', apply: 'Apply', why: 'See why', notNow: 'Not now', open: 'Open', readAll: 'Mark all as read', times: n => `×${n}`,
|
|
360
|
+
task: (t, r) => `Task “${t}”: ${r}`, update: v => `Version ${v} available` },
|
|
361
|
+
ver: { tab: 'Releases', sub: 'Installed version, newest on npm and what changed.', installed: 'Installed', latest: 'On npm', checked: 'Checked', never: 'not yet', upToDate: 'You are on the newest version.', news: 'See what\'s new', copy: 'Copy command', copied: 'Copied!', later: 'Remind me later', npx: 'If you use npx:', global: 'If installed globally:', plugin: 'In the Claude Code plugin: /plugin update coworking-agents', note: 'Copying the command updates nothing by itself: run it, restart, and the new version shows here.', admin: 'Office admin', celebrate: v => `Version ${v} is live! 🎉` },
|
|
332
362
|
queue: { tab: n => n ? `Queue (${n})` : 'Queue', sub: 'Who is waiting on you, longest wait first.', empty: 'Nobody is waiting on you. 🎉', answer: 'Answer', reply: 'Next instruction' },
|
|
333
363
|
quick: ['Go ahead.', 'Run the tests.', 'Commit and push.', 'Explain what you did.', 'Pick up where you left off.', 'Review the code before going on.'],
|
|
334
364
|
welcome: { title: a => `Welcome back! You were away ${a}`, body: (d, a, l, need) => `Meanwhile: ${d} finished, ${a} arrived, ${l} left. ${need} waiting on you now.`, queue: 'Open the queue' },
|
|
@@ -357,7 +387,7 @@ const I18N = {
|
|
|
357
387
|
clockIn: 'Punch clock: who arrived today', clockNone: 'Nobody clocked in today yet.',
|
|
358
388
|
thoughts: ['did they forget about me?', 'coffee time?', 'done… anyone?', 'zzz… kidding, still here', 'I can do one more thing', 'so quiet…', 'has anyone seen my stapler?', 'reviewing my commits in my head', 'watching the clock', 'call me, I am ready!'],
|
|
359
389
|
radio: { label: '♪ Chiptune radio', on: 'Radio on: the station follows the AI working the most, the beat follows the office (click to turn off)', off: 'Radio off (click to turn on)' },
|
|
360
|
-
menu: 'Menu', langName: 'Language: English (switch to Portuguese)', menuItems: { launch: 'New session', deco: 'Decorate (move items)', walk: 'Walk around the office', office: 'Customise office', photo: 'Office photo', radio: 'Radio', notify: 'System alerts', sound: 'Sounds', focus: 'Focus mode', replay: 'Replay last hour', search: 'Search agents', lang: 'Português' },
|
|
390
|
+
menu: 'Menu', langName: 'Language: English (switch to Portuguese)', menuItems: { launch: 'New session', deco: 'Decorate (move items)', walk: 'Walk around the office', office: 'Customise office', photo: 'Office photo', radio: 'Radio', notify: 'System alerts', sound: 'Sounds', focus: 'Focus mode', replay: 'Replay last hour', search: 'Search agents', tasks: 'Tasks', notices: 'Notices', lang: 'Português' },
|
|
361
391
|
focus: { off: 'Focus mode: mutes sounds and notifications for 25 min', on: m => `Focus mode: ${m} min left (click to extend or turn off)`, ended: 'Focus mode ended' },
|
|
362
392
|
feed: { title: 'History', sub: 'What happened in the last 24 hours (kept in this browser only)', empty: 'Nothing yet. Arrivals, departures, requests and finished work show up here.', clear: 'Clear', thisHour: 'This hour', kinds: { arrive: 'arrived', leave: 'left', done: 'finished', needs_you: 'needs you', waiting: 'waits for approval' } },
|
|
363
393
|
git: { dirty: n => `${n} uncommitted file${n > 1 ? 's' : ''}`, clean: 'all committed', ahead: n => `${n} commit${n > 1 ? 's' : ''} to push`, behind: n => `${n} commit${n > 1 ? 's' : ''} to pull`, label: 'Git' },
|
package/public/js/report.js
CHANGED
|
@@ -41,7 +41,7 @@ let reportTab = 'usage', todayData = null;
|
|
|
41
41
|
function openReport(tab) { reportTab = tab || 'usage'; achBump('tab_' + reportTab); reportEl.hidden = false; renderReport(); loadUsage(true); if (reportTab === 'today') loadToday(); }
|
|
42
42
|
async function loadToday() { try { todayData = await fetch('api/report').then(r => r.json()); } catch {} if (!reportEl.hidden) renderReport(); }
|
|
43
43
|
reportEl.addEventListener('click', e => { const t = e.target.closest('[data-tab]'); if (t) { reportTab = t.dataset.tab; achBump('tab_' + reportTab); if (reportTab === 'today') loadToday(); renderReport(); } });
|
|
44
|
-
function tabsHtml() { return `<div class="rtabs">${[['queue', T.queue.tab(typeof queueList === 'function' ? queueList().filter(p => p.state !== 'idle').length : 0)], ['usage', T.usage.title], ['today', T.today.title], ['paper', T.paper.tab], ['ach', T.ach.title], ['shop', T.shop.title], ['office', T.office.tab], ['feed', T.feed.title]].map(([k, l]) => `<button data-tab="${k}" class="${reportTab === k ? 'on' : ''}">${esc(l)}</button>`).join('')}</div>`; }
|
|
44
|
+
function tabsHtml() { return `<div class="rtabs">${[['tasks', T.tasks.tab(typeof tasksWaiting === 'function' ? tasksWaiting() : 0)], ['notices', T.notices.tab(typeof unreadNotices === 'function' ? unreadNotices() : 0)], ['queue', T.queue.tab(typeof queueList === 'function' ? queueList().filter(p => p.state !== 'idle').length : 0)], ['usage', T.usage.title], ['today', T.today.title], ['paper', T.paper.tab], ['ach', T.ach.title], ['shop', T.shop.title], ['office', T.office.tab], ['feed', T.feed.title], ['version', T.ver.tab]].map(([k, l]) => `<button data-tab="${k}" class="${reportTab === k ? 'on' : ''}">${esc(l)}</button>`).join('')}</div>`; }
|
|
45
45
|
// activity feed: newest first, grouped by hour
|
|
46
46
|
function renderFeed() {
|
|
47
47
|
const F = T.feed, body = reportEl.querySelector('.report-body');
|
|
@@ -97,6 +97,9 @@ reportEl.addEventListener('click', async e => {
|
|
|
97
97
|
function closeReport() { reportEl.hidden = true; }
|
|
98
98
|
|
|
99
99
|
function renderReport() {
|
|
100
|
+
if (reportTab === 'tasks') return renderTasks();
|
|
101
|
+
if (reportTab === 'notices') return renderNotices();
|
|
102
|
+
if (reportTab === 'version') return renderVersion();
|
|
100
103
|
if (reportTab === 'today') return renderToday();
|
|
101
104
|
if (reportTab === 'feed') return renderFeed();
|
|
102
105
|
if (reportTab === 'ach') return renderAchievements();
|