@phnx-labs/agents-cli 1.20.5 → 1.20.6
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/README.md +1 -1
- package/dist/commands/browser.js +31 -4
- package/dist/commands/computer.js +10 -2
- package/dist/commands/defaults.d.ts +7 -0
- package/dist/commands/defaults.js +89 -0
- package/dist/commands/exec.js +24 -6
- package/dist/commands/rules.js +3 -3
- package/dist/commands/secrets.js +46 -9
- package/dist/commands/setup.js +2 -2
- package/dist/commands/teams.js +108 -11
- package/dist/commands/view.d.ts +12 -1
- package/dist/commands/view.js +121 -38
- package/dist/index.js +38 -21
- package/dist/lib/agents.d.ts +10 -6
- package/dist/lib/agents.js +23 -14
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +84 -3
- package/dist/lib/exec.js +24 -4
- package/dist/lib/migrate.js +6 -4
- package/dist/lib/permissions.d.ts +23 -0
- package/dist/lib/permissions.js +89 -7
- package/dist/lib/plugin-marketplace.js +1 -1
- package/dist/lib/project-launch.d.ts +5 -0
- package/dist/lib/project-launch.js +37 -0
- package/dist/lib/pty-server.js +7 -4
- package/dist/lib/resources/rules.js +1 -1
- package/dist/lib/resources/skills.js +1 -1
- package/dist/lib/resources.d.ts +2 -0
- package/dist/lib/resources.js +2 -1
- package/dist/lib/rotate.js +6 -18
- package/dist/lib/run-config.d.ts +9 -0
- package/dist/lib/run-config.js +35 -0
- package/dist/lib/run-defaults.d.ts +42 -0
- package/dist/lib/run-defaults.js +180 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/install-helper.d.ts +11 -3
- package/dist/lib/secrets/install-helper.js +48 -6
- package/dist/lib/secrets/linux.d.ts +12 -0
- package/dist/lib/secrets/linux.js +30 -16
- package/dist/lib/shims.d.ts +9 -1
- package/dist/lib/shims.js +35 -3
- package/dist/lib/staleness/detectors/hooks.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/teams/api.d.ts +67 -0
- package/dist/lib/teams/api.js +78 -0
- package/dist/lib/types.d.ts +15 -6
- package/dist/lib/versions.js +4 -4
- package/package.json +5 -2
package/dist/commands/view.js
CHANGED
|
@@ -15,6 +15,7 @@ import { isGitRepo, getGitSyncStatus } from '../lib/git.js';
|
|
|
15
15
|
import { getCentralRulesFileName } from '../lib/rules/rules.js';
|
|
16
16
|
import { composeRulesFromState } from '../lib/rules/compose.js';
|
|
17
17
|
import { getConfiguredRunStrategy } from '../lib/rotate.js';
|
|
18
|
+
import { resolveRunDefaults } from '../lib/run-defaults.js';
|
|
18
19
|
import { listProfiles, profileSummary } from '../lib/profiles.js';
|
|
19
20
|
import { loadManifest, isStale } from '../lib/staleness/index.js';
|
|
20
21
|
import { confirm } from '@inquirer/prompts';
|
|
@@ -113,6 +114,29 @@ function getProjectVersionFromCwd(agent) {
|
|
|
113
114
|
return null;
|
|
114
115
|
}
|
|
115
116
|
}
|
|
117
|
+
const SECTION_KEYS = ['commands', 'skills', 'mcp', 'workflows', 'plugins', 'rules', 'hooks', 'promptcuts'];
|
|
118
|
+
/**
|
|
119
|
+
* Decide whether a section should render given the filter. If no flags are set,
|
|
120
|
+
* everything renders (current behavior). If any flag is set, only those sections
|
|
121
|
+
* render — flags are additive.
|
|
122
|
+
*/
|
|
123
|
+
function shouldRenderSection(key, filter) {
|
|
124
|
+
if (!filter)
|
|
125
|
+
return true;
|
|
126
|
+
const anySet = SECTION_KEYS.some((k) => filter[k]);
|
|
127
|
+
if (!anySet)
|
|
128
|
+
return true;
|
|
129
|
+
return filter[key] === true;
|
|
130
|
+
}
|
|
131
|
+
/** Trim a description to a column-friendly snippet. Strips newlines, collapses whitespace. */
|
|
132
|
+
function summarizeDescription(desc, maxLen = 80) {
|
|
133
|
+
if (!desc)
|
|
134
|
+
return '';
|
|
135
|
+
const cleaned = desc.replace(/\s+/g, ' ').trim();
|
|
136
|
+
if (cleaned.length <= maxLen)
|
|
137
|
+
return cleaned;
|
|
138
|
+
return cleaned.slice(0, maxLen - 1).trimEnd() + '…';
|
|
139
|
+
}
|
|
116
140
|
function getProfileSummaries(filterAgentId) {
|
|
117
141
|
return listProfiles()
|
|
118
142
|
.filter((profile) => !filterAgentId || profile.host.agent === filterAgentId)
|
|
@@ -338,6 +362,12 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
338
362
|
// Otherwise it reflects install time (misleading "just now" for fresh installs).
|
|
339
363
|
const activeStr = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
|
|
340
364
|
const hasActive = activeStr.length > 0;
|
|
365
|
+
const runDefaults = resolveRunDefaults(agentId, version);
|
|
366
|
+
const runDefaultBits = [];
|
|
367
|
+
if (runDefaults.mode)
|
|
368
|
+
runDefaultBits.push(`mode:${runDefaults.mode}`);
|
|
369
|
+
if (runDefaults.model)
|
|
370
|
+
runDefaultBits.push(`model:${runDefaults.model}`);
|
|
341
371
|
if (!hasEmail && !hasUsage) {
|
|
342
372
|
// Installed but never signed in
|
|
343
373
|
parts.push(chalk.gray('(not signed in — run ' + agent.cliCommand + ' to log in)'));
|
|
@@ -359,6 +389,9 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
359
389
|
if (hasActive)
|
|
360
390
|
parts.push(activeStr);
|
|
361
391
|
}
|
|
392
|
+
if (runDefaultBits.length > 0) {
|
|
393
|
+
parts.push(chalk.gray(`run ${runDefaultBits.join(' ')}`));
|
|
394
|
+
}
|
|
362
395
|
console.log(parts.join(' '));
|
|
363
396
|
if (showPaths) {
|
|
364
397
|
const versionDir = getVersionDir(agentId, version);
|
|
@@ -568,7 +601,7 @@ async function showInstalledVersions(filterAgentId) {
|
|
|
568
601
|
* Show detailed resources for a specific agent version.
|
|
569
602
|
* Called when: `agents view claude@2.0.65` or `agents view claude@default`
|
|
570
603
|
*/
|
|
571
|
-
async function showAgentResources(agentId, requestedVersion) {
|
|
604
|
+
async function showAgentResources(agentId, requestedVersion, filter) {
|
|
572
605
|
const spinner = ora({ text: 'Loading...', isSilent: !process.stdout.isTTY }).start();
|
|
573
606
|
const cwd = process.cwd();
|
|
574
607
|
const agentsDir = getAgentsDir();
|
|
@@ -660,6 +693,8 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
660
693
|
})),
|
|
661
694
|
skills: resources.skills.map(r => ({
|
|
662
695
|
...r,
|
|
696
|
+
// ruleCount of 0 is noise — every skill has 0 unless it ships subrules, which is rare.
|
|
697
|
+
ruleCount: r.ruleCount && r.ruleCount > 0 ? r.ruleCount : undefined,
|
|
663
698
|
syncState: r.scope === 'project' ? undefined : getSyncState(r.name, 'skills', skillsSync),
|
|
664
699
|
})),
|
|
665
700
|
skillErrors: resources.skillErrors,
|
|
@@ -705,7 +740,9 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
705
740
|
: chalk.gray('[system]');
|
|
706
741
|
display += ` ${sourceTag}`;
|
|
707
742
|
const syncStr = r.syncState ? chalk.gray(` [${r.syncState}]`) : '';
|
|
708
|
-
|
|
743
|
+
const descSnippet = summarizeDescription(r.description);
|
|
744
|
+
const descStr = descSnippet ? chalk.gray(` ${descSnippet}`) : '';
|
|
745
|
+
console.log(` ${display}${syncStr}${descStr}`);
|
|
709
746
|
}
|
|
710
747
|
}
|
|
711
748
|
// Render promptcuts (cross-agent, not per-version). Shortcuts are layered
|
|
@@ -722,43 +759,53 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
722
759
|
const label = `${count} shortcut${count === 1 ? '' : 's'}`;
|
|
723
760
|
console.log(` ${chalk.green(label).padEnd(24)} ${chalk.gray(formatPath(getEffectivePromptcutsPath(), cwd))}`);
|
|
724
761
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
home
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
console.log();
|
|
742
|
-
|
|
743
|
-
|
|
762
|
+
const anyFilterSet = filter && SECTION_KEYS.some((k) => filter[k]);
|
|
763
|
+
// 1. Agent CLI info — skip the header entirely when the user asked for a
|
|
764
|
+
// specific section. They want "nothing more or less."
|
|
765
|
+
if (!anyFilterSet) {
|
|
766
|
+
console.log(chalk.bold('Agent CLIs\n'));
|
|
767
|
+
const accountInfo = await getAccountInfo(agentId, home);
|
|
768
|
+
const usageInfo = await getUsageInfoForIdentity({
|
|
769
|
+
agentId,
|
|
770
|
+
home,
|
|
771
|
+
cliVersion: version,
|
|
772
|
+
info: accountInfo,
|
|
773
|
+
});
|
|
774
|
+
const emailStr = accountInfo.email ? chalk.cyan(` ${accountInfo.email}`) : '';
|
|
775
|
+
const status = chalk.green(version);
|
|
776
|
+
const usageStr = formatUsageSummary(accountInfo.plan, null);
|
|
777
|
+
const usagePart = usageStr ? ` ${usageStr}` : '';
|
|
778
|
+
console.log(` ${colorAgent(agentId)(AGENTS[agentId].name.padEnd(14))} ${status}${emailStr}${usagePart}`);
|
|
779
|
+
const usageLines = formatUsageSection(usageInfo);
|
|
780
|
+
if (usageLines.length > 0) {
|
|
781
|
+
console.log();
|
|
782
|
+
for (const line of usageLines) {
|
|
783
|
+
console.log(line);
|
|
784
|
+
}
|
|
744
785
|
}
|
|
745
786
|
}
|
|
746
787
|
// 2. Resources
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
console.log(
|
|
788
|
+
if (shouldRenderSection('commands', filter)) {
|
|
789
|
+
renderSection('Commands', agentData.commands);
|
|
790
|
+
}
|
|
791
|
+
if (shouldRenderSection('skills', filter)) {
|
|
792
|
+
renderSection('Skills', agentData.skills);
|
|
793
|
+
// Show skill parse errors only when skills section is visible
|
|
794
|
+
if (agentData.skillErrors.length > 0) {
|
|
795
|
+
console.log(`\n ${chalk.red('Skill Errors')}:`);
|
|
796
|
+
for (const err of agentData.skillErrors) {
|
|
797
|
+
console.log(` ${chalk.red(err.name.padEnd(20))} ${chalk.gray(err.error)}`);
|
|
798
|
+
console.log(` ${chalk.gray(formatPath(err.path, cwd))}`);
|
|
799
|
+
}
|
|
755
800
|
}
|
|
756
801
|
}
|
|
757
|
-
|
|
758
|
-
|
|
802
|
+
if (shouldRenderSection('mcp', filter)) {
|
|
803
|
+
renderSection('MCP Servers', agentData.mcp);
|
|
804
|
+
}
|
|
805
|
+
if (shouldRenderSection('workflows', filter) && isCapable(agentId, 'workflows')) {
|
|
759
806
|
renderSection('Workflows', agentData.workflows);
|
|
760
807
|
}
|
|
761
|
-
if (isCapable(agentId, 'plugins')) {
|
|
808
|
+
if (shouldRenderSection('plugins', filter) && isCapable(agentId, 'plugins')) {
|
|
762
809
|
const plugins = discoverPlugins().filter(p => pluginSupportsAgent(p, agentId));
|
|
763
810
|
console.log(chalk.bold('\nPlugins\n'));
|
|
764
811
|
if (plugins.length === 0) {
|
|
@@ -847,11 +894,18 @@ async function showAgentResources(agentId, requestedVersion) {
|
|
|
847
894
|
}
|
|
848
895
|
}
|
|
849
896
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
897
|
+
if (shouldRenderSection('rules', filter)) {
|
|
898
|
+
renderRulesSection();
|
|
899
|
+
}
|
|
900
|
+
if (shouldRenderSection('hooks', filter)) {
|
|
901
|
+
renderSection('Hooks', agentData.hooks);
|
|
902
|
+
}
|
|
903
|
+
if (shouldRenderSection('promptcuts', filter)) {
|
|
904
|
+
renderPromptcuts();
|
|
905
|
+
}
|
|
906
|
+
// Show legend at the end if git repo exists and we showed all sections.
|
|
907
|
+
// Filtered single-section views skip it — noise for promptcuts or plugins.
|
|
908
|
+
if (hasGitRepo && !anyFilterSet) {
|
|
855
909
|
console.log();
|
|
856
910
|
console.log(chalk.gray('Legend:'), chalk.green('Tracked'), chalk.blue('Local-only'), chalk.yellow('Modified'), chalk.red('Deleted'));
|
|
857
911
|
}
|
|
@@ -1131,6 +1185,17 @@ export async function viewAction(agentArg, options) {
|
|
|
1131
1185
|
const prune = options?.prune === true;
|
|
1132
1186
|
const yes = options?.yes === true;
|
|
1133
1187
|
const dryRun = options?.dryRun === true;
|
|
1188
|
+
const filter = {
|
|
1189
|
+
commands: options?.commands,
|
|
1190
|
+
skills: options?.skills,
|
|
1191
|
+
mcp: options?.mcp,
|
|
1192
|
+
workflows: options?.workflows,
|
|
1193
|
+
plugins: options?.plugins,
|
|
1194
|
+
rules: options?.rules,
|
|
1195
|
+
hooks: options?.hooks,
|
|
1196
|
+
promptcuts: options?.promptcuts,
|
|
1197
|
+
};
|
|
1198
|
+
const filterIsSet = SECTION_KEYS.some((k) => filter[k]);
|
|
1134
1199
|
if (!agentArg) {
|
|
1135
1200
|
if (prune) {
|
|
1136
1201
|
await pruneDuplicates(undefined, yes, dryRun);
|
|
@@ -1180,7 +1245,12 @@ export async function viewAction(agentArg, options) {
|
|
|
1180
1245
|
}
|
|
1181
1246
|
if (requestedVersion) {
|
|
1182
1247
|
// Specific version requested: show detailed resources
|
|
1183
|
-
await showAgentResources(agentId, requestedVersion);
|
|
1248
|
+
await showAgentResources(agentId, requestedVersion, filter);
|
|
1249
|
+
}
|
|
1250
|
+
else if (filterIsSet) {
|
|
1251
|
+
// `agents view claude --skills` → fall through to detail view on default.
|
|
1252
|
+
// Section filters only make sense for the per-version detail view.
|
|
1253
|
+
await showAgentResources(agentId, 'default', filter);
|
|
1184
1254
|
}
|
|
1185
1255
|
else {
|
|
1186
1256
|
// Just agent name: show versions for that agent
|
|
@@ -1196,6 +1266,14 @@ export function registerViewCommand(program) {
|
|
|
1196
1266
|
.option('--prune', 'Remove older installed versions that share an account with a newer installed version. Skips the global default.')
|
|
1197
1267
|
.option('--dry-run', 'With --prune, show duplicate versions without deleting')
|
|
1198
1268
|
.option('-y, --yes', 'Skip the prune confirmation prompt.')
|
|
1269
|
+
.option('--commands', 'Show only commands in the detail view.')
|
|
1270
|
+
.option('--skills', 'Show only skills in the detail view.')
|
|
1271
|
+
.option('--mcp', 'Show only MCP servers in the detail view.')
|
|
1272
|
+
.option('--workflows', 'Show only workflows in the detail view.')
|
|
1273
|
+
.option('--plugins', 'Show only plugins in the detail view.')
|
|
1274
|
+
.option('--rules', 'Show only rules in the detail view.')
|
|
1275
|
+
.option('--hooks', 'Show only hooks in the detail view.')
|
|
1276
|
+
.option('--promptcuts', 'Show only promptcuts in the detail view.')
|
|
1199
1277
|
.addHelpText('after', `
|
|
1200
1278
|
Examples:
|
|
1201
1279
|
# Show all installed agents with versions, accounts, and usage
|
|
@@ -1216,6 +1294,11 @@ Examples:
|
|
|
1216
1294
|
agents view claude --prune
|
|
1217
1295
|
agents view claude --prune -y
|
|
1218
1296
|
|
|
1297
|
+
# Filter the detail view to a single section (combinable)
|
|
1298
|
+
agents view claude@default --skills
|
|
1299
|
+
agents view claude@default --plugins --workflows
|
|
1300
|
+
agents view claude --commands # implicitly the default version
|
|
1301
|
+
|
|
1219
1302
|
When to use:
|
|
1220
1303
|
- Checking which agents are installed and what their default versions are
|
|
1221
1304
|
- Seeing which account each version is logged into (useful for multi-account setups)
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,7 @@ import { registerDaemonCommands } from './commands/daemon.js';
|
|
|
76
76
|
import { registerRoutinesCommands } from './commands/routines.js';
|
|
77
77
|
import { registerRunCommand } from './commands/exec.js';
|
|
78
78
|
import { registerModelsCommand } from './commands/models.js';
|
|
79
|
+
import { registerDefaultsCommands } from './commands/defaults.js';
|
|
79
80
|
import { registerPruneCommand } from './commands/prune.js';
|
|
80
81
|
import { registerTrashCommands } from './commands/trash.js';
|
|
81
82
|
import { registerDoctorCommand } from './commands/doctor.js';
|
|
@@ -151,6 +152,7 @@ Packages:
|
|
|
151
152
|
|
|
152
153
|
Run and dispatch:
|
|
153
154
|
run <agent|profile> [prompt] Run an agent. Omit prompt for interactive mode.
|
|
155
|
+
defaults Configure run defaults by agent/version selector
|
|
154
156
|
teams Coordinate multiple agents on shared work
|
|
155
157
|
routines Run agents on a cron schedule (scheduler auto-starts)
|
|
156
158
|
sessions Browse, search, and replay past runs (live-search in TTY; grouped by workspace)
|
|
@@ -217,9 +219,12 @@ async function showWhatsNew(fromVersion, toVersion) {
|
|
|
217
219
|
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
|
|
218
220
|
if (versionMatch) {
|
|
219
221
|
currentVersion = versionMatch[1];
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
// Only the range the user actually moved through: (fromVersion, toVersion].
|
|
223
|
+
// Bounding the top end matters when upgrading to a specific older
|
|
224
|
+
// version, and guards against a changelog that lists unreleased entries.
|
|
225
|
+
const inRange = compareVersions(currentVersion, fromVersion) > 0 &&
|
|
226
|
+
compareVersions(currentVersion, toVersion) <= 0;
|
|
227
|
+
inRelevantSection = inRange;
|
|
223
228
|
if (inRelevantSection) {
|
|
224
229
|
relevantChanges.push('');
|
|
225
230
|
relevantChanges.push(chalk.bold(`v${currentVersion}`));
|
|
@@ -279,11 +284,14 @@ function saveUpdateCheck(latestVersion) {
|
|
|
279
284
|
}
|
|
280
285
|
}
|
|
281
286
|
/** Fetch the exact latest npm version plus its registry integrity hash. */
|
|
282
|
-
async function
|
|
283
|
-
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}
|
|
287
|
+
async function fetchNpmPackageMetadata(versionOrTag = 'latest', timeoutMs = 5000) {
|
|
288
|
+
const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}/${versionOrTag}`, {
|
|
284
289
|
signal: AbortSignal.timeout(timeoutMs),
|
|
285
290
|
});
|
|
286
291
|
if (!response.ok) {
|
|
292
|
+
if (response.status === 404) {
|
|
293
|
+
throw new Error(`${NPM_PACKAGE_NAME}@${versionOrTag} not found on npm`);
|
|
294
|
+
}
|
|
287
295
|
throw new Error('Could not reach npm registry');
|
|
288
296
|
}
|
|
289
297
|
const data = await response.json();
|
|
@@ -338,7 +346,7 @@ async function promptUpgrade(latestVersion) {
|
|
|
338
346
|
const { spawnSync } = await import('child_process');
|
|
339
347
|
let spinner = ora('Resolving package metadata...').start();
|
|
340
348
|
try {
|
|
341
|
-
const metadata = await
|
|
349
|
+
const metadata = await fetchNpmPackageMetadata();
|
|
342
350
|
spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${metadata.version}`);
|
|
343
351
|
printResolvedPackage(metadata);
|
|
344
352
|
const approved = await confirm({
|
|
@@ -583,6 +591,7 @@ registerPackagesCommands(program);
|
|
|
583
591
|
registerDaemonCommands(program);
|
|
584
592
|
registerRoutinesCommands(program);
|
|
585
593
|
registerRunCommand(program);
|
|
594
|
+
registerDefaultsCommands(program);
|
|
586
595
|
registerModelsCommand(program);
|
|
587
596
|
registerPruneCommand(program);
|
|
588
597
|
registerTrashCommands(program);
|
|
@@ -627,26 +636,31 @@ for (const alias of ['jobs', 'cron']) {
|
|
|
627
636
|
}
|
|
628
637
|
program
|
|
629
638
|
.command('upgrade')
|
|
630
|
-
.description('Upgrade agents-cli to the latest version')
|
|
639
|
+
.description('Upgrade agents-cli to the latest version (or a specific [version])')
|
|
640
|
+
.argument('[version]', 'Target version or dist-tag to install (default: latest)')
|
|
631
641
|
.option('-y, --yes', 'Install without an interactive confirmation prompt')
|
|
632
|
-
.action(async (options) => {
|
|
633
|
-
|
|
642
|
+
.action(async (version, options) => {
|
|
643
|
+
const target = version ?? 'latest';
|
|
644
|
+
let spinner = ora(version ? `Resolving ${NPM_PACKAGE_NAME}@${target}...` : 'Checking for updates...').start();
|
|
634
645
|
try {
|
|
635
|
-
const metadata = await
|
|
636
|
-
const
|
|
637
|
-
if (
|
|
638
|
-
spinner.succeed(`Already on
|
|
646
|
+
const metadata = await fetchNpmPackageMetadata(target);
|
|
647
|
+
const resolvedVersion = metadata.version;
|
|
648
|
+
if (resolvedVersion === VERSION) {
|
|
649
|
+
spinner.succeed(`Already on ${VERSION}`);
|
|
639
650
|
return;
|
|
640
651
|
}
|
|
641
|
-
|
|
642
|
-
|
|
652
|
+
// For `latest` (no explicit version) skip when already ahead. When a
|
|
653
|
+
// version is named explicitly, honor it even if it's a downgrade.
|
|
654
|
+
if (!version && compareVersions(resolvedVersion, VERSION) <= 0) {
|
|
655
|
+
spinner.succeed(`Already ahead of latest (${VERSION} >= ${resolvedVersion})`);
|
|
643
656
|
return;
|
|
644
657
|
}
|
|
645
|
-
|
|
658
|
+
const direction = compareVersions(resolvedVersion, VERSION) < 0 ? 'Downgrade' : 'Upgrade';
|
|
659
|
+
spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${resolvedVersion}`);
|
|
646
660
|
printResolvedPackage(metadata);
|
|
647
661
|
if (isInteractiveTerminal() && !options.yes) {
|
|
648
662
|
const approved = await confirm({
|
|
649
|
-
message: `Install ${NPM_PACKAGE_NAME}@${
|
|
663
|
+
message: `Install ${NPM_PACKAGE_NAME}@${resolvedVersion}?`,
|
|
650
664
|
default: false,
|
|
651
665
|
});
|
|
652
666
|
if (!approved) {
|
|
@@ -654,14 +668,17 @@ program
|
|
|
654
668
|
return;
|
|
655
669
|
}
|
|
656
670
|
}
|
|
657
|
-
spinner = ora(
|
|
671
|
+
spinner = ora(`${direction === 'Downgrade' ? 'Downgrading' : 'Upgrading'} ${VERSION} -> ${resolvedVersion}...`).start();
|
|
658
672
|
await installResolvedPackage(metadata);
|
|
659
|
-
spinner.succeed(
|
|
660
|
-
|
|
673
|
+
spinner.succeed(`${direction}d to ${resolvedVersion}`);
|
|
674
|
+
// Only show the changelog for a genuine upgrade range.
|
|
675
|
+
if (compareVersions(resolvedVersion, VERSION) > 0) {
|
|
676
|
+
await showWhatsNew(VERSION, resolvedVersion);
|
|
677
|
+
}
|
|
661
678
|
}
|
|
662
679
|
catch (err) {
|
|
663
680
|
spinner.fail('Upgrade failed');
|
|
664
|
-
console.log(chalk.gray(
|
|
681
|
+
console.log(chalk.gray(`Run manually: agents upgrade ${version ? version + ' ' : ''}--yes`));
|
|
665
682
|
}
|
|
666
683
|
});
|
|
667
684
|
registerPullCommand(program);
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -69,12 +69,16 @@ export declare function ensureSkillsDir(agentId: AgentId): void;
|
|
|
69
69
|
* The agent's config-dir name relative to $HOME — e.g. '.claude',
|
|
70
70
|
* '.gemini/antigravity-cli', '.config/amp', '.kimi-code'.
|
|
71
71
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
72
|
+
* Path segment to join onto a (version) home root when locating an agent's
|
|
73
|
+
* commands/skills/plugins. Do NOT hardcode `.${agentId}`: it is wrong for
|
|
74
|
+
* every agent whose config dir is nested or under ~/.config — antigravity
|
|
75
|
+
* (~/.gemini/antigravity-cli), amp (~/.config/amp), goose (~/.config/goose),
|
|
76
|
+
* kimi (~/.kimi-code). Mirrors the shim configDirName derivation in shims.ts.
|
|
77
|
+
*
|
|
78
|
+
* Relativized against the module-level HOME constant (the same value used to
|
|
79
|
+
* build every `configDir`), NOT a fresh `os.homedir()` — so the result stays a
|
|
80
|
+
* clean relative name even when HOME is overridden after module load (tests,
|
|
81
|
+
* sandboxes). Using `os.homedir()` here would yield `../../real/home/.claude`.
|
|
78
82
|
*/
|
|
79
83
|
export declare function agentConfigDirName(agentId: AgentId): string;
|
|
80
84
|
/** Account identity and billing information extracted from an agent's auth config. */
|
package/dist/lib/agents.js
CHANGED
|
@@ -446,13 +446,18 @@ export const AGENTS = {
|
|
|
446
446
|
rulesImports: true,
|
|
447
447
|
},
|
|
448
448
|
},
|
|
449
|
+
// Kimi Code CLI (`kimi`) — Moonshot AI coding agent.
|
|
450
|
+
// Install: `curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash`
|
|
451
|
+
// or: `npm install -g @moonshot-ai/kimi-code`
|
|
452
|
+
// Config: `~/.kimi-code/config.toml`, `~/.kimi-code/mcp.json`,
|
|
453
|
+
// `~/.kimi-code/skills/`, `~/.kimi-code/hooks/`
|
|
449
454
|
kimi: {
|
|
450
455
|
id: 'kimi',
|
|
451
456
|
name: 'Kimi',
|
|
452
457
|
color: 'magentaBright',
|
|
453
|
-
cliCommand: 'kimi
|
|
454
|
-
npmPackage: '',
|
|
455
|
-
installScript: '',
|
|
458
|
+
cliCommand: 'kimi',
|
|
459
|
+
npmPackage: '@moonshot-ai/kimi-code',
|
|
460
|
+
installScript: 'curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash',
|
|
456
461
|
configDir: path.join(HOME, '.kimi-code'),
|
|
457
462
|
commandsDir: '',
|
|
458
463
|
commandsSubdir: '',
|
|
@@ -465,14 +470,14 @@ export const AGENTS = {
|
|
|
465
470
|
capabilities: {
|
|
466
471
|
hooks: true,
|
|
467
472
|
mcp: true,
|
|
468
|
-
allowlist:
|
|
469
|
-
skills:
|
|
473
|
+
allowlist: true,
|
|
474
|
+
skills: true,
|
|
470
475
|
commands: false,
|
|
471
|
-
plugins:
|
|
476
|
+
plugins: true,
|
|
472
477
|
subagents: false,
|
|
473
478
|
rules: { file: 'AGENTS.md' },
|
|
474
479
|
workflows: false,
|
|
475
|
-
modes: ['plan', 'edit', 'skip'],
|
|
480
|
+
modes: ['plan', 'edit', 'auto', 'skip'],
|
|
476
481
|
rulesImports: false,
|
|
477
482
|
},
|
|
478
483
|
},
|
|
@@ -690,15 +695,19 @@ export function ensureSkillsDir(agentId) {
|
|
|
690
695
|
* The agent's config-dir name relative to $HOME — e.g. '.claude',
|
|
691
696
|
* '.gemini/antigravity-cli', '.config/amp', '.kimi-code'.
|
|
692
697
|
*
|
|
693
|
-
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
698
|
+
* Path segment to join onto a (version) home root when locating an agent's
|
|
699
|
+
* commands/skills/plugins. Do NOT hardcode `.${agentId}`: it is wrong for
|
|
700
|
+
* every agent whose config dir is nested or under ~/.config — antigravity
|
|
701
|
+
* (~/.gemini/antigravity-cli), amp (~/.config/amp), goose (~/.config/goose),
|
|
702
|
+
* kimi (~/.kimi-code). Mirrors the shim configDirName derivation in shims.ts.
|
|
703
|
+
*
|
|
704
|
+
* Relativized against the module-level HOME constant (the same value used to
|
|
705
|
+
* build every `configDir`), NOT a fresh `os.homedir()` — so the result stays a
|
|
706
|
+
* clean relative name even when HOME is overridden after module load (tests,
|
|
707
|
+
* sandboxes). Using `os.homedir()` here would yield `../../real/home/.claude`.
|
|
699
708
|
*/
|
|
700
709
|
export function agentConfigDirName(agentId) {
|
|
701
|
-
return path.relative(
|
|
710
|
+
return path.relative(HOME, AGENTS[agentId].configDir);
|
|
702
711
|
}
|
|
703
712
|
/** Return the email address associated with the agent's auth config, or null. */
|
|
704
713
|
export async function getAccountEmail(agentId, home) {
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { ChromeOptions } from './types.js';
|
|
2
2
|
import type { BrowserType } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* True when `binaryPath` is a shebang script rather than a native browser
|
|
5
|
+
* executable. The Linux distro launchers (`/usr/bin/brave-browser`, …) are such
|
|
6
|
+
* scripts; `launchBrowser` can't drive one over `--remote-debugging-pipe` (see
|
|
7
|
+
* resolveBrowserBinary). `profiles doctor` uses this to flag a profile whose
|
|
8
|
+
* binary resolves to a wrapper we couldn't unwrap. Shebang scripts are a
|
|
9
|
+
* Linux/Unix concept — returns false on Windows/macOS app bundles.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isLauncherScript(binaryPath: string): boolean;
|
|
12
|
+
export declare function resolveBrowserBinary(binaryPath: string): string;
|
|
3
13
|
export declare function findBrowserPath(browserType: BrowserType, customBinary?: string): string;
|
|
4
14
|
/**
|
|
5
15
|
* Walk the per-platform priority list and return the first browser that's
|
|
@@ -42,12 +42,90 @@ const BROWSER_PATHS = {
|
|
|
42
42
|
custom: [],
|
|
43
43
|
},
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* On Debian/Ubuntu the canonical launchers under `/usr/bin`
|
|
47
|
+
* (`brave-browser`, `google-chrome`, `chromium`) are not the browser ELF —
|
|
48
|
+
* they're `#!/bin/bash` wrapper scripts (the upstream Chromium wrapper) that,
|
|
49
|
+
* as their final step, run the real binary as a NON-exec child:
|
|
50
|
+
*
|
|
51
|
+
* exec < /dev/null
|
|
52
|
+
* exec > >(exec cat)
|
|
53
|
+
* exec 2> >(exec cat >&2)
|
|
54
|
+
* "$HERE/brave" "$@" || true
|
|
55
|
+
*
|
|
56
|
+
* That breaks `launchBrowser`'s `--remote-debugging-pipe` transport two ways:
|
|
57
|
+
* the std-fd sanitization (and the extra `cat` process-substitution children)
|
|
58
|
+
* disturbs the inherited CDP pipe on fd 3/4, and the pid we record is the
|
|
59
|
+
* wrapper's, not the browser's. The symptom is `read ECONNRESET` /
|
|
60
|
+
* `CDP connection closed` right after spawn (issue #229).
|
|
61
|
+
*
|
|
62
|
+
* Follow the wrapper to the ELF it execs. The wrapper sets
|
|
63
|
+
* `HERE="dirname(readlink -f "$0")"` and invokes `"$HERE/<name>"`, so we
|
|
64
|
+
* resolve the script path, scan for that invocation line, and join the two.
|
|
65
|
+
* Returns the original path untouched when it's already an ELF, when it's not
|
|
66
|
+
* a resolvable wrapper, or on any non-Linux platform.
|
|
67
|
+
*/
|
|
68
|
+
function readsAsShebangScript(binaryPath) {
|
|
69
|
+
let fd;
|
|
70
|
+
try {
|
|
71
|
+
fd = fs.openSync(binaryPath, 'r');
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const head = Buffer.alloc(2);
|
|
78
|
+
fs.readSync(fd, head, 0, 2, 0);
|
|
79
|
+
// ELF binaries start with 0x7f 'E'; shebang scripts with '#!'.
|
|
80
|
+
return head[0] === 0x23 && head[1] === 0x21;
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
fs.closeSync(fd);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* True when `binaryPath` is a shebang script rather than a native browser
|
|
88
|
+
* executable. The Linux distro launchers (`/usr/bin/brave-browser`, …) are such
|
|
89
|
+
* scripts; `launchBrowser` can't drive one over `--remote-debugging-pipe` (see
|
|
90
|
+
* resolveBrowserBinary). `profiles doctor` uses this to flag a profile whose
|
|
91
|
+
* binary resolves to a wrapper we couldn't unwrap. Shebang scripts are a
|
|
92
|
+
* Linux/Unix concept — returns false on Windows/macOS app bundles.
|
|
93
|
+
*/
|
|
94
|
+
export function isLauncherScript(binaryPath) {
|
|
95
|
+
if (os.platform() === 'win32')
|
|
96
|
+
return false;
|
|
97
|
+
return readsAsShebangScript(binaryPath);
|
|
98
|
+
}
|
|
99
|
+
export function resolveBrowserBinary(binaryPath) {
|
|
100
|
+
if (os.platform() !== 'linux')
|
|
101
|
+
return binaryPath;
|
|
102
|
+
// Only shebang scripts need unwrapping; a real ELF passes straight through.
|
|
103
|
+
if (!readsAsShebangScript(binaryPath))
|
|
104
|
+
return binaryPath;
|
|
105
|
+
let script;
|
|
106
|
+
let realScriptPath;
|
|
107
|
+
try {
|
|
108
|
+
realScriptPath = fs.realpathSync(binaryPath);
|
|
109
|
+
script = fs.readFileSync(realScriptPath, 'utf8');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return binaryPath;
|
|
113
|
+
}
|
|
114
|
+
// Match the Chromium wrapper's launch line: `"$HERE/<name>" "$@"`, optionally
|
|
115
|
+
// prefixed with `exec -a "$0"`. The captured name is the real ELF, sitting in
|
|
116
|
+
// the same directory as the resolved wrapper.
|
|
117
|
+
const match = script.match(/"\$HERE\/([A-Za-z0-9._-]+)"\s+"\$@"/);
|
|
118
|
+
if (!match)
|
|
119
|
+
return binaryPath;
|
|
120
|
+
const realBinary = path.join(path.dirname(realScriptPath), match[1]);
|
|
121
|
+
return fs.existsSync(realBinary) ? realBinary : binaryPath;
|
|
122
|
+
}
|
|
45
123
|
export function findBrowserPath(browserType, customBinary) {
|
|
46
124
|
if (customBinary) {
|
|
47
125
|
if (!fs.existsSync(customBinary)) {
|
|
48
126
|
throw new Error(`Custom binary not found: ${customBinary}`);
|
|
49
127
|
}
|
|
50
|
-
return customBinary;
|
|
128
|
+
return resolveBrowserBinary(customBinary);
|
|
51
129
|
}
|
|
52
130
|
if (browserType === 'custom') {
|
|
53
131
|
throw new Error('browser: custom requires a binary path in the profile');
|
|
@@ -60,9 +138,12 @@ export function findBrowserPath(browserType, customBinary) {
|
|
|
60
138
|
const candidates = platformPaths[browserType] || [];
|
|
61
139
|
for (const p of candidates) {
|
|
62
140
|
if (fs.existsSync(p)) {
|
|
63
|
-
return p;
|
|
141
|
+
return resolveBrowserBinary(p);
|
|
64
142
|
}
|
|
65
143
|
}
|
|
144
|
+
if (browserType === 'comet' && platform !== 'darwin') {
|
|
145
|
+
throw new Error('Browser "comet" is macOS-only (Comet is a macOS Chromium fork). Use chrome, chromium, brave, or edge on this platform.');
|
|
146
|
+
}
|
|
66
147
|
throw new Error(`Browser "${browserType}" not found. Install it first.`);
|
|
67
148
|
}
|
|
68
149
|
// Per-platform Chromium-family priority list for "no --profile" auto-pick.
|
|
@@ -98,7 +179,7 @@ export function findFirstInstalledBrowser(platform = os.platform()) {
|
|
|
98
179
|
const candidates = platformPaths[browserType] || [];
|
|
99
180
|
for (const p of candidates) {
|
|
100
181
|
if (fs.existsSync(p)) {
|
|
101
|
-
return { browserType, binary: p };
|
|
182
|
+
return { browserType, binary: resolveBrowserBinary(p) };
|
|
102
183
|
}
|
|
103
184
|
}
|
|
104
185
|
}
|