@phnx-labs/agents-cli 1.20.20 → 1.20.22
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/CHANGELOG.md +14 -0
- package/dist/commands/cloud.js +16 -7
- package/dist/commands/menubar.d.ts +10 -0
- package/dist/commands/menubar.js +83 -0
- package/dist/commands/routines.js +34 -1
- package/dist/commands/secrets.d.ts +1 -1
- package/dist/commands/secrets.js +95 -38
- package/dist/index.js +28 -3
- package/dist/lib/agents.js +8 -0
- package/dist/lib/cloud/antigravity.d.ts +70 -0
- package/dist/lib/cloud/antigravity.js +196 -0
- package/dist/lib/cloud/factory.d.ts +68 -18
- package/dist/lib/cloud/factory.js +269 -26
- package/dist/lib/cloud/registry.d.ts +18 -2
- package/dist/lib/cloud/registry.js +28 -4
- package/dist/lib/cloud/types.d.ts +32 -2
- package/dist/lib/daemon.js +3 -0
- package/dist/lib/menubar/install-menubar.d.ts +57 -0
- package/dist/lib/menubar/install-menubar.js +291 -0
- package/dist/lib/secrets/agent.d.ts +9 -1
- package/dist/lib/secrets/agent.js +91 -10
- package/dist/lib/secrets/bundles.d.ts +19 -12
- package/dist/lib/secrets/bundles.js +22 -14
- package/dist/lib/self-update.d.ts +34 -0
- package/dist/lib/self-update.js +63 -2
- package/dist/lib/session/sync/config.d.ts +17 -5
- package/dist/lib/session/sync/config.js +51 -4
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/version.d.ts +11 -0
- package/dist/lib/version.js +20 -0
- package/package.json +3 -2
- package/scripts/postinstall.js +35 -0
|
@@ -12,6 +12,31 @@
|
|
|
12
12
|
* to PATH resolution.
|
|
13
13
|
*/
|
|
14
14
|
export declare const NPM_PACKAGE_NAME = "@phnx-labs/agents-cli";
|
|
15
|
+
export type PackageManager = 'npm' | 'bun';
|
|
16
|
+
/**
|
|
17
|
+
* The directory bun installs global packages into:
|
|
18
|
+
* <BUN_INSTALL>/install/global (BUN_INSTALL defaults to ~/.bun)
|
|
19
|
+
*
|
|
20
|
+
* A globally-installed scoped package then lives at
|
|
21
|
+
* `<bunGlobalDir>/node_modules/@phnx-labs/agents-cli` — note there is NO `lib`
|
|
22
|
+
* segment, unlike npm's POSIX layout. That single difference is why an
|
|
23
|
+
* npm-based upgrade silently misses a bun install (see deriveGlobalPrefix).
|
|
24
|
+
*/
|
|
25
|
+
export declare function bunGlobalDir(): string;
|
|
26
|
+
/**
|
|
27
|
+
* Identify which package manager owns the install at `packageRoot`, so the
|
|
28
|
+
* upgrade can shell out to the one that actually replaces this copy.
|
|
29
|
+
*
|
|
30
|
+
* bun lays a global package out as `<bunGlobalDir>/node_modules/<scoped pkg>`,
|
|
31
|
+
* so the prefix (the parent of `node_modules`) is the bun global dir itself.
|
|
32
|
+
* Everything else — npm's `<prefix>/lib/node_modules` and the Windows
|
|
33
|
+
* `<prefix>/node_modules` — is treated as npm.
|
|
34
|
+
*
|
|
35
|
+
* Detection is path-based (no subprocess): it matches the resolved bun global
|
|
36
|
+
* dir from BUN_INSTALL/$HOME, and falls back to the structural `.bun/install/
|
|
37
|
+
* global` tail for a relocated BUN_INSTALL not exported into this process.
|
|
38
|
+
*/
|
|
39
|
+
export declare function detectPackageManager(packageRoot: string): PackageManager;
|
|
15
40
|
export interface UpdateCheckCache {
|
|
16
41
|
lastCheck: number;
|
|
17
42
|
latestVersion: string;
|
|
@@ -49,6 +74,15 @@ export declare function deriveGlobalPrefix(packageRoot: string): string;
|
|
|
49
74
|
* refreshAliasShims().
|
|
50
75
|
*/
|
|
51
76
|
export declare function installPackageIntoPrefix(spec: string, prefix: string): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Install `spec` into bun's global store with `bun add -g`. bun writes to
|
|
79
|
+
* `<bunGlobalDir>/node_modules/<pkg>`, which is exactly the running package
|
|
80
|
+
* root for a bun install — so verifyInstalledVersion() sees the new version
|
|
81
|
+
* in place. bun skips untrusted lifecycle scripts, so the caller refreshes
|
|
82
|
+
* alias shims afterwards via refreshAliasShims() rather than relying on the
|
|
83
|
+
* package's postinstall hook.
|
|
84
|
+
*/
|
|
85
|
+
export declare function installPackageWithBun(spec: string): Promise<void>;
|
|
52
86
|
/** Read the version field of the package.json at `packageRoot`, fresh from disk. */
|
|
53
87
|
export declare function readInstalledVersion(packageRoot: string): string;
|
|
54
88
|
/**
|
package/dist/lib/self-update.js
CHANGED
|
@@ -12,10 +12,55 @@
|
|
|
12
12
|
* to PATH resolution.
|
|
13
13
|
*/
|
|
14
14
|
import * as fs from 'fs';
|
|
15
|
+
import * as os from 'os';
|
|
15
16
|
import * as path from 'path';
|
|
16
17
|
import { spawnSync } from 'child_process';
|
|
17
18
|
import { compareVersions } from './versions.js';
|
|
18
19
|
export const NPM_PACKAGE_NAME = '@phnx-labs/agents-cli';
|
|
20
|
+
/**
|
|
21
|
+
* The directory bun installs global packages into:
|
|
22
|
+
* <BUN_INSTALL>/install/global (BUN_INSTALL defaults to ~/.bun)
|
|
23
|
+
*
|
|
24
|
+
* A globally-installed scoped package then lives at
|
|
25
|
+
* `<bunGlobalDir>/node_modules/@phnx-labs/agents-cli` — note there is NO `lib`
|
|
26
|
+
* segment, unlike npm's POSIX layout. That single difference is why an
|
|
27
|
+
* npm-based upgrade silently misses a bun install (see deriveGlobalPrefix).
|
|
28
|
+
*/
|
|
29
|
+
export function bunGlobalDir() {
|
|
30
|
+
const bunInstall = process.env.BUN_INSTALL || path.join(os.homedir(), '.bun');
|
|
31
|
+
return path.join(bunInstall, 'install', 'global');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Identify which package manager owns the install at `packageRoot`, so the
|
|
35
|
+
* upgrade can shell out to the one that actually replaces this copy.
|
|
36
|
+
*
|
|
37
|
+
* bun lays a global package out as `<bunGlobalDir>/node_modules/<scoped pkg>`,
|
|
38
|
+
* so the prefix (the parent of `node_modules`) is the bun global dir itself.
|
|
39
|
+
* Everything else — npm's `<prefix>/lib/node_modules` and the Windows
|
|
40
|
+
* `<prefix>/node_modules` — is treated as npm.
|
|
41
|
+
*
|
|
42
|
+
* Detection is path-based (no subprocess): it matches the resolved bun global
|
|
43
|
+
* dir from BUN_INSTALL/$HOME, and falls back to the structural `.bun/install/
|
|
44
|
+
* global` tail for a relocated BUN_INSTALL not exported into this process.
|
|
45
|
+
*/
|
|
46
|
+
export function detectPackageManager(packageRoot) {
|
|
47
|
+
const resolved = path.resolve(packageRoot);
|
|
48
|
+
const prefix = path.dirname(path.dirname(path.dirname(resolved))); // strip <scope>/<pkg>/node_modules
|
|
49
|
+
if (prefix === path.resolve(bunGlobalDir()))
|
|
50
|
+
return 'bun';
|
|
51
|
+
const parts = prefix.split(path.sep);
|
|
52
|
+
const n = parts.length;
|
|
53
|
+
if (n >= 3 && parts[n - 1] === 'global' && parts[n - 2] === 'install' && parts[n - 3] === '.bun') {
|
|
54
|
+
return 'bun';
|
|
55
|
+
}
|
|
56
|
+
return 'npm';
|
|
57
|
+
}
|
|
58
|
+
/** The shell command a user can run by hand to reproduce the upgrade for `manager`. */
|
|
59
|
+
function manualInstallHint(manager, packageRoot, spec) {
|
|
60
|
+
if (manager === 'bun')
|
|
61
|
+
return `bun add -g ${spec}`;
|
|
62
|
+
return `npm install -g --prefix ${deriveGlobalPrefix(packageRoot)} ${spec}`;
|
|
63
|
+
}
|
|
19
64
|
/** Read the cached update-check state from disk. Returns null if the file is missing or corrupt. */
|
|
20
65
|
export function readUpdateCache(file) {
|
|
21
66
|
try {
|
|
@@ -102,6 +147,20 @@ export async function installPackageIntoPrefix(spec, prefix) {
|
|
|
102
147
|
const execFileAsync = promisify(execFile);
|
|
103
148
|
await execFileAsync('npm', ['install', '-g', '--prefix', prefix, spec, '--ignore-scripts']);
|
|
104
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Install `spec` into bun's global store with `bun add -g`. bun writes to
|
|
152
|
+
* `<bunGlobalDir>/node_modules/<pkg>`, which is exactly the running package
|
|
153
|
+
* root for a bun install — so verifyInstalledVersion() sees the new version
|
|
154
|
+
* in place. bun skips untrusted lifecycle scripts, so the caller refreshes
|
|
155
|
+
* alias shims afterwards via refreshAliasShims() rather than relying on the
|
|
156
|
+
* package's postinstall hook.
|
|
157
|
+
*/
|
|
158
|
+
export async function installPackageWithBun(spec) {
|
|
159
|
+
const { execFile } = await import('child_process');
|
|
160
|
+
const { promisify } = await import('util');
|
|
161
|
+
const execFileAsync = promisify(execFile);
|
|
162
|
+
await execFileAsync('bun', ['add', '-g', spec]);
|
|
163
|
+
}
|
|
105
164
|
/** Read the version field of the package.json at `packageRoot`, fresh from disk. */
|
|
106
165
|
export function readInstalledVersion(packageRoot) {
|
|
107
166
|
return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8')).version;
|
|
@@ -113,8 +172,10 @@ export function readInstalledVersion(packageRoot) {
|
|
|
113
172
|
export function verifyInstalledVersion(packageRoot, expectedVersion) {
|
|
114
173
|
const actual = readInstalledVersion(packageRoot);
|
|
115
174
|
if (actual !== expectedVersion) {
|
|
116
|
-
|
|
117
|
-
|
|
175
|
+
const manager = detectPackageManager(packageRoot);
|
|
176
|
+
const hint = manualInstallHint(manager, packageRoot, `${NPM_PACKAGE_NAME}@${expectedVersion}`);
|
|
177
|
+
throw new Error(`the package manager reported success but ${packageRoot} is still ${actual} (expected ${expectedVersion}). ` +
|
|
178
|
+
`Run manually: ${hint}`);
|
|
118
179
|
}
|
|
119
180
|
}
|
|
120
181
|
/**
|
|
@@ -13,14 +13,26 @@ export interface R2Config {
|
|
|
13
13
|
/** S3-compatible endpoint for the account (no bucket, no trailing slash). */
|
|
14
14
|
endpoint: string;
|
|
15
15
|
}
|
|
16
|
+
/** Window after a prompt-bearing resolution failure during which we skip
|
|
17
|
+
* re-attempting (and thus re-prompting). SIGHUP / restart bypasses it. */
|
|
18
|
+
export declare const RESOLVE_RETRY_COOLDOWN_MS: number;
|
|
19
|
+
/** Drop the cached resolution so the next call reads the bundle fresh. Called on
|
|
20
|
+
* daemon SIGHUP (to pick up rotated credentials) and between tests. */
|
|
21
|
+
export declare function clearR2ConfigCache(): void;
|
|
16
22
|
/**
|
|
17
|
-
* Resolve R2 credentials
|
|
18
|
-
*
|
|
19
|
-
*
|
|
23
|
+
* Resolve R2 credentials, reading the keychain at most once per process. The
|
|
24
|
+
* first call reads (and may prompt for Touch ID); every later call returns the
|
|
25
|
+
* memoized result. Throws if the bundle/keys are missing — failures are not
|
|
26
|
+
* memoized, but see isSyncConfigured for the re-prompt cooldown.
|
|
20
27
|
*/
|
|
21
28
|
export declare function loadR2Config(): R2Config;
|
|
22
|
-
/**
|
|
23
|
-
|
|
29
|
+
/**
|
|
30
|
+
* True when the sync bundle exists and resolves, without throwing. After a
|
|
31
|
+
* prompt-bearing failure (e.g. a cancelled Touch ID) it returns false without
|
|
32
|
+
* re-reading the keychain for RESOLVE_RETRY_COOLDOWN_MS, so a dismissed prompt
|
|
33
|
+
* does not re-storm every cycle. `now` is injectable for tests.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isSyncConfigured(now?: number): boolean;
|
|
24
36
|
/**
|
|
25
37
|
* This machine's stable, human-readable id, used as its R2 prefix and mirror
|
|
26
38
|
* directory name. Tailnet hostnames (zion, yosemite-s0, mac-mini) are already
|
|
@@ -12,7 +12,7 @@ export const SYNC_BUNDLE = 'r2.backups';
|
|
|
12
12
|
* actionable error if the bundle or any key is missing — sync cannot proceed
|
|
13
13
|
* without real credentials (no silent fallback).
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
function resolveR2Config() {
|
|
16
16
|
const { env } = readAndResolveBundleEnv(SYNC_BUNDLE, { caller: 'sessions-sync' });
|
|
17
17
|
const accountId = env.R2_ACCOUNT_ID?.trim();
|
|
18
18
|
const bucket = env.R2_BUCKET_NAME?.trim();
|
|
@@ -36,13 +36,60 @@ export function loadR2Config() {
|
|
|
36
36
|
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
// ── Resolution cache ────────────────────────────────────────────────────────
|
|
40
|
+
// The daemon calls isSyncConfigured() + syncSessions() every ~90s, and each used
|
|
41
|
+
// to trigger a fresh read of the biometry-gated `r2.backups` keychain items —
|
|
42
|
+
// one Touch ID prompt per gated item, every cycle, forever. We instead resolve
|
|
43
|
+
// at most once per process: a success is memoized for the process lifetime
|
|
44
|
+
// (cleared on daemon SIGHUP via clearR2ConfigCache), so subsequent cycles never
|
|
45
|
+
// touch the keychain again. A *prompt-bearing* failure (cancelled Touch ID, etc.)
|
|
46
|
+
// starts a cooldown so a dismissed prompt is not re-issued every cycle. A simply
|
|
47
|
+
// absent bundle never prompts, so it is re-checked each cycle (fast pickup when
|
|
48
|
+
// the user later adds credentials).
|
|
49
|
+
let cachedConfig = null;
|
|
50
|
+
let lastPromptFailureAt = 0;
|
|
51
|
+
/** Window after a prompt-bearing resolution failure during which we skip
|
|
52
|
+
* re-attempting (and thus re-prompting). SIGHUP / restart bypasses it. */
|
|
53
|
+
export const RESOLVE_RETRY_COOLDOWN_MS = 30 * 60 * 1000; // 30 minutes
|
|
54
|
+
/** Drop the cached resolution so the next call reads the bundle fresh. Called on
|
|
55
|
+
* daemon SIGHUP (to pick up rotated credentials) and between tests. */
|
|
56
|
+
export function clearR2ConfigCache() {
|
|
57
|
+
cachedConfig = null;
|
|
58
|
+
lastPromptFailureAt = 0;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve R2 credentials, reading the keychain at most once per process. The
|
|
62
|
+
* first call reads (and may prompt for Touch ID); every later call returns the
|
|
63
|
+
* memoized result. Throws if the bundle/keys are missing — failures are not
|
|
64
|
+
* memoized, but see isSyncConfigured for the re-prompt cooldown.
|
|
65
|
+
*/
|
|
66
|
+
export function loadR2Config() {
|
|
67
|
+
if (cachedConfig)
|
|
68
|
+
return cachedConfig;
|
|
69
|
+
cachedConfig = resolveR2Config();
|
|
70
|
+
return cachedConfig;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* True when the sync bundle exists and resolves, without throwing. After a
|
|
74
|
+
* prompt-bearing failure (e.g. a cancelled Touch ID) it returns false without
|
|
75
|
+
* re-reading the keychain for RESOLVE_RETRY_COOLDOWN_MS, so a dismissed prompt
|
|
76
|
+
* does not re-storm every cycle. `now` is injectable for tests.
|
|
77
|
+
*/
|
|
78
|
+
export function isSyncConfigured(now = Date.now()) {
|
|
79
|
+
if (cachedConfig)
|
|
80
|
+
return true;
|
|
81
|
+
if (lastPromptFailureAt && now - lastPromptFailureAt < RESOLVE_RETRY_COOLDOWN_MS)
|
|
82
|
+
return false;
|
|
41
83
|
try {
|
|
42
84
|
loadR2Config();
|
|
43
85
|
return true;
|
|
44
86
|
}
|
|
45
|
-
catch {
|
|
87
|
+
catch (err) {
|
|
88
|
+
// A missing bundle never prompts, so keep re-checking it each cycle (so a
|
|
89
|
+
// later `agents secrets add` is picked up quickly). Any other failure may
|
|
90
|
+
// have cost a prompt (cancelled Touch ID, keychain error) — back off.
|
|
91
|
+
if (!/not found/i.test(err.message))
|
|
92
|
+
lastPromptFailureAt = now;
|
|
46
93
|
return false;
|
|
47
94
|
}
|
|
48
95
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* configuration schemas, resource tracking, registry types, and permission
|
|
6
6
|
* formats for each supported agent.
|
|
7
7
|
*/
|
|
8
|
+
import type { CloudProviderId } from './cloud/types.js';
|
|
8
9
|
/** Unique identifier for a supported AI coding agent. */
|
|
9
10
|
export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'roo' | 'antigravity' | 'grok' | 'kimi' | 'droid';
|
|
10
11
|
/** How `agents run <agent>` chooses an installed version when none is pinned. */
|
|
@@ -83,6 +84,13 @@ export interface AgentConfig {
|
|
|
83
84
|
variableSyntax: string;
|
|
84
85
|
supportsHooks: boolean;
|
|
85
86
|
nativeAgentsSkillsDir?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* This agent's *own* cloud backend. `agents cloud run --agent <id>` routes
|
|
89
|
+
* here when no `--provider` is given (precedence: --provider > this >
|
|
90
|
+
* cloud.default_provider > rush). Undefined means the agent has no native
|
|
91
|
+
* cloud and falls back to the configured default.
|
|
92
|
+
*/
|
|
93
|
+
cloudProvider?: CloudProviderId;
|
|
86
94
|
capabilities: {
|
|
87
95
|
hooks: Capability;
|
|
88
96
|
mcp: Capability;
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -5,3 +5,14 @@
|
|
|
5
5
|
* get stale behavior without this check.
|
|
6
6
|
*/
|
|
7
7
|
export declare function getCliVersion(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Read the version from package.json on disk every call, bypassing the cache.
|
|
10
|
+
*
|
|
11
|
+
* `getCliVersion()` memoizes the version a long-running process *started* with.
|
|
12
|
+
* After `npm i -g` overwrites the install in place, the on-disk package.json
|
|
13
|
+
* changes but the running process keeps its old in-memory code. Comparing this
|
|
14
|
+
* fresh read against the cached startup value is how a daemon/broker detects it
|
|
15
|
+
* is now stale and should reload onto the new code (self-healing). Returns
|
|
16
|
+
* 'unknown' on any error.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getCliVersionFresh(): string;
|
package/dist/lib/version.js
CHANGED
|
@@ -23,3 +23,23 @@ export function getCliVersion() {
|
|
|
23
23
|
}
|
|
24
24
|
return cached;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the version from package.json on disk every call, bypassing the cache.
|
|
28
|
+
*
|
|
29
|
+
* `getCliVersion()` memoizes the version a long-running process *started* with.
|
|
30
|
+
* After `npm i -g` overwrites the install in place, the on-disk package.json
|
|
31
|
+
* changes but the running process keeps its old in-memory code. Comparing this
|
|
32
|
+
* fresh read against the cached startup value is how a daemon/broker detects it
|
|
33
|
+
* is now stale and should reload onto the new code (self-healing). Returns
|
|
34
|
+
* 'unknown' on any error.
|
|
35
|
+
*/
|
|
36
|
+
export function getCliVersionFresh() {
|
|
37
|
+
try {
|
|
38
|
+
const pkgPath = path.join(__dirname, '..', '..', 'package.json');
|
|
39
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
40
|
+
return String(pkg.version || 'unknown');
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return 'unknown';
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.22",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"dist/**/*.d.ts",
|
|
26
26
|
"dist/**/*.json",
|
|
27
27
|
"dist/lib/secrets/Agents CLI.app/**",
|
|
28
|
+
"dist/lib/menubar/MenubarHelper.app/**",
|
|
28
29
|
"scripts/postinstall.js",
|
|
29
30
|
"scripts/install-helper.js",
|
|
30
31
|
"CHANGELOG.md",
|
|
@@ -43,7 +44,7 @@
|
|
|
43
44
|
"url": "https://github.com/phnx-labs/agents-cli/issues"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
|
-
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' && ([ \"$(uname)\" = \"Darwin\" ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true)",
|
|
47
|
+
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ \"$(uname)\" = \"Darwin\" ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ \"$(uname)\" = \"Darwin\" ] && [ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
|
|
47
48
|
"prepack": "scripts/verify-keychain-helper.sh",
|
|
48
49
|
"postinstall": "node scripts/postinstall.js",
|
|
49
50
|
"dev": "tsx src/index.ts",
|
package/scripts/postinstall.js
CHANGED
|
@@ -239,6 +239,8 @@ To enable version-aware shims, add this to your shell config:
|
|
|
239
239
|
console.log(` Installed shorthands: ${written.join(', ')}`);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
await healLongRunningProcesses();
|
|
243
|
+
|
|
242
244
|
const version = getVersion();
|
|
243
245
|
if (version) {
|
|
244
246
|
const section = getChangelogSection(version);
|
|
@@ -250,6 +252,39 @@ To enable version-aware shims, add this to your shell config:
|
|
|
250
252
|
}
|
|
251
253
|
}
|
|
252
254
|
|
|
255
|
+
/**
|
|
256
|
+
* Self-heal long-running processes onto the just-installed code (macOS).
|
|
257
|
+
*
|
|
258
|
+
* The root cause behind stale-behavior bugs is a daemon/broker that keeps
|
|
259
|
+
* running pre-upgrade code for days. An in-place `npm i -g` swaps the files but
|
|
260
|
+
* not the running processes — so we bounce them here, the one moment we know the
|
|
261
|
+
* code just changed. Best-effort and non-fatal: a failure must never break the
|
|
262
|
+
* install. Skipped in CI and when AGENTS_NO_HEAL=1.
|
|
263
|
+
*/
|
|
264
|
+
async function healLongRunningProcesses() {
|
|
265
|
+
if (process.platform !== 'darwin') return;
|
|
266
|
+
if (process.env.CI || process.env.AGENTS_NO_HEAL === '1') return;
|
|
267
|
+
// Routines daemon: restart so it reloads new code (e.g. picks up keychain
|
|
268
|
+
// read-memoization / broker fast-path that a stale daemon wouldn't have).
|
|
269
|
+
try {
|
|
270
|
+
const d = await import('../dist/lib/daemon.js');
|
|
271
|
+
if (d.isDaemonRunning?.()) {
|
|
272
|
+
d.stopDaemon?.();
|
|
273
|
+
d.startDaemon?.();
|
|
274
|
+
console.log(' Restarted the routines daemon onto this version.');
|
|
275
|
+
}
|
|
276
|
+
} catch { /* best effort */ }
|
|
277
|
+
// Persistent secrets-agent broker: kickstart so launchd relaunches it on the
|
|
278
|
+
// new code. No-op if the service isn't installed; never blocks.
|
|
279
|
+
try {
|
|
280
|
+
const a = await import('../dist/lib/secrets/agent.js');
|
|
281
|
+
if (a.secretsAgentServiceInstalled?.()) {
|
|
282
|
+
a.kickstartSecretsAgentService?.();
|
|
283
|
+
console.log(' Reloaded the secrets-agent service onto this version.');
|
|
284
|
+
}
|
|
285
|
+
} catch { /* best effort */ }
|
|
286
|
+
}
|
|
287
|
+
|
|
253
288
|
main().catch((err) => {
|
|
254
289
|
console.error(err);
|
|
255
290
|
process.exit(0);
|