@phnx-labs/agents-cli 1.20.8 → 1.20.10
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 +11 -0
- package/README.md +1 -1
- package/dist/commands/daemon.js +6 -6
- package/dist/commands/import.js +3 -6
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +75 -28
- 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,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
**`agents inspect .` reads the project `.agents/`, and plugin drill-down shows bundled skills**
|
|
6
|
+
|
|
7
|
+
- `agents inspect .` (and any path to a repo root) now resolves to the project's nested `.agents/` tree when that tree is a populated DotAgents root, instead of the project root itself. Previously a top-level `agents.yaml` version-pin or an unrelated source `skills/` dir at the repo root was mistaken for a DotAgents root, so `inspect .` reported the wrong directory's resources (e.g. `plugins 0` while the real `.agents/plugins/` held a plugin). A bare `.agents`-named dir still resolves to itself, and standalone clones / extra repos that keep resources at the top level (using `.agents/` only for worktrees) are unaffected — their nested `.agents/` is not a DotAgents root, so the top level still wins.
|
|
8
|
+
- `agents inspect <repo> --plugins` now reads plugin bundles through the plugin discoverer: the list shows each plugin's manifest description, and drilling into one (`--plugins <name>`) reports its bundled skills, commands, subagents, hooks, MCP servers, and version. Previously plugins were treated as opaque directories with no description and no view into what they ship.
|
|
9
|
+
|
|
10
|
+
**Single-typo agent names auto-correct everywhere, not just `agents run`**
|
|
11
|
+
|
|
12
|
+
- `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`.
|
|
13
|
+
- 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.
|
|
14
|
+
- Fixes `kimi` being listed as a valid agent but missing from the alias map — `agents view kimi` previously errored. Added `kimi` / `kimi-code` entries.
|
|
15
|
+
|
|
5
16
|
## 1.20.7
|
|
6
17
|
|
|
7
18
|
**`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
|
|
@@ -24,6 +24,8 @@ interface ResourceItem {
|
|
|
24
24
|
linkTarget: string;
|
|
25
25
|
/** One-line description (frontmatter `description:` or first non-frontmatter line). */
|
|
26
26
|
description: string;
|
|
27
|
+
/** Extra detail rows surfaced in detail mode (e.g. a plugin's bundled skills/commands). */
|
|
28
|
+
extra?: Array<[string, string]>;
|
|
27
29
|
}
|
|
28
30
|
interface InspectOptions {
|
|
29
31
|
brief?: boolean;
|
package/dist/commands/inspect.js
CHANGED
|
@@ -17,13 +17,13 @@ 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';
|
|
24
24
|
import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
|
|
25
25
|
import { getAgentResources, listResources, } from '../lib/resources.js';
|
|
26
|
-
import { discoverPlugins } from '../lib/plugins.js';
|
|
26
|
+
import { discoverPlugins, discoverPluginsInDir } from '../lib/plugins.js';
|
|
27
27
|
import { countSessionsInScope } from '../lib/session/discover.js';
|
|
28
28
|
import { damerauLevenshtein } from '../lib/fuzzy.js';
|
|
29
29
|
/** Resource kinds the inspect command can drill into. */
|
|
@@ -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();
|
|
@@ -152,18 +161,23 @@ export function resolveRepoTarget(target, cwd) {
|
|
|
152
161
|
const stat = safeStat(abs);
|
|
153
162
|
if (!stat || !stat.isDirectory())
|
|
154
163
|
return null;
|
|
155
|
-
// A dir
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
// A dir literally named `.agents` is the root itself.
|
|
165
|
+
if (path.basename(abs) === '.agents') {
|
|
166
|
+
return { label: path.basename(path.dirname(abs)), root: abs };
|
|
167
|
+
}
|
|
168
|
+
// A nested `.agents/` that is a populated DotAgents root wins over `abs` — the
|
|
169
|
+
// project case (`agents inspect .` from a repo root whose resources live under
|
|
170
|
+
// `.agents/`, while the repo's own top-level `skills/`, `agents.yaml` pin, etc.
|
|
171
|
+
// are unrelated source, not a DotAgents tree).
|
|
172
|
+
const nested = path.join(abs, '.agents');
|
|
173
|
+
if (isDotAgentsRoot(nested)) {
|
|
174
|
+
return { label: path.basename(abs), root: nested };
|
|
175
|
+
}
|
|
176
|
+
// Otherwise treat `abs` itself as the root: standalone clones and extra repos
|
|
177
|
+
// like ~/.agents-extras keep resources at the top level and use `.agents/`
|
|
178
|
+
// only for worktrees (so their nested `.agents/` is not a DotAgents root).
|
|
158
179
|
if (isDotAgentsRoot(abs)) {
|
|
159
|
-
|
|
160
|
-
return { label, root: abs };
|
|
161
|
-
}
|
|
162
|
-
if (path.basename(abs) !== '.agents') {
|
|
163
|
-
const nested = path.join(abs, '.agents');
|
|
164
|
-
if (safeStat(nested)?.isDirectory()) {
|
|
165
|
-
return { label: path.basename(abs), root: nested };
|
|
166
|
-
}
|
|
180
|
+
return { label: path.basename(abs), root: abs };
|
|
167
181
|
}
|
|
168
182
|
return null;
|
|
169
183
|
}
|
|
@@ -195,6 +209,14 @@ async function inspectRepo(repo, options) {
|
|
|
195
209
|
}
|
|
196
210
|
/** List one resource kind from a single repo root — no layering, no overrides. */
|
|
197
211
|
export function collectRepoKind(repo, kind) {
|
|
212
|
+
// Plugins are bundles with a manifest + nested skills/commands/hooks — read
|
|
213
|
+
// them through the plugin discoverer so the manifest description and bundled
|
|
214
|
+
// resources surface, rather than treating each as an opaque directory.
|
|
215
|
+
if (kind === 'plugins') {
|
|
216
|
+
return discoverPluginsInDir(path.join(repo.root, 'plugins'))
|
|
217
|
+
.map(p => pluginToItem(p, repo.label))
|
|
218
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
219
|
+
}
|
|
198
220
|
const dir = path.join(repo.root, kind);
|
|
199
221
|
let entries;
|
|
200
222
|
try {
|
|
@@ -477,14 +499,35 @@ function collectKind(agent, versionHome, kind) {
|
|
|
477
499
|
}
|
|
478
500
|
}
|
|
479
501
|
function pluginItems() {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
502
|
+
return discoverPlugins().map(p => pluginToItem(p, 'user'));
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Map a discovered plugin to a resource item, surfacing the manifest description
|
|
506
|
+
* and the bundle's nested resources (skills, commands, hooks, ...) as detail rows.
|
|
507
|
+
*/
|
|
508
|
+
function pluginToItem(plugin, source) {
|
|
509
|
+
const extra = [];
|
|
510
|
+
const list = (names) => names.length <= 8 ? names.join(', ') : `${names.slice(0, 8).join(', ')}, +${names.length - 8} more`;
|
|
511
|
+
if (plugin.skills.length)
|
|
512
|
+
extra.push(['skills', `${plugin.skills.length} (${list(plugin.skills)})`]);
|
|
513
|
+
if (plugin.commands.length)
|
|
514
|
+
extra.push(['commands', `${plugin.commands.length} (${list(plugin.commands)})`]);
|
|
515
|
+
if (plugin.agentDefs.length)
|
|
516
|
+
extra.push(['subagents', `${plugin.agentDefs.length} (${list(plugin.agentDefs)})`]);
|
|
517
|
+
if (plugin.hooks.length)
|
|
518
|
+
extra.push(['hooks', String(plugin.hooks.length)]);
|
|
519
|
+
if (plugin.mcpServers.length)
|
|
520
|
+
extra.push(['mcp', list(plugin.mcpServers)]);
|
|
521
|
+
if (plugin.manifest.version)
|
|
522
|
+
extra.push(['version', plugin.manifest.version]);
|
|
523
|
+
return {
|
|
524
|
+
name: plugin.name,
|
|
525
|
+
source,
|
|
526
|
+
path: plugin.root,
|
|
527
|
+
linkTarget: linkTarget(plugin.root),
|
|
528
|
+
description: plugin.manifest.description ?? '',
|
|
529
|
+
extra,
|
|
530
|
+
};
|
|
488
531
|
}
|
|
489
532
|
function entriesFromAgentResources(agent, versionHome, kind) {
|
|
490
533
|
const res = getAgentResources(agent, { home: versionHome });
|
|
@@ -553,6 +596,10 @@ function buildDetailRows(item, kind) {
|
|
|
553
596
|
rows.push(['tools', fm.tools.join(', ')]);
|
|
554
597
|
}
|
|
555
598
|
}
|
|
599
|
+
// Plugin bundles carry their nested resources as pre-built rows.
|
|
600
|
+
if (kind === 'plugins' && item.extra) {
|
|
601
|
+
rows.push(...item.extra);
|
|
602
|
+
}
|
|
556
603
|
return rows;
|
|
557
604
|
}
|
|
558
605
|
function findMatches(items, query) {
|
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
|
});
|