amicus 4.5.2 → 4.5.4
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 +54 -0
- package/README.md +77 -16
- 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.4",
|
|
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,60 @@
|
|
|
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.4] - 2026-08-01
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- **README: corrected a false claim about Electron and install channels.** The install section
|
|
11
|
+
said "the standalone Electron window is npm-only." That is not true — the Council Workspace
|
|
12
|
+
auto-open gate (`src/sidecar/workspace-auto-open.js`) keys on `client === 'code-local'` plus
|
|
13
|
+
Electron presence, **not** on install channel, so a plugin-channel user in Claude Code local
|
|
14
|
+
does get the window. Removed.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **README now leads with npm as the recommended install**, with a per-channel comparison table.
|
|
19
|
+
The accurate reason npm is preferable for the interactive experience: the plugin's MCP config
|
|
20
|
+
sets `AMICUS_SKIP_POSTINSTALL=1`, and `scripts/postinstall.js` returns early on that — *before*
|
|
21
|
+
`provisionElectron()`. So the plugin channel gets no `amicus` on `PATH` (every window-opening
|
|
22
|
+
command becomes an `npx` call), no Electron provisioning or cache-heal, no reachable
|
|
23
|
+
`amicus doctor --fix` when the GUI breaks, and a fresh npx cache directory on every release.
|
|
24
|
+
The plugin block keeps its genuine strengths — native registration and the slash commands the
|
|
25
|
+
npm paths don't have — alongside an accurate statement of the tradeoff.
|
|
26
|
+
- **README documents the single-MCP-registration behavior** when both channels are installed.
|
|
27
|
+
Config, API keys, and session history are shared, but the MCP server is one registration named
|
|
28
|
+
`amicus` that resolves to whichever install registered most recently — so the copy the CLI runs
|
|
29
|
+
and the copy Claude's MCP tools run can differ. This is the #76 confusion; `amicus doctor`
|
|
30
|
+
reports the MCP launch path and `--fix` repairs that copy in place.
|
|
31
|
+
|
|
32
|
+
## [4.5.3] - 2026-08-01
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **Unknown CLI flags are rejected instead of silently absorbed.** `parseArgs` treated any
|
|
37
|
+
`--token` as a flag: an unrecognized one landed on the parsed args object, no handler read it,
|
|
38
|
+
and the command ran as though it were never typed. Found while smoke-testing v4.5.2 —
|
|
39
|
+
`amicus start -m deepseek --prompt "…" --headless` printed no error and exited 0, but `start`
|
|
40
|
+
has no `--headless`; the run silently took the **interactive** path, ignored `-m`, and left a
|
|
41
|
+
session running against the default model. A typo (`--modl`), a flag borrowed from another
|
|
42
|
+
command, or an invented one all behaved the same way, and an unknown flag followed by a
|
|
43
|
+
positional would swallow it as its value. Unknown flags now get the same treatment amicus
|
|
44
|
+
already gave an unknown *command*: name it, suggest the nearest real flag, point at `--help`,
|
|
45
|
+
exit 1. The known-flag set is **derived from the usage text** (the same source `getCommandNames()`
|
|
46
|
+
uses, for the same anti-rot reason) plus the boolean-flag list, plus a small explicitly-documented
|
|
47
|
+
allowlist of internal MCP→CLI passthroughs (`--task-id`, `--run-id`, `--council-name`,
|
|
48
|
+
`--cowork-process`) and undocumented-but-working flags (`--briefing`, `--mode`, `--quiet`).
|
|
49
|
+
A regression test asserts every `args.<flag>` any CLI handler reads is in the known set, so the
|
|
50
|
+
check can never silently start rejecting a flag that works.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- **README Quick start is now four numbered steps**, with **Configure** promoted from a bold line
|
|
55
|
+
buried between two callouts to its own step and TOC entry. Installing without configuring is the
|
|
56
|
+
step people skip, and every council fails at the first model call when they do — so it now says
|
|
57
|
+
so plainly, notes that one OpenRouter key is enough to start, and ends with `amicus doctor` as
|
|
58
|
+
the confirmation that setup actually took.
|
|
59
|
+
|
|
6
60
|
## [4.5.2] - 2026-07-31
|
|
7
61
|
|
|
8
62
|
### 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)
|
|
@@ -165,24 +169,40 @@ Any flag you also type on that second line overrides just that value — a pack
|
|
|
165
169
|
|
|
166
170
|
> **Two install channels — read this first.** Amicus ships two ways, and CLI commands look different in each:
|
|
167
171
|
>
|
|
168
|
-
> - **npm global** (`npm install -g amicus` or the install script)
|
|
172
|
+
> - **npm global** (`npm install -g amicus` or the install script) — **the recommended path.** Puts `amicus`/`am` on your `PATH`, so every `amicus <command>` example in this README works as written, and provisions the Electron GUI that the parallel window runs in.
|
|
169
173
|
> - **Claude Code plugin** (`/plugin install amicus@bourbondog-amicus`) does **not** put a CLI on your `PATH`. CLI calls go through `npx -y amicus@latest <command>` instead — e.g. `amicus doctor` becomes `npx -y amicus@latest doctor`. In exchange, the plugin channel gets two things npm does **not**: the slash commands `/amicus:council` and `/amicus:sidecar`. **These are plugin-channel-ONLY — npm users don't get them** and drive the same skills by saying "council review this" / talking to Claude instead.
|
|
170
174
|
>
|
|
175
|
+
> See the [comparison table below](#1-install) for the full tradeoff — the short version is that npm is what you want for the interactive window, and the two can be installed side by side.
|
|
176
|
+
>
|
|
171
177
|
> **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
178
|
|
|
173
|
-
|
|
179
|
+
### 1. Install
|
|
174
180
|
|
|
175
|
-
|
|
181
|
+
Every path delivers the MCP server and both skills. They differ in what else you get:
|
|
176
182
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
/
|
|
180
|
-
|
|
183
|
+
| | **npm / install script** | **Claude Code plugin** |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `amicus` / `am` on your `PATH` | ✅ | ❌ — every call is `npx -y amicus@latest <command>` |
|
|
186
|
+
| **Interactive Electron window** (`amicus start`, `watch --ui`) | ✅ provisioned at install | ⚠️ best-effort — see below |
|
|
187
|
+
| Self-heal when the GUI breaks (`amicus doctor --fix`) | ✅ | ❌ no CLI to run it with |
|
|
188
|
+
| MCP server + both skills | ✅ | ✅ |
|
|
189
|
+
| Slash commands `/amicus:council`, `/amicus:sidecar` | ❌ | ✅ |
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
#### With npm — recommended
|
|
194
|
+
|
|
195
|
+
The canonical path, and the one that gets you the full interactive experience (needs [Node.js](https://nodejs.org) ≥ 18):
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
npm install -g amicus
|
|
181
199
|
```
|
|
182
200
|
|
|
183
|
-
|
|
201
|
+
This is the path to pick unless you specifically want the plugin's slash commands. It puts `amicus`/`am` on your `PATH` — which is what the **parallel window** is driven by — and its postinstall provisions the Electron GUI, with `amicus doctor --fix` to repair it in place if anything goes wrong later.
|
|
202
|
+
|
|
203
|
+
#### With the install script
|
|
184
204
|
|
|
185
|
-
|
|
205
|
+
Same result as npm, one command — macOS, Linux, or Windows (needs [Node.js](https://nodejs.org) ≥ 18):
|
|
186
206
|
|
|
187
207
|
```bash
|
|
188
208
|
# macOS / Linux
|
|
@@ -194,12 +214,26 @@ curl -fsSL https://raw.githubusercontent.com/BourbonDog/amicus/main/install.sh |
|
|
|
194
214
|
irm https://raw.githubusercontent.com/BourbonDog/amicus/main/install.ps1 | iex
|
|
195
215
|
```
|
|
196
216
|
|
|
197
|
-
|
|
217
|
+
#### As a Claude Code plugin
|
|
198
218
|
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
The most native *registration* path if you use Claude Code, and the only one with slash commands:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
/plugin marketplace add BourbonDog/amicus
|
|
223
|
+
/plugin install amicus@bourbondog-amicus
|
|
224
|
+
/reload-plugins
|
|
201
225
|
```
|
|
202
226
|
|
|
227
|
+
Claude Code registers the MCP server and both skills for you — nothing to configure. You also get **`/amicus:council`** (run a full council review) and **`/amicus:sidecar`** (fork a conversation to another model), which the npm paths don't have.
|
|
228
|
+
|
|
229
|
+
> **Know the tradeoff before you pick this.** The plugin does not put `amicus` on your `PATH`, so every CLI call goes through `npx -y amicus@latest <command>` — including the ones that open the interactive window. It also skips amicus's postinstall, which is what provisions and self-heals the Electron GUI. The window still works when Electron lands in the npx cache, and the Council Workspace still auto-opens on a council run from Claude Code — but nothing repairs it when Electron *doesn't* land, and each new release re-resolves into a fresh cache directory. **If you want the parallel window as a daily driver, install with npm.**
|
|
230
|
+
>
|
|
231
|
+
> (Also: the first council/sidecar call downloads the OpenCode engine.)
|
|
232
|
+
|
|
233
|
+
**Running both is supported** — and is what you want if you like the slash commands *and* the window. Install with npm for the CLI and the GUI, then add the plugin for `/amicus:council`. Your config, API keys, and session history live outside either install and are shared automatically.
|
|
234
|
+
|
|
235
|
+
> One thing to know if you do: the MCP server is a **single registration named `amicus`**, so it resolves to one install — whichever registered most recently, which is usually the plugin's `npx -y amicus@latest mcp`. That's harmless (both serve the same tools), but it means the copy your CLI runs and the copy Claude's MCP tools run can differ. `amicus doctor` reports the MCP launch path explicitly and `--fix` repairs that copy in place, so if a GUI or engine problem ever shows up in Claude but not in your terminal, that's the first thing to check.
|
|
236
|
+
|
|
203
237
|
For the **npm** and **install-script** paths, a postinstall auto-configures everything — no manual registration:
|
|
204
238
|
|
|
205
239
|
- Registers the **MCP server** in Claude Code and in Claude Desktop / Cowork, so the Amicus tools appear natively.
|
|
@@ -207,7 +241,15 @@ For the **npm** and **install-script** paths, a postinstall auto-configures ever
|
|
|
207
241
|
|
|
208
242
|
> **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
243
|
|
|
210
|
-
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
### 2. Configure — don't skip this
|
|
247
|
+
|
|
248
|
+
> ### ⚠️ Installing is not enough. Run this or nothing will work.
|
|
249
|
+
>
|
|
250
|
+
> Amicus has **no API keys of its own** — it drives *your* accounts at OpenRouter, Google,
|
|
251
|
+
> OpenAI, Anthropic, or DeepSeek. Until you add at least one key, every council and every
|
|
252
|
+
> sidecar fails at the first model call. **This is the step people skip.**
|
|
211
253
|
|
|
212
254
|
```bash
|
|
213
255
|
amicus setup
|
|
@@ -215,6 +257,10 @@ amicus setup
|
|
|
215
257
|
npx -y amicus@latest setup
|
|
216
258
|
```
|
|
217
259
|
|
|
260
|
+
**One key is enough to start.** [OpenRouter](https://openrouter.ai/keys) is the usual choice —
|
|
261
|
+
a single key reaches every model in the catalog, which is what makes a mixed-vendor council
|
|
262
|
+
work without four separate accounts.
|
|
263
|
+
|
|
218
264
|
This opens a graphical wizard:
|
|
219
265
|
|
|
220
266
|
| Step | What it does |
|
|
@@ -226,7 +272,20 @@ This opens a graphical wizard:
|
|
|
226
272
|
|
|
227
273
|
> **Headless environments:** if Electron can't open a window, the wizard falls back to a readline-based setup in the terminal.
|
|
228
274
|
|
|
229
|
-
**
|
|
275
|
+
**Confirm it took** — this is the fastest way to know you're actually ready:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
amicus doctor
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Green across the board (`0 error(s)`) means keys, catalog, engine, skills, and MCP registration
|
|
282
|
+
are all in place. Anything red, `amicus doctor` tells you exactly what to run next.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
### 3. Your first council
|
|
287
|
+
|
|
288
|
+
No flags to learn. In Claude Code or Cowork, give Claude a document and say:
|
|
230
289
|
|
|
231
290
|
> *council review this*
|
|
232
291
|
|
|
@@ -234,7 +293,9 @@ Plugin-channel users can also type **`/amicus:council`** directly instead of phr
|
|
|
234
293
|
|
|
235
294
|
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
295
|
|
|
237
|
-
|
|
296
|
+
### 4. Your first sidecar
|
|
297
|
+
|
|
298
|
+
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
299
|
|
|
239
300
|
```bash
|
|
240
301
|
amicus start --model gemini --prompt "Fact-check the auth approach Claude just proposed"
|
|
@@ -380,7 +441,7 @@ $ amicus status demo123 --json
|
|
|
380
441
|
"taskId": "demo123",
|
|
381
442
|
"status": "complete",
|
|
382
443
|
"elapsed": "5m 0s",
|
|
383
|
-
"version": "4.5.
|
|
444
|
+
"version": "4.5.4",
|
|
384
445
|
"model": "google/gemini-2.5-flash",
|
|
385
446
|
"phase": "terminal"
|
|
386
447
|
}
|
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.4",
|
|
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 };
|