amicus 4.5.2 → 4.5.3
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 +28 -0
- package/README.md +38 -5
- package/bin/amicus.js +23 -0
- package/docs/usage.md +1 -1
- package/package.json +1 -1
- package/src/cli.js +21 -5
- package/src/utils/known-flags.js +90 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.3",
|
|
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": {
|
|
6
6
|
"name": "Christian Wagner"
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
All notable changes to Amicus are documented here. Format follows
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
|
|
5
5
|
|
|
6
|
+
## [4.5.3] - 2026-08-01
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **Unknown CLI flags are rejected instead of silently absorbed.** `parseArgs` treated any
|
|
11
|
+
`--token` as a flag: an unrecognized one landed on the parsed args object, no handler read it,
|
|
12
|
+
and the command ran as though it were never typed. Found while smoke-testing v4.5.2 —
|
|
13
|
+
`amicus start -m deepseek --prompt "…" --headless` printed no error and exited 0, but `start`
|
|
14
|
+
has no `--headless`; the run silently took the **interactive** path, ignored `-m`, and left a
|
|
15
|
+
session running against the default model. A typo (`--modl`), a flag borrowed from another
|
|
16
|
+
command, or an invented one all behaved the same way, and an unknown flag followed by a
|
|
17
|
+
positional would swallow it as its value. Unknown flags now get the same treatment amicus
|
|
18
|
+
already gave an unknown *command*: name it, suggest the nearest real flag, point at `--help`,
|
|
19
|
+
exit 1. The known-flag set is **derived from the usage text** (the same source `getCommandNames()`
|
|
20
|
+
uses, for the same anti-rot reason) plus the boolean-flag list, plus a small explicitly-documented
|
|
21
|
+
allowlist of internal MCP→CLI passthroughs (`--task-id`, `--run-id`, `--council-name`,
|
|
22
|
+
`--cowork-process`) and undocumented-but-working flags (`--briefing`, `--mode`, `--quiet`).
|
|
23
|
+
A regression test asserts every `args.<flag>` any CLI handler reads is in the known set, so the
|
|
24
|
+
check can never silently start rejecting a flag that works.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **README Quick start is now four numbered steps**, with **Configure** promoted from a bold line
|
|
29
|
+
buried between two callouts to its own step and TOC entry. Installing without configuring is the
|
|
30
|
+
step people skip, and every council fails at the first model call when they do — so it now says
|
|
31
|
+
so plainly, notes that one OpenRouter key is enough to start, and ends with `amicus doctor` as
|
|
32
|
+
the confirmation that setup actually took.
|
|
33
|
+
|
|
6
34
|
## [4.5.2] - 2026-07-31
|
|
7
35
|
|
|
8
36
|
### Fixed
|
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ Hand Claude a plan, a design, a diff, an architecture decision, a manuscript —
|
|
|
29
29
|
- [The Council](#the-council)
|
|
30
30
|
- [Ways to run the council](#ways-to-run-the-council)
|
|
31
31
|
- [Quick start](#quick-start)
|
|
32
|
+
- [1. Install](#1-install)
|
|
33
|
+
- [**2. Configure — don't skip this**](#2-configure--dont-skip-this)
|
|
34
|
+
- [3. Your first council](#3-your-first-council)
|
|
35
|
+
- [4. Your first sidecar](#4-your-first-sidecar)
|
|
32
36
|
- [Requirements & Dependencies](#requirements--dependencies)
|
|
33
37
|
- [The parallel window](#the-parallel-window)
|
|
34
38
|
- [Commands](#commands)
|
|
@@ -170,7 +174,9 @@ Any flag you also type on that second line overrides just that value — a pack
|
|
|
170
174
|
>
|
|
171
175
|
> **Convention used throughout this README:** plugin-channel users: prefix CLI examples with `npx -y amicus@latest` (skip the bare `amicus`/`am`). Individual code blocks are not duplicated per channel — this note is the one translation you need.
|
|
172
176
|
|
|
173
|
-
|
|
177
|
+
### 1. Install
|
|
178
|
+
|
|
179
|
+
Pick whichever fits. Every path delivers the MCP server and both skills; the `amicus`/`am` CLI lands on your PATH with the **npm and install-script paths** (the plugin path runs the CLI on demand via `npx -y amicus@latest <command>`):
|
|
174
180
|
|
|
175
181
|
**As a Claude Code plugin** — the most native path if you use Claude Code:
|
|
176
182
|
|
|
@@ -207,7 +213,15 @@ For the **npm** and **install-script** paths, a postinstall auto-configures ever
|
|
|
207
213
|
|
|
208
214
|
> **Skipped the postinstall?** `--ignore-scripts` npm installs never run it, and the plugin channel skips it by design (Claude Code registers the plugin's MCP server and skills itself). Either way, run `amicus init` (plugin channel: `npx -y amicus@latest init`) any time to (re)register on demand — e.g. to also wire up Claude Desktop, which the plugin path doesn't touch. See [`amicus init`](./docs/usage.md#amicus-init).
|
|
209
215
|
|
|
210
|
-
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### 2. Configure — don't skip this
|
|
219
|
+
|
|
220
|
+
> ### ⚠️ Installing is not enough. Run this or nothing will work.
|
|
221
|
+
>
|
|
222
|
+
> Amicus has **no API keys of its own** — it drives *your* accounts at OpenRouter, Google,
|
|
223
|
+
> OpenAI, Anthropic, or DeepSeek. Until you add at least one key, every council and every
|
|
224
|
+
> sidecar fails at the first model call. **This is the step people skip.**
|
|
211
225
|
|
|
212
226
|
```bash
|
|
213
227
|
amicus setup
|
|
@@ -215,6 +229,10 @@ amicus setup
|
|
|
215
229
|
npx -y amicus@latest setup
|
|
216
230
|
```
|
|
217
231
|
|
|
232
|
+
**One key is enough to start.** [OpenRouter](https://openrouter.ai/keys) is the usual choice —
|
|
233
|
+
a single key reaches every model in the catalog, which is what makes a mixed-vendor council
|
|
234
|
+
work without four separate accounts.
|
|
235
|
+
|
|
218
236
|
This opens a graphical wizard:
|
|
219
237
|
|
|
220
238
|
| Step | What it does |
|
|
@@ -226,7 +244,20 @@ This opens a graphical wizard:
|
|
|
226
244
|
|
|
227
245
|
> **Headless environments:** if Electron can't open a window, the wizard falls back to a readline-based setup in the terminal.
|
|
228
246
|
|
|
229
|
-
**
|
|
247
|
+
**Confirm it took** — this is the fastest way to know you're actually ready:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
amicus doctor
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Green across the board (`0 error(s)`) means keys, catalog, engine, skills, and MCP registration
|
|
254
|
+
are all in place. Anything red, `amicus doctor` tells you exactly what to run next.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
### 3. Your first council
|
|
259
|
+
|
|
260
|
+
No flags to learn. In Claude Code or Cowork, give Claude a document and say:
|
|
230
261
|
|
|
231
262
|
> *council review this*
|
|
232
263
|
|
|
@@ -234,7 +265,9 @@ Plugin-channel users can also type **`/amicus:council`** directly instead of phr
|
|
|
234
265
|
|
|
235
266
|
Claude prepares the material, recommends a bench of models, discloses the run shape and cost, and orchestrates the rest. You make the accept/deny calls at the end. (The `second-opinion` skill is what teaches Claude to recognize this — if nothing happens, run `amicus doctor` (or `npx -y amicus@latest doctor`). npm/install-script installs place the skill at `~/.claude/skills/second-opinion/`; plugin installs keep it inside the plugin itself — check `/plugin` in Claude Code to confirm amicus is enabled.)
|
|
236
267
|
|
|
237
|
-
|
|
268
|
+
### 4. Your first sidecar
|
|
269
|
+
|
|
270
|
+
The sidecar is the lower-level path — you can invoke it by phrase through Claude too, but the CLI gives you the flags directly:
|
|
238
271
|
|
|
239
272
|
```bash
|
|
240
273
|
amicus start --model gemini --prompt "Fact-check the auth approach Claude just proposed"
|
|
@@ -380,7 +413,7 @@ $ amicus status demo123 --json
|
|
|
380
413
|
"taskId": "demo123",
|
|
381
414
|
"status": "complete",
|
|
382
415
|
"elapsed": "5m 0s",
|
|
383
|
-
"version": "4.5.
|
|
416
|
+
"version": "4.5.3",
|
|
384
417
|
"model": "google/gemini-2.5-flash",
|
|
385
418
|
"phase": "terminal"
|
|
386
419
|
}
|
package/bin/amicus.js
CHANGED
|
@@ -22,6 +22,7 @@ const { handleStart, handleFanout, handleRead } = require('../src/cli-handlers-r
|
|
|
22
22
|
const { handleResume, handleContinue } = require('../src/cli-handlers-resume-continue');
|
|
23
23
|
const { isOneShotCommand, armExitWatchdog } = require('../src/utils/lifecycle');
|
|
24
24
|
const { suggestCommand } = require('../src/utils/input-validators');
|
|
25
|
+
const { unknownFlags, getKnownFlags } = require('../src/utils/known-flags');
|
|
25
26
|
const { logger } = require('../src/utils/logger');
|
|
26
27
|
|
|
27
28
|
const VERSION = require('../package.json').version;
|
|
@@ -30,6 +31,28 @@ async function main() {
|
|
|
30
31
|
const args = parseArgs(process.argv.slice(2));
|
|
31
32
|
const command = args._[0];
|
|
32
33
|
|
|
34
|
+
// Reject unknown flags BEFORE anything can act on a half-understood command
|
|
35
|
+
// line. parseArgs accepts any `--token`, so an unrecognized one used to land
|
|
36
|
+
// on `args`, go unread, and let the command run as though it were never typed
|
|
37
|
+
// — `start … --headless` silently took the interactive path, ignored `--model`
|
|
38
|
+
// and left a session running. Same treatment as an unknown command: name it,
|
|
39
|
+
// suggest the nearest real flag, point at help, exit 1. See
|
|
40
|
+
// src/utils/known-flags.js for what counts as known and why.
|
|
41
|
+
const badFlags = unknownFlags(args);
|
|
42
|
+
if (badFlags.length > 0) {
|
|
43
|
+
for (const flag of badFlags) {
|
|
44
|
+
console.error(`Unknown option: --${flag}`);
|
|
45
|
+
const candidates = suggestCommand(flag, [...getKnownFlags()]);
|
|
46
|
+
if (candidates.length > 0) {
|
|
47
|
+
console.error(`Did you mean: ${candidates.map(c => `--${c}`).join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
console.error(command
|
|
51
|
+
? `Run \`amicus ${command} --help\` to see valid options.`
|
|
52
|
+
: 'Run `amicus --help` to see valid options.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
// Install crash handler for MCP-spawned processes (have --task-id)
|
|
34
57
|
if (args['task-id'] && (command === 'start' || command === 'continue')) {
|
|
35
58
|
const { installCrashHandler } = require('../src/sidecar/crash-handler');
|
package/docs/usage.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amicus",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.3",
|
|
4
4
|
"mcpName": "io.github.BourbonDog/amicus",
|
|
5
5
|
"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.",
|
|
6
6
|
"keywords": [
|
package/src/cli.js
CHANGED
|
@@ -122,10 +122,12 @@ function parseArgs(argv) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
|
-
*
|
|
125
|
+
* Flags that take no value. Module-level (not rebuilt per call) so
|
|
126
|
+
* `getBooleanFlags()` can hand the same list to src/utils/known-flags.js —
|
|
127
|
+
* which needs it to tell a real boolean flag from an unknown token, and must
|
|
128
|
+
* not keep a second copy that can rot out of sync with this one.
|
|
126
129
|
*/
|
|
127
|
-
|
|
128
|
-
const booleanFlags = [
|
|
130
|
+
const BOOLEAN_FLAGS = [
|
|
129
131
|
'no-ui',
|
|
130
132
|
'no-mcp',
|
|
131
133
|
'no-context',
|
|
@@ -156,8 +158,21 @@ function isBooleanFlag(key) {
|
|
|
156
158
|
'ui', // watch: open the Council Workspace window; v4.4 seam (v4.3 Task 11)
|
|
157
159
|
'follow', // fanout / council run: stream this run's own events to stderr (v4.3 Task 13)
|
|
158
160
|
'fallback', // fanout / council run: opt-in cheaper-model substitution (v4.3 Task 18, spec 6.2); --no-fallback negates via the generic no-* catch-all below
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if a flag is boolean (doesn't take a value)
|
|
165
|
+
*/
|
|
166
|
+
function isBooleanFlag(key) {
|
|
167
|
+
return BOOLEAN_FLAGS.includes(key);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The boolean-flag names, for the unknown-flag check.
|
|
172
|
+
* @returns {string[]} copy — callers must not mutate the source list
|
|
173
|
+
*/
|
|
174
|
+
function getBooleanFlags() {
|
|
175
|
+
return [...BOOLEAN_FLAGS];
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
/**
|
|
@@ -765,5 +780,6 @@ module.exports = {
|
|
|
765
780
|
validateStartArgs,
|
|
766
781
|
getUsage,
|
|
767
782
|
getCommandNames,
|
|
783
|
+
getBooleanFlags,
|
|
768
784
|
DEFAULTS
|
|
769
785
|
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// src/utils/known-flags.js
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module utils/known-flags
|
|
6
|
+
* The set of `--flags` amicus accepts, and the check that rejects the rest.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS EXISTS. `parseArgs` (src/cli.js) treats ANY `--token` as a flag: an
|
|
9
|
+
* unrecognized one lands on the parsed object, no handler reads it, and the
|
|
10
|
+
* command proceeds as if it were never typed. Found in the field while smoke-
|
|
11
|
+
* testing v4.5.2 — `amicus start -m deepseek --prompt "…" --headless` printed no
|
|
12
|
+
* error and exited 0, but `start` has no `--headless`. The run silently took the
|
|
13
|
+
* INTERACTIVE path, ignored `-m`, and left a session running against the default
|
|
14
|
+
* model. A typo (`--modl`), a flag borrowed from another command, or an invented
|
|
15
|
+
* one all behave the same way, and an unknown flag followed by a positional
|
|
16
|
+
* SWALLOWS it as its value.
|
|
17
|
+
*
|
|
18
|
+
* amicus already handles an unknown COMMAND correctly — error, "Did you mean",
|
|
19
|
+
* usage, exit 1. This gives flags the same treatment.
|
|
20
|
+
*
|
|
21
|
+
* DERIVED, NOT HAND-MAINTAINED. The bulk of the set is scraped from the usage
|
|
22
|
+
* text, which is the same source `getCommandNames()` uses and for the same
|
|
23
|
+
* stated reason: a second hand-maintained list would rot out of sync with the
|
|
24
|
+
* first. Only flags that are deliberately absent from usage are listed here, and
|
|
25
|
+
* each says why.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Real flags that appear in NO usage block. Rejecting any of these would break
|
|
30
|
+
* working callers, so they are enumerated deliberately rather than derived.
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ The first four are spawned by the MCP server onto its own CLI children
|
|
33
|
+
* (src/mcp-server.js, src/mcp-council-run.js). They are not user-facing and are
|
|
34
|
+
* intentionally undocumented — but they are on the argv of every MCP-launched
|
|
35
|
+
* run, so rejecting them would break the entire MCP surface.
|
|
36
|
+
*/
|
|
37
|
+
const INTERNAL_FLAGS = new Set([
|
|
38
|
+
'task-id', // MCP → `start`/`continue`: pins the child's task id
|
|
39
|
+
'run-id', // MCP → `council run`: pins the child's run id
|
|
40
|
+
'council-name', // MCP → `council run`: preset name for ledger attribution
|
|
41
|
+
'cowork-process', // MCP → `start`: Cowork process handle for context capture
|
|
42
|
+
|
|
43
|
+
// User-facing but undocumented, and read by real handlers today. Listed so the
|
|
44
|
+
// rejection is a bug fix and not a silent removal of working behaviour; if any
|
|
45
|
+
// of these should be dropped, that is a separate, deliberate change.
|
|
46
|
+
'briefing', // src/cli-handlers-resume-continue.js — alias for --prompt
|
|
47
|
+
'mode', // src/cli-handlers-run.js — legacy alias for --agent
|
|
48
|
+
'quiet', // src/cli-handlers.js — suppresses the interactive prompt
|
|
49
|
+
'help', // handled before dispatch; never reaches a usage block body
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Every flag amicus accepts: usage-derived ∪ boolean flags ∪ INTERNAL_FLAGS.
|
|
54
|
+
*
|
|
55
|
+
* Computed on each call rather than cached at module load, because `getUsage()`
|
|
56
|
+
* is itself composed at call time and a cached copy would pin whatever the first
|
|
57
|
+
* caller happened to see.
|
|
58
|
+
*
|
|
59
|
+
* @returns {Set<string>} kebab-case flag names, without the leading `--`
|
|
60
|
+
*/
|
|
61
|
+
function getKnownFlags() {
|
|
62
|
+
// Required lazily: src/cli.js is the module that will consume this one, and a
|
|
63
|
+
// top-level require here would close a cycle.
|
|
64
|
+
const { getUsage, getBooleanFlags } = require('../cli');
|
|
65
|
+
const known = new Set(INTERNAL_FLAGS);
|
|
66
|
+
for (const m of getUsage().matchAll(/--([a-z][a-z0-9-]*)/gi)) {
|
|
67
|
+
known.add(m[1].toLowerCase());
|
|
68
|
+
}
|
|
69
|
+
for (const f of getBooleanFlags()) { known.add(f); }
|
|
70
|
+
return known;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The unknown flags present on a parsed-argv object, in the order they were
|
|
75
|
+
* typed.
|
|
76
|
+
*
|
|
77
|
+
* Reads `__explicit` — the set parseArgs fills with every key it saw on the
|
|
78
|
+
* command line — so defaults (which are merged in from DEFAULTS and were never
|
|
79
|
+
* typed) are correctly ignored.
|
|
80
|
+
*
|
|
81
|
+
* @param {object} parsed result of parseArgs()
|
|
82
|
+
* @returns {string[]} kebab-case flag names, without the leading `--`
|
|
83
|
+
*/
|
|
84
|
+
function unknownFlags(parsed) {
|
|
85
|
+
if (!parsed || !parsed.__explicit) { return []; }
|
|
86
|
+
const known = getKnownFlags();
|
|
87
|
+
return [...parsed.__explicit].filter(f => !known.has(String(f).toLowerCase()));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
module.exports = { getKnownFlags, unknownFlags, INTERNAL_FLAGS };
|