@phnx-labs/agents-cli 1.20.8 → 1.20.9
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 +6 -0
- package/README.md +1 -1
- package/dist/commands/daemon.js +6 -6
- package/dist/commands/import.js +3 -6
- package/dist/commands/inspect.js +17 -8
- package/dist/commands/models.js +2 -1
- package/dist/commands/plugins.js +3 -2
- package/dist/commands/refresh-rules.js +4 -4
- package/dist/commands/routines.js +8 -7
- package/dist/commands/sessions.js +17 -2
- package/dist/commands/subagents.js +2 -1
- package/dist/commands/usage.js +11 -3
- package/dist/index.js +69 -47
- package/dist/lib/agents.d.ts +18 -1
- package/dist/lib/agents.js +89 -23
- package/dist/lib/browser/chrome.d.ts +4 -3
- package/dist/lib/browser/chrome.js +87 -12
- package/dist/lib/browser/ipc.js +59 -13
- package/dist/lib/daemon.js +20 -8
- package/dist/lib/fs-walk.d.ts +7 -1
- package/dist/lib/fs-walk.js +45 -11
- package/dist/lib/git.js +5 -2
- package/dist/lib/log-follow.d.ts +7 -0
- package/dist/lib/log-follow.js +65 -0
- package/dist/lib/platform/index.d.ts +1 -0
- package/dist/lib/platform/index.js +1 -0
- package/dist/lib/platform/ipc.d.ts +11 -0
- package/dist/lib/platform/ipc.js +21 -0
- package/dist/lib/platform/paths.d.ts +7 -0
- package/dist/lib/platform/paths.js +9 -0
- package/dist/lib/platform/process.d.ts +9 -1
- package/dist/lib/platform/process.js +27 -0
- package/dist/lib/plugins.js +5 -3
- package/dist/lib/self-update.d.ts +86 -0
- package/dist/lib/self-update.js +178 -0
- package/dist/lib/versions.js +3 -3
- package/package.json +1 -1
- package/scripts/postinstall.js +29 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
**Single-typo agent names auto-correct everywhere, not just `agents run`**
|
|
6
|
+
|
|
7
|
+
- `agents view cladue` used to print `Unknown agent 'cladue'` even though `agents run cladue` auto-corrected. `resolveAgentName` — the canonical resolver behind `view`, `usage`, `inspect`, `doctor`, `sync`, `models`, `skills`, `hooks`, `import`, `sessions --agent`, and every `agent@version` spec (`agents add claud@latest`, `agents use codx@2.1.170`) — now falls back to Damerau-Levenshtein distance-1 matching against canonical ids and multi-letter aliases: `cladue` -> `claude` (transposition), `kim` -> `kimi`, `codx` -> `codex`, `gemni` -> `gemini`.
|
|
8
|
+
- Corrections apply only when unambiguous: every distance-1 candidate must agree on one agent. `kiri` (one edit from both `kiro` and `kimi`) and inputs under 3 characters still error. `agents run` keeps its existing exact -> profile -> workflow -> fuzzy precedence, so a profile named `claud` still beats the typo correction.
|
|
9
|
+
- Fixes `kimi` being listed as a valid agent but missing from the alias map — `agents view kimi` previously errored. Added `kimi` / `kimi-code` entries.
|
|
10
|
+
|
|
5
11
|
## 1.20.7
|
|
6
12
|
|
|
7
13
|
**`agents inspect` — DotAgents repo targets (#256)**
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ agents run codex "Fix the issues Claude found"
|
|
|
122
122
|
agents run gemini "Write tests for the fixed code"
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
-
Each resolves to the project-pinned version with skills, MCP servers, and permissions already synced.
|
|
125
|
+
Each resolves to the project-pinned version with skills, MCP servers, and permissions already synced. Single-typo names auto-correct across every command — `agents view cladue` resolves to `claude`, `agents add codx@latest` to `codex`.
|
|
126
126
|
|
|
127
127
|
### Rate-limited? Keep working.
|
|
128
128
|
|
package/dist/commands/daemon.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* aliases for `agents routines` scheduler lifecycle commands. Scheduled
|
|
6
6
|
* for removal in v2.0.
|
|
7
7
|
*/
|
|
8
|
-
import { spawn } from 'child_process';
|
|
9
8
|
import chalk from 'chalk';
|
|
10
9
|
import * as path from 'path';
|
|
11
10
|
import { startDaemon, stopDaemon, isDaemonRunning, readDaemonPid, readDaemonLog, runDaemon, } from '../lib/daemon.js';
|
|
@@ -92,12 +91,13 @@ you never need to start it manually.
|
|
|
92
91
|
warnDeprecated('logs', 'agents routines scheduler-logs');
|
|
93
92
|
if (options.follow) {
|
|
94
93
|
const { getDaemonDir } = await import('../lib/state.js');
|
|
94
|
+
const { followFile } = await import('../lib/log-follow.js');
|
|
95
95
|
const logPath = path.join(getDaemonDir(), 'logs.jsonl');
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
process.on('SIGINT', () => {
|
|
96
|
+
const recent = readDaemonLog(parseInt(options.lines, 10));
|
|
97
|
+
if (recent)
|
|
98
|
+
console.log(recent);
|
|
99
|
+
const stop = followFile(logPath, (text) => process.stdout.write(text), { fromEnd: true });
|
|
100
|
+
process.on('SIGINT', () => { stop(); process.exit(0); });
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
const lines = parseInt(options.lines, 10);
|
package/dist/commands/import.js
CHANGED
|
@@ -27,20 +27,17 @@ import * as os from 'os';
|
|
|
27
27
|
import * as path from 'path';
|
|
28
28
|
import { confirm } from '@inquirer/prompts';
|
|
29
29
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
30
|
-
import { AGENTS, getCliPath, getCliVersion, agentLabel } from '../lib/agents.js';
|
|
30
|
+
import { AGENTS, getCliPath, getCliVersion, agentLabel, resolveAgentName } from '../lib/agents.js';
|
|
31
31
|
import { getVersionDir } from '../lib/versions.js';
|
|
32
32
|
import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
33
33
|
import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
|
|
34
|
-
function isValidAgentId(value) {
|
|
35
|
-
return ALL_AGENT_IDS.includes(value);
|
|
36
|
-
}
|
|
37
34
|
async function runImport(agentArg, opts) {
|
|
38
|
-
|
|
35
|
+
const agentId = resolveAgentName(agentArg);
|
|
36
|
+
if (!agentId) {
|
|
39
37
|
console.error(chalk.red(`Unknown agent: ${agentArg}`));
|
|
40
38
|
console.error(chalk.gray(`Known agents: ${ALL_AGENT_IDS.join(', ')}`));
|
|
41
39
|
process.exit(1);
|
|
42
40
|
}
|
|
43
|
-
const agentId = agentArg;
|
|
44
41
|
const agent = AGENTS[agentId];
|
|
45
42
|
// installScript-based agents (Grok, Antigravity, Cursor, Kiro, Goose, Roo)
|
|
46
43
|
// don't have an npm package; their binary lives wherever the curl/brew
|
package/dist/commands/inspect.js
CHANGED
|
@@ -17,7 +17,7 @@ import * as os from 'os';
|
|
|
17
17
|
import * as path from 'path';
|
|
18
18
|
import chalk from 'chalk';
|
|
19
19
|
import * as yaml from 'yaml';
|
|
20
|
-
import { AGENTS, getCliState } from '../lib/agents.js';
|
|
20
|
+
import { AGENTS, getCliState, resolveAgentName } from '../lib/agents.js';
|
|
21
21
|
import { supports } from '../lib/capabilities.js';
|
|
22
22
|
import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../lib/state.js';
|
|
23
23
|
import { getVersionHomePath } from '../lib/versions.js';
|
|
@@ -63,12 +63,16 @@ export async function inspectAction(target, options) {
|
|
|
63
63
|
await inspectRepo(repo, options);
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
// Repo targets take precedence over typo correction; only fall through to
|
|
67
|
+
// parseTarget when the key resolves to an agent (alias or single-edit fix).
|
|
68
|
+
if (!resolveAgentName(agentKey)) {
|
|
69
|
+
const extras = getEnabledExtraRepos();
|
|
70
|
+
console.error(chalk.red(`Unknown target: ${target}`));
|
|
71
|
+
console.error(chalk.gray(`Agents: ${Object.keys(AGENTS).join(', ')}`));
|
|
72
|
+
const aliases = extras.length > 0 ? `, ${extras.map(e => e.alias).join(', ')}` : '';
|
|
73
|
+
console.error(chalk.gray(`Repos: user, system, project${aliases} — or a path to a repo with a .agents/ dir`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
72
76
|
}
|
|
73
77
|
const { agent, version } = parseTarget(target);
|
|
74
78
|
const versionHome = getVersionHomePath(agent, version);
|
|
@@ -93,7 +97,12 @@ export async function inspectAction(target, options) {
|
|
|
93
97
|
}
|
|
94
98
|
function parseTarget(target) {
|
|
95
99
|
const [rawAgent, rawVersion] = target.split('@');
|
|
96
|
-
const agent = (rawAgent || '')
|
|
100
|
+
const agent = resolveAgentName(rawAgent || '');
|
|
101
|
+
if (!agent) {
|
|
102
|
+
console.error(chalk.red(`Unknown agent: ${rawAgent}`));
|
|
103
|
+
console.error(chalk.gray(`Known agents: ${Object.keys(AGENTS).join(', ')}`));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
97
106
|
let version = rawVersion;
|
|
98
107
|
if (!version || version === 'default') {
|
|
99
108
|
const meta = readMeta();
|
package/dist/commands/models.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import chalk from 'chalk';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
|
+
import { homeDir } from '../lib/platform/index.js';
|
|
10
11
|
import { resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
|
|
11
12
|
import { listInstalledVersions, getGlobalDefault, resolveVersion, resolveVersionAlias } from '../lib/versions.js';
|
|
12
13
|
import { getModelCatalog, locateModelSource } from '../lib/models.js';
|
|
@@ -166,5 +167,5 @@ function printCatalog(agent, version, isDefault, options) {
|
|
|
166
167
|
}
|
|
167
168
|
/** Abbreviate a path by replacing the home directory with ~. */
|
|
168
169
|
function shortPath(p) {
|
|
169
|
-
return p.replace(
|
|
170
|
+
return p.replace(homeDir(), '~');
|
|
170
171
|
}
|
package/dist/commands/plugins.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import chalk from 'chalk';
|
|
11
|
+
import { homeDir } from '../lib/platform/index.js';
|
|
11
12
|
import { input } from '@inquirer/prompts';
|
|
12
13
|
import { agentLabel } from '../lib/agents.js';
|
|
13
14
|
import { capableAgents, isCapable } from '../lib/capabilities.js';
|
|
@@ -21,7 +22,7 @@ import { safeJoin } from '../lib/paths.js';
|
|
|
21
22
|
import { discoverMarketplaces } from '../lib/plugin-marketplace.js';
|
|
22
23
|
/** Replace the home directory prefix with ~ for display. */
|
|
23
24
|
function formatPath(p) {
|
|
24
|
-
const home =
|
|
25
|
+
const home = homeDir();
|
|
25
26
|
if (home && p.startsWith(home)) {
|
|
26
27
|
return '~' + p.slice(home.length);
|
|
27
28
|
}
|
|
@@ -355,7 +356,7 @@ Examples:
|
|
|
355
356
|
});
|
|
356
357
|
}
|
|
357
358
|
const name = nameArg;
|
|
358
|
-
const pluginsDir = path.join(
|
|
359
|
+
const pluginsDir = path.join(homeDir(), '.agents', 'plugins');
|
|
359
360
|
const pluginRoot = safeJoin(pluginsDir, name);
|
|
360
361
|
// Use discovered plugin when present; fall back to name+root if source is already gone
|
|
361
362
|
const plugin = getPlugin(name);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Recompiles only when source files have changed.
|
|
7
7
|
*/
|
|
8
8
|
import chalk from 'chalk';
|
|
9
|
-
import { AGENTS } from '../lib/agents.js';
|
|
9
|
+
import { AGENTS, resolveAgentName } from '../lib/agents.js';
|
|
10
10
|
import { isVersionInstalled } from '../lib/versions.js';
|
|
11
11
|
import { ensureRulesFresh, supportsRulesImports } from '../lib/rules/compile.js';
|
|
12
12
|
/**
|
|
@@ -23,12 +23,12 @@ export function registerRefreshRulesCommand(program) {
|
|
|
23
23
|
.requiredOption('--agent-version <version>', 'Installed version whose rules file should be refreshed')
|
|
24
24
|
.option('--quiet', 'Suppress all output (exit code indicates success)', false)
|
|
25
25
|
.action((opts) => {
|
|
26
|
-
const agentId = opts.agent;
|
|
26
|
+
const agentId = resolveAgentName(opts.agent);
|
|
27
27
|
const version = opts.agentVersion;
|
|
28
28
|
const quiet = !!opts.quiet;
|
|
29
|
-
if (!
|
|
29
|
+
if (!agentId) {
|
|
30
30
|
if (!quiet)
|
|
31
|
-
console.error(chalk.red(`Unknown agent '${
|
|
31
|
+
console.error(chalk.red(`Unknown agent '${opts.agent}'`));
|
|
32
32
|
process.exitCode = 1;
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
@@ -14,6 +14,7 @@ import { isDaemonRunning, signalDaemonReload, startDaemon, stopDaemon, readDaemo
|
|
|
14
14
|
import { humanizeCron, humanizeNextRun, formatRepoLink, REPO_DISPLAY_MAX } from '../lib/routines-format.js';
|
|
15
15
|
import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, getLatestRun, getRunDir, getJobPath, parseAtTime, } from '../lib/routines.js';
|
|
16
16
|
import { getRoutinesDir } from '../lib/state.js';
|
|
17
|
+
import { IS_WINDOWS } from '../lib/platform/index.js';
|
|
17
18
|
import { safeJoin } from '../lib/paths.js';
|
|
18
19
|
import { executeJob, executeJobDetached } from '../lib/runner.js';
|
|
19
20
|
import { JobScheduler } from '../lib/scheduler.js';
|
|
@@ -385,7 +386,7 @@ export function registerRoutinesCommands(program) {
|
|
|
385
386
|
console.log(chalk.gray(`Created new job file: ${newPath}`));
|
|
386
387
|
}
|
|
387
388
|
const targetPath = jobPath || path.join(getRoutinesDir(), `${name}.yml`);
|
|
388
|
-
const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
|
|
389
|
+
const editor = process.env.EDITOR || process.env.VISUAL || (IS_WINDOWS ? 'notepad' : 'vi');
|
|
389
390
|
const editorParts = editor.split(/\s+/).filter(Boolean);
|
|
390
391
|
const editorBin = editorParts[0];
|
|
391
392
|
const editorArgs = [...editorParts.slice(1), targetPath];
|
|
@@ -691,14 +692,14 @@ export function registerRoutinesCommands(program) {
|
|
|
691
692
|
.option('-f, --follow', 'Stream log output in real time (like tail -f)')
|
|
692
693
|
.action(async (options) => {
|
|
693
694
|
if (options.follow) {
|
|
694
|
-
const { spawn } = await import('child_process');
|
|
695
695
|
const { getDaemonDir } = await import('../lib/state.js');
|
|
696
|
+
const { followFile } = await import('../lib/log-follow.js');
|
|
696
697
|
const logPath = path.join(getDaemonDir(), 'logs.jsonl');
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
process.on('SIGINT', () => {
|
|
698
|
+
const recent = readDaemonLog(parseInt(options.lines, 10));
|
|
699
|
+
if (recent)
|
|
700
|
+
console.log(recent);
|
|
701
|
+
const stop = followFile(logPath, (text) => process.stdout.write(text), { fromEnd: true });
|
|
702
|
+
process.on('SIGINT', () => { stop(); process.exit(0); });
|
|
702
703
|
return;
|
|
703
704
|
}
|
|
704
705
|
const lines = parseInt(options.lines, 10);
|
|
@@ -23,6 +23,7 @@ import { parseSession } from '../lib/session/parse.js';
|
|
|
23
23
|
import { renderConversationMarkdown, renderSummary, renderSummaryHeader, computeSummaryStats, renderJson, filterEvents, parseRoleList } from '../lib/session/render.js';
|
|
24
24
|
import { renderMarkdown } from '../lib/markdown.js';
|
|
25
25
|
import { colorAgent, resolveAgentName } from '../lib/agents.js';
|
|
26
|
+
import { fuzzyMatch, FUZZY_PRESETS } from '../lib/fuzzy.js';
|
|
26
27
|
import { resolveVersionAliasLoose } from '../lib/versions.js';
|
|
27
28
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
28
29
|
import { sessionPicker } from './sessions-picker.js';
|
|
@@ -822,8 +823,22 @@ function parseAgentFilter(agentName) {
|
|
|
822
823
|
if (!agentName)
|
|
823
824
|
return {};
|
|
824
825
|
const [name, version] = agentName.split('@', 2);
|
|
825
|
-
|
|
826
|
-
|
|
826
|
+
let agent = SESSION_AGENTS.includes(name)
|
|
827
|
+
? name
|
|
828
|
+
: null;
|
|
829
|
+
if (!agent) {
|
|
830
|
+
// Aliases and single-typo corrections (cladue -> claude). SESSION_AGENTS
|
|
831
|
+
// includes ids (rush, hermes) that resolveAgentName doesn't know, so fall
|
|
832
|
+
// back to fuzzy-matching the session list directly.
|
|
833
|
+
const resolved = resolveAgentName(name);
|
|
834
|
+
if (resolved && SESSION_AGENTS.includes(resolved)) {
|
|
835
|
+
agent = resolved;
|
|
836
|
+
}
|
|
837
|
+
else {
|
|
838
|
+
agent = fuzzyMatch(name, SESSION_AGENTS, FUZZY_PRESETS.agents);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (!agent) {
|
|
827
842
|
console.error(chalk.red(`Unknown agent: ${name}. Use: ${SESSION_AGENTS.join(', ')}`));
|
|
828
843
|
process.exit(1);
|
|
829
844
|
}
|
|
@@ -10,6 +10,7 @@ import ora from 'ora';
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { agentLabel } from '../lib/agents.js';
|
|
13
|
+
import { homeDir } from '../lib/platform/index.js';
|
|
13
14
|
import { capableAgents } from '../lib/capabilities.js';
|
|
14
15
|
import { cloneRepo } from '../lib/git.js';
|
|
15
16
|
import { discoverSubagentsFromRepo, installSubagentCentrally, listInstalledSubagents, getInstalledSubagent, listSubagentsForAgent, iterSubagentsCapableVersions, removeSubagentFromVersion, } from '../lib/subagents.js';
|
|
@@ -19,7 +20,7 @@ import { requireDestructiveArg, promptRemovalTargets, parseCommaSeparatedList, r
|
|
|
19
20
|
import { showResourceList, buildTargetsSection, } from './resource-view.js';
|
|
20
21
|
/** Replace the home directory prefix with ~ for display. */
|
|
21
22
|
function formatPath(p) {
|
|
22
|
-
const home =
|
|
23
|
+
const home = homeDir();
|
|
23
24
|
if (home && p.startsWith(home)) {
|
|
24
25
|
return '~' + p.slice(home.length);
|
|
25
26
|
}
|
package/dist/commands/usage.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { ALL_AGENT_IDS, AGENTS, getAccountInfo, agentLabel, } from '../lib/agents.js';
|
|
2
|
+
import { ALL_AGENT_IDS, AGENTS, getAccountInfo, agentLabel, resolveAgentName, formatAgentError, } from '../lib/agents.js';
|
|
3
3
|
import { listInstalledVersions, getGlobalDefault, getVersionHomePath } from '../lib/versions.js';
|
|
4
4
|
import { formatUsageSection, getUsageInfoForIdentity } from '../lib/usage.js';
|
|
5
5
|
/** Agents whose CLI surfaces usage data we can read today. */
|
|
@@ -15,9 +15,17 @@ Examples:
|
|
|
15
15
|
agents usage codex Show usage for Codex only
|
|
16
16
|
`)
|
|
17
17
|
.action(async (agentFilter) => {
|
|
18
|
-
|
|
18
|
+
let filter;
|
|
19
|
+
if (agentFilter) {
|
|
20
|
+
const resolved = resolveAgentName(agentFilter);
|
|
21
|
+
if (!resolved) {
|
|
22
|
+
console.error(chalk.red(formatAgentError(agentFilter)));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
filter = resolved;
|
|
26
|
+
}
|
|
19
27
|
const targets = filter
|
|
20
|
-
? [filter]
|
|
28
|
+
? [filter]
|
|
21
29
|
: ALL_AGENT_IDS.filter((id) => listInstalledVersions(id).length > 0);
|
|
22
30
|
if (targets.length === 0) {
|
|
23
31
|
console.log(chalk.gray('No agents installed. Run `agents add <agent>` first.'));
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
23
23
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
24
24
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
25
25
|
const VERSION = packageJson.version;
|
|
26
|
-
|
|
26
|
+
import { NPM_PACKAGE_NAME, deriveGlobalPrefix, installPackageIntoPrefix, verifyInstalledVersion, refreshAliasShims, } from './lib/self-update.js';
|
|
27
27
|
// Detect dev/working-tree builds and default the noisy startup steps off.
|
|
28
28
|
// Three cases trip this:
|
|
29
29
|
// 1. Dev install (scripts/install.sh) — package.json version stamped 0.0.0-dev.<sha>
|
|
@@ -268,17 +268,52 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
271
|
-
import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir } from './lib/state.js';
|
|
271
|
+
import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir, getRuntimeStateDir } from './lib/state.js';
|
|
272
|
+
import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, findAgentsCliInstalls, } from './lib/self-update.js';
|
|
272
273
|
const UPDATE_CHECK_FILE = getUpdateCheckPath();
|
|
273
|
-
/**
|
|
274
|
-
|
|
274
|
+
/**
|
|
275
|
+
* Warn once when PATH resolves `agents` to a different agents-cli install
|
|
276
|
+
* than the copy that is currently running (or to several). Divergent installs
|
|
277
|
+
* are how self-updates "succeed" without changing the command the user types.
|
|
278
|
+
* The warning re-fires only when the set of install roots changes; dev builds
|
|
279
|
+
* (0.0.0-dev) are ignored because side-by-side dev installs are a supported
|
|
280
|
+
* workflow.
|
|
281
|
+
*/
|
|
282
|
+
function maybeWarnMultiInstall() {
|
|
283
|
+
const sentinel = path.join(getRuntimeStateDir(), 'multi-install-warned');
|
|
284
|
+
const runningRoot = path.resolve(__dirname, '..');
|
|
285
|
+
const byRoot = new Map();
|
|
286
|
+
byRoot.set(runningRoot, { version: VERSION, note: 'running' });
|
|
287
|
+
for (const install of findAgentsCliInstalls(process.env.PATH || '')) {
|
|
288
|
+
if (install.version.startsWith('0.0.0-dev'))
|
|
289
|
+
continue;
|
|
290
|
+
if (!byRoot.has(install.packageRoot)) {
|
|
291
|
+
byRoot.set(install.packageRoot, { version: install.version, note: `agents on PATH: ${install.binPath}` });
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (byRoot.size < 2) {
|
|
295
|
+
try {
|
|
296
|
+
fs.unlinkSync(sentinel);
|
|
297
|
+
}
|
|
298
|
+
catch { /* nothing recorded */ }
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const key = [...byRoot.keys()].sort().join('\n');
|
|
275
302
|
try {
|
|
276
|
-
|
|
303
|
+
if (fs.readFileSync(sentinel, 'utf-8') === key)
|
|
304
|
+
return;
|
|
277
305
|
}
|
|
278
|
-
catch {
|
|
279
|
-
|
|
280
|
-
|
|
306
|
+
catch { /* not warned for this set yet */ }
|
|
307
|
+
console.error(chalk.yellow('Multiple agents-cli installs detected:'));
|
|
308
|
+
for (const [root, info] of byRoot) {
|
|
309
|
+
console.error(chalk.gray(` ${root} ${info.version} (${info.note})`));
|
|
310
|
+
}
|
|
311
|
+
console.error(chalk.gray('Upgrades apply to the running copy. Remove a stale copy with: npm uninstall -g --prefix <prefix> @phnx-labs/agents-cli'));
|
|
312
|
+
try {
|
|
313
|
+
fs.mkdirSync(path.dirname(sentinel), { recursive: true });
|
|
314
|
+
fs.writeFileSync(sentinel, key);
|
|
281
315
|
}
|
|
316
|
+
catch { /* best-effort; worst case the warning repeats */ }
|
|
282
317
|
}
|
|
283
318
|
/** Determine whether enough time has elapsed since the last registry fetch. */
|
|
284
319
|
function shouldFetchLatest(cache) {
|
|
@@ -286,18 +321,6 @@ function shouldFetchLatest(cache) {
|
|
|
286
321
|
return true;
|
|
287
322
|
return Date.now() - cache.lastCheck > UPDATE_CHECK_INTERVAL_MS;
|
|
288
323
|
}
|
|
289
|
-
/** Persist the latest known version and current timestamp to the update-check cache. */
|
|
290
|
-
function saveUpdateCheck(latestVersion) {
|
|
291
|
-
try {
|
|
292
|
-
const dir = path.dirname(UPDATE_CHECK_FILE);
|
|
293
|
-
if (!fs.existsSync(dir))
|
|
294
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
295
|
-
fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify({ lastCheck: Date.now(), latestVersion }));
|
|
296
|
-
}
|
|
297
|
-
catch {
|
|
298
|
-
/* best-effort cache update */
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
324
|
/** Fetch the exact latest npm version plus its registry integrity hash. */
|
|
302
325
|
async function fetchNpmPackageMetadata(versionOrTag = 'latest', timeoutMs = 5000) {
|
|
303
326
|
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}/${versionOrTag}`, {
|
|
@@ -320,12 +343,11 @@ function printResolvedPackage(metadata) {
|
|
|
320
343
|
console.log(chalk.gray(`Integrity: ${metadata.integrity}`));
|
|
321
344
|
}
|
|
322
345
|
async function installResolvedPackage(metadata) {
|
|
323
|
-
const
|
|
324
|
-
const
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
await execFileAsync('npm', installArgs);
|
|
346
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
347
|
+
const prefix = deriveGlobalPrefix(packageRoot);
|
|
348
|
+
await installPackageIntoPrefix(`${NPM_PACKAGE_NAME}@${metadata.version}`, prefix);
|
|
349
|
+
verifyInstalledVersion(packageRoot, metadata.version);
|
|
350
|
+
refreshAliasShims(packageRoot);
|
|
329
351
|
}
|
|
330
352
|
/** Present an interactive upgrade prompt (TTY) or a one-line hint (non-TTY). */
|
|
331
353
|
async function promptUpgrade(latestVersion) {
|
|
@@ -342,19 +364,7 @@ async function promptUpgrade(latestVersion) {
|
|
|
342
364
|
],
|
|
343
365
|
});
|
|
344
366
|
if (answer === 'dismiss') {
|
|
345
|
-
|
|
346
|
-
const dir = path.dirname(UPDATE_CHECK_FILE);
|
|
347
|
-
if (!fs.existsSync(dir))
|
|
348
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
349
|
-
const existing = readUpdateCache();
|
|
350
|
-
fs.writeFileSync(UPDATE_CHECK_FILE, JSON.stringify({
|
|
351
|
-
...existing,
|
|
352
|
-
lastCheck: existing?.lastCheck ?? Date.now(),
|
|
353
|
-
latestVersion,
|
|
354
|
-
dismissed: latestVersion,
|
|
355
|
-
}));
|
|
356
|
-
}
|
|
357
|
-
catch { /* best-effort */ }
|
|
367
|
+
dismissUpdateVersion(UPDATE_CHECK_FILE, latestVersion);
|
|
358
368
|
return;
|
|
359
369
|
}
|
|
360
370
|
if (answer === 'now') {
|
|
@@ -362,6 +372,10 @@ async function promptUpgrade(latestVersion) {
|
|
|
362
372
|
let spinner = ora('Resolving package metadata...').start();
|
|
363
373
|
try {
|
|
364
374
|
const metadata = await fetchNpmPackageMetadata();
|
|
375
|
+
// The prompt showed the cached latest, which can lag the registry (the
|
|
376
|
+
// 24h window) — sync the cache to what was actually resolved so later
|
|
377
|
+
// prompts and the install agree on the same version.
|
|
378
|
+
saveUpdateCheck(UPDATE_CHECK_FILE, metadata.version);
|
|
365
379
|
spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${metadata.version}`);
|
|
366
380
|
printResolvedPackage(metadata);
|
|
367
381
|
const approved = await confirm({
|
|
@@ -377,15 +391,20 @@ async function promptUpgrade(latestVersion) {
|
|
|
377
391
|
spinner.succeed(`Upgraded to ${metadata.version}`);
|
|
378
392
|
await showWhatsNew(VERSION, metadata.version);
|
|
379
393
|
console.log();
|
|
380
|
-
// Re-exec
|
|
381
|
-
|
|
394
|
+
// Re-exec the verified install's entrypoint and exit. PATH lookup of
|
|
395
|
+
// `agents` could resolve a different copy (dev build, another prefix)
|
|
396
|
+
// than the one that was just upgraded.
|
|
397
|
+
const entrypoint = path.resolve(__dirname, '..', 'dist', 'index.js');
|
|
398
|
+
const result = spawnSync(process.execPath, [entrypoint, ...process.argv.slice(2)], {
|
|
382
399
|
stdio: 'inherit',
|
|
383
400
|
shell: false,
|
|
384
401
|
});
|
|
385
402
|
process.exit(result.status ?? 0);
|
|
386
403
|
}
|
|
387
|
-
catch {
|
|
388
|
-
|
|
404
|
+
catch (err) {
|
|
405
|
+
if (isPromptCancelled(err))
|
|
406
|
+
return;
|
|
407
|
+
spinner.fail(`Upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
389
408
|
console.log(chalk.gray('Run manually: agents upgrade --yes'));
|
|
390
409
|
}
|
|
391
410
|
console.log();
|
|
@@ -405,7 +424,7 @@ function refreshUpdateCacheInBackground() {
|
|
|
405
424
|
.then((response) => (response.ok ? response.json() : null))
|
|
406
425
|
.then((data) => {
|
|
407
426
|
if (data && typeof data.version === 'string') {
|
|
408
|
-
saveUpdateCheck(data.version);
|
|
427
|
+
saveUpdateCheck(UPDATE_CHECK_FILE, data.version);
|
|
409
428
|
}
|
|
410
429
|
})
|
|
411
430
|
.catch(() => {
|
|
@@ -416,7 +435,8 @@ function refreshUpdateCacheInBackground() {
|
|
|
416
435
|
async function checkForUpdates() {
|
|
417
436
|
if (process.env.AGENTS_CLI_DISABLE_AUTO_UPDATE)
|
|
418
437
|
return;
|
|
419
|
-
|
|
438
|
+
maybeWarnMultiInstall();
|
|
439
|
+
const cache = readUpdateCache(UPDATE_CHECK_FILE);
|
|
420
440
|
// Kick off network refresh in background if stale. Does not block.
|
|
421
441
|
if (shouldFetchLatest(cache)) {
|
|
422
442
|
refreshUpdateCacheInBackground();
|
|
@@ -424,7 +444,7 @@ async function checkForUpdates() {
|
|
|
424
444
|
// Prompt based on current cache (may be from a previous run's background refresh).
|
|
425
445
|
// Skip if the user dismissed this exact version — they'll be prompted again when
|
|
426
446
|
// a newer version appears.
|
|
427
|
-
if (
|
|
447
|
+
if (shouldPromptUpgrade(cache, VERSION)) {
|
|
428
448
|
try {
|
|
429
449
|
await promptUpgrade(cache.latestVersion);
|
|
430
450
|
}
|
|
@@ -699,7 +719,9 @@ program
|
|
|
699
719
|
}
|
|
700
720
|
}
|
|
701
721
|
catch (err) {
|
|
702
|
-
|
|
722
|
+
if (isPromptCancelled(err))
|
|
723
|
+
return;
|
|
724
|
+
spinner.fail(`Upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
703
725
|
console.log(chalk.gray(`Run manually: agents upgrade ${version ? version + ' ' : ''}--yes`));
|
|
704
726
|
}
|
|
705
727
|
});
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -104,6 +104,17 @@ export declare function getAccountEmail(agentId: AgentId, home?: string): Promis
|
|
|
104
104
|
* the agent's local auth/config files. Supports Claude, Codex, and Gemini.
|
|
105
105
|
*/
|
|
106
106
|
export declare function getAccountInfo(agentId: AgentId, home?: string): Promise<AccountInfo>;
|
|
107
|
+
/**
|
|
108
|
+
* Determine when the agent was last used by checking session file mtimes,
|
|
109
|
+
* falling back to config mtime.
|
|
110
|
+
*
|
|
111
|
+
* The session walk stats every transcript under the home's session dir —
|
|
112
|
+
* thousands of files on long-lived installs — and `agents run` rotation calls
|
|
113
|
+
* this once per installed version on every launch. The walk result is cached
|
|
114
|
+
* on disk for a short window so back-to-back launches skip it entirely.
|
|
115
|
+
* Cache read/write is best-effort: any failure falls back to walking.
|
|
116
|
+
*/
|
|
117
|
+
export declare function resolveLastActive(agentId: AgentId, base: string, configPath?: string, cachePath?: string, now?: Date): Date | null;
|
|
107
118
|
/**
|
|
108
119
|
* Quick count of session files for an agent (without full DB scan).
|
|
109
120
|
* Used during init to show approximate session count to user.
|
|
@@ -190,7 +201,13 @@ export declare function listInstalledMcpsWithScope(agentId: AgentId, cwd?: strin
|
|
|
190
201
|
}): InstalledMcp[];
|
|
191
202
|
/** Map of agent name aliases and shorthand identifiers to canonical AgentId values. */
|
|
192
203
|
export declare const AGENT_NAME_ALIASES: Record<string, AgentId>;
|
|
193
|
-
/**
|
|
204
|
+
/**
|
|
205
|
+
* Resolve a user-provided agent name (alias, shorthand, or canonical) to its AgentId.
|
|
206
|
+
* Tolerates a single typo (insertion/deletion/substitution/transposition) against
|
|
207
|
+
* canonical ids and aliases — `cladue` -> claude, `kim` -> kimi, `codx` -> codex —
|
|
208
|
+
* but only when the correction is unambiguous (all distance-1 candidates agree on
|
|
209
|
+
* one agent). Two-letter shorthands are excluded as fuzzy candidates.
|
|
210
|
+
*/
|
|
194
211
|
export declare function resolveAgentName(input: string): AgentId | null;
|
|
195
212
|
/** Check whether the input string matches any known agent name or alias. */
|
|
196
213
|
export declare function isAgentName(input: string): boolean;
|