aloita-extensions 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/LICENSE +21 -0
- package/README.md +413 -0
- package/config.example.json +91 -0
- package/docs/status-indicator-preview.md +65 -0
- package/docs/ui-customisation-limits.md +274 -0
- package/package.json +55 -0
- package/src/agent-log.test.ts +290 -0
- package/src/agent-log.ts +298 -0
- package/src/aloita-configure.test.ts +303 -0
- package/src/aloita-configure.ts +656 -0
- package/src/aura-client.test.ts +188 -0
- package/src/aura-client.ts +211 -0
- package/src/capabilities.ts +158 -0
- package/src/config-store.test.ts +184 -0
- package/src/config-store.ts +117 -0
- package/src/config.ts +298 -0
- package/src/diff.test.ts +331 -0
- package/src/diff.ts +184 -0
- package/src/index.ts +824 -0
- package/src/logger.test.ts +197 -0
- package/src/logger.ts +160 -0
- package/src/project-launcher.test.ts +323 -0
- package/src/project-launcher.ts +359 -0
- package/src/prompts.test.ts +204 -0
- package/src/prompts.ts +253 -0
- package/src/signalr.test.ts +102 -0
- package/src/signalr.ts +432 -0
- package/src/status.test.ts +183 -0
- package/src/status.ts +123 -0
- package/src/tools.ts +587 -0
- package/src/usage.ts +173 -0
- package/src/working-dir.ts +92 -0
- package/tsconfig.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Frank Vliegen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
# aloita-extensions
|
|
2
|
+
|
|
3
|
+
Pi extensions that **replace both** the [Aura SignalR receiver](https://github.com/frankvl76/aura-signalr-client) and the [Aura MCP server](https://github.com/frankvl76/aura-mcp-server) with a single Pi extension.
|
|
4
|
+
|
|
5
|
+
Instead of spawning `claude -p` / `opencode` per ticket and talking to Aloita over MCP stdio, **one Pi session connects to Aloita over SignalR and works on tickets natively.** The 28 `aura_*` tools are registered as first-class Pi tools — no MCP, no subprocess, no separate FastAPI dashboard.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌─────────────────────────────────────────────┐
|
|
9
|
+
│ ONE Pi session (this extension loaded) │
|
|
10
|
+
│ │
|
|
11
|
+
Aloita ──SignalR──► onWebhookEvent │
|
|
12
|
+
board │ ├─ dedup → resolve folder │
|
|
13
|
+
(formerly │ ├─ set "In Progress" + agent-task │
|
|
14
|
+
"Aura") │ └─ pi.sendUserMessage(prompt) ──┐ │
|
|
15
|
+
◄─REST───│◄── aura_* tools (28) ◄─────────────── the │
|
|
16
|
+
│ (get ticket, comment, checklist, …) agent │
|
|
17
|
+
│ │
|
|
18
|
+
│ on agent_end → "Done" + drain next queued │
|
|
19
|
+
└─────────────────────────────────────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What this replaces
|
|
23
|
+
|
|
24
|
+
| Old (Python) | New (this extension) |
|
|
25
|
+
|---------------------------------------|--------------------------------------------------------|
|
|
26
|
+
| `signalr_receiver.py` (2848 lines) | `src/signalr.ts` + `src/index.ts` webhook handling |
|
|
27
|
+
| `signalr_loop_bridge.py` + supervisor | gone — the Pi session **is** the long-lived consumer |
|
|
28
|
+
| FastAPI dashboard + `static/` | `ctx.ui.setStatus()` footer + `notify()` + `/aloita-*` |
|
|
29
|
+
| `aura-mcp-server/server.py` (28 tools)| `src/tools.ts` — same 28 tools, registered natively |
|
|
30
|
+
| `aura_client.py` (httpx) | `src/aura-client.ts` (`fetch`) |
|
|
31
|
+
| Subprocess spawn + `.cmd` shims | gone — no subprocesses |
|
|
32
|
+
| Usage reconstruction from transcripts | `message_end` usage → `/agent-run-usage` on `agent_end` |
|
|
33
|
+
| `AuraAgentLogPusher` + stdout line reader | `src/agent-log.ts` — fed from Pi streaming events (see §Agent log streaming) |
|
|
34
|
+
| `--resume <sessionID>` dance | gone — the session is already long-lived |
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
**Prerequisites:** Pi (`@earendil-works/pi-coding-agent`) on your PATH and Node 18+.
|
|
39
|
+
|
|
40
|
+
### 1. Install the extension (one command)
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
pi install npm:aloita-extensions
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
That's it. `pi install` writes the package to Pi's global settings (`~/.pi/agent/settings.json` → `packages: [...]`) and fetches its `dependencies` (`ws`). Plain `pi` (no flags) now auto-loads the extension on **every startup** — no manual path configuration, no `--extension` flag, no rebuild step.
|
|
47
|
+
|
|
48
|
+
Pi bundles the `@earendil-works/*` and `typebox` packages this extension imports (declared as `peerDependencies`), so they are provided by Pi at runtime.
|
|
49
|
+
|
|
50
|
+
> **Offline / air-gapped install:** if the npm tarball (`.tgz`) is available locally instead of published to a registry, install directly from it: `pi install ./aloita-extensions-0.2.0.tgz`. See [§Distributable](#distributable) for details.
|
|
51
|
+
|
|
52
|
+
Manage the registration:
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
pi list # confirm it's registered
|
|
56
|
+
pi remove npm:aloita-extensions # unregister
|
|
57
|
+
pi update --extensions # update all installed packages
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Configure Aloita access
|
|
61
|
+
|
|
62
|
+
Set your Aloita server URL + API key (env vars are the simplest — good for secrets):
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
# PowerShell (current session):
|
|
66
|
+
$env:ALOITA_URL = "https://your-aloita-server"
|
|
67
|
+
$env:ALOITA_API_KEY = "kai_your_key_here"
|
|
68
|
+
|
|
69
|
+
# Or persist as user environment variables (run once):
|
|
70
|
+
setx ALOITA_URL "https://your-aloita-server"
|
|
71
|
+
setx ALOITA_API_KEY "kai_your_key_here"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Alternatively, copy `config.example.json` to one of these paths and edit (full schema in [§Configure](#configure)):
|
|
75
|
+
- `~/.pi/aloita.json` — global (**recommended** for `repos`, capabilities, and auto-start settings)
|
|
76
|
+
- `<cwd>/.pi/aloita.json` — project-local (preferred for per-project overrides)
|
|
77
|
+
|
|
78
|
+
**Or skip the env vars entirely** — run `/aloita-configure` inside Pi and set the server URL + API key in the **Connection settings** screen. They persist to `~/.pi/aloita.json` (global) so every Pi session on this machine uses them.
|
|
79
|
+
|
|
80
|
+
Launch `pi`. The footer shows `● Aloita: connected` when the SignalR socket is up.
|
|
81
|
+
|
|
82
|
+
### 3. (Optional) Auto-start + `/aloita-configure`
|
|
83
|
+
|
|
84
|
+
To let this Pi session automatically start a dedicated child `pi` in specific project folders, open the interactive settings UI:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
/aloita-configure
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Toggle which projects are in the auto-start allow-list, adjust options, then **Save & close** — changes persist to the config file and hot-reload immediately. See [§Auto-start supervisor](#auto-start-supervisor) and [§`/aloita-configure`](#aloita-configure--interactive-settings-ui) for detail. (Auto-start is opt-in and off by default.)
|
|
91
|
+
|
|
92
|
+
## Distributable
|
|
93
|
+
|
|
94
|
+
The extension ships as an npm tarball (`npm pack` output): `aloita-extensions-0.2.0.tgz`. This is the single artifact for distribution — it contains exactly the package contents (source, docs, README, LICENSE, config example, tsconfig) and nothing else (no `node_modules`, no `.git`, no test artifacts).
|
|
95
|
+
|
|
96
|
+
Two install paths from the tarball:
|
|
97
|
+
|
|
98
|
+
1. **Publish to npm**, then install by name (recommended for repeated installs):
|
|
99
|
+
```powershell
|
|
100
|
+
npm publish aloita-extensions-0.2.0.tgz # one-time publish
|
|
101
|
+
pi install npm:aloita-extensions # install on any machine
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
2. **Install directly from the tarball** (no registry needed — good for air-gapped / private setups):
|
|
105
|
+
```powershell
|
|
106
|
+
pi install ./aloita-extensions-0.2.0.tgz
|
|
107
|
+
```
|
|
108
|
+
This registers the tarball path in Pi's settings and auto-loads the extension on every startup, just like the npm path.
|
|
109
|
+
|
|
110
|
+
A `distributable.zip` is attached to the packaging ticket — it contains the `.tgz` plus this README, so a developer has everything needed in one download.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Install (local development)
|
|
115
|
+
|
|
116
|
+
For hacking on this extension from a clone:
|
|
117
|
+
|
|
118
|
+
```powershell
|
|
119
|
+
git clone https://github.com/frankvl76/aloita-extensions
|
|
120
|
+
cd aloita-extensions
|
|
121
|
+
npm install # fetches ws + dev/type deps
|
|
122
|
+
npm run build # tsc --noEmit type-check
|
|
123
|
+
npm test # node --test src/*.test.ts
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Register the local folder so it auto-loads (reads the `pi.extensions` manifest from `package.json`):
|
|
127
|
+
|
|
128
|
+
```powershell
|
|
129
|
+
# Global — loads in EVERY project folder (matches the one-Pi-per-folder model).
|
|
130
|
+
pi install C:\path\to\aloita-extensions
|
|
131
|
+
|
|
132
|
+
# Project-local — only the current folder:
|
|
133
|
+
pi install -l C:\path\to\aloita-extensions
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`pi install` stores a path relative to `~/.pi/agent`; `pi list` prints the resolved absolute path so you can sanity-check it. Global packages are loaded regardless of project trust, so they apply in every cwd.
|
|
137
|
+
|
|
138
|
+
Because Pi loads extensions via [jiti](https://github.com/unjs/jiti) on each launch, **source edits are picked up immediately on the next `pi` start** and `/reload` hot-reloads them in a running session — no rebuild step.
|
|
139
|
+
|
|
140
|
+
### Manual / one-off (no registration)
|
|
141
|
+
|
|
142
|
+
For a single run with no registration, use the explicit flag:
|
|
143
|
+
|
|
144
|
+
```powershell
|
|
145
|
+
pi --extension C:\path\to\aloita-extensions\src\index.ts
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Configure
|
|
149
|
+
|
|
150
|
+
Two sources, first non-empty wins:
|
|
151
|
+
|
|
152
|
+
1. **Env vars** (good for secrets): `ALOITA_URL`, `ALOITA_API_KEY` (also `AURA_REPOS`).
|
|
153
|
+
2. **JSON file** discovered at one of:
|
|
154
|
+
- `<extensionDir>/aloita.json` — sibling of `package.json`
|
|
155
|
+
- `<cwd>/.pi/aloita.json` — project-local (**preferred**, per-folder)
|
|
156
|
+
- `~/.pi/aloita.json` — global
|
|
157
|
+
|
|
158
|
+
Copy `config.example.json` to one of those paths and edit. Full schema:
|
|
159
|
+
|
|
160
|
+
| Field | Default | Purpose |
|
|
161
|
+
|------------------------|---------|-------------------------------------------------------------------------|
|
|
162
|
+
| `aloitaUrl` | — | Base URL of the Aloita server (env `ALOITA_URL`). |
|
|
163
|
+
| `apiKey` | — | `kai_…` key (env `ALOITA_API_KEY`). |
|
|
164
|
+
| `subscription` | see ex | `{ events, allProjects, allUsers, projectIds, userIds }` bitmask filter.|
|
|
165
|
+
| `projectFolders` | `{}` | `projectId → local path` override (wins over Aura-side `LocalFolder`). |
|
|
166
|
+
| `projectFilter` | `""` | If set, this session only claims tickets for that project id. |
|
|
167
|
+
| `matchAllProjects` | `false` | If true, claim every ticket regardless of folder (uses this cwd). |
|
|
168
|
+
| `repos` | `""` | `${REPOS}` expansion target (env `AURA_REPOS`). |
|
|
169
|
+
| `labelToCommand` | see ex | Ticket label → command template. |
|
|
170
|
+
| `defaultCommand` | `aura-work-on-ticket` | Fallback command. |
|
|
171
|
+
| `guardStatus` | `true` | Block the agent from changing ticket status (enforces the #1 rule). |
|
|
172
|
+
| `autoFinalizeOnAgentEnd` | `true` | Set ticket "Done" when the triggered turn ends. |
|
|
173
|
+
| `postUsage` | `true` | POST per-model token/cost to Aloita on `agent_end` (see §Usage post-back).|
|
|
174
|
+
| `postDiff` | `true` | POST the run's git diff to Aloita's per-ticket diff viewer on `agent_end` (see §Run diff post-back).|
|
|
175
|
+
| `streamAgentLog` | `true` | Stream the agent's text to Aloita's per-ticket agent-log tab in real time (see §Agent log streaming).|
|
|
176
|
+
| `dedupWindowSeconds` | `2` | Webhook dedup window. |
|
|
177
|
+
| `autoStartProjects` | `false` | Supervisor: poll `/api/projects` and spawn a child `pi` in each NEW project's folder (see §Auto-start supervisor). |
|
|
178
|
+
| `autoStartPollSeconds` | `60` | Auto-start poll interval (seconds). |
|
|
179
|
+
| `autoStartCreateFolder`| `true` | Auto-start: `mkdir -p` the project folder if it doesn't exist yet. |
|
|
180
|
+
| `autoStartPiCommand` | `""` | Auto-start: override the `pi` command to spawn (default `pi.cmd`/`pi`). |
|
|
181
|
+
| `autoStartExcludeProjects` | `[]` | Auto-start: project ids to never launch. |
|
|
182
|
+
| `autoStartSeedExisting`| `false` | Auto-start: launch for all existing projects on startup (not just new). |
|
|
183
|
+
| `autoStartProjectIds` | `[]` | Auto-start: allow-list of project ids to launch for (new projects do nothing until added here — use `/aloita-configure`). |
|
|
184
|
+
| `capabilities` | `[]` | GLOBAL fallback caps registered against the auth user (see §Capability tiers). |
|
|
185
|
+
| `userMappings` | `{}` | GLOBAL per-agent-user profiles + their capability tiers (see §Capability tiers). |
|
|
186
|
+
|
|
187
|
+
## How a ticket flows through Pi
|
|
188
|
+
|
|
189
|
+
1. **`session_start`** → opens the SignalR WebSocket to `<aloitaUrl>/hubs/webhooks`, handshakes, subscribes. Status footer shows `○ aloita: connected`.
|
|
190
|
+
2. **`WebhookEvent`** arrives → dedup (2s fingerprint) → resolve working dir → decide whether *this* session claims it (see *Multi-project* below) → fetch ticket context → resolve label → command → set **In Progress** + `agent-task/start` → `pi.sendUserMessage(prompt)`.
|
|
191
|
+
3. The Pi agent **works the ticket** using the native `aura_*` tools (get context, comment, attachments, checklist, conversations, search data…). Its streamed text is mirrored live to Aloita's per-ticket agent-log tab (see §Agent log streaming).
|
|
192
|
+
4. **`agent_end`** → finalize: set **Done** + `agent-task/end` → drain the next queued ticket if any.
|
|
193
|
+
5. **`StopTask`** (user clicks Stop in Aloita UI) → drops queued + `ctx.abort()` the active turn.
|
|
194
|
+
6. **`session_shutdown`** → closes the socket cleanly.
|
|
195
|
+
|
|
196
|
+
## Label routing (which prompt runs)
|
|
197
|
+
|
|
198
|
+
When a ticket is claimed, its labels are matched (case-insensitive, trimmed, first match wins) against `labelToCommand` to pick the prompt template; any unmatched label (or no label) falls back to `defaultCommand`. Defaults (`src/config.ts`) — override/add in your `aloita.json`:
|
|
199
|
+
|
|
200
|
+
| Label | Command | What the agent does |
|
|
201
|
+
|------------------------|--------------------------------------|----------------------------------------------------------------------------------------------|
|
|
202
|
+
| `plan` | `aura-plan-ticket` | Write a technical plan/spec, attach it; no code. |
|
|
203
|
+
| `review` | `aura-review-ticket` | Review recent changes for the ticket; no code. |
|
|
204
|
+
| `document` | `aura-document` | Generate documentation. |
|
|
205
|
+
| `workflow-implementation` | `aloita-workflow-implement-with-pr` | Implement on a `feature/<name>` branch off latest `main`, then **open a PR into `staging`**. |
|
|
206
|
+
| *(no / unmatched label)* | `aura-work-on-ticket` *(default)* | Implement the ticket directly (full work loop, no git/PR). |
|
|
207
|
+
|
|
208
|
+
To add your own, map a label to a command name and define that template in `PROMPTS` (`src/prompts.ts`).
|
|
209
|
+
|
|
210
|
+
## Multi-project (one Pi per folder)
|
|
211
|
+
|
|
212
|
+
A Pi session is bound to its launch cwd, so run **one Pi per project folder** — exactly the model `RUNNING.md` already documents for the supervisor:
|
|
213
|
+
|
|
214
|
+
```powershell
|
|
215
|
+
# In project A's folder, dedicated to project A:
|
|
216
|
+
cd C:\path\to\projectA
|
|
217
|
+
pi --extension ...\aloita-extensions\src\index.ts
|
|
218
|
+
# → set projectFilter to project A's id in .\.pi\aloita.json
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Ticket-claiming logic (`src/working-dir.ts shouldClaimTicket`):
|
|
222
|
+
- `matchAllProjects: true` → claim everything (use this cwd).
|
|
223
|
+
- `projectFilter: "<id>"` → claim only that project.
|
|
224
|
+
- **default** → claim only when the ticket's resolved folder exists **and equals this session's cwd**.
|
|
225
|
+
|
|
226
|
+
## Auto-start supervisor
|
|
227
|
+
|
|
228
|
+
Normally you start one Pi per project folder manually (`cd <folder> && pi`). With `autoStartProjects: true`, this Pi session becomes a **supervisor**: it polls `GET /api/projects` every `autoStartPollSeconds` and, for each project that is in your **allow-list** (`autoStartProjectIds`) and not yet launched, spawns a **detached child `pi`** in that project's folder.
|
|
229
|
+
|
|
230
|
+
**Allow-list model:** a brand-new project does **nothing** until you explicitly opt it in — either by adding its id to `autoStartProjectIds` in the config file, or interactively via `/aloita-configure` (recommended). This keeps you in control of which folders get a child pi.
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"autoStartProjects": true,
|
|
235
|
+
"repos": "C:\\Users\\frank\\source\\repos",
|
|
236
|
+
"autoStartCreateFolder": true,
|
|
237
|
+
"autoStartPollSeconds": 60
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**How a project is launched** (pure logic in `src/project-launcher.ts`, I/O in `src/index.ts`):
|
|
242
|
+
1. Poll → `normalizeProjects` → `detectProjectsToLaunch` (allow-listed projects not yet launched).
|
|
243
|
+
2. Resolve the folder, in order: `projectFolders[id]` override → project `LocalFolder` → `$REPOS/<sanitized-name>` (derived from `repos`).
|
|
244
|
+
3. If the folder doesn't exist and `autoStartCreateFolder` is on, `mkdir -p` it.
|
|
245
|
+
4. Write a tiny project-local `.pi/aloita.json` containing `{ "projectFilter": "<id>" }` so the child claims only that project.
|
|
246
|
+
5. `spawn(<pi>, { detached: true, stdio: "ignore" })` + `child.unref()` — the child survives the supervisor.
|
|
247
|
+
|
|
248
|
+
Credentials are passed via `ALOITA_URL` / `ALOITA_API_KEY` env vars, **not** written into the per-project file, so no secrets are duplicated.
|
|
249
|
+
|
|
250
|
+
**Why polling:** Aloita's `WebhookEventType` bitmask only covers ticket events today — there is no `ProjectCreated` flag. Polling `/api/projects` is the server-agnostic signal. If Aloita adds a `ProjectCreated` event later, only the supervisor's trigger changes; the pure planning layer stays as-is.
|
|
251
|
+
|
|
252
|
+
**Safe by default:** off unless you opt in; never blocks the session; failed launches/polls log a warning and continue.
|
|
253
|
+
|
|
254
|
+
## `/aloita-configure` — interactive settings UI
|
|
255
|
+
|
|
256
|
+
The `/aloita-configure` command opens a full-screen TUI wizard (built from Pi's `SelectList` + `SettingsList` components) where you can manage all Aloita extension settings without editing JSON. All settings persist to the **global** config file (`~/.pi/aloita.json`) so they apply to every Pi session on this machine.
|
|
257
|
+
|
|
258
|
+
- **Connection settings** — server URL, API key (`kai_…`), and agent user IDs. These are the credentials every Pi session needs to connect to Aloita. Set them once here and they're global — no need to repeat `ALOITA_URL`/`ALOITA_API_KEY` env vars per session. Changing them triggers an immediate SignalR reconnect.
|
|
259
|
+
- **Model capabilities** — browse the models you've configured in Pi (from your providers — `ctx.modelRegistry.getAvailable()`) and toggle which to broadcast to Aloita. Costs are **auto-filled from Pi's model pricing** (`Model.cost`). The display name defaults to the model name. This is how Aloita learns what models this machine can run and what they cost — used for `/cost` and complexity-tier mapping.
|
|
260
|
+
- **Auto-start projects** — browse every project on the Aloita board and toggle which ones are in the allow-list (`autoStartProjectIds`). Searchable for large boards.
|
|
261
|
+
- **Auto-start options** — master switch, poll interval (30s/60s/2m/5m/10m presets), folder creation, seed-existing.
|
|
262
|
+
- **General options** — status guard, auto-finalize, token-usage / diff / agent-log post-backs.
|
|
263
|
+
- **Save & close** — persists changes to the config JSON file and hot-reloads. **Cancel** discards.
|
|
264
|
+
|
|
265
|
+
The API key is masked in the input dialog (`kai_••••••`). Press Esc at any prompt to skip that field without changing it. Connection settings are written to `~/.pi/aloita.json` (global) — env vars (`ALOITA_URL`/`ALOITA_API_KEY`) still take precedence at load time if set.
|
|
266
|
+
|
|
267
|
+
Changes persist via `src/config-store.ts` (read → merge → write), which shallow-merges so untouched keys survive and secrets (`aloitaUrl`/`apiKey`) are never written into the file.
|
|
268
|
+
|
|
269
|
+
## Commands
|
|
270
|
+
|
|
271
|
+
| Command | Action |
|
|
272
|
+
|---------------------|-----------------------------------------------------------|
|
|
273
|
+
| `/aloita-status` | Print connection state, active ticket, queue depth. |
|
|
274
|
+
| `/aloita-connect` | Force reconnect the SignalR socket. |
|
|
275
|
+
| `/aloita-disconnect`| Disconnect the socket. |
|
|
276
|
+
| `/aloita-next` | Force-drain the next queued ticket into this session. |
|
|
277
|
+
| `/aloita-configure` | Interactive settings UI: toggle auto-start projects, edit options, persist. |
|
|
278
|
+
|
|
279
|
+
## Tools (28, grouped like the MCP server)
|
|
280
|
+
|
|
281
|
+
**Essential** — `aura_get_ticket_context`, `aura_add_comment`, `aura_upload_attachment`, `aura_update_ticket_status` *(guarded)*, `aura_create_subtask`, `aura_add_checklist_item`, `aura_toggle_checklist_item`, `aura_set_custom_field`.
|
|
282
|
+
|
|
283
|
+
**Important** — `aura_search_tickets`, `aura_list_tickets`, `aura_update_ticket` *(status guarded)*, `aura_list_projects`, `aura_list_comments`, `aura_upload_comment_attachment`, `aura_list_attachments`, `aura_download_attachment`.
|
|
284
|
+
|
|
285
|
+
**Nice-to-have** — `aura_assign_ticket`, `aura_create_ticket`, `aura_list_labels`, `aura_list_users`, `aura_log_time`, `aura_get_activity`.
|
|
286
|
+
|
|
287
|
+
**Search data** — `aura_search_console_query`, `aura_ga4_report`, `aura_pagespeed_audit`.
|
|
288
|
+
|
|
289
|
+
**Conversation** — `aura_start_conversation`, `aura_reply_in_conversation`, `aura_resolve_conversation`.
|
|
290
|
+
|
|
291
|
+
> Asking a question (`aura_start_conversation`) used to require an exit/resume dance because `claude -p` was stateless. In Pi the session just idles — when the human answers, a new webhook event arrives and `sendUserMessage` continues the **same** session. No session-ID bookkeeping.
|
|
292
|
+
|
|
293
|
+
## Critical rules (preserved from the Python stack)
|
|
294
|
+
|
|
295
|
+
- **Agents must not change ticket status.** Enforced at runtime by a `tool_call` interceptor (`guardStatus`): `aura_update_ticket_status` is blocked outright; the `status` field is stripped from `aura_update_ticket`. The orchestrator owns status (In Progress on start, Done on `agent_end`).
|
|
296
|
+
- **Working dir must already exist** — never auto-created (`resolveWorkingDir` returns null if missing; the ticket is skipped with a log line).
|
|
297
|
+
- **Run one SignalR subscription per Aloita auth user.** Like the receiver, one connection per Pi process.
|
|
298
|
+
- **Stable per-run `sessionId`.** On `session_start` the extension mints one UUID (`runSessionId`) and sends it in the Subscribe payload's `SessionId` field, then reuses that same id for every per-ticket artifact — `agent-task/start`|`end`, usage, diff, and the agent-log stream. This lets the Agent Activity orbit render each Pi run as its own satellite and pin activity to it (without it, the orbit merges an agent's connections into a single satellite).
|
|
299
|
+
|
|
300
|
+
## Capability tiers (global, broadcast on connect)
|
|
301
|
+
|
|
302
|
+
Capabilities tell Aloita what models this machine can run and what they cost. Aloita uses them two ways:
|
|
303
|
+
|
|
304
|
+
1. **Pricing** — upserts an `AgentModelPricing` row (`Source=ClientRegistered`, never clobbering an `AdminOverride`) so `/cost` can price runs.
|
|
305
|
+
2. **Complexity mapping** — links each model into the per-user complexity-tier admin UI, so Aloita's `AnalyzeTaskComplexity` flow can stamp the right model on a ticket based on its tier.
|
|
306
|
+
|
|
307
|
+
This is **global config** (not per-project) — put it in `~/.pi/aloita.json` so every Pi session on this machine advertises the same set. Two sources, unioned:
|
|
308
|
+
|
|
309
|
+
- `capabilities[]` — top-level fallback, registered against the auth user (the `.env` key).
|
|
310
|
+
- `userMappings[<userId>].capabilities[]` — explicit per agent user (**preferred**).
|
|
311
|
+
|
|
312
|
+
```json
|
|
313
|
+
"userMappings": {
|
|
314
|
+
"927fc980-4cd6-4323-ac72-2a9be24f2730": {
|
|
315
|
+
"tool": "pi",
|
|
316
|
+
"capabilities": [
|
|
317
|
+
{ "tool": "pi", "provider": "anthropic", "model": "claude-sonnet-4-5",
|
|
318
|
+
"displayName": "Sonnet 4.5 (medium)",
|
|
319
|
+
"inputCostPerMTok": 3, "outputCostPerMTok": 15,
|
|
320
|
+
"cachedInputCostPerMTok": 0.3, "cacheWriteCostPerMTok": 3.75 }
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
**Auth binding:** Aloita attributes `RegisterCapabilities` to the *connected* user; only SuperAdmin can register for other users. So the auth user's caps go on the main connection; each *other* mapped user gets its own helper connection (authenticated with that user's `apiKey`, `registerOnly` mode — no Subscribe, just kept alive so the caps stay registered). Include `apiKey` on every non-auth `userMappings` entry.
|
|
327
|
+
|
|
328
|
+
On `session_start`, the extension resolves the auth user via `GET /api/profile`, builds the plan (`src/capabilities.ts`), and starts the main + helper connections. `/aloita-connect` re-runs the whole flow.
|
|
329
|
+
|
|
330
|
+
## Usage post-back (on `agent_end`)
|
|
331
|
+
|
|
332
|
+
When the Pi job for a ticket finishes (`agent_end`), the extension POSTs the run's token/cost to `/api/tickets/{id}/agent-run-usage`. This is the smart orchestration moment — usage is accumulated **while a ticket is active** and flushed exactly once when the turn ends.
|
|
333
|
+
|
|
334
|
+
- **Source of truth:** Pi's per-assistant-message `usage` on `message_end` (`input`, `output`, `cacheRead`, `cacheWrite`, `totalTokens`, `cost.total`), plus `message.provider` / `message.model`. This is the Pi equivalent of Claude's authoritative `result.modelUsage`.
|
|
335
|
+
- **Per-model breakdown:** aggregated by `(provider, model)` and sent as the `models[]` array Aloita's `AgentRunUsageEndpoints` expects. One `AgentRunUsage` row is written per model.
|
|
336
|
+
- **Best-effort, never blocks:** posts happen after the turn ends and before finalize; a failure logs a warning but does not block status/queue drain.
|
|
337
|
+
- **Zero-row visibility:** if no usage was captured (e.g. the turn was aborted), a single zero-token row is still posted so the run shows up in `/cost` rather than silently absent — matching the Python receiver.
|
|
338
|
+
- **Auto-stub pricing:** Aloita auto-creates an `AgentModelPricing` stub for any unknown `(tool, provider, model)` triple, so cost is never a silent `$0`.
|
|
339
|
+
|
|
340
|
+
Toggle with `postUsage: false` to disable.
|
|
341
|
+
|
|
342
|
+
## Run diff post-back (on `agent_end`)
|
|
343
|
+
|
|
344
|
+
When the Pi job for a ticket finishes, the extension captures what the agent changed in the ticket's working directory and POSTs a unified diff to Aloita's per-ticket **diff viewer** — `POST /api/tickets/{id}/diffs` (`TicketDiffEndpoints.cs`). This is the "review without leaving the kanban" artifact, ported 1:1 from the Python receiver's `post_agent_run_diff`.
|
|
345
|
+
|
|
346
|
+
- **Baseline snapshot at start:** when a ticket is injected, the extension snapshots the working dir's HEAD (`git rev-parse --short HEAD`) *before* the agent runs. The post-run diff is taken against that baseline, so it captures **both committed and uncommitted changes** made during this run — not the whole history.
|
|
347
|
+
- **Untracked files included:** `git diff <base>` is appended with each untracked file (`git ls-files --others --exclude-standard`) rendered as a new-file diff via `git diff --no-index -- /dev/null <path>` — exactly the receiver's technique.
|
|
348
|
+
- **Body:** `{ title, diffText, source: AgentRun(1), baseRef, headRef, sessionId }`. The server stores the raw diff verbatim and caches parsed summary counts (files/additions/deletions).
|
|
349
|
+
- **Best-effort, never blocks:** runs last in `agent_end` (after status is finalized), gated on `postDiff`. A non-git folder, a git failure, or an API error logs a warning and never affects the ticket. `postDiff: false` disables it.
|
|
350
|
+
- **No working dir → skipped:** when a ticket's working dir doesn't resolve (or isn't a git repo), capture is skipped silently.
|
|
351
|
+
|
|
352
|
+
## Agent log streaming (real time)
|
|
353
|
+
|
|
354
|
+
While a ticket is active, the extension mirrors the agent's activity to Aloita's per-ticket **agent-log** tab in real time — a direct port of the Python receiver's `AuraAgentLogPusher`. This is what makes a run observable live (and reviewable afterward) without the old subprocess/dashboard.
|
|
355
|
+
|
|
356
|
+
- **Same pipeline & contract as the receiver:** entries are buffered and flushed in batches of ≤ 50 every 0.5 s to `POST /api/tickets/{id}/agent-log/batch`, each carrying `{ content, sessionId, source, timestamp }`. Best-effort — a failed push logs a warning and never blocks the run.
|
|
357
|
+
- **No subprocess → events are the source.** The old receiver read the CLI's stdout line-by-line. Here Pi *is* the agent, so the equivalent "display lines" are captured from Pi's streaming events and fed to the same pusher:
|
|
358
|
+
- `message_update` → assistant **text deltas**, split into newline-delimited lines (the analog of the receiver's `read_line_unbounded`). Thinking blocks are intentionally not streamed, matching the old adapter which surfaced display text only.
|
|
359
|
+
- **No tool noise.** Tool calls and results (`▶ bash(…)` / `✓ bash` / `✗ … (error)` markers) are intentionally **not** logged — only the assistant's own text and the lifecycle markers are streamed, to keep the log readable.
|
|
360
|
+
- **Lifecycle mirrors the receiver:** a `--- started ---` line is pushed when the ticket is injected; the pusher is drained + flushed (`close()`) in `agent_end` **before** finalize/usage (the same ordering the receiver's `finally` block uses), and on StopTask/shutdown.
|
|
361
|
+
- **Toggle:** `streamAgentLog: false` disables it with zero overhead (no pusher is created; the feed handlers short-circuit).
|
|
362
|
+
|
|
363
|
+
The implementation is isolated in `src/agent-log.ts` (`AgentLogPusher`, `TextLineBuffer`, `createAuraAgentLogPoster`), unit-tested with Node's built-in test runner (`npm test`).
|
|
364
|
+
|
|
365
|
+
## Logging
|
|
366
|
+
|
|
367
|
+
The extension **never writes to stdout/stderr** — that would corrupt Pi's TUI rendering. All diagnostics (connection events, webhook handling, usage/diff post-backs, auto-start launches, capability registration) go to a **log file**:
|
|
368
|
+
|
|
369
|
+
``
|
|
370
|
+
~/.pi/aloita.log
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Tail it in a separate terminal while Pi runs:
|
|
374
|
+
|
|
375
|
+
```powershell
|
|
376
|
+
Get-Content ~/.pi/aloita.log -Wait -Tail 20
|
|
377
|
+
# or on Linux/macOS:
|
|
378
|
+
tail -f ~/.pi/aloita.log
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
**Log levels** (set via `ALOITA_LOG_LEVEL`, case-insensitive): `debug` > `info` (default) > `warn` > `error` > `off`.
|
|
382
|
+
|
|
383
|
+
**Custom log file** (optional): set `ALOITA_LOG_FILE` to any path (`~` is expanded).
|
|
384
|
+
|
|
385
|
+
User-facing events (ticket started/finished, errors) still appear as transient toast notifications via `ctx.ui.notify()`, and connection state lives in the footer via `ctx.ui.setStatus()` — the correct Pi UI channels. `/aloita-status` shows a concise one-line summary as a toast; the full detail goes to the log file.
|
|
386
|
+
|
|
387
|
+
## Status & caveats
|
|
388
|
+
|
|
389
|
+
This is a v1 port. Tested live: REST (`GET /api/projects` → 28 projects), SignalR (connect → handshake → subscribe → ping → clean disconnect), capability plan build + auth-user resolution, and usage POST + read-back all pass against the real server.
|
|
390
|
+
|
|
391
|
+
Known differences from the Python receiver, by design:
|
|
392
|
+
- **Subscription billing only.** The old receiver had two billing paths (Agent SDK credit via `claude -p`, and subscription via the loop bridge). A live Pi session is subscription-billed.
|
|
393
|
+
- **No web dashboard.** Visibility is the Pi status footer + notifications + `/aloita-status`. The full event log / terminal-stream dashboard is gone.
|
|
394
|
+
- **`autoFinalizeOnAgentEnd` marks Done after the triggered turn.** If the agent posts a `aura_start_conversation` and stops, the ticket is still marked Done (matching the old receiver's behavior on `claude -p` exit) — the conversation is resumed when the next webhook arrives. Set `autoFinalizeOnAgentEnd: false` to keep tickets In Progress until a human moves them.
|
|
395
|
+
|
|
396
|
+
## Layout
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
src/
|
|
400
|
+
├── index.ts # entry: lifecycle, webhook→sendUserMessage, queue, finalize, status guard, usage flush, commands
|
|
401
|
+
├── config.ts # env + JSON config loader (global userMappings + capabilities)
|
|
402
|
+
├── aura-client.ts # fetch-based port of aura_client.py (get/post/put/delete/upload/download)
|
|
403
|
+
├── signalr.ts # hand-rolled SignalR JSON protocol over ws (main + registerOnly helper mode)
|
|
404
|
+
├── tools.ts # all 28 aura_* tools (port of server.py)
|
|
405
|
+
├── prompts.ts # embedded command templates + label→command routing
|
|
406
|
+
├── capabilities.ts # builds the capability broadcast plan (main + per-user helper split)
|
|
407
|
+
├── agent-log.ts # real-time agent-log streamer (port of AuraAgentLogPusher) + TextLineBuffer + tool-summary helpers
|
|
408
|
+
├── usage.ts # accumulates per-model token/cost, posts to /agent-run-usage on agent_end
|
|
409
|
+
├── working-dir.ts # resolveWorkingDir + shouldClaimTicket (port of resolve_working_dir)
|
|
410
|
+
├── project-launcher.ts # pure auto-start logic (normalize/detect/plan) — see §Auto-start supervisor
|
|
411
|
+
├── config-store.ts # read/write/merge the JSON config file for /aloita-configure
|
|
412
|
+
├── aloita-configure.ts # interactive /aloita-configure settings UI (SelectList + SettingsList)
|
|
413
|
+
```
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./config.schema.json",
|
|
3
|
+
"aloitaUrl": "https://srv1322862.hstgr.cloud",
|
|
4
|
+
"apiKey": "kai_REPLACE_ME",
|
|
5
|
+
|
|
6
|
+
"subscription": {
|
|
7
|
+
"eventNames": ["TicketCreated", "TicketStatusChanged"],
|
|
8
|
+
"statusFilters": {
|
|
9
|
+
"TicketCreated": ["Todo"],
|
|
10
|
+
"TicketStatusChanged": ["Todo"]
|
|
11
|
+
},
|
|
12
|
+
"allProjects": true,
|
|
13
|
+
"allUsers": false,
|
|
14
|
+
"projectIds": [],
|
|
15
|
+
"userIds": ["5fcb57d1-f3a8-4501-b6f6-82228425b6a3"]
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
"projectFolders": {
|
|
19
|
+
"00000000-0000-0000-0000-000000000000": "C:\\Users\\frank\\source\\repos\\my-project"
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
"projectFilter": "",
|
|
23
|
+
"matchAllProjects": false,
|
|
24
|
+
"repos": "C:\\Users\\frank\\source\\repos",
|
|
25
|
+
|
|
26
|
+
"labelToCommand": {
|
|
27
|
+
"plan": "aura-plan-ticket",
|
|
28
|
+
"review": "aura-review-ticket",
|
|
29
|
+
"document": "aura-document",
|
|
30
|
+
"workflow-implementation": "aloita-workflow-implement-with-pr"
|
|
31
|
+
},
|
|
32
|
+
"defaultCommand": "aura-work-on-ticket",
|
|
33
|
+
|
|
34
|
+
"guardStatus": true,
|
|
35
|
+
"autoFinalizeOnAgentEnd": true,
|
|
36
|
+
"postUsage": true,
|
|
37
|
+
"// postDiff": "Capture the run's git diff (vs the HEAD snapshot taken at ticket start) and POST it to Aloita's per-ticket diff viewer (POST /api/tickets/{id}/diffs). Port of the Python receiver's post_agent_run_diff. Best-effort; never blocks or fails the ticket.",
|
|
38
|
+
"postDiff": true,
|
|
39
|
+
"// streamAgentLog": "Stream the agent's text + tool activity to Aloita's per-ticket agent-log tab in real time (POST /api/tickets/{id}/agent-log/batch). Port of the Python receiver's AuraAgentLogPusher. Best-effort; never blocks.",
|
|
40
|
+
"streamAgentLog": true,
|
|
41
|
+
"dedupWindowSeconds": 2,
|
|
42
|
+
|
|
43
|
+
"// logging": "Diagnostics go to ~/.pi/aloita.log (never stdout/stderr, which would corrupt Pi's TUI). Override the path via env ALOITA_LOG_FILE. Level via ALOITA_LOG_LEVEL (debug/info/warn/error/off, default info). See README §Logging.",
|
|
44
|
+
|
|
45
|
+
"// autoStart": "Supervisor mode: when on, this Pi session periodically polls /api/projects and spawns a detached child `pi` in each NEW project's folder, so a Pi CLI is online the moment a project is created — no manual `cd <folder> && pi`. Off by default. See README §Auto-start supervisor.",
|
|
46
|
+
"autoStartProjects": false,
|
|
47
|
+
"// autoStartPollSeconds": "Poll interval for new projects, in seconds.",
|
|
48
|
+
"autoStartPollSeconds": 60,
|
|
49
|
+
"// autoStartCreateFolder": "mkdir the project folder if it doesn't exist yet (a brand-new Aloita project usually has no folder on disk). This is what makes 'no manual step' actually work.",
|
|
50
|
+
"autoStartCreateFolder": true,
|
|
51
|
+
"// autoStartPiCommand": "Override the pi command to spawn (defaults to pi.cmd on Windows, pi elsewhere).",
|
|
52
|
+
"autoStartPiCommand": "",
|
|
53
|
+
"// autoStartExcludeProjects": "Project ids to never auto-start.",
|
|
54
|
+
"autoStartExcludeProjects": [],
|
|
55
|
+
"// autoStartSeedExisting": "When true, launch a child pi for EVERY existing project on supervisor startup (not just new ones). Default false — only newly-created projects are launched.",
|
|
56
|
+
"autoStartSeedExisting": false,
|
|
57
|
+
"// autoStartProjectIds": "Allow-list of project ids that the supervisor should auto-start a child pi for. A brand-new project does nothing until you add its id here. Easiest via /aloita-configure.",
|
|
58
|
+
"autoStartProjectIds": [],
|
|
59
|
+
|
|
60
|
+
"// capabilities": "GLOBAL fallback caps registered against the auth user. Prefer userMappings below. Each entry pushes pricing into Aloita AND links the model into the per-user complexity-tier admin UI.",
|
|
61
|
+
"capabilities": [],
|
|
62
|
+
|
|
63
|
+
"// userMappings": "Per-agent-user profiles. Key = Aloita agent user id. The auth user (env key) may omit apiKey; OTHER users must include their own apiKey so their capabilities register over a connection authenticated as them (Aloita binds RegisterCapabilities to the connected user).",
|
|
64
|
+
"userMappings": {
|
|
65
|
+
"927fc980-4cd6-4323-ac72-2a9be24f2730": {
|
|
66
|
+
"tool": "pi",
|
|
67
|
+
"capabilities": [
|
|
68
|
+
{
|
|
69
|
+
"tool": "pi",
|
|
70
|
+
"provider": "anthropic",
|
|
71
|
+
"model": "claude-sonnet-4-5",
|
|
72
|
+
"displayName": "Sonnet 4.5 (medium complexity)",
|
|
73
|
+
"inputCostPerMTok": 3,
|
|
74
|
+
"outputCostPerMTok": 15,
|
|
75
|
+
"cachedInputCostPerMTok": 0.3,
|
|
76
|
+
"cacheWriteCostPerMTok": 3.75
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"tool": "pi",
|
|
80
|
+
"provider": "anthropic",
|
|
81
|
+
"model": "claude-haiku-4-5",
|
|
82
|
+
"displayName": "Haiku 4.5 (low complexity)",
|
|
83
|
+
"inputCostPerMTok": 0.8,
|
|
84
|
+
"outputCostPerMTok": 4,
|
|
85
|
+
"cachedInputCostPerMTok": 0.08,
|
|
86
|
+
"cacheWriteCostPerMTok": 1
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Aloita Status Bar — Indicator Styling (before / after)
|
|
2
|
+
|
|
3
|
+
Ticket: **Pi Status Bar: Improve Aloita Connection Indicator Styling**
|
|
4
|
+
|
|
5
|
+
## Requirements (all met)
|
|
6
|
+
|
|
7
|
+
1. ✅ Status circle (dot) **green** to signal an active connection
|
|
8
|
+
2. ✅ Label **capitalized** to "Aloita" (was "aloita")
|
|
9
|
+
3. ✅ "connected" text **green** for visual clarity
|
|
10
|
+
|
|
11
|
+
## How it renders
|
|
12
|
+
|
|
13
|
+
Color is applied via Pi's `Theme.fg(color, text)` (`ctx.ui.theme`), which embeds
|
|
14
|
+
ANSI codes the footer renders natively. `success` = green by default and respects
|
|
15
|
+
the user's theme. The footer's `sanitizeStatusText` does not strip ANSI, and its
|
|
16
|
+
`truncateToWidth` (from `@earendil-works/pi-tui`) is ANSI-aware, so truncation
|
|
17
|
+
stays correct.
|
|
18
|
+
|
|
19
|
+
### Before
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
○ aloita: connected
|
|
23
|
+
● aloita: connected · working #abc-123 · +2 queued
|
|
24
|
+
✕ aloita: connection lost: timeout
|
|
25
|
+
```
|
|
26
|
+
(all one color — the terminal's default fg)
|
|
27
|
+
|
|
28
|
+
### After
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
{green}○{reset} Aloita: {green}connected{reset}
|
|
32
|
+
{green}●{reset} Aloita: {green}connected{reset} · working #abc-123 · +2 queued
|
|
33
|
+
{dim}✕{reset} Aloita: {dim}connection lost: timeout{reset}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Concrete ANSI (what `Theme.fg` actually emits):
|
|
37
|
+
|
|
38
|
+
| State | Dot | Label | Connection |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| Connected & idle | `\x1b[32m○\x1b[39m` (green hollow) | `Aloita` | `\x1b[32mconnected\x1b[39m` (green) |
|
|
41
|
+
| Working a ticket | `\x1b[32m●\x1b[39m` (green filled) | `Aloita` | `\x1b[32mconnected\x1b[39m` (green) |
|
|
42
|
+
| Disconnected | `\x1b[2m✕\x1b[39m` (dim) | `Aloita` | `\x1b[2m<connectionState>\x1b[39m` (dim) |
|
|
43
|
+
|
|
44
|
+
## Design decisions
|
|
45
|
+
|
|
46
|
+
- **Green = "active connection" only.** Reserved for the connected/working
|
|
47
|
+
indicator as the ticket specifies. The working/queue suffix is left uncolored
|
|
48
|
+
so the green indicator stands out.
|
|
49
|
+
- **Disconnected is dim, not red.** Neutral — avoids crying wolf during benign
|
|
50
|
+
states like "not started" / "stopped" at startup. The `✕` glyph already
|
|
51
|
+
communicates "down". (A red-for-real-errors variant is a trivial follow-up if
|
|
52
|
+
desired: pass `"error"` instead of `"dim"` in `buildStatusText`.)
|
|
53
|
+
- **Internal `setStatus` key stays lowercase `"aloita"`.** The footer renders
|
|
54
|
+
only the value, not the key; the capitalized "Aloita" is the visible label.
|
|
55
|
+
|
|
56
|
+
## Files
|
|
57
|
+
|
|
58
|
+
- `src/status.ts` — pure `buildStatusText(input, colorizer)` + types
|
|
59
|
+
- `src/status.test.ts` — 12 unit tests (`node:test`, fake tag-wrapping colorizer)
|
|
60
|
+
- `src/index.ts` — `renderStatus` delegates to `buildStatusText(..., ctx.ui.theme)`
|
|
61
|
+
|
|
62
|
+
## Verification
|
|
63
|
+
|
|
64
|
+
- `npm run build` (`tsc --noEmit`): **green**
|
|
65
|
+
- `npm test`: **42/42 pass** (12 new + 30 prior)
|