amicus 1.6.1 → 1.7.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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +31 -0
- package/electron/load-failsafe.js +11 -4
- package/electron/main.js +12 -9
- package/electron/opencode-theme.js +130 -0
- package/electron/preload.js +1 -1
- package/package.json +1 -1
- package/scripts/postinstall.js +59 -35
- package/src/cli-handlers-doctor.js +90 -14
- package/src/cli-handlers-run.js +4 -0
- package/src/cli.js +3 -0
- package/src/mcp-server.js +26 -6
- package/src/mcp-tools.js +17 -1
- package/src/sidecar/electron-cache.js +42 -0
- package/src/sidecar/electron-ensure.js +92 -0
- package/src/sidecar/electron-install.js +244 -0
- package/src/sidecar/fanout.js +3 -0
- package/src/sidecar/interactive.js +22 -10
- package/src/sidecar/setup-window.js +12 -7
- package/src/sidecar/setup.js +2 -0
- package/src/utils/project-root-sanity.js +66 -0
- package/src/utils/remediation-hints.js +51 -0
- package/src/utils/result-schema.js +16 -3
- package/src/utils/version-info.js +49 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// src/utils/project-root-sanity.js
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Project-root sanity heuristic (#43).
|
|
6
|
+
*
|
|
7
|
+
* `amicus doctor` resolves the project dir from process.cwd(); when a user
|
|
8
|
+
* launches amicus from a packaged-app/install directory (e.g. the Claude
|
|
9
|
+
* Desktop app dir) instead of their repo, sessions land in the wrong place.
|
|
10
|
+
* This pure helper inspects a path string + its directory markers and decides
|
|
11
|
+
* whether to WARN. It never touches the filesystem and never throws.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Path fragments that signal a packaged-app / install directory rather than a
|
|
16
|
+
* user project. Matched case-insensitively against the normalized path.
|
|
17
|
+
*/
|
|
18
|
+
const INSTALL_PATTERNS = [
|
|
19
|
+
/anthropicclaude/i, // Claude Desktop app dir
|
|
20
|
+
/[\\/]app-\d/i, // versioned electron app dir, e.g. app-1.2.3
|
|
21
|
+
/appdata[\\/]local/i, // Windows per-user app data
|
|
22
|
+
/program files/i, // Windows install root
|
|
23
|
+
/[\\/]\.asar/i, // packaged electron resources
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** @returns {boolean} true if the path looks like an app/install dir */
|
|
27
|
+
function looksLikeInstallDir(dir) {
|
|
28
|
+
if (!dir || typeof dir !== 'string') { return false; }
|
|
29
|
+
return INSTALL_PATTERNS.some((re) => re.test(dir));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Assess a resolved project dir.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} dir Resolved project directory (e.g. process.cwd()).
|
|
36
|
+
* @param {{hasGit?:boolean, hasPackageJson?:boolean, hasClaude?:boolean}} markers
|
|
37
|
+
* Presence of .git / package.json / .claude in `dir`.
|
|
38
|
+
* @returns {{status:'ok'|'warn', message:string, hint:string|null}}
|
|
39
|
+
*/
|
|
40
|
+
function assessProjectRoot(dir, markers) {
|
|
41
|
+
const m = markers || {};
|
|
42
|
+
const safeDir = (dir && typeof dir === 'string') ? dir : '';
|
|
43
|
+
const hint =
|
|
44
|
+
'pass an explicit project (amicus … --project <path>) or cd into your repo before running amicus';
|
|
45
|
+
|
|
46
|
+
if (looksLikeInstallDir(safeDir)) {
|
|
47
|
+
return {
|
|
48
|
+
status: 'warn',
|
|
49
|
+
message: `${safeDir || '(empty)'} looks like an app/install dir, not a project`,
|
|
50
|
+
hint,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const hasMarker = !!(m.hasGit || m.hasPackageJson || m.hasClaude);
|
|
55
|
+
if (!hasMarker) {
|
|
56
|
+
return {
|
|
57
|
+
status: 'warn',
|
|
58
|
+
message: `${safeDir || '(empty)'} has no project markers (.git / package.json / .claude)`,
|
|
59
|
+
hint,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return { status: 'ok', message: safeDir, hint: null };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = { assessProjectRoot, looksLikeInstallDir, INSTALL_PATTERNS };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// src/utils/remediation-hints.js
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared, copy-paste remediation hint strings.
|
|
6
|
+
*
|
|
7
|
+
* One source of truth for the fix commands surfaced by `amicus doctor` and by
|
|
8
|
+
* failure messages across the CLI, so the guidance never drifts. Consumed by
|
|
9
|
+
* src/cli-handlers-doctor.js (per-check hints) and reused by sweep'd failure
|
|
10
|
+
* sites (#32) and the MCP recovery surface (#43).
|
|
11
|
+
*
|
|
12
|
+
* The object is frozen — these are a stable copy-paste contract; callers read
|
|
13
|
+
* fields, they do not mutate them.
|
|
14
|
+
*/
|
|
15
|
+
const REMEDIATION_HINTS = Object.freeze({
|
|
16
|
+
/** Canonical global (re)install. */
|
|
17
|
+
reinstall: 'npm install -g amicus',
|
|
18
|
+
|
|
19
|
+
/** `npm cache clean --force` — clears a corrupt npm cache before reinstalling. */
|
|
20
|
+
cacheClean: 'npm cache clean --force',
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Engine binaries missing/rolled back. A transient install error can roll
|
|
24
|
+
* back the platform engine packages; re-run, or clean the cache and reinstall.
|
|
25
|
+
*/
|
|
26
|
+
reinstallEngine:
|
|
27
|
+
'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)',
|
|
28
|
+
|
|
29
|
+
/** Electron absent — reinstall to add the interactive GUI (headless still works). */
|
|
30
|
+
reinstallElectron: 'npm install -g amicus (reinstall to add Electron)',
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Electron present but broken (ABI mismatch / partial unpack). Delete the
|
|
34
|
+
* vendored copy and reinstall to force a clean rebuild.
|
|
35
|
+
*/
|
|
36
|
+
rebuildElectron:
|
|
37
|
+
'rm -rf node_modules/electron && npm install -g amicus (rebuild Electron after an ABI mismatch or partial unpack)',
|
|
38
|
+
|
|
39
|
+
/** Point the user at the single recovery hub. */
|
|
40
|
+
runDoctor: 'run: amicus doctor (diagnoses config, keys, engine & MCP, with copy-paste fixes)',
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Self-heal the optional Electron GUI in place (#56). This is the convergence
|
|
44
|
+
* target for the three "reinstall to fix Electron" hints — it provisions the
|
|
45
|
+
* binary from cache (or downloads on demand) WITHOUT a global reinstall, so it
|
|
46
|
+
* can't loop the way `npm install -g amicus` could when the rollback recurs.
|
|
47
|
+
*/
|
|
48
|
+
doctorFix: 'amicus doctor --fix (self-heal the Electron GUI in place — provisions the binary; no reinstall, so it can\'t loop)',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
module.exports = REMEDIATION_HINTS;
|
|
@@ -106,9 +106,19 @@ function waveExitCode(waveStatus) {
|
|
|
106
106
|
* @param {object} opts
|
|
107
107
|
* @param {string} opts.waveId
|
|
108
108
|
* @param {Array<object>} opts.legs - run documents (in --models order)
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
109
|
+
*
|
|
110
|
+
* COUNTS REMAINDER RULE (stable, no schemaVersion bump): `counts` exposes four
|
|
111
|
+
* NAMED terminal buckets — complete, error, timeout, aborted — plus `total`
|
|
112
|
+
* (= legs.length). The remaining TERMINAL_STATUSES ('crashed', 'idle-timeout')
|
|
113
|
+
* and any non-terminal status (e.g. 'running' in a live-rebuilt wave) are
|
|
114
|
+
* deliberately NOT given their own bucket; they are reflected ONLY in `total`.
|
|
115
|
+
* Therefore a consumer must treat the unnamed remainder as
|
|
116
|
+
* total − (complete + error + timeout + aborted)
|
|
117
|
+
* and must NOT assume the named buckets sum to `total`. Adding new buckets
|
|
118
|
+
* would change the document shape and REQUIRES bumping SCHEMA_VERSION.
|
|
119
|
+
* This agrees with the MCP wave path (mcp-server.js), which counts a leg as
|
|
120
|
+
* "done" iff its status is in TERMINAL_STATUSES — including 'crashed' — so a
|
|
121
|
+
* crashed leg is done/total there exactly as it is total-only here.
|
|
112
122
|
* @param {{source: string, file: string|null, chars: number}|null} [opts.promptMeta]
|
|
113
123
|
* @param {string|null} [opts.createdAt]
|
|
114
124
|
* @param {string|null} [opts.completedAt]
|
|
@@ -117,6 +127,9 @@ function waveExitCode(waveStatus) {
|
|
|
117
127
|
*/
|
|
118
128
|
function buildWaveResult({ waveId, legs = [], promptMeta = null, createdAt = null, completedAt = null, status = null }) {
|
|
119
129
|
const { sumWaveUsage } = require('./pricing');
|
|
130
|
+
// Named buckets only (see "COUNTS REMAINDER RULE" above). 'crashed' and
|
|
131
|
+
// 'idle-timeout' legs are intentionally NOT bucketed — they land in `total`
|
|
132
|
+
// only, so total may exceed complete+error+timeout+aborted.
|
|
120
133
|
const counts = {
|
|
121
134
|
total: legs.length,
|
|
122
135
|
complete: legs.filter(l => l.status === 'complete').length,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module utils/version-info — running vs. on-disk amicus version (#33)
|
|
3
|
+
*
|
|
4
|
+
* After an `npm i -g amicus` upgrade, a long-lived MCP server process keeps
|
|
5
|
+
* running the OLD code until the client restarts it. That staleness is
|
|
6
|
+
* invisible from inside an agent session. These helpers surface the running
|
|
7
|
+
* version in MCP responses and, via a CALL-TIME re-read of the on-disk
|
|
8
|
+
* package.json, flag when the two have diverged.
|
|
9
|
+
*/
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/** Absolute path to this install's package.json (repo root). */
|
|
14
|
+
const PKG_PATH = path.join(__dirname, '..', '..', 'package.json');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The version baked into the running process at load time. Free: package.json
|
|
18
|
+
* is already require()'d elsewhere, so this hits the module cache.
|
|
19
|
+
*/
|
|
20
|
+
const RUNNING_VERSION = require('../../package.json').version;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Re-read the on-disk package.json version at call time. Wrapped in try/catch
|
|
24
|
+
* — the file may be mid-rewrite, missing, or unreadable during/after an
|
|
25
|
+
* upgrade — and returns null on any failure rather than throwing.
|
|
26
|
+
* @returns {string|null}
|
|
27
|
+
*/
|
|
28
|
+
function readOnDiskVersion() {
|
|
29
|
+
try {
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf-8'));
|
|
31
|
+
return (pkg && typeof pkg.version === 'string') ? pkg.version : null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* One-line staleness warning, or null when there's nothing to warn about
|
|
39
|
+
* (on-disk version unreadable or identical to the running version).
|
|
40
|
+
* @returns {string|null}
|
|
41
|
+
*/
|
|
42
|
+
function versionWarning() {
|
|
43
|
+
const onDisk = readOnDiskVersion();
|
|
44
|
+
if (!onDisk || onDisk === RUNNING_VERSION) { return null; }
|
|
45
|
+
return `Amicus was upgraded on disk (running v${RUNNING_VERSION}, on-disk v${onDisk}). `
|
|
46
|
+
+ `Restart your MCP client to load v${onDisk}.`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { RUNNING_VERSION, readOnDiskVersion, versionWarning, PKG_PATH };
|