@bivy/bivy 0.18.3 → 0.18.4-staging.10

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.
@@ -15,7 +15,9 @@ const isNote = (value) => {
15
15
  const note = value;
16
16
  return !!note && typeof note.id === "string" && typeof note.at === "number" && typeof note.note === "string" && note.note.length <= 1000
17
17
  && typeof note.selector === "string" && typeof note.text === "string" && typeof note.path === "string"
18
- && typeof note.viewport?.width === "number" && typeof note.viewport?.height === "number";
18
+ && typeof note.viewport?.width === "number" && typeof note.viewport?.height === "number"
19
+ && (note.context === undefined || (typeof note.context === "string" && note.context.length <= 8000))
20
+ && (note.shot === undefined || (typeof note.shot?.hash === "string" && /^[a-f0-9]{64}$/.test(note.shot.hash) && Number.isFinite(note.shot.size) && Number.isFinite(note.shot.width) && Number.isFinite(note.shot.height)));
19
21
  };
20
22
  const sameFiles = (a, b) => a.size === b.size && [...a].every(([key, data]) => b.get(key)?.equals(data));
21
23
  /** Snapshot, not a live filesystem server: symlinks and hidden files are never published. */
@@ -295,6 +297,10 @@ export class AppRegistry extends EventEmitter {
295
297
  this.save();
296
298
  }
297
299
  agentNotes(id) { return this.apps.get(id)?.agentNotes === true; }
300
+ /** Includes persisted apps not restored yet; unread pictures survive GC. */
301
+ notePictureHashes() {
302
+ return [...this.persisted.values()].flatMap(record => Object.values(record.notes ?? {}).flatMap(notes => Array.isArray(notes) ? notes.filter(isNote).flatMap(note => note.shot ? [note.shot.hash] : []) : []));
303
+ }
298
304
  /** A reviewer's note: kept bounded, saved with the app, announced (`notes`). */
299
305
  addNote(viewId, note) {
300
306
  const entry = this.views.get(viewId);
@@ -9,15 +9,17 @@ export class RemotePreview {
9
9
  registry;
10
10
  returnOrigins;
11
11
  signIn;
12
+ captureNote;
12
13
  gateway;
13
14
  origin;
14
15
  relayUrl;
15
16
  online = false;
16
17
  sockets = new Set();
17
- constructor(registry, returnOrigins, signIn) {
18
+ constructor(registry, returnOrigins, signIn, captureNote) {
18
19
  this.registry = registry;
19
20
  this.returnOrigins = returnOrigins;
20
21
  this.signIn = signIn;
22
+ this.captureNote = captureNote;
21
23
  }
22
24
  get available() { return this.online && Boolean(this.gateway); }
23
25
  ready(origin, relayUrl) {
@@ -30,7 +32,7 @@ export class RemotePreview {
30
32
  return;
31
33
  if (this.origin !== template) {
32
34
  this.gateway?.close();
33
- this.gateway = new AppGateway(this.registry, template, this.returnOrigins, this.signIn);
35
+ this.gateway = new AppGateway(this.registry, template, this.returnOrigins, this.signIn, this.captureNote);
34
36
  this.origin = template;
35
37
  }
36
38
  this.relayUrl = relayUrl.replace(/\/$/, "") + "/preview/stream";
@@ -27,13 +27,16 @@
27
27
 
28
28
  * { box-sizing: border-box; }
29
29
  html, body, #root { height: 100%; margin: 0; }
30
+ /* iOS samples the page and fixed shell backgrounds for its status-bar tint.
31
+ Paint both explicitly so the tint follows theme changes instead of sampling
32
+ a transparent shell. Include the loading/sign-in shell before App mounts. */
33
+ html, body, .app, .setup { background: var(--bg); }
30
34
  /* Lock the shell to the viewport: no whole-page scroll or rubber-band drift on
31
35
  iOS (the app's own panes scroll). Without this the page itself scrolls under
32
36
  the fixed shell and the layout appears to "move around / off screen". */
33
37
  html, body { overflow: hidden; overscroll-behavior: none; }
34
38
  body {
35
39
  font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
36
- background: var(--bg);
37
40
  color: var(--ink);
38
41
  -webkit-font-smoothing: antialiased;
39
42
  }
@@ -1094,24 +1097,25 @@ button.sheet-action:last-child { border-bottom: 0; }
1094
1097
  }
1095
1098
  .image-viewer-img { min-width: 0; min-height: 0; max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--radius-sm); cursor: default; }
1096
1099
  .image-viewer-close {
1097
- position: absolute; top: max(12px, env(safe-area-inset-top)); right: 16px;
1098
- border: 0; background: var(--overlay); color: var(--accent-contrast); width: 40px; height: 40px;
1099
- border-radius: var(--radius-full); font-size: 26px; line-height: 1; cursor: pointer;
1100
+ position: absolute; top: max(var(--space-3), env(safe-area-inset-top)); right: max(var(--space-4), env(safe-area-inset-right));
1100
1101
  }
1102
+ .image-viewer-close, .image-viewer-nav {
1103
+ border: 1px solid var(--line-strong); background: var(--surface-3); color: var(--ink);
1104
+ width: var(--space-7); height: var(--space-7); border-radius: var(--radius-full);
1105
+ font-size: var(--text-3xl); line-height: 1; cursor: pointer;
1106
+ display: grid; place-items: center;
1107
+ }
1108
+ .image-viewer-close:hover, .image-viewer-nav:hover { background: var(--surface-2); }
1101
1109
  /* Prev/next page controls in the multi-image gallery — mirror the close button. */
1102
1110
  .image-viewer-nav {
1103
1111
  position: absolute; top: 50%; transform: translateY(-50%);
1104
- border: 0; background: var(--overlay); color: var(--accent-contrast);
1105
- width: 44px; height: 44px; border-radius: var(--radius-full);
1106
- font-size: 30px; line-height: 1; cursor: pointer;
1107
- display: grid; place-items: center;
1108
1112
  }
1109
1113
  .image-viewer-nav.prev { left: max(var(--space-3), env(safe-area-inset-left)); }
1110
1114
  .image-viewer-nav.next { right: max(var(--space-4), env(safe-area-inset-right)); }
1111
1115
  .image-viewer-count {
1112
1116
  position: absolute; bottom: max(var(--space-4), env(safe-area-inset-bottom));
1113
1117
  left: 50%; transform: translateX(-50%); cursor: default;
1114
- background: var(--overlay); color: var(--accent-contrast);
1118
+ background: var(--surface-3); color: var(--ink);
1115
1119
  padding: var(--space-1) var(--space-3); border-radius: var(--radius-full);
1116
1120
  font-size: var(--text-sm); font-variant-numeric: tabular-nums;
1117
1121
  }
@@ -1168,8 +1172,7 @@ button.sheet-action:last-child { border-bottom: 0; }
1168
1172
  /* Agent/model catalogs should not collapse into a tiny strip when the composer
1169
1173
  grows. Keep their normal composer clearance, but cap that reservation and
1170
1174
  give the scrollable catalog a stable, roomy height. */
1171
- .sheet[data-size="large"] .sheet-body,
1172
- .sheet[data-size="mobile-full"] .sheet-body {
1175
+ .sheet[data-size="large"] .sheet-body {
1173
1176
  --sheet-clear: max(
1174
1177
  calc(env(safe-area-inset-bottom, 0px) + var(--space-2)),
1175
1178
  min(calc(var(--composer-h, 108px) + var(--space-2) - var(--keyboard-h, 0px)), calc(var(--app-h, 100dvh) * 0.24))
@@ -1197,20 +1200,19 @@ button.sheet-action:last-child { border-bottom: 0; }
1197
1200
  page) handles a long tool list; touch scrolling + overscroll containment keep
1198
1201
  the fling smooth on iOS and stop it chaining to the chat underneath. */
1199
1202
  .sheet-content { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 8px; min-height: 0; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
1200
- /* Embedded apps use the whole mobile viewport, including the space normally
1201
- reserved for the chat composer. Keep controls clear of device safe areas. */
1202
- @media (max-width: 900px) {
1203
- .sheet[data-size="mobile-full"] .sheet-body {
1204
- height: 100%; max-height: 100%; max-width: none; margin: 0;
1205
- border: 0; border-radius: 0;
1206
- padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
1207
- }
1208
- .sheet[data-size="mobile-full"] .sheet-grabber { display: none; }
1209
- .sheet[data-size="mobile-full"] .sheet-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1210
- .sheet[data-size="mobile-full"] .sheet-head > button { flex-shrink: 0; min-height: var(--space-7); }
1211
- .sheet[data-size="mobile-full"] .sheet-content { padding: 0; }
1212
- .sheet[data-size="mobile-full"] .preview-peek { border-radius: 0; }
1213
- }
1203
+ /* Embedded apps use the whole viewport on every screen size, including the
1204
+ space normally reserved for the chat composer. Keep controls clear of
1205
+ device safe areas. */
1206
+ .sheet[data-size="full"] .sheet-body {
1207
+ height: 100%; max-height: 100%; max-width: none; margin: 0;
1208
+ border: 0; border-radius: 0;
1209
+ padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
1210
+ }
1211
+ .sheet[data-size="full"] .sheet-grabber { display: none; }
1212
+ .sheet[data-size="full"] .sheet-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1213
+ .sheet[data-size="full"] .sheet-head > button { flex-shrink: 0; min-height: var(--space-7); }
1214
+ .sheet[data-size="full"] .sheet-content { padding: 0; }
1215
+ .sheet[data-size="full"] .preview-peek { border-radius: 0; }
1214
1216
  .picker-search { margin-bottom: 6px; }
1215
1217
  /* In a picker sheet the search is a direct child of the scroll container, so pin
1216
1218
  it to the top: the list scrolls beneath it and every option stays reachable
@@ -2136,20 +2138,6 @@ a.queue-item:hover { border-color: var(--line-strong); background: var(--surface
2136
2138
  .run-sheet-recovery { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
2137
2139
  .run-sheet-recovery .btn.sm { padding: var(--space-1) var(--space-3); }
2138
2140
 
2139
- .composer-starter {
2140
- display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
2141
- margin: 0 var(--space-3) var(--space-2); padding: var(--space-3); border: 1px solid var(--line);
2142
- border-radius: var(--radius-md); background: var(--surface);
2143
- }
2144
- .composer-starter > div { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
2145
- .composer-starter strong { font-size: var(--text-sm); color: var(--ink); }
2146
- .composer-starter span { font-size: var(--text-xs); line-height: var(--lh-sm); color: var(--muted); }
2147
- .composer-starter .btn { flex: none; max-width: 100%; white-space: normal; overflow-wrap: anywhere; }
2148
- @media (max-width: 520px) {
2149
- .composer-starter { align-items: stretch; flex-direction: column; }
2150
- .composer-starter .btn { min-height: var(--space-7); }
2151
- }
2152
-
2153
2141
  /* Review surface (C3): state badges, checks strip, view-mode toggle, file tree,
2154
2142
  per-file actions, and the split (side-by-side) diff layout. */
2155
2143
  .changes-meta { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; padding: 6px 10px; border-bottom: 1px solid var(--line); }
@@ -3075,8 +3063,8 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3075
3063
  .review-banner { margin: 0 var(--space-3); }
3076
3064
  .review-note { margin: var(--space-3) var(--space-3) 0; padding-inline-start: var(--space-3); border-inline-start: 2px solid var(--line-strong); color: var(--ink); font-size: var(--text-sm); white-space: pre-wrap; }
3077
3065
  .review-status { margin: var(--space-2) var(--space-3) 0; color: var(--muted); font-size: var(--text-sm); }
3078
- .review-actions { display: flex; padding: var(--space-3); }
3079
- .review-actions .btn { flex: 1; min-block-size: 44px; }
3066
+ .review-actions { display: flex; flex-wrap: wrap; gap: var(--space-1); padding: var(--space-3); }
3067
+ .review-actions .btn { min-block-size: var(--space-7); }
3080
3068
 
3081
3069
  /* Apps sheet (AppsSheet.tsx): one card per app, its views nested, one primary
3082
3070
  action per view; secondary tools as quiet icon buttons; rare or destructive
@@ -3093,8 +3081,6 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3093
3081
  .apps-more { position: relative; }
3094
3082
  .apps-view-tools .apps-more { margin-inline-start: auto; }
3095
3083
  .apps-more-menu { position: absolute; right: 0; top: calc(100% + var(--space-1)); z-index: var(--z-dropdown); }
3096
- .apps-notice { display: flex; align-items: flex-start; gap: var(--space-2); margin: 0 var(--space-3) var(--space-3); padding: var(--space-2) var(--space-3); border-radius: var(--radius-sm); background: color-mix(in srgb, var(--ok) 12%, transparent); font-size: var(--text-sm); }
3097
- .apps-notice > svg { flex: none; margin-top: 1px; color: var(--ok); }
3098
3084
  .apps-notes { display: flex; flex-direction: column; gap: var(--space-2); margin: 0 var(--space-3) var(--space-3); padding-top: var(--space-3); border-top: 1px dashed var(--line); }
3099
3085
  .apps-notes-head { display: flex; align-items: center; gap: var(--space-2); color: var(--muted); font-size: var(--text-xs); font-weight: var(--weight-semibold); letter-spacing: 0.04em; text-transform: uppercase; }
3100
3086
  .apps-count { min-inline-size: 18px; padding: 0 6px; border-radius: var(--radius-full); background: var(--surface-3); color: var(--ink); text-align: center; letter-spacing: 0; }
@@ -3118,6 +3104,8 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3118
3104
  .share-dest { display: block; inline-size: 100%; padding: 0; border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); color: inherit; font: inherit; text-align: start; cursor: pointer; }
3119
3105
  .share-dest:hover { background: var(--surface-2); }
3120
3106
  .share-dest .app-row { min-block-size: 44px; }
3107
+ .preview-head-actions { display: flex; flex: none; align-items: center; gap: var(--space-1); }
3108
+ .preview-head-actions .btn { min-block-size: var(--space-7); }
3121
3109
  .preview-peek-stage { position: relative; block-size: 100%; }
3122
3110
  /* Point and speak: the recording bar floats over the top of the preview while
3123
3111
  the finger stays on the shell's mic, so nothing under it moves. */
@@ -3166,9 +3154,8 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3166
3154
  letter-spacing: -0.01em;
3167
3155
  }
3168
3156
 
3169
- /* Activation readiness strip (src/components/ReadinessChecklist.tsx) — the honest
3170
- "get to your first agent response" checklist shown until the agent answers. */
3171
- .readiness { margin: 10px 12px 0; }
3157
+ /* Setup checks and the ready-to-message confirmation. */
3158
+ .readiness { margin: var(--space-3) var(--space-3) 0; }
3172
3159
  .readiness-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
3173
3160
  .readiness-title { font-weight: 600; font-size: var(--text-base); letter-spacing: -0.01em; }
3174
3161
  .readiness-dismiss {
@@ -3178,8 +3165,7 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3178
3165
  .readiness-dismiss:hover { background: var(--surface-2); color: var(--ink); }
3179
3166
  .readiness-passed { margin: 0 0 var(--space-2); color: var(--ok); font-size: var(--text-sm); font-weight: var(--weight-medium); }
3180
3167
  .readiness-checks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
3181
- /* Completed setup is summarized above. Keeping six verbose rows after five are
3182
- already done buried the only useful state (waiting for the first answer). */
3168
+ /* Summarize completed checks so the next setup action stays visible. */
3183
3169
  .readiness-check.state-passed { display: none; }
3184
3170
  .readiness-check { display: flex; align-items: baseline; gap: 8px; font-size: var(--text-sm); }
3185
3171
  .readiness-mark {
@@ -3368,3 +3354,9 @@ p.setup-note .btn.link { min-height: 44px; margin-block: calc((1lh - 44px) / 2);
3368
3354
  the composer itself, with one line of another device's draft. */
3369
3355
  .handoff-banner { border-radius: var(--radius-md); }
3370
3356
  .handoff-preview { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3357
+
3358
+ /* Keep editorial type on the wordmark; session titles retain the app's sans. */
3359
+ .brand {
3360
+ font-family: var(--font-serif);
3361
+ font-weight: var(--weight-medium);
3362
+ }
@@ -30,37 +30,37 @@
30
30
  /* ---- Core surfaces & text (neutral) ---- */
31
31
  --bg: #ffffff;
32
32
  --surface: #ffffff;
33
- --surface-2: #f3f5f7;
34
- --surface-3: #e8ecef;
35
- --ink: #0f1215;
36
- --muted: #5f6a73;
37
- --line: #e3e7ea;
38
- --line-strong: #cdd4d9;
33
+ --surface-2: #f5f5f5;
34
+ --surface-3: #ebebeb;
35
+ --ink: #222222;
36
+ --muted: #666666;
37
+ --line: #e5e5e5;
38
+ --line-strong: #c5c5c5;
39
39
  --overlay: rgba(0, 0, 0, 0.46);
40
40
  --overlay-strong: rgba(0, 0, 0, 0.8);
41
41
 
42
42
  /* ---- Accent (reserved for primary actions and active status) ---- */
43
- --accent: #3f6377;
43
+ --accent: #ac4b24;
44
44
  --accent-contrast: #ffffff;
45
- --accent-hover: #324f60;
45
+ --accent-hover: #873b1c;
46
46
  --accent-active: color-mix(in srgb, var(--accent), #000 22%);
47
- --accent-soft: color-mix(in srgb, var(--accent) 16%, var(--surface));
47
+ --accent-soft: #f5e5d9;
48
48
 
49
49
  /* ---- Status ---- */
50
- --danger: #b0473d; --danger-soft: #f3e3df; --danger-contrast: #ffffff;
51
- --ok: #4f7a5c;
52
- --merged: #7a6a9e;
50
+ --danger: #a63d35; --danger-soft: #f4e3dd; --danger-contrast: #ffffff;
51
+ --ok: #506d43;
52
+ --merged: #796188;
53
53
  /* A run that finished while you weren't looking — its own signal, deliberately
54
54
  not accent (working) / danger (needs input) / ok / merged, so
55
55
  "something's ready" reads at a glance. */
56
- --unseen: #a8823f;
56
+ --unseen: #967021;
57
57
  --unseen-contrast: #111111;
58
58
 
59
59
  /* ---- Per-source hues for session/run marks (see packages/web sessionSource).
60
60
  Kept clear of the status palette above so a source is never mistaken for an
61
61
  outcome; app/cli stay quiet so hand-opened sessions don't shout. ---- */
62
62
  --s-github: #24292f; --s-slack: #7c2d92; --s-linear: #5e6ad2; --s-schedule: #0e7490;
63
- --s-webhook: #6d28d9; --s-manual: #b45309; --s-cli: #3f6212; --s-app: #64748b;
63
+ --s-webhook: #6d28d9; --s-manual: #b45309; --s-cli: #526d3b; --s-app: #6a785a;
64
64
 
65
65
  /* ---- Annotation: marks drawn over an app preview. The same in both themes
66
66
  (the app underneath has its own), vivid and rare in real UIs, with a white
@@ -69,8 +69,8 @@
69
69
  --annotate-halo: #ffffff;
70
70
 
71
71
  /* ---- Terminal text selection ---- */
72
- --term-selection: rgba(63, 99, 119, 0.30);
73
- --term-selection-inactive: rgba(63, 99, 119, 0.20);
72
+ --term-selection: rgba(119, 141, 91, 0.30);
73
+ --term-selection-inactive: rgba(119, 141, 91, 0.18);
74
74
 
75
75
  /* ---- Syntax highlighting ---- */
76
76
  --syntax-comment: #6a737d; --syntax-keyword: #d73a49; --syntax-string: #032f62;
@@ -106,18 +106,18 @@
106
106
  --space-5: 24px; --space-6: 32px; --space-7: 48px; --space-8: 64px;
107
107
 
108
108
  /* ---- Radius ---- */
109
- --radius: 12px; /* app default — same value as --radius-md */
110
- --radius-sm: 8px;
111
- --radius-md: 12px;
112
- --radius-lg: 16px;
113
- --radius-xl: 20px;
109
+ --radius: 8px; /* app default — same value as --radius-md */
110
+ --radius-sm: 6px;
111
+ --radius-md: 8px;
112
+ --radius-lg: 12px;
113
+ --radius-xl: 16px;
114
114
  --radius-full: 999px;
115
115
 
116
116
  /* ---- Elevation ---- */
117
- --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.08);
118
- --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
119
- --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.12);
120
- --shadow-xl: 0 24px 70px rgba(0, 0, 0, 0.16);
117
+ --shadow-sm: 0 1px 3px rgba(40, 45, 36, 0.06);
118
+ --shadow-md: 0 4px 16px rgba(40, 45, 36, 0.08);
119
+ --shadow-lg: 0 12px 32px rgba(40, 45, 36, 0.12);
120
+ --shadow-xl: 0 24px 70px rgba(40, 45, 36, 0.16);
121
121
 
122
122
  /* ---- Motion ---- */
123
123
  --dur-fast: 120ms;
@@ -150,59 +150,66 @@
150
150
  definition (they only "worked" via an inline var(--x, #fallback)). Defined
151
151
  here so the fallback is never load-bearing and the value is consistent
152
152
  everywhere the name appears. Prefer the canonical name on the right. ==== */
153
- --warn: #b8862f; --warn-soft: color-mix(in srgb, #b8862f 16%, var(--surface));
153
+ --warn: #967021; --warn-soft: #f0e8cf;
154
154
  --err: var(--danger);
155
155
  --add: var(--ok); /* diff addition */
156
156
  --del: var(--danger); /* diff deletion */
157
157
  --fg: var(--ink);
158
158
  --surface-1: var(--surface);
159
159
  --mono: var(--font-mono);
160
+
160
161
  }
161
162
 
162
163
  :root[data-theme="light"] { color-scheme: light; }
163
164
 
164
165
  :root[data-theme="dark"] {
165
166
  color-scheme: dark;
166
- --bg: #000000; --surface: #000000; --surface-2: #15181b; --surface-3: #22272b;
167
- --ink: #f3f5f7; --muted: #99a2aa; --line: #1f2327; --line-strong: #353b41;
167
+ --bg: #000000; --surface: #080808; --surface-2: #141414; --surface-3: #202020;
168
+ --ink: #f3f3f3; --muted: #aaaaaa; --line: #262626; --line-strong: #484848;
168
169
  --overlay: rgba(0, 0, 0, 0.7); --overlay-strong: rgba(0, 0, 0, 0.8);
169
- --accent: #8fb6cd; --accent-contrast: #0a0a0a; --accent-hover: #a7cadd;
170
- --danger: #c08a83; --danger-soft: #3a2420; --danger-contrast: #0a0a0a; --ok: #86a99f; --merged: #a99bc4;
171
- --unseen: #cba96a; --unseen-contrast: #0a0a0a;
172
- --warn: #d9a441;
170
+ --accent: #edab7e; --accent-contrast: #271c12; --accent-hover: #f6c29f;
171
+ --danger: #f0a497; --danger-soft: #4a3029; --danger-contrast: #0a0a0a; --ok: #b3ce96; --merged: #cab4d4;
172
+ --unseen: #ddc181; --unseen-contrast: #0a0a0a;
173
+ --warn: #ddc181;
173
174
  --s-github: #d7dde3; --s-slack: #d68ce6; --s-linear: #a5adf5; --s-schedule: #4bc4de;
174
- --s-webhook: #b79bf7; --s-manual: #f0a45a; --s-cli: #a3c987; --s-app: #9aa4b2;
175
- --term-selection: rgba(143, 182, 205, 0.36); --term-selection-inactive: rgba(143, 182, 205, 0.24);
175
+ --s-webhook: #b79bf7; --s-manual: #f0a45a; --s-cli: #adc58f; --s-app: #b2c69a;
176
+ --term-selection: rgba(178, 198, 154, 0.30); --term-selection-inactive: rgba(178, 198, 154, 0.18);
176
177
  --syntax-comment: #8b949e; --syntax-keyword: #ff7b72; --syntax-string: #a5d6ff;
177
178
  --syntax-number: #79c0ff; --syntax-title: #d2a8ff; --syntax-type: #ffa657;
178
179
  --syntax-name: #7ee787; --syntax-deletion: #ffdcd7; --syntax-addition: #85e89d;
179
- --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
180
- --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
181
- --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
182
- --shadow-xl: 0 24px 70px rgba(0, 0, 0, 0.6);
180
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.20);
181
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.24);
182
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.28);
183
+ --shadow-xl: 0 24px 70px rgba(0, 0, 0, 0.32);
184
+
185
+ --accent-soft: #493c2c;
186
+ --warn-soft: #49432b;
183
187
  }
184
188
 
185
189
  @media (prefers-color-scheme: dark) {
186
190
  :root:not([data-theme]) {
187
191
  color-scheme: dark;
188
- --bg: #000000; --surface: #000000; --surface-2: #15181b; --surface-3: #22272b;
189
- --ink: #f3f5f7; --muted: #99a2aa; --line: #1f2327; --line-strong: #353b41;
192
+ --bg: #000000; --surface: #080808; --surface-2: #141414; --surface-3: #202020;
193
+ --ink: #f3f3f3; --muted: #aaaaaa; --line: #262626; --line-strong: #484848;
190
194
  --overlay: rgba(0, 0, 0, 0.7); --overlay-strong: rgba(0, 0, 0, 0.8);
191
- --accent: #8fb6cd; --accent-contrast: #0a0a0a; --accent-hover: #a7cadd;
192
- --danger: #c08a83; --danger-soft: #3a2420; --danger-contrast: #0a0a0a; --ok: #86a99f; --merged: #a99bc4;
193
- --unseen: #cba96a; --unseen-contrast: #0a0a0a;
194
- --warn: #d9a441;
195
+ --accent: #edab7e; --accent-contrast: #271c12; --accent-hover: #f6c29f;
196
+ --danger: #f0a497; --danger-soft: #4a3029; --danger-contrast: #0a0a0a; --ok: #b3ce96; --merged: #cab4d4;
197
+ --unseen: #ddc181; --unseen-contrast: #0a0a0a;
198
+ --warn: #ddc181;
195
199
  --s-github: #d7dde3; --s-slack: #d68ce6; --s-linear: #a5adf5; --s-schedule: #4bc4de;
196
- --s-webhook: #b79bf7; --s-manual: #f0a45a; --s-cli: #a3c987; --s-app: #9aa4b2;
197
- --term-selection: rgba(143, 182, 205, 0.36); --term-selection-inactive: rgba(143, 182, 205, 0.24);
200
+ --s-webhook: #b79bf7; --s-manual: #f0a45a; --s-cli: #adc58f; --s-app: #b2c69a;
201
+ --term-selection: rgba(178, 198, 154, 0.30); --term-selection-inactive: rgba(178, 198, 154, 0.18);
198
202
  --syntax-comment: #8b949e; --syntax-keyword: #ff7b72; --syntax-string: #a5d6ff;
199
203
  --syntax-number: #79c0ff; --syntax-title: #d2a8ff; --syntax-type: #ffa657;
200
204
  --syntax-name: #7ee787; --syntax-deletion: #ffdcd7; --syntax-addition: #85e89d;
201
- --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
202
- --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.45);
203
- --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.5);
204
- --shadow-xl: 0 24px 70px rgba(0, 0, 0, 0.6);
205
- }
205
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.20);
206
+ --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.24);
207
+ --shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.28);
208
+ --shadow-xl: 0 24px 70px rgba(0, 0, 0, 0.32);
209
+
210
+ --accent-soft: #493c2c;
211
+ --warn-soft: #49432b;
212
+ }
206
213
  }
207
214
 
208
215
  @media (prefers-reduced-motion: reduce) {
package/dist/git-auth.js CHANGED
@@ -148,14 +148,22 @@ export function gitNonInteractiveEnv() {
148
148
  */
149
149
  export async function configureRepoCredentialHelper(git, dest) {
150
150
  const helper = ensureCredentialHelper();
151
- await git(["-C", dest, "config", "--local", "credential.https://github.com.useHttpPath", "true"]);
152
151
  // Reset the helper chain for this repo, then set ONLY bivy's helper, so AGENT-run
153
152
  // git never falls back to an inherited host helper holding the human's personal
154
- // token (see credConfigArgs for the full rationale). Clear any prior values first
155
- // so re-running stays idempotent (--unset-all returns 5 when the key is absent).
156
- await git(["-C", dest, "config", "--local", "--unset-all", "credential.helper"]).catch(() => { });
157
- await git(["-C", dest, "config", "--local", "--unset-all", "credential.https://github.com.helper"]).catch(() => { });
153
+ // token (see credConfigArgs for the full rationale).
154
+ //
155
+ // Git reads `credential.helper` and `credential.<url>.helper` into ONE list in
156
+ // FILE order, and an empty value clears everything before it. So every reset must
157
+ // sit above bivy's helper in .git/config — an empty `[credential] helper` written
158
+ // after the `[credential "https://github.com"]` section wipes bivy's helper and
159
+ // leaves git with none (the lazy blob fetch in `git worktree add` then dies with
160
+ // "could not read Username"). `--add` appends to an existing section, so drop both
161
+ // sections first and write them back in order; this also repairs clones written in
162
+ // the old order (remove-section fails harmlessly when a section is absent).
163
+ await git(["-C", dest, "config", "--local", "--remove-section", "credential"]).catch(() => { });
164
+ await git(["-C", dest, "config", "--local", "--remove-section", "credential.https://github.com"]).catch(() => { });
158
165
  await git(["-C", dest, "config", "--local", "--add", "credential.helper", ""]);
166
+ await git(["-C", dest, "config", "--local", "credential.https://github.com.useHttpPath", "true"]);
159
167
  await git(["-C", dest, "config", "--local", "--add", "credential.https://github.com.helper", ""]);
160
168
  await git(["-C", dest, "config", "--local", "--add", "credential.https://github.com.helper", helper]);
161
169
  }
@@ -0,0 +1,15 @@
1
+ /** Use the same configured providers as the model picker, including environment
2
+ * credentials. An unrelated rejected key must not hide a usable provider. */
3
+ export async function credentialReadiness(providers, probe) {
4
+ const configured = providers.filter((provider) => provider.configured);
5
+ const results = await Promise.all(configured.map((provider) => probe(provider.id)));
6
+ const usable = results.find((result) => result.ok);
7
+ const result = usable ?? results[0];
8
+ return {
9
+ configured: configured.length > 0,
10
+ providers: configured.map((provider) => provider.id),
11
+ probed: result?.probed ?? false,
12
+ ok: Boolean(usable),
13
+ ...(result?.reason ? { reason: result.reason } : {}),
14
+ };
15
+ }
@@ -85,7 +85,7 @@ export function isAnthropicAuthError(raw) {
85
85
  export async function probeAnthropicAccess(apiKey, deps = {}) {
86
86
  const key = apiKey?.trim();
87
87
  // Subscription/OAuth tokens are not API keys; there is no safe read probe.
88
- if (!key || !key.startsWith("sk-"))
88
+ if (!key || !key.startsWith("sk-") || key.startsWith("sk-ant-oat"))
89
89
  return { probed: false, ok: true, reason: "no API key to probe" };
90
90
  const doFetch = deps.fetch ?? fetch;
91
91
  const base = (deps.baseUrl ?? "https://api.anthropic.com").replace(/\/+$/, "");