amicus 1.4.0 → 1.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amicus",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Multi-model LLM Council + parallel AI window for Claude Code. Run structured council reviews across Gemini, GPT, DeepSeek and more — or fork a conversation to any model and fold the results back.",
5
5
  "author": { "name": "Christian Wagner" },
6
6
  "homepage": "https://bourbondog.github.io/amicus/",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,30 @@ All notable changes to Amicus are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [1.5.0] - 2026-06-29
9
+
10
+ A visual refresh plus a council-reliability fix and a config-dir consolidation.
11
+
12
+ ### Added
13
+ - **Amicus design system**: the Electron app (setup wizard, toolbar, fold overlay), the council
14
+ HTML report, and the marketing site now render from one shared token layer (`src/design/tokens.css`
15
+ + a `src/design/tokens.js` loader) — the clay/gold rail-yard brand on a neutral-black ramp, with
16
+ bundled Outfit + IBM Plex Mono fonts. Previously each surface defined its own colors independently;
17
+ the site is pixel-identical to before, now bound to the shared tokens by a drift-guard test.
18
+
19
+ ### Fixed
20
+ - **Council reliability ledger now persists.** `amicus council tally` (and the MCP
21
+ `amicus_council_tally`) computed the tally record but never wrote it, so `council-ledger.jsonl`
22
+ stayed empty and `amicus council stats` always reported "No council runs recorded yet." The tally
23
+ finalize step now auto-appends the row(s) — best-effort, so a ledger write failure never fails the
24
+ tally — and a new `--no-ledger` flag computes a record without recording it (e.g. a re-tally).
25
+
26
+ ### Changed
27
+ - **Unified config directory.** On startup Amicus now migrates a legacy `~/.config/sidecar` directory
28
+ onto the canonical `~/.config/amicus` once, non-destructively (copy; the legacy dir is kept as a
29
+ backup). This collapses the two-directory split that could let config resolution flip between them
30
+ and orphan your config, catalog, and ledger. A `CONFIG_DIR` override opts out.
31
+
8
32
  ## [1.4.0] - 2026-06-28
9
33
 
10
34
  ### Added
package/bin/amicus.js CHANGED
@@ -7,6 +7,17 @@
7
7
  * Routes commands to appropriate handlers.
8
8
  */
9
9
 
10
+ // One-time, non-destructive migration of the legacy ~/.config/sidecar dir onto
11
+ // the canonical ~/.config/amicus (copy; legacy kept as a backup). Runs before
12
+ // any config/credential read so both resolve to the unified dir. Best-effort.
13
+ try {
14
+ const { migrateLegacyConfigDir } = require('../src/utils/config');
15
+ const _m = migrateLegacyConfigDir();
16
+ if (_m && _m.migrated) {
17
+ process.stderr.write(`[amicus] Migrated config ${_m.from} → ${_m.to} (legacy kept as a backup).\n`);
18
+ }
19
+ } catch { /* best-effort: never block startup on migration */ }
20
+
10
21
  // Load API keys from all sources: process.env > sidecar .env > auth.json
11
22
  const { loadCredentials } = require('../src/utils/env-loader');
12
23
  loadCredentials();
package/electron/fold.js CHANGED
@@ -8,6 +8,7 @@
8
8
  const { logger } = require('../src/utils/logger');
9
9
  const { requestSummaryFromModel } = require('./summary');
10
10
  const { getSummaryTemplate } = require('../src/prompt-builder');
11
+ const { tokenCss } = require('../src/design/tokens');
11
12
 
12
13
  /**
13
14
  * Create a fold handler bound to the window state
@@ -59,11 +60,11 @@ function createFoldHandler(state) {
59
60
  if (contentView) {
60
61
  await contentView.webContents.executeJavaScript(`
61
62
  (function() {
62
- var overlay = document.getElementById('sidecar-fold-overlay');
63
+ var overlay = document.getElementById('amicus-fold-overlay');
63
64
  if (overlay) {
64
65
  while (overlay.firstChild) { overlay.removeChild(overlay.firstChild); }
65
66
  var msg = document.createElement('div');
66
- msg.style.cssText = 'color:#E8E0D8;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:15px;font-weight:500;text-align:center;max-width:320px;';
67
+ msg.style.cssText = 'color:var(--text-1);font-family:var(--font-sans),-apple-system,BlinkMacSystemFont,sans-serif;font-size:15px;font-weight:500;text-align:center;max-width:320px;';
67
68
  msg.textContent = 'Summary saved. Tell Claude you\\u2019re done with the Amicus session so it can read the results.';
68
69
  overlay.appendChild(msg);
69
70
  }
@@ -110,7 +111,7 @@ function showFoldOverlay(mainWindow, contentView) {
110
111
  var span = document.createElement('span');
111
112
  span.style.cssText = 'display:inline-flex;align-items:center;gap:6px;';
112
113
  var spinner = document.createElement('span');
113
- spinner.style.cssText = 'width:12px;height:12px;border:2px solid rgba(255,255,255,0.3);border-top-color:#fff;border-radius:50%;animation:spin 0.8s linear infinite;display:inline-block;';
114
+ spinner.style.cssText = 'width:12px;height:12px;border:2px solid rgba(255,255,255,0.3);border-top-color:var(--on-accent);border-radius:50%;animation:spin 0.8s linear infinite;display:inline-block;';
114
115
  span.appendChild(spinner);
115
116
  span.appendChild(document.createTextNode('Generating summary\\u2026'));
116
117
  btn.appendChild(span);
@@ -128,6 +129,15 @@ function showFoldOverlay(mainWindow, contentView) {
128
129
  `).catch(() => {});
129
130
  }
130
131
  if (contentView) {
132
+ // Scope token vars to the overlay container so var(--x) resolves without
133
+ // touching OpenCode's own :root (which would clobber its CSS variables).
134
+ const rawCss = tokenCss({ absoluteFontUrls: true });
135
+ // Replace only the :root selector (not occurrences inside comments) so the
136
+ // custom properties are defined on #amicus-fold-overlay and inherited by
137
+ // its descendants. @font-face blocks are left at global scope (no selector).
138
+ const scopedCss = rawCss.replace(/:root\s*\{/, '#amicus-fold-overlay {');
139
+ contentView.webContents.insertCSS(scopedCss).catch(() => {});
140
+
131
141
  contentView.webContents.executeJavaScript(`
132
142
  (function() {
133
143
  if (!document.getElementById('fold-spin-style')) {
@@ -137,20 +147,20 @@ function showFoldOverlay(mainWindow, contentView) {
137
147
  document.head.appendChild(style);
138
148
  }
139
149
  var overlay = document.createElement('div');
140
- overlay.id = 'sidecar-fold-overlay';
150
+ overlay.id = 'amicus-fold-overlay';
141
151
  overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.55);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:99999;';
142
152
 
143
153
  var spinDiv = document.createElement('div');
144
- spinDiv.style.cssText = 'width:32px;height:32px;border:3px solid rgba(217,119,87,0.3);border-top-color:#D97757;border-radius:50%;animation:spin 0.8s linear infinite;margin-bottom:16px;';
154
+ spinDiv.style.cssText = 'width:32px;height:32px;border:3px solid var(--accent-line);border-top-color:var(--accent);border-radius:50%;animation:spin 0.8s linear infinite;margin-bottom:16px;';
145
155
  overlay.appendChild(spinDiv);
146
156
 
147
157
  var titleDiv = document.createElement('div');
148
- titleDiv.style.cssText = 'color:#E8E0D8;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:15px;font-weight:500;';
158
+ titleDiv.style.cssText = 'color:var(--text-1);font-family:var(--font-sans),-apple-system,BlinkMacSystemFont,sans-serif;font-size:15px;font-weight:500;';
149
159
  titleDiv.textContent = 'Generating summary\\u2026';
150
160
  overlay.appendChild(titleDiv);
151
161
 
152
162
  var subtitleDiv = document.createElement('div');
153
- subtitleDiv.style.cssText = 'color:#7A756F;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:12px;margin-top:6px;';
163
+ subtitleDiv.style.cssText = 'color:var(--text-3);font-family:var(--font-sans),-apple-system,BlinkMacSystemFont,sans-serif;font-size:12px;margin-top:6px;';
154
164
  subtitleDiv.textContent = 'Folding session back to Claude Code';
155
165
  overlay.appendChild(subtitleDiv);
156
166
 
@@ -51,9 +51,9 @@ function buildAliasEditorHTML(aliases) {
51
51
  const exampleModel = aliases[exampleAlias] || 'openrouter/google/gemini-3.1-flash-lite-preview';
52
52
 
53
53
  // SVG icons for the example box
54
- const terminalIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="2" width="14" height="12" rx="2" stroke="#D97757" stroke-width="1.5"/><path d="M4 6l2.5 2L4 10" stroke="#D97757" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8.5 10H11" stroke="#5A5550" stroke-width="1.5" stroke-linecap="round"/></svg>`;
55
- const arrowIcon = `<svg width="20" height="12" viewBox="0 0 20 12" fill="none"><path d="M2 6h14" stroke="#D97757" stroke-width="1.5" stroke-linecap="round"/><path d="M13 2l4 4-4 4" stroke="#D97757" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
56
- const modelIcon = `<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="5" r="3" stroke="#6BBF6B" stroke-width="1.5"/><path d="M8 8v3" stroke="#6BBF6B" stroke-width="1.5" stroke-linecap="round"/><circle cx="4" cy="13" r="1.5" stroke="#6BBF6B" stroke-width="1.2"/><circle cx="8" cy="13" r="1.5" stroke="#6BBF6B" stroke-width="1.2"/><circle cx="12" cy="13" r="1.5" stroke="#6BBF6B" stroke-width="1.2"/><path d="M4 11.5L8 11M8 11l4 .5" stroke="#6BBF6B" stroke-width="1" stroke-linecap="round"/></svg>`;
54
+ const terminalIcon = `<svg class="alias-icon-accent" width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="2" width="14" height="12" rx="2" stroke-width="1.5"/><path d="M4 6l2.5 2L4 10" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/><path d="M8.5 10H11" class="alias-icon-faint-path" stroke-width="1.5" stroke-linecap="round"/></svg>`;
55
+ const arrowIcon = `<svg class="alias-icon-accent" width="20" height="12" viewBox="0 0 20 12" fill="none"><path d="M2 6h14" stroke-width="1.5" stroke-linecap="round"/><path d="M13 2l4 4-4 4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>`;
56
+ const modelIcon = `<svg class="alias-icon-ok" width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="5" r="3" stroke-width="1.5"/><path d="M8 8v3" stroke-width="1.5" stroke-linecap="round"/><circle cx="4" cy="13" r="1.5" stroke-width="1.2"/><circle cx="8" cy="13" r="1.5" stroke-width="1.2"/><circle cx="12" cy="13" r="1.5" stroke-width="1.2"/><path d="M4 11.5L8 11M8 11l4 .5" stroke-width="1" stroke-linecap="round"/></svg>`;
57
57
 
58
58
  const exampleBox = `<div class="routing-example">
59
59
  <div class="example-label">How it works</div>