@cabane/companion 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.
@@ -0,0 +1,537 @@
1
+ /* Cabane Bridge dashboard. Utilitarian, no framework. Light by
2
+ default, dark via prefers-color-scheme. The job is to answer "is it
3
+ running / what's it doing / why isn't it replying" at a glance.
4
+
5
+ Brand tokens: bone ground, ink foreground, ember accent + mark, the warm
6
+ dark set. The status dots stay emerald/amber/red intent tints — health is
7
+ not a brand slot. */
8
+
9
+ :root {
10
+ --bg: #fafaf7; /* Bone — ground */
11
+ --panel: #ffffff; /* Surface — cards */
12
+ --ink: #141413; /* Ink — foreground */
13
+ --muted: rgba(20, 20, 19, 0.55); /* fg-faint — labels, secondary */
14
+ --line: rgba(20, 20, 19, 0.09); /* hairline */
15
+ --inset: #f2f0e9; /* recessed warm well — code, detail */
16
+ --accent: #c8552a; /* Ember — fills, mark, large accents */
17
+ --accent-text: #c8552a; /* link / small accent text (lifts on dark) */
18
+ --green: #059669; /* healthy */
19
+ --amber: #d97706; /* degraded */
20
+ --red: #b8423a; /* error / destructive */
21
+ --danger: #b8423a;
22
+ --radius: 9px;
23
+ }
24
+
25
+ @media (prefers-color-scheme: dark) {
26
+ :root {
27
+ --bg: #121110; /* base — the page */
28
+ --panel: #1c1a18; /* surface — cards, panels */
29
+ --ink: #ece9e2; /* warm off-white */
30
+ --muted: rgba(236, 233, 226, 0.48);
31
+ --line: rgba(236, 233, 226, 0.11);
32
+ --inset: #161513;
33
+ --accent: #c8552a; /* ember holds for fills */
34
+ --accent-text: #e2794a; /* ember-lift — clears AA on dark */
35
+ --green: #10b981;
36
+ --amber: #f59e0b;
37
+ --red: #e0635a;
38
+ --danger: #e0635a;
39
+ }
40
+ }
41
+
42
+ * {
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ body {
47
+ margin: 0;
48
+ background: var(--bg);
49
+ color: var(--ink);
50
+ font:
51
+ 14px/1.5 system-ui,
52
+ -apple-system,
53
+ 'Segoe UI',
54
+ Roboto,
55
+ sans-serif;
56
+ }
57
+
58
+ .shell {
59
+ max-width: 720px;
60
+ margin: 0 auto;
61
+ padding: 28px 20px 48px;
62
+ }
63
+
64
+ .topbar {
65
+ margin-bottom: 24px;
66
+ }
67
+
68
+ .brand {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 10px;
72
+ }
73
+
74
+ /* the Cabane mark: an A-frame roofline ("cabin"), ember stroke (viewBox 0 0 24 18). */
75
+ .logo {
76
+ height: 17px;
77
+ width: auto;
78
+ color: var(--accent);
79
+ flex: 0 0 auto;
80
+ }
81
+
82
+ .brand h1 {
83
+ font-size: 20px;
84
+ margin: 0;
85
+ font-weight: 650;
86
+ letter-spacing: -0.01em;
87
+ }
88
+
89
+ .dot {
90
+ width: 11px;
91
+ height: 11px;
92
+ border-radius: 50%;
93
+ flex: 0 0 auto;
94
+ background: var(--muted);
95
+ box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent);
96
+ }
97
+ .dot--green {
98
+ background: var(--green);
99
+ color: var(--green);
100
+ }
101
+ .dot--amber {
102
+ background: var(--amber);
103
+ color: var(--amber);
104
+ }
105
+ .dot--red {
106
+ background: var(--red);
107
+ color: var(--red);
108
+ }
109
+ .dot--unknown {
110
+ background: var(--muted);
111
+ color: var(--muted);
112
+ }
113
+
114
+ .conn-line {
115
+ margin: 10px 0 2px;
116
+ font-weight: 500;
117
+ }
118
+ .listen-line {
119
+ margin: 0;
120
+ color: var(--muted);
121
+ font-size: 13px;
122
+ }
123
+
124
+ .panel {
125
+ background: var(--panel);
126
+ border: 1px solid var(--line);
127
+ border-radius: var(--radius);
128
+ padding: 16px 18px;
129
+ margin-bottom: 16px;
130
+ }
131
+
132
+ .panel h2 {
133
+ font-size: 13px;
134
+ text-transform: uppercase;
135
+ letter-spacing: 0.04em;
136
+ color: var(--muted);
137
+ margin: 0 0 12px;
138
+ font-weight: 600;
139
+ display: flex;
140
+ align-items: baseline;
141
+ gap: 8px;
142
+ }
143
+
144
+ .count {
145
+ color: var(--muted);
146
+ font-weight: 400;
147
+ }
148
+ .hint {
149
+ color: var(--muted);
150
+ font-weight: 400;
151
+ font-size: 12px;
152
+ text-transform: none;
153
+ letter-spacing: 0;
154
+ }
155
+
156
+ .ws-list,
157
+ .dispatch-list {
158
+ list-style: none;
159
+ margin: 0;
160
+ padding: 0;
161
+ }
162
+
163
+ .ws-row {
164
+ display: flex;
165
+ align-items: center;
166
+ flex-wrap: wrap;
167
+ gap: 10px;
168
+ padding: 9px 0;
169
+ border-top: 1px solid var(--line);
170
+ }
171
+ .ws-row:first-child {
172
+ border-top: none;
173
+ }
174
+ .ws-row .ws-name {
175
+ font-weight: 550;
176
+ }
177
+ .ws-row .ws-meta {
178
+ color: var(--muted);
179
+ font-size: 13px;
180
+ margin-left: 2px;
181
+ }
182
+ .ws-row .spacer {
183
+ flex: 1;
184
+ }
185
+ /* a terminally auth-failed workspace (rotated/revoked PAT). The
186
+ meta line carries the "re-add this workspace" call to action in red so it
187
+ reads as actionable, not as a transient reconnect. */
188
+ .ws-row .ws-meta.ws-meta--alert {
189
+ color: var(--red);
190
+ }
191
+
192
+ /* CT29: the agents this device runs in this workspace, pulled from Cabane.
193
+ Wraps to its own line under the workspace row (flex-basis 100%). */
194
+ .ws-row .ws-agents {
195
+ flex-basis: 100%;
196
+ list-style: none;
197
+ margin: 4px 0 0;
198
+ padding: 0 0 0 18px;
199
+ display: flex;
200
+ flex-direction: column;
201
+ gap: 4px;
202
+ }
203
+ .ws-row .ws-agent {
204
+ display: flex;
205
+ align-items: center;
206
+ gap: 8px;
207
+ font-size: 13px;
208
+ }
209
+ .ws-row .ws-agent-name {
210
+ font-weight: 550;
211
+ }
212
+ .ws-row .ws-agent-mode {
213
+ color: var(--muted);
214
+ font-size: 12px;
215
+ padding: 1px 6px;
216
+ border: 1px solid var(--line);
217
+ border-radius: 6px;
218
+ }
219
+ .ws-row .ws-agent-warn {
220
+ color: var(--red);
221
+ font-size: 12px;
222
+ }
223
+
224
+ button {
225
+ font: inherit;
226
+ cursor: pointer;
227
+ border-radius: 7px;
228
+ border: 1px solid var(--line);
229
+ background: var(--panel);
230
+ color: var(--ink);
231
+ padding: 6px 12px;
232
+ }
233
+ button:hover {
234
+ border-color: var(--accent);
235
+ }
236
+ button.primary {
237
+ background: var(--accent);
238
+ border-color: var(--accent);
239
+ color: #fff;
240
+ }
241
+ button.ghost {
242
+ background: transparent;
243
+ }
244
+ button.danger,
245
+ button.danger:hover {
246
+ color: var(--danger);
247
+ border-color: color-mix(in srgb, var(--danger) 40%, var(--line));
248
+ }
249
+ button.small {
250
+ padding: 3px 9px;
251
+ font-size: 13px;
252
+ }
253
+ button:disabled {
254
+ opacity: 0.5;
255
+ cursor: default;
256
+ }
257
+
258
+ .actions {
259
+ display: flex;
260
+ gap: 8px;
261
+ flex-wrap: wrap;
262
+ margin: 4px 0 16px;
263
+ }
264
+
265
+ .empty {
266
+ color: var(--muted);
267
+ font-size: 13px;
268
+ margin: 4px 0 0;
269
+ }
270
+ .empty.error {
271
+ color: var(--red);
272
+ }
273
+
274
+ /* CT586: harnesses surface — which of the three the device exposes, how healthy
275
+ each is, and (loudly) when it exposes none. */
276
+
277
+ /* a text-style button in a panel heading (the "Recheck" affordance). */
278
+ .link-btn {
279
+ border: none;
280
+ background: none;
281
+ padding: 0;
282
+ margin-left: auto;
283
+ color: var(--accent-text);
284
+ font-size: 12px;
285
+ text-transform: none;
286
+ letter-spacing: 0;
287
+ font-weight: 500;
288
+ }
289
+ .link-btn:hover {
290
+ border: none;
291
+ text-decoration: underline;
292
+ }
293
+
294
+ /* The loud empty state — the headline requirement. A device exposing zero
295
+ harnesses is useless, so this is impossible to miss: an ember-tinted well with
296
+ a large lead line, not a grey dot. */
297
+ .harness-empty {
298
+ border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
299
+ background: rgba(200, 85, 42, 0.1);
300
+ border-radius: var(--radius);
301
+ padding: 14px 16px;
302
+ margin-bottom: 14px;
303
+ }
304
+ .harness-empty-title {
305
+ margin: 0 0 6px;
306
+ font-size: 15px;
307
+ font-weight: 650;
308
+ color: var(--accent-text);
309
+ }
310
+ .harness-empty-body {
311
+ margin: 0;
312
+ font-size: 13px;
313
+ color: var(--ink);
314
+ }
315
+
316
+ .harness-list {
317
+ list-style: none;
318
+ margin: 0;
319
+ padding: 0;
320
+ }
321
+ .harness-row {
322
+ display: flex;
323
+ align-items: center;
324
+ flex-wrap: wrap;
325
+ gap: 10px;
326
+ padding: 11px 0;
327
+ border-top: 1px solid var(--line);
328
+ }
329
+ .harness-row:first-child {
330
+ border-top: none;
331
+ }
332
+ .harness-name {
333
+ font-weight: 600;
334
+ }
335
+ .harness-version {
336
+ color: var(--muted);
337
+ font-size: 12px;
338
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
339
+ }
340
+ .harness-badge {
341
+ font-size: 11px;
342
+ font-weight: 600;
343
+ text-transform: uppercase;
344
+ letter-spacing: 0.03em;
345
+ padding: 2px 8px;
346
+ border-radius: 999px;
347
+ border: 1px solid currentColor;
348
+ }
349
+ .harness-badge--exposed {
350
+ color: var(--green);
351
+ }
352
+ .harness-badge--detected_not_exposed {
353
+ color: var(--amber);
354
+ }
355
+ .harness-badge--needs_attention {
356
+ color: var(--red);
357
+ }
358
+ .harness-badge--not_detected {
359
+ color: var(--muted);
360
+ }
361
+ .harness-spacer {
362
+ flex: 1;
363
+ }
364
+ /* the per-harness guidance line ("install and sign in…", "run opencode serve…"),
365
+ wrapping to its own line under the row. */
366
+ .harness-detail {
367
+ flex-basis: 100%;
368
+ color: var(--muted);
369
+ font-size: 12.5px;
370
+ margin: 0;
371
+ }
372
+ .harness-detail code {
373
+ font-size: 11.5px;
374
+ }
375
+ /* the inline opencode-URL enable form (URL field + Add). */
376
+ .harness-enable {
377
+ flex-basis: 100%;
378
+ display: flex;
379
+ gap: 8px;
380
+ flex-wrap: wrap;
381
+ margin-top: 4px;
382
+ }
383
+ .harness-enable input {
384
+ font: inherit;
385
+ flex: 1;
386
+ min-width: 180px;
387
+ padding: 5px 9px;
388
+ border-radius: 6px;
389
+ border: 1px solid var(--line);
390
+ background: var(--inset);
391
+ color: var(--ink);
392
+ }
393
+
394
+ .dispatch-row {
395
+ border-top: 1px solid var(--line);
396
+ padding: 8px 0;
397
+ }
398
+ .dispatch-row:first-child {
399
+ border-top: none;
400
+ }
401
+ .dispatch-head {
402
+ display: flex;
403
+ align-items: center;
404
+ gap: 8px;
405
+ cursor: pointer;
406
+ }
407
+ .dispatch-head .time {
408
+ color: var(--muted);
409
+ font-size: 12px;
410
+ flex: 0 0 auto;
411
+ min-width: 42px;
412
+ }
413
+ .dispatch-head .ws {
414
+ color: var(--muted);
415
+ font-size: 12px;
416
+ flex: 0 0 auto;
417
+ }
418
+ .dispatch-head .preview {
419
+ flex: 1;
420
+ white-space: nowrap;
421
+ overflow: hidden;
422
+ text-overflow: ellipsis;
423
+ }
424
+ .dispatch-head .verdict {
425
+ flex: 0 0 auto;
426
+ font-size: 12px;
427
+ font-variant-numeric: tabular-nums;
428
+ }
429
+ .verdict.ok {
430
+ color: var(--green);
431
+ }
432
+ .verdict.err {
433
+ color: var(--red);
434
+ }
435
+ .verdict.run {
436
+ color: var(--amber);
437
+ }
438
+ .dispatch-detail {
439
+ margin: 6px 0 2px;
440
+ padding: 10px 12px;
441
+ background: var(--inset);
442
+ border-radius: 7px;
443
+ font-size: 13px;
444
+ }
445
+ /* labeled message / reply sections inside the expanded row. */
446
+ .dispatch-detail .d-block + .d-block {
447
+ margin-top: 10px;
448
+ }
449
+ .dispatch-detail .d-label {
450
+ font-size: 11px;
451
+ text-transform: uppercase;
452
+ letter-spacing: 0.04em;
453
+ color: var(--muted);
454
+ margin: 0 0 3px;
455
+ }
456
+ .dispatch-detail .d-body {
457
+ white-space: pre-wrap;
458
+ word-break: break-word;
459
+ }
460
+ .dispatch-detail .d-body.muted {
461
+ color: var(--muted);
462
+ }
463
+ .dispatch-detail .err-text {
464
+ color: var(--red);
465
+ }
466
+
467
+ .logs {
468
+ margin: 0;
469
+ max-height: 320px;
470
+ overflow: auto;
471
+ font:
472
+ 12px/1.45 ui-monospace,
473
+ SFMono-Regular,
474
+ Menlo,
475
+ monospace;
476
+ white-space: pre-wrap;
477
+ word-break: break-word;
478
+ color: var(--muted);
479
+ }
480
+
481
+ .setting {
482
+ display: flex;
483
+ align-items: center;
484
+ gap: 10px;
485
+ padding: 7px 0;
486
+ border-top: 1px solid var(--line);
487
+ }
488
+ .setting:first-of-type {
489
+ border-top: none;
490
+ }
491
+ .setting label {
492
+ flex: 0 0 180px;
493
+ font-weight: 500;
494
+ }
495
+ .readonly {
496
+ color: var(--muted);
497
+ }
498
+ /* green ✓ flashed to the right of a setting after it saves, then cleared. */
499
+ .saved-check {
500
+ color: var(--green);
501
+ font-weight: 700;
502
+ font-size: 14px;
503
+ line-height: 1;
504
+ }
505
+ select {
506
+ font: inherit;
507
+ padding: 4px 8px;
508
+ border-radius: 6px;
509
+ border: 1px solid var(--line);
510
+ background: var(--panel);
511
+ color: var(--ink);
512
+ }
513
+
514
+ .version {
515
+ text-align: center;
516
+ color: var(--muted);
517
+ font-size: 12px;
518
+ margin-top: 8px;
519
+ }
520
+
521
+ .toast {
522
+ position: fixed;
523
+ left: 50%;
524
+ bottom: 22px;
525
+ transform: translateX(-50%);
526
+ background: var(--ink);
527
+ color: var(--bg);
528
+ padding: 9px 16px;
529
+ border-radius: 8px;
530
+ font-size: 13px;
531
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.2);
532
+ max-width: 90vw;
533
+ }
534
+ .toast.error {
535
+ background: var(--red);
536
+ color: #fff;
537
+ }
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@cabane/companion",
3
+ "version": "0.5.0",
4
+ "type": "module",
5
+ "description": "The Cabane Companion (headless): connect a coding agent on your machine to your Cabane workspace as a responder — drive work against your own codebase, files, and MCP servers without putting any of it in Cabane.",
6
+ "license": "UNLICENSED",
7
+ "homepage": "https://cabane.ai",
8
+ "packageManager": "pnpm@11.1.2",
9
+ "publishConfig": {
10
+ "access": "public"
11
+ },
12
+ "bin": {
13
+ "cabane-companion": "./dist/cli.js",
14
+ "cabane-bridge": "./dist/cli.js"
15
+ },
16
+ "main": "./dist/cli.js",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "scripts": {
24
+ "build": "tsup && node scripts/copy-static.mjs",
25
+ "dev": "tsx watch src/cli.ts",
26
+ "lint": "eslint .",
27
+ "typecheck": "tsc -p tsconfig.json --noEmit",
28
+ "test": "vitest run",
29
+ "prepublishOnly": "pnpm build"
30
+ },
31
+ "dependencies": {
32
+ "@anthropic-ai/claude-agent-sdk": "0.3.165",
33
+ "@hono/node-server": "1.19.14",
34
+ "@inquirer/prompts": "7.10.1",
35
+ "commander": "12.1.0",
36
+ "eventsource-parser": "3.0.8",
37
+ "hono": "4.12.18",
38
+ "pino": "10.3.1",
39
+ "pino-pretty": "13.1.3",
40
+ "zod": "4.4.3"
41
+ },
42
+ "devDependencies": {
43
+ "@cabane/agent-runtime": "workspace:*",
44
+ "@eslint/js": "9.39.4",
45
+ "@types/node": "22.19.19",
46
+ "eslint": "9.39.4",
47
+ "globals": "15.15.0",
48
+ "tsup": "8.5.1",
49
+ "tsx": "4.21.0",
50
+ "typescript": "5.9.3",
51
+ "typescript-eslint": "8.59.3",
52
+ "vitest": "3.2.4"
53
+ }
54
+ }