amicus 1.5.1 → 1.6.1
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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +67 -0
- package/README.md +40 -11
- package/bin/amicus.js +3 -2
- package/electron/main.js +10 -4
- package/electron/session-route.js +28 -0
- package/package.json +4 -3
- package/scripts/postinstall.js +103 -3
- package/scripts/setup-hooks.js +7 -3
- package/src/cli-handlers-doctor.js +1 -1
- package/src/cli.js +95 -8
- package/src/headless.js +43 -8
- package/src/mcp-server.js +178 -20
- package/src/opencode-client.js +99 -15
- package/src/session-manager.js +5 -0
- package/src/sidecar/continue.js +3 -2
- package/src/sidecar/interactive.js +21 -7
- package/src/sidecar/resume.js +4 -2
- package/src/sidecar/session-finalize.js +41 -1
- package/src/sidecar/session-utils.js +8 -2
- package/src/sidecar/setup.js +32 -0
- package/src/utils/api-key-validation.js +72 -0
- package/src/utils/project-path.js +61 -0
- package/src/utils/result-schema.js +1 -0
- package/src/utils/session-index.js +98 -0
- package/src/utils/session-path.js +68 -0
- package/src/utils/validators.js +3 -27
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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,73 @@ All notable changes to Amicus are documented here. Format follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.6.1] - 2026-06-30
|
|
9
|
+
|
|
10
|
+
Project-directory and session-addressing correctness — agents, sessions, and the interactive GUI now agree on which project they're in.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **`AMICUS_PROJECT_DIR` + MCP `roots` support.** When the project is not passed explicitly, the MCP
|
|
14
|
+
server now resolves the working directory from the client's first `file://` workspace root (falling
|
|
15
|
+
back to `AMICUS_PROJECT_DIR`, then the process cwd) — so a stdio MCP server spawned by a desktop
|
|
16
|
+
client no longer roots agents in the app install directory where they can't see your files.
|
|
17
|
+
- **Global session index.** `amicus_status` / `amicus_read` / `amicus_list` now consult a global
|
|
18
|
+
`taskId -> project` index on a per-project miss, so a session created in one project is still found
|
|
19
|
+
when looked up from another.
|
|
20
|
+
- **Per-command help for the rest of the CLI.** `amicus council --help` (and `continue`, `resume`,
|
|
21
|
+
`doctor`, `setup`, `key`, `mcp`) now print their own scoped usage instead of the full global help.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- **Interactive `--cwd`: follow-up prompts no longer fail "unable to retrieve session."** When the
|
|
25
|
+
launch directory differs from `--cwd` (the normal sidecar-skill pattern), the OpenCode session is now
|
|
26
|
+
scoped to the project directory and the Electron Web-UI route is built from the **server-echoed**
|
|
27
|
+
session directory rather than a guessed one, so turn 2+ resolve correctly.
|
|
28
|
+
- **Shared-server MCP sessions are scoped to the project directory** — every create and follow-up call
|
|
29
|
+
carries the directory, so headless MCP sessions are found on a server shared across projects.
|
|
30
|
+
- **`amicus_read` surfaces the failure reason** for crashed / timed-out / aborted runs that wrote no
|
|
31
|
+
summary, instead of a bare "No summary available."
|
|
32
|
+
- **`amicus_abort`'s "session not found"** now names the resolved project, matching `status` / `read`.
|
|
33
|
+
- Internal: a single `canonicalProjectPath()` now normalizes project paths (slash direction, drive-letter
|
|
34
|
+
case, trailing slash, UNC shares) so creation and lookup always agree.
|
|
35
|
+
|
|
36
|
+
## [1.6.0] - 2026-06-30
|
|
37
|
+
|
|
38
|
+
Install resilience and council-failure correctness — the first two blocks of the post-1.5 backlog program.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- **Per-subcommand help.** `amicus <command> --help` now prints only that command's options instead of
|
|
42
|
+
the full global usage; bare `amicus --help` is unchanged.
|
|
43
|
+
- **Zero-credit OpenRouter key warning at setup.** Setup now does a non-blocking `GET /api/v1/key`
|
|
44
|
+
check and warns when a key is free-tier or has no remaining credit, so a credit-less key is flagged
|
|
45
|
+
up front instead of 402-ing on the first paid model call.
|
|
46
|
+
- **`amicus doctor` engine-recovery guidance.** The opencode-engine check now explains the
|
|
47
|
+
transient install-rollback failure mode and gives copy-paste recovery steps.
|
|
48
|
+
- **Postinstall verifies the Electron binary.** When the optional Electron download/extract fails (or
|
|
49
|
+
AV quarantines the binary), the install now prints a clear non-fatal notice that headless runs and
|
|
50
|
+
the council still work — instead of silently leaving a broken GUI to discover later.
|
|
51
|
+
- **CI tarball guard.** A new `check:tarball` step asserts every lifecycle-referenced script actually
|
|
52
|
+
ships in the published package, so a future packaging change can't silently drop it.
|
|
53
|
+
- **README "Requirements & Dependencies" section** consolidating Node, git, OpenRouter credits, API-key
|
|
54
|
+
env vars, the optional Electron GUI, the bundled opencode engine, and OS support.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
- **Council / headless runs no longer report success when every model call fails.** On the shared-server
|
|
58
|
+
MCP path, a run whose calls all errored (e.g. an OpenRouter 402) was finalized as `complete` with a
|
|
59
|
+
0-byte summary, so `amicus_status` showed success and the error was lost. Non-2xx/402 responses are
|
|
60
|
+
now detected at the OpenCode client boundary even when no assistant message is emitted, the
|
|
61
|
+
shared-server finalize routes through the same terminal-state classifier as the CLI, and a failed run
|
|
62
|
+
can never silently default to `complete`; `amicus_read` surfaces the failure reason.
|
|
63
|
+
- **`amicus_status` elapsed time** is now bounded by the run's completed/aborted/crashed timestamp
|
|
64
|
+
instead of wall-clock-since-start, so a finished run reports its real duration.
|
|
65
|
+
- **`amicus_setup` (MCP)** no longer claims an Electron window appeared when Electron is unavailable —
|
|
66
|
+
it pre-flights and returns an honest error directing you to the headless terminal wizard.
|
|
67
|
+
- **Clearer "session not found"** — the message now names the resolved project so you know to pass the
|
|
68
|
+
original `project`.
|
|
69
|
+
- **Non-fatal postinstall.** An internal skill-copy / MCP-registration failure no longer exits non-zero
|
|
70
|
+
and rolls back the entire global install; it warns and continues.
|
|
71
|
+
- **`github:` install on Windows now runs identically to the registry install.** Removed the
|
|
72
|
+
consumer-facing `prepare` lifecycle that triggered npm's clone→prepare→nested-install→cached-pack
|
|
73
|
+
path (the rollback source); git hooks are still configured for contributors via `postinstall`.
|
|
74
|
+
|
|
8
75
|
## [1.5.1] - 2026-06-29
|
|
9
76
|
|
|
10
77
|
A headless-reliability fix for reasoning-heavy models.
|
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ Hand Claude a plan, a design, a diff, an architecture decision, a manuscript —
|
|
|
23
23
|
|
|
24
24
|
- [What is Amicus](#what-is-amicus)
|
|
25
25
|
- [Quick start](#quick-start)
|
|
26
|
+
- [Requirements & Dependencies](#requirements--dependencies)
|
|
26
27
|
- [The Council](#the-council)
|
|
27
28
|
- [The parallel window](#the-parallel-window)
|
|
28
29
|
- [Commands](#commands)
|
|
@@ -67,16 +68,6 @@ The postinstall step auto-configures everything — no manual registration:
|
|
|
67
68
|
- Registers the **MCP server** in Claude Code and in Claude Desktop / Cowork, so the Amicus tools appear natively.
|
|
68
69
|
- Installs **both skills** into `~/.claude/skills/` — `second-opinion` (the council) and `sidecar` (the chat skill).
|
|
69
70
|
|
|
70
|
-
## Prerequisites & what it costs you
|
|
71
|
-
|
|
72
|
-
Before your first run:
|
|
73
|
-
|
|
74
|
-
- **Node.js ≥ 18** — `node --version` to check.
|
|
75
|
-
- **An active Claude Code or Cowork session** — Amicus is orchestrated by Claude; it is not a standalone chatbot.
|
|
76
|
-
- **At least one paid model API key** — OpenRouter (covers the most models) or a direct Google / OpenAI / Anthropic / DeepSeek key. Add one with `amicus setup` or `amicus key <provider> <key>`.
|
|
77
|
-
|
|
78
|
-
**What a run costs.** A sidecar is a single model call. A full council is typically **~5–8 paid model calls** (e.g. 3 reviewers across 2 fan-out waves + 1 chair). Amicus shows an estimate before each council and enforces a built-in budget gate that refuses ultra-expensive models (o3-pro class) unless you opt in with `--no-cost-gate`. You pay your providers directly for the tokens; Amicus itself is free and open-source.
|
|
79
|
-
|
|
80
71
|
**Configure:**
|
|
81
72
|
|
|
82
73
|
```bash
|
|
@@ -110,18 +101,55 @@ A window opens alongside your editor with Gemini ready, pre-loaded with your con
|
|
|
110
101
|
|
|
111
102
|
### Install from GitHub
|
|
112
103
|
|
|
113
|
-
The npm package is the primary path. To install straight from the repo instead
|
|
104
|
+
The npm package is the primary path. To install straight from the repo instead — the postinstall runs **identically** (same MCP registration, same two skills) — you just need `git` on your `PATH`:
|
|
114
105
|
|
|
115
106
|
```bash
|
|
116
107
|
npm install -g github:BourbonDog/amicus
|
|
117
108
|
```
|
|
118
109
|
|
|
110
|
+
See [Requirements & Dependencies](#requirements--dependencies) for the full prerequisite list.
|
|
111
|
+
|
|
119
112
|
### Contributor setup
|
|
120
113
|
|
|
121
114
|
Cloning to develop Amicus? See **[CONTRIBUTING.md](./CONTRIBUTING.md)** for the dev setup, git-hook wiring, and test commands.
|
|
122
115
|
|
|
123
116
|
---
|
|
124
117
|
|
|
118
|
+
## Requirements & Dependencies
|
|
119
|
+
|
|
120
|
+
Everything you need before your first run, and what's optional.
|
|
121
|
+
|
|
122
|
+
**Runtime**
|
|
123
|
+
|
|
124
|
+
- **Node.js ≥ 18** — `node --version` to check. This is the only hard runtime prerequisite.
|
|
125
|
+
- **An active Claude Code or Cowork session** — Amicus is orchestrated by Claude; it is not a standalone chatbot.
|
|
126
|
+
|
|
127
|
+
**Install path & the git toolchain**
|
|
128
|
+
|
|
129
|
+
- **From the npm registry (recommended):** `npm install -g amicus`. No build toolchain required — the package ships prebuilt.
|
|
130
|
+
- **From GitHub:** `npm install -g github:BourbonDog/amicus` runs **identically** — same MCP registration, same two skills, same postinstall. The one extra requirement is a working **git** on your `PATH`, since npm clones the repo to install it (`git --version` to check).
|
|
131
|
+
|
|
132
|
+
**Model API keys** — at least one is required
|
|
133
|
+
|
|
134
|
+
- **OpenRouter** covers the most models with one key, or use a **direct Google / OpenAI / Anthropic / DeepSeek** key. Add one with `amicus setup` or `amicus key <provider> <key>`; keys live in `~/.config/amicus/.env`. The supported env vars are `OPENROUTER_API_KEY`, `GOOGLE_GENERATIVE_AI_API_KEY` (the legacy `GEMINI_API_KEY` is still accepted), `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and `DEEPSEEK_API_KEY`.
|
|
135
|
+
- ⚠️ **OpenRouter keys need purchased credits.** A brand-new, zero-credit key *passes* setup's live validation but then fails at runtime with a **402** on the first real call. Buy a small amount of credit before you run a council.
|
|
136
|
+
|
|
137
|
+
**Electron — optional (GUI only)**
|
|
138
|
+
|
|
139
|
+
- Electron is an **optional** dependency. It powers the graphical setup wizard and the parallel sidecar window. **Headless runs and the full council work without it** — if Electron is absent or can't open a window, `amicus setup` falls back to a readline wizard and sidecars run headless. `amicus doctor` reports Electron's presence accurately and never treats its absence as fatal.
|
|
140
|
+
|
|
141
|
+
**The OpenCode engine**
|
|
142
|
+
|
|
143
|
+
- The bundled **`opencode-ai`** engine (the conversation runtime) installs automatically as a normal dependency — you don't install it separately. Its own postinstall lays down ~11 per-platform binaries; a **transient** failure there (a spawn `ENOENT`, or an antivirus file-lock) can roll back the atomic install. If install fails partway or `amicus doctor` reports the OpenCode binary "not found", just **re-run** `npm install -g amicus` (clear the cache first if it persists: `npm cache clean --force`).
|
|
144
|
+
|
|
145
|
+
**OS support**
|
|
146
|
+
|
|
147
|
+
- **Windows 11, macOS, and Linux** are all supported; Amicus is first-class on **Windows** (developed and tested there, no WSL required). See the [Windows](#windows) section for platform specifics.
|
|
148
|
+
|
|
149
|
+
**What a run costs.** A sidecar is a single model call. A full council is typically **~5–8 paid model calls** (e.g. 3 reviewers across 2 fan-out waves + 1 chair). Amicus shows an estimate before each council and enforces a built-in budget gate that refuses ultra-expensive models (o3-pro class) unless you opt in with `--no-cost-gate`. You pay your providers directly for the tokens; Amicus itself is free and open-source.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
125
153
|
## The Council
|
|
126
154
|
|
|
127
155
|
**Why multi-model.** Any single model — including the one running your session — has consistent blind spots. Route the *same* material through models from *different* families and the disagreements surface: missed issues, overstated confidence, claims one model alone would have waved through. The council is the structured version of that idea.
|
|
@@ -442,6 +470,7 @@ Most Claude-adjacent tooling assumes macOS/Linux; Amicus doesn't.
|
|
|
442
470
|
|---------|--------------|-----|
|
|
443
471
|
| "council review this" does nothing | The `second-opinion` skill isn't installed | Check `~/.claude/skills/second-opinion/SKILL.md` exists; re-run `npm install -g amicus` (postinstall installs both skills) |
|
|
444
472
|
| `npm install -g amicus` fails with `EEXIST: … claude-sidecar` | The old upstream `claude-sidecar` package is still installed globally; npm won't overwrite another package's bin shims | `npm uninstall -g claude-sidecar`, then `npm install -g amicus`. Your config and sessions carry over (legacy paths are still read). |
|
|
473
|
+
| Install fails partway, or `amicus doctor` reports the OpenCode binary "not found" | A **transient** error during the OpenCode engine's own postinstall (a spawn `ENOENT`, or an antivirus file-lock while it lays down its 11 per-platform binaries) can roll back the whole atomic install — retrying usually succeeds | Just re-run `npm install -g amicus`. If it still fails, clear the cache first: `npm cache clean --force && npm install -g amicus`. |
|
|
445
474
|
| `401` / auth error | API key missing, or the model prefix doesn't match the key you have | Run `amicus setup`; make sure the prefix (`openrouter/…` vs `google/…` vs `openai/…` vs `anthropic/…`) matches the credentials you configured. |
|
|
446
475
|
| Session not found | No session matches the given ID | Run `amicus list`, or omit `--session-id` to use the most recent. |
|
|
447
476
|
| No conversation history found | Project-path encoding | Check `~/.claude/projects/`; `/` and `_` in the project path are encoded as `-` in the directory name. |
|
package/bin/amicus.js
CHANGED
|
@@ -75,9 +75,10 @@ async function main() {
|
|
|
75
75
|
process.exit(0);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// Handle --help or no command
|
|
78
|
+
// Handle --help or no command. 'amicus <cmd> --help' scopes the usage to that
|
|
79
|
+
// subcommand; bare 'amicus --help' (or no command) prints the full usage.
|
|
79
80
|
if (args.help || args._.length === 0) {
|
|
80
|
-
console.log(getUsage());
|
|
81
|
+
console.log(getUsage(args.help ? command : undefined));
|
|
81
82
|
process.exit(0);
|
|
82
83
|
}
|
|
83
84
|
|
package/electron/main.js
CHANGED
|
@@ -21,6 +21,7 @@ const { createFoldHandler } = require('./fold');
|
|
|
21
21
|
const { registerSetupHandlers } = require('./ipc-setup');
|
|
22
22
|
const { computeWindowPosition } = require('./window-position');
|
|
23
23
|
const { attachLoadFailsafe, buildLoadErrorHTML } = require('./load-failsafe');
|
|
24
|
+
const { buildSessionRoute } = require('./session-route');
|
|
24
25
|
|
|
25
26
|
const ICON_PATH = path.join(__dirname, 'assets', 'icon.png');
|
|
26
27
|
|
|
@@ -51,6 +52,11 @@ const MODE = getCompatEnv('MODE') || 'sidecar';
|
|
|
51
52
|
const TASK_ID = getCompatEnv('TASK_ID') || 'unknown';
|
|
52
53
|
const MODEL = getCompatEnv('MODEL') || 'unknown';
|
|
53
54
|
const CWD = getCompatEnv('CWD') || process.cwd();
|
|
55
|
+
// The directory the OpenCode session was actually scoped to (#45). Set by the
|
|
56
|
+
// interactive launcher as canonicalProjectPath(--cwd) so the Web-UI route is
|
|
57
|
+
// built from the SAME directory createSession used. Falls back to CWD for
|
|
58
|
+
// back-compat with launchers that predate this env var.
|
|
59
|
+
const SESSION_DIRECTORY = getCompatEnv('SESSION_DIRECTORY') || CWD;
|
|
54
60
|
const CLIENT = getCompatEnv('CLIENT') || 'code-local';
|
|
55
61
|
const OPENCODE_PORT = parseInt(getCompatEnv('OPENCODE_PORT') || '4096', 10);
|
|
56
62
|
const OPENCODE_SESSION_ID = getCompatEnv('SESSION_ID');
|
|
@@ -152,10 +158,10 @@ function createAmicusWindow() {
|
|
|
152
158
|
});
|
|
153
159
|
|
|
154
160
|
// Navigate directly to the session URL to bypass the project selection screen.
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
161
|
+
// Build the route from SESSION_DIRECTORY — the directory the session was
|
|
162
|
+
// actually scoped to — NOT a fresh base64url(CWD) guess, so Web-UI follow-up
|
|
163
|
+
// prompts resolve the session even when process cwd != --cwd (#45).
|
|
164
|
+
const contentUrl = buildSessionRoute(OPENCODE_URL, OPENCODE_SESSION_ID, SESSION_DIRECTORY);
|
|
159
165
|
|
|
160
166
|
// The window only becomes visible on the success path below. Without this
|
|
161
167
|
// failsafe, a failed/stalled UI load leaves an invisible window and a
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-UI session route builder (#45).
|
|
3
|
+
*
|
|
4
|
+
* OpenCode's router format is `/<base64url(projectPath)>/session/<sessionId>`.
|
|
5
|
+
* The route MUST be built from the directory the OpenCode session was actually
|
|
6
|
+
* created/scoped to — NOT a fresh base64url(process.cwd()) guess. When the
|
|
7
|
+
* amicus process cwd != --cwd (the normal sidecar-skill launch), a CWD-derived
|
|
8
|
+
* route points at a project route with no matching session, so Web-UI follow-up
|
|
9
|
+
* prompts fail "unable to retrieve session". The caller passes the same
|
|
10
|
+
* directory it used to scope createSession (the server-echoed session.directory,
|
|
11
|
+
* or a consistent canonicalProjectPath(--cwd) fallback).
|
|
12
|
+
*
|
|
13
|
+
* Pure function: no electron, no fs, no process state. Safe to unit-test.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} baseUrl - OpenCode server base URL (e.g. http://localhost:4096)
|
|
16
|
+
* @param {string} [sessionId] - OpenCode session id; falsy → return baseUrl only
|
|
17
|
+
* @param {string} sessionDirectory - The directory the session is scoped to
|
|
18
|
+
* @returns {string} Fully-qualified route URL, or baseUrl when no session id.
|
|
19
|
+
*/
|
|
20
|
+
function buildSessionRoute(baseUrl, sessionId, sessionDirectory) {
|
|
21
|
+
if (!sessionId) {
|
|
22
|
+
return baseUrl;
|
|
23
|
+
}
|
|
24
|
+
const seg = Buffer.from(sessionDirectory).toString('base64url');
|
|
25
|
+
return `${baseUrl}/${seg}/session/${sessionId}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { buildSessionRoute };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -63,9 +63,10 @@
|
|
|
63
63
|
"generate-docs": "node scripts/generate-docs.js",
|
|
64
64
|
"generate-docs:check": "node scripts/generate-docs.js --check",
|
|
65
65
|
"validate-docs": "node scripts/validate-docs.js --full",
|
|
66
|
-
"
|
|
66
|
+
"setup-hooks": "node scripts/setup-hooks.js",
|
|
67
67
|
"check:secrets": "node scripts/check-secrets.js --all",
|
|
68
|
-
"check:sizes": "node scripts/check-file-sizes.js --all"
|
|
68
|
+
"check:sizes": "node scripts/check-file-sizes.js --all",
|
|
69
|
+
"check:tarball": "node scripts/check-tarball-lifecycle.js"
|
|
69
70
|
},
|
|
70
71
|
"dependencies": {
|
|
71
72
|
"@modelcontextprotocol/sdk": "^1.27.0",
|
package/scripts/postinstall.js
CHANGED
|
@@ -13,6 +13,8 @@ const path = require('path');
|
|
|
13
13
|
const os = require('os');
|
|
14
14
|
const { execFileSync } = require('child_process');
|
|
15
15
|
|
|
16
|
+
const SETUP_HOOKS_SCRIPT = path.join(__dirname, 'setup-hooks.js');
|
|
17
|
+
|
|
16
18
|
const SKILL_SOURCE = path.join(__dirname, '..', 'skills', 'sidecar', 'SKILL.md');
|
|
17
19
|
const COUNCIL_SOURCE_DIR = path.join(__dirname, '..', 'skills', 'second-opinion');
|
|
18
20
|
|
|
@@ -172,6 +174,79 @@ function registerClaudeDesktop() {
|
|
|
172
174
|
addMcpToConfigFile(configPath, 'sidecar', MCP_CONFIG);
|
|
173
175
|
}
|
|
174
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Resolve the Electron binary path the same way src/sidecar/interactive.js
|
|
179
|
+
* getElectronPath() does: require('electron') returns the absolute path to the
|
|
180
|
+
* binary (or throws if the optionalDependency never installed/extracted).
|
|
181
|
+
* @returns {string|null} Path to the Electron binary, or null if unresolvable.
|
|
182
|
+
*/
|
|
183
|
+
function resolveElectron() {
|
|
184
|
+
try {
|
|
185
|
+
return require('electron');
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* NON-FATAL verification that the OPTIONAL electron binary actually extracted.
|
|
193
|
+
* Electron is an optionalDependency: npm exits 0 even if its download/extract
|
|
194
|
+
* fails (or AV quarantines electron.exe), so without this the user only finds
|
|
195
|
+
* out the GUI is broken much later. Warn clearly that headless runs + the
|
|
196
|
+
* council still work, and point at `amicus doctor` / reinstall to get the GUI.
|
|
197
|
+
*
|
|
198
|
+
* This MUST never throw out of postinstall — the whole body is guarded so a
|
|
199
|
+
* resolver failure or a missing fs can never turn into a non-zero exit.
|
|
200
|
+
*
|
|
201
|
+
* @param {object} deps - { resolveElectron } override for testing.
|
|
202
|
+
*/
|
|
203
|
+
function verifyElectron(deps = {}) {
|
|
204
|
+
try {
|
|
205
|
+
const _resolve = deps.resolveElectron || resolveElectron;
|
|
206
|
+
const binPath = _resolve();
|
|
207
|
+
if (binPath && fs.existsSync(binPath)) { return; }
|
|
208
|
+
console.warn('[amicus] Warning: the Electron binary did not install — the interactive GUI / setup-wizard is unavailable.');
|
|
209
|
+
console.warn('[amicus] Headless runs and the council still work. Run `amicus doctor` to check, or `npm install -g amicus` to reinstall and add the GUI.');
|
|
210
|
+
} catch {
|
|
211
|
+
// Never let the electron check throw out of postinstall.
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Configure git hooks for DEVELOPERS, folded into postinstall (#35).
|
|
217
|
+
*
|
|
218
|
+
* Previously this ran via npm's "prepare" lifecycle. But "prepare" also fires
|
|
219
|
+
* on the github: install path (`npm install -g github:BourbonDog/amicus`),
|
|
220
|
+
* where npm clones the repo, runs prepare, does a NESTED devDependency install,
|
|
221
|
+
* and re-packs — a fragile pipeline the registry install SKIPS entirely. That
|
|
222
|
+
* divergence (plus a reusable corrupt cached artifact) is what made github:
|
|
223
|
+
* installs roll back, especially on Windows. Removing the consumer-facing
|
|
224
|
+
* prepare lifecycle makes the github: path behave identically to the registry
|
|
225
|
+
* path.
|
|
226
|
+
*
|
|
227
|
+
* Hook setup still needs to happen for devs, so we run setup-hooks.js here.
|
|
228
|
+
* It is a no-op for consumers: setup-hooks.js exits 0 outside a git checkout
|
|
229
|
+
* (the published tarball / github: export has no .git), so this changes
|
|
230
|
+
* nothing for end users while keeping `npm install` wiring hooks for devs.
|
|
231
|
+
*
|
|
232
|
+
* Best-effort and self-contained: never throws (a hook-setup failure must
|
|
233
|
+
* never roll back the install), so it is safe to call before the optional
|
|
234
|
+
* skill/MCP setup.
|
|
235
|
+
*
|
|
236
|
+
* @param {object} deps - { env } override for testing.
|
|
237
|
+
*/
|
|
238
|
+
function setupHooks(deps = {}) {
|
|
239
|
+
try {
|
|
240
|
+
execFileSync('node', [SETUP_HOOKS_SCRIPT], {
|
|
241
|
+
stdio: 'inherit',
|
|
242
|
+
env: deps.env || process.env,
|
|
243
|
+
});
|
|
244
|
+
} catch (err) {
|
|
245
|
+
// Hook setup is for devs only and must never fail the install.
|
|
246
|
+
console.warn(`[amicus] Warning: could not configure git hooks: ${err && err.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
175
250
|
function main(deps = {}) {
|
|
176
251
|
if (process.env.AMICUS_SKIP_POSTINSTALL === '1') {
|
|
177
252
|
console.log('[amicus] AMICUS_SKIP_POSTINSTALL set — skipping global setup (plugin channel handles registration).');
|
|
@@ -181,22 +256,47 @@ function main(deps = {}) {
|
|
|
181
256
|
const _installCouncilSkill = deps.installCouncilSkill || installCouncilSkill;
|
|
182
257
|
const _registerClaudeCode = deps.registerClaudeCode || registerClaudeCode;
|
|
183
258
|
const _registerClaudeDesktop = deps.registerClaudeDesktop || registerClaudeDesktop;
|
|
259
|
+
const _setupHooks = deps.setupHooks || setupHooks;
|
|
184
260
|
|
|
185
261
|
console.log('[amicus] Installing...');
|
|
262
|
+
// Dev-only: configure git hooks (no-op for consumers). Folded in from the
|
|
263
|
+
// removed "prepare" lifecycle (#35) so github: installs run identically.
|
|
264
|
+
_setupHooks();
|
|
186
265
|
_installSkill();
|
|
187
266
|
_installCouncilSkill();
|
|
188
267
|
_registerClaudeCode();
|
|
189
268
|
_registerClaudeDesktop();
|
|
190
269
|
|
|
270
|
+
// Non-fatal: warn (only) if the optional Electron binary failed to extract.
|
|
271
|
+
verifyElectron(deps);
|
|
272
|
+
|
|
191
273
|
console.log('');
|
|
192
274
|
console.log('[amicus] Setup:');
|
|
193
275
|
console.log(' - Configure API: Run `amicus setup` or set API keys directly');
|
|
194
276
|
console.log(' - API keys: OPENROUTER_API_KEY, GOOGLE_GENERATIVE_AI_API_KEY, OPENAI_API_KEY, etc.');
|
|
195
277
|
}
|
|
196
278
|
|
|
197
|
-
|
|
279
|
+
/**
|
|
280
|
+
* Top-level entry point. Wraps main() so a failure is never fatal: npm treats
|
|
281
|
+
* a non-zero postinstall as a reason to roll back / uninstall the ENTIRE global
|
|
282
|
+
* package, but skill-copy + MCP registration are optional — amicus itself still
|
|
283
|
+
* works without them. Warn clearly and exit 0 (mirrors scripts/setup-hooks.js).
|
|
284
|
+
*/
|
|
285
|
+
function runCli(deps = {}) {
|
|
286
|
+
try {
|
|
287
|
+
main(deps);
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.warn(`[amicus] Warning: optional post-install setup failed: ${err && err.message}`);
|
|
290
|
+
console.warn('[amicus] Skill install + MCP registration are optional — amicus itself still works.');
|
|
291
|
+
console.warn('[amicus] Run `amicus doctor` to check setup, or re-register manually later.');
|
|
292
|
+
}
|
|
293
|
+
// Always exit 0 so a failure here never rolls back the global install.
|
|
294
|
+
process.exit(0);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Only run when executed directly (not when required for testing)
|
|
198
298
|
if (require.main === module) {
|
|
199
|
-
|
|
299
|
+
runCli();
|
|
200
300
|
}
|
|
201
301
|
|
|
202
|
-
module.exports = { main, addMcpToConfigFile, installSkill, installCouncilSkill, COUNCIL_FILES };
|
|
302
|
+
module.exports = { main, runCli, addMcpToConfigFile, installSkill, installCouncilSkill, setupHooks, verifyElectron, resolveElectron, COUNCIL_FILES };
|
package/scripts/setup-hooks.js
CHANGED
|
@@ -12,10 +12,14 @@
|
|
|
12
12
|
* core.hooksPath at it makes hooks fire in the main clone and in every
|
|
13
13
|
* worktree with no per-worktree setup.
|
|
14
14
|
*
|
|
15
|
-
* Runs automatically via
|
|
16
|
-
*
|
|
15
|
+
* Runs automatically via the postinstall flow (scripts/postinstall.js) so a
|
|
16
|
+
* fresh dev `npm install` still wires hooks. It is NOT a "prepare" script:
|
|
17
|
+
* prepare also fires on the github: install path, where it triggers a nested
|
|
18
|
+
* devDependency install + re-pack that the registry path skips (#35) — the
|
|
19
|
+
* source of the Windows github: rollback. If you install with --ignore-scripts
|
|
20
|
+
* (recommended for this repo), run it once by hand:
|
|
17
21
|
*
|
|
18
|
-
* node scripts/setup-hooks.js
|
|
22
|
+
* npm run setup-hooks # or: node scripts/setup-hooks.js
|
|
19
23
|
*
|
|
20
24
|
* Safe to run anywhere: exits 0 outside a git checkout (npm tarball
|
|
21
25
|
* installs, exported archives) and never fails the install.
|
|
@@ -105,7 +105,7 @@ function runDoctorChecks(depsOverride = {}) {
|
|
|
105
105
|
checks.push(guard('opencode-bin', 'OpenCode binary', () => (
|
|
106
106
|
d.hasOpencodeBinary()
|
|
107
107
|
? { id: 'opencode-bin', name: 'OpenCode binary', status: 'ok', message: 'found', hint: null }
|
|
108
|
-
: { id: 'opencode-bin', name: 'OpenCode binary', status: 'error', message: 'not found', hint: 'npm install -g amicus' }
|
|
108
|
+
: { id: 'opencode-bin', name: 'OpenCode binary', status: 'error', message: 'not found', hint: 'npm install -g amicus (a transient install error can roll back the engine binaries — re-run, or: npm cache clean --force && npm install -g amicus)' }
|
|
109
109
|
)));
|
|
110
110
|
|
|
111
111
|
checks.push(guard('electron', 'Electron (interactive GUI)', () => (
|
package/src/cli.js
CHANGED
|
@@ -292,10 +292,15 @@ function isValidDurationFormat(duration) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
/**
|
|
295
|
-
*
|
|
295
|
+
* Usage text, split into composable parts so 'amicus <cmd> --help' can print
|
|
296
|
+
* only the relevant block while bare 'amicus --help' still composes the full
|
|
297
|
+
* text byte-identically.
|
|
298
|
+
*
|
|
299
|
+
* Each value is a section of the original monolithic template; concatenating
|
|
300
|
+
* USAGE_HEADER + every USAGE_COMMAND_BLOCKS value (in insertion order) +
|
|
301
|
+
* USAGE_TRAILER reproduces the original string exactly.
|
|
296
302
|
*/
|
|
297
|
-
|
|
298
|
-
return `
|
|
303
|
+
const USAGE_HEADER = `
|
|
299
304
|
Usage: amicus <command> [options]
|
|
300
305
|
|
|
301
306
|
Commands:
|
|
@@ -320,7 +325,12 @@ Commands:
|
|
|
320
325
|
(no args) List all configured providers
|
|
321
326
|
update Update to latest version
|
|
322
327
|
mcp Start MCP server (stdio transport)
|
|
328
|
+
`;
|
|
323
329
|
|
|
330
|
+
// Per-command option blocks, keyed by the invoked subcommand. Insertion order
|
|
331
|
+
// must match the original template so the composed full usage is unchanged.
|
|
332
|
+
const USAGE_COMMAND_BLOCKS = {
|
|
333
|
+
start: `
|
|
324
334
|
Options for 'start':
|
|
325
335
|
--model <model> Optional (uses config default). Model to use:
|
|
326
336
|
- Short aliases: gemini, opus, gpt (see 'amicus setup')
|
|
@@ -354,7 +364,8 @@ Options for 'start':
|
|
|
354
364
|
--validate-model (Deprecated: validation is on by default)
|
|
355
365
|
--no-validate-model Skip model-catalog validation before launch
|
|
356
366
|
--position <pos> Window position: right (default), left, center
|
|
357
|
-
|
|
367
|
+
`,
|
|
368
|
+
fanout: `
|
|
358
369
|
Options for 'fanout':
|
|
359
370
|
--models <a,b,c> Required. Comma-separated aliases or provider/model IDs
|
|
360
371
|
--council <name> Run a saved council instead of --models (e.g. free). Mutually exclusive with --models
|
|
@@ -369,27 +380,84 @@ Options for 'fanout':
|
|
|
369
380
|
Shared per-leg knobs: --agent, --thinking, --timeout, --summary-length,
|
|
370
381
|
--no-context, --context-*, --mcp*, --no-validate-model, --cwd
|
|
371
382
|
Exit codes: 0 all legs complete, 2 partial, 1 none complete / hard failure
|
|
372
|
-
|
|
383
|
+
`,
|
|
384
|
+
models: `
|
|
373
385
|
Options for 'models':
|
|
374
386
|
--search <q> Filter by substring over model id and name
|
|
375
387
|
--refresh Force-refresh the catalog from provider APIs
|
|
376
388
|
--check Audit aliases against the catalog (exit = stale count)
|
|
377
389
|
--json Machine-readable output
|
|
378
|
-
|
|
390
|
+
`,
|
|
391
|
+
list: `
|
|
379
392
|
Options for 'list':
|
|
380
393
|
--status <filter> Filter by status (running, complete)
|
|
381
394
|
--all Show all projects
|
|
382
395
|
--json Output as JSON
|
|
383
|
-
|
|
396
|
+
`,
|
|
397
|
+
abort: `
|
|
384
398
|
Options for 'abort':
|
|
385
399
|
--all Abort all running sessions in this project
|
|
386
|
-
|
|
400
|
+
`,
|
|
401
|
+
read: `
|
|
387
402
|
Options for 'read':
|
|
388
403
|
--summary Show summary (default)
|
|
389
404
|
--conversation Show full conversation
|
|
390
405
|
--metadata Show session metadata
|
|
391
406
|
--json Emit the run/wave result as stable JSON
|
|
407
|
+
`,
|
|
408
|
+
continue: `
|
|
409
|
+
Options for 'continue':
|
|
410
|
+
<task_id> Required. Session to build on (positional)
|
|
411
|
+
--prompt <text> Required. Briefing for the new session
|
|
412
|
+
--model <model> Optional. Override the model (alias or provider/model)
|
|
413
|
+
--cwd <path> Project directory (default: cwd)
|
|
414
|
+
--no-ui Run without GUI (autonomous mode)
|
|
415
|
+
--timeout <minutes> Headless timeout (default: 15)
|
|
416
|
+
--context-turns <N> Max conversation turns (default: 50)
|
|
417
|
+
--context-max-tokens <N> Max context tokens (default: 80000)
|
|
418
|
+
`,
|
|
419
|
+
resume: `
|
|
420
|
+
Options for 'resume':
|
|
421
|
+
<task_id> Required. Session to reopen (positional)
|
|
422
|
+
--cwd <path> Project directory (default: cwd)
|
|
423
|
+
--no-ui Run without GUI (autonomous mode)
|
|
424
|
+
--timeout <minutes> Headless timeout (default: 15)
|
|
425
|
+
`,
|
|
426
|
+
council: `
|
|
427
|
+
Subcommands for 'council':
|
|
428
|
+
tally <input.json> Tally findings → tiers/street-cred (appends to ledger)
|
|
429
|
+
--no-ledger Compute the record without appending to the ledger
|
|
430
|
+
--json Machine-readable output
|
|
431
|
+
stats Reviewer-reliability aggregates from the ledger
|
|
432
|
+
--json Machine-readable output
|
|
433
|
+
report <verdict.json> Disagreement + verdict report
|
|
434
|
+
--wave <wave.json> Include per-leg run stats from a wave file
|
|
435
|
+
--md Emit Markdown (default)
|
|
436
|
+
--html Emit a self-contained HTML page
|
|
437
|
+
`,
|
|
438
|
+
doctor: `
|
|
439
|
+
Options for 'doctor':
|
|
440
|
+
--json Machine-readable output
|
|
441
|
+
`,
|
|
442
|
+
setup: `
|
|
443
|
+
Options for 'setup':
|
|
444
|
+
(no args) Run the interactive setup wizard
|
|
445
|
+
--api-keys Open the API key setup window
|
|
446
|
+
--add-alias <name=model> Add a model alias without the full wizard
|
|
447
|
+
`,
|
|
448
|
+
key: `
|
|
449
|
+
Usage for 'key':
|
|
450
|
+
key <provider> <apikey> Validate and save a key
|
|
451
|
+
key <provider> --remove Remove a saved key
|
|
452
|
+
key List all configured providers
|
|
453
|
+
`,
|
|
454
|
+
mcp: `
|
|
455
|
+
Usage for 'mcp':
|
|
456
|
+
mcp Start the MCP server (stdio transport)
|
|
457
|
+
`
|
|
458
|
+
};
|
|
392
459
|
|
|
460
|
+
const USAGE_TRAILER = `
|
|
393
461
|
OpenCode Agent Types:
|
|
394
462
|
Chat Reads auto, writes/bash ask permission (interactive default)
|
|
395
463
|
Build Full tool access (headless default)
|
|
@@ -409,6 +477,25 @@ Examples:
|
|
|
409
477
|
amicus resume abc123
|
|
410
478
|
amicus read abc123 --conversation
|
|
411
479
|
`;
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get usage text.
|
|
483
|
+
*
|
|
484
|
+
* @param {string} [command] When provided AND it has a dedicated options block,
|
|
485
|
+
* returns the top-level header plus only that command's block (scoped --help).
|
|
486
|
+
* When omitted, or when the command has no dedicated block, returns the full
|
|
487
|
+
* composed usage (byte-identical to the original monolithic string).
|
|
488
|
+
*/
|
|
489
|
+
function getUsage(command) {
|
|
490
|
+
// Each part carries its own leading/trailing newlines exactly as they sat in
|
|
491
|
+
// the original single template literal, so plain concatenation reproduces the
|
|
492
|
+
// blank-line separators (the header's trailing "\n" + a block's leading "\n"
|
|
493
|
+
// form the blank line between them). Scoped help keeps the header + trailer
|
|
494
|
+
// and substitutes just the one command's block in place of all blocks.
|
|
495
|
+
const block = command && Object.prototype.hasOwnProperty.call(USAGE_COMMAND_BLOCKS, command)
|
|
496
|
+
? USAGE_COMMAND_BLOCKS[command]
|
|
497
|
+
: Object.values(USAGE_COMMAND_BLOCKS).join('');
|
|
498
|
+
return USAGE_HEADER + block + USAGE_TRAILER;
|
|
412
499
|
}
|
|
413
500
|
|
|
414
501
|
module.exports = {
|