@playdrop/playdrop-cli 0.8.8 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,257 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.showClientStatus = showClientStatus;
7
+ exports.getClientStatus = getClientStatus;
8
+ exports.installClient = installClient;
9
+ exports.updateClient = updateClient;
10
+ exports.launchClient = launchClient;
11
+ exports.installMacDmg = installMacDmg;
12
+ const node_fs_1 = require("node:fs");
13
+ const node_os_1 = __importDefault(require("node:os"));
14
+ const node_path_1 = require("node:path");
15
+ const node_child_process_1 = require("node:child_process");
16
+ const output_1 = require("../output");
17
+ const versionCompare_1 = require("../versionCompare");
18
+ const MAC_APP_PATH = '/Applications/PlayDrop.app';
19
+ const MAC_CLIENT_LATEST_URL = 'https://www.playdrop.ai/api/mac-client/latest';
20
+ async function showClientStatus(options = {}) {
21
+ const status = await getClientStatus(options);
22
+ if (options.json) {
23
+ (0, output_1.printJson)(status);
24
+ }
25
+ else {
26
+ printClientStatus(status);
27
+ }
28
+ return status;
29
+ }
30
+ async function getClientStatus(options = {}) {
31
+ const platform = options.platform ?? process.platform;
32
+ if (platform !== 'darwin') {
33
+ return {
34
+ platform,
35
+ supported: false,
36
+ status: 'unavailable',
37
+ installed: false,
38
+ reason: `PlayDrop native clients are not available for ${platform}.`,
39
+ };
40
+ }
41
+ const appPath = options.appPath ?? MAC_APP_PATH;
42
+ const installed = (0, node_fs_1.existsSync)(appPath);
43
+ const bundle = installed ? readMacBundleInfo(appPath) : {};
44
+ const latest = await (options.fetchLatestManifest ?? fetchLatestMacClientManifest)();
45
+ const updateAvailable = Boolean(installed && (0, versionCompare_1.firstVersionIsNewer)(latest?.version, bundle.version));
46
+ return {
47
+ platform,
48
+ supported: true,
49
+ status: !installed ? 'missing' : updateAvailable ? 'update-available' : 'installed',
50
+ installed,
51
+ appPath,
52
+ version: bundle.version,
53
+ build: bundle.build,
54
+ latestVersion: latest?.version,
55
+ latestBuild: latest?.build,
56
+ updateAvailable,
57
+ nextAction: !installed ? 'playdrop clients install' : updateAvailable ? 'playdrop clients update' : undefined,
58
+ };
59
+ }
60
+ async function installClient(options = {}) {
61
+ await installOrUpdateClient('install', options);
62
+ }
63
+ async function updateClient(options = {}) {
64
+ await installOrUpdateClient('update', options);
65
+ }
66
+ async function launchClient(options = {}) {
67
+ const platform = options.platform ?? process.platform;
68
+ if (platform !== 'darwin') {
69
+ printUnsupportedPlatform(platform);
70
+ return;
71
+ }
72
+ const appPath = options.appPath ?? MAC_APP_PATH;
73
+ if (!(0, node_fs_1.existsSync)(appPath)) {
74
+ console.error(`PlayDrop is not installed at ${appPath}.`);
75
+ console.error('Run "playdrop clients install" first.');
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+ const runCommand = options.runCommand ?? runCommandSync;
80
+ const result = runCommand('open', [appPath]);
81
+ if (result.status !== 0) {
82
+ console.error(`Failed to launch PlayDrop from ${appPath}.`);
83
+ process.exitCode = 1;
84
+ return;
85
+ }
86
+ console.log('Launched PlayDrop.');
87
+ }
88
+ async function installOrUpdateClient(action, options) {
89
+ const platform = options.platform ?? process.platform;
90
+ if (platform !== 'darwin') {
91
+ printUnsupportedPlatform(platform);
92
+ return;
93
+ }
94
+ const appPath = options.appPath ?? MAC_APP_PATH;
95
+ const runCommand = options.runCommand ?? runCommandSync;
96
+ if (isPlayDropRunning(runCommand)) {
97
+ console.error('PlayDrop is running. Quit PlayDrop before installing or updating.');
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+ try {
102
+ (0, node_fs_1.accessSync)('/Applications', node_fs_1.constants.W_OK);
103
+ }
104
+ catch {
105
+ console.error('Cannot write to /Applications.');
106
+ console.error('Run this command from a terminal with permission to install apps into /Applications.');
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+ const manifest = await (options.fetchLatestManifest ?? fetchLatestMacClientManifest)();
111
+ if (!manifest) {
112
+ console.error('Could not resolve the latest PlayDrop Mac client manifest.');
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+ const installed = await installMacDmg(manifest, appPath, runCommand);
117
+ if (installed) {
118
+ console.log(`${action === 'install' ? 'Installed' : 'Updated'} PlayDrop at ${appPath}.`);
119
+ }
120
+ }
121
+ function printUnsupportedPlatform(platform) {
122
+ console.error(`PlayDrop native clients are not available for ${platform}.`);
123
+ process.exitCode = 1;
124
+ }
125
+ function printClientStatus(status) {
126
+ if (!status.supported) {
127
+ console.log(`PlayDrop client: unavailable on ${status.platform}`);
128
+ return;
129
+ }
130
+ if (!status.installed) {
131
+ console.log('PlayDrop client: missing');
132
+ console.log('Run: playdrop clients install');
133
+ return;
134
+ }
135
+ const version = status.version ? ` ${status.version}` : '';
136
+ console.log(`PlayDrop client: ${status.status}${version}`);
137
+ if (status.appPath) {
138
+ console.log(`Location: ${status.appPath}`);
139
+ }
140
+ if (status.updateAvailable) {
141
+ console.log('Run: playdrop clients update');
142
+ }
143
+ }
144
+ async function fetchLatestMacClientManifest() {
145
+ try {
146
+ const response = await fetch(MAC_CLIENT_LATEST_URL, { headers: { Accept: 'application/json' } });
147
+ if (!response.ok) {
148
+ return null;
149
+ }
150
+ const payload = await response.json();
151
+ if (payload.platform !== 'mac' || !payload.version || !payload.build || !payload.downloadUrl || !payload.dmgFilename) {
152
+ return null;
153
+ }
154
+ return {
155
+ platform: 'mac',
156
+ version: payload.version,
157
+ build: payload.build,
158
+ versionCode: payload.versionCode ?? payload.build,
159
+ filename: payload.filename ?? 'PlayDrop.app',
160
+ dmgFilename: payload.dmgFilename,
161
+ downloadUrl: payload.downloadUrl,
162
+ };
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
168
+ function readMacBundleInfo(appPath) {
169
+ const infoPath = (0, node_path_1.join)(appPath, 'Contents', 'Info.plist');
170
+ try {
171
+ const xml = (0, node_fs_1.readFileSync)(infoPath, 'utf8');
172
+ return {
173
+ version: readPlistString(xml, 'CFBundleShortVersionString'),
174
+ build: readPlistString(xml, 'CFBundleVersion'),
175
+ };
176
+ }
177
+ catch {
178
+ return {};
179
+ }
180
+ }
181
+ function readPlistString(xml, key) {
182
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
183
+ const match = xml.match(new RegExp(`<key>\\s*${escaped}\\s*</key>\\s*<string>([^<]+)</string>`));
184
+ return match?.[1]?.trim();
185
+ }
186
+ function isPlayDropRunning(runCommand) {
187
+ const result = (runCommand ?? runCommandSync)('pgrep', ['-x', 'PlayDrop']);
188
+ return result.status === 0;
189
+ }
190
+ async function installMacDmg(manifest, appPath, runCommand) {
191
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)(node_os_1.default.tmpdir(), 'playdrop-client-'));
192
+ const dmgPath = (0, node_path_1.join)(tempDir, (0, node_path_1.basename)(manifest.dmgFilename || 'PlayDrop.dmg'));
193
+ const mountPath = (0, node_path_1.join)(tempDir, 'mount');
194
+ (0, node_fs_1.mkdirSync)(mountPath, { recursive: true });
195
+ try {
196
+ const response = await fetch(manifest.downloadUrl);
197
+ if (!response.ok) {
198
+ throw new Error(`download_failed:${response.status}`);
199
+ }
200
+ const arrayBuffer = await response.arrayBuffer();
201
+ (0, node_fs_1.writeFileSync)(dmgPath, Buffer.from(arrayBuffer));
202
+ const attach = runCommand('hdiutil', ['attach', dmgPath, '-nobrowse', '-quiet', '-mountpoint', mountPath]);
203
+ if (attach.status !== 0) {
204
+ throw new Error('hdiutil_attach_failed');
205
+ }
206
+ const sourceApp = (0, node_path_1.join)(mountPath, 'PlayDrop.app');
207
+ if (!(0, node_fs_1.existsSync)(sourceApp)) {
208
+ throw new Error('playdrop_app_missing_in_dmg');
209
+ }
210
+ replaceAppBundle(sourceApp, appPath);
211
+ return true;
212
+ }
213
+ catch (error) {
214
+ const message = error instanceof Error ? error.message : String(error);
215
+ console.error(`Failed to install PlayDrop Mac client: ${message}`);
216
+ process.exitCode = 1;
217
+ return false;
218
+ }
219
+ finally {
220
+ runCommand('hdiutil', ['detach', mountPath, '-quiet']);
221
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
222
+ }
223
+ }
224
+ function replaceAppBundle(sourceApp, appPath) {
225
+ const parentDirectory = (0, node_path_1.dirname)(appPath);
226
+ const appName = (0, node_path_1.basename)(appPath);
227
+ const uniqueSuffix = `${process.pid}-${Date.now()}`;
228
+ const stagedPath = (0, node_path_1.join)(parentDirectory, `.${appName}.installing-${uniqueSuffix}`);
229
+ const backupPath = (0, node_path_1.join)(parentDirectory, `.${appName}.previous-${uniqueSuffix}`);
230
+ let backupCreated = false;
231
+ try {
232
+ (0, node_fs_1.cpSync)(sourceApp, stagedPath, { recursive: true });
233
+ if ((0, node_fs_1.existsSync)(appPath)) {
234
+ (0, node_fs_1.renameSync)(appPath, backupPath);
235
+ backupCreated = true;
236
+ }
237
+ (0, node_fs_1.renameSync)(stagedPath, appPath);
238
+ if (backupCreated) {
239
+ (0, node_fs_1.rmSync)(backupPath, { recursive: true, force: true });
240
+ }
241
+ }
242
+ catch (error) {
243
+ (0, node_fs_1.rmSync)(stagedPath, { recursive: true, force: true });
244
+ if (backupCreated && !(0, node_fs_1.existsSync)(appPath) && (0, node_fs_1.existsSync)(backupPath)) {
245
+ (0, node_fs_1.renameSync)(backupPath, appPath);
246
+ }
247
+ throw error;
248
+ }
249
+ }
250
+ function runCommandSync(command, args) {
251
+ const result = (0, node_child_process_1.spawnSync)(command, args, { encoding: 'utf8' });
252
+ return {
253
+ status: result.status,
254
+ stdout: result.stdout ?? '',
255
+ stderr: result.stderr ?? '',
256
+ };
257
+ }
@@ -0,0 +1,51 @@
1
+ import { type ClientStatus } from './clients';
2
+ import { type AgentsStatus } from './agents';
3
+ import { type WorkspaceCacheView } from './workspaces';
4
+ interface ToolHealth {
5
+ installed: boolean;
6
+ version?: string;
7
+ path?: string;
8
+ identity?: string;
9
+ }
10
+ interface ToolsHealth {
11
+ node: ToolHealth;
12
+ npm: ToolHealth;
13
+ git: ToolHealth;
14
+ }
15
+ interface AccountHealth {
16
+ loggedIn: boolean;
17
+ identity?: string;
18
+ }
19
+ interface CliHealth {
20
+ version: string;
21
+ latestVersion?: string;
22
+ updateStatus: 'current' | 'update-available' | 'unknown';
23
+ updateAvailable?: boolean;
24
+ nextAction?: string;
25
+ }
26
+ export interface DoctorReport {
27
+ cli: CliHealth;
28
+ account: AccountHealth;
29
+ tools: ToolsHealth;
30
+ clients?: ClientStatus;
31
+ agents?: AgentsStatus;
32
+ workspaces?: ReturnType<typeof buildWorkspaceDoctorStatus>;
33
+ nextActions: string[];
34
+ }
35
+ interface DoctorProviders {
36
+ cli?: () => Promise<CliHealth>;
37
+ clients?: () => Promise<ClientStatus>;
38
+ agents?: () => Promise<AgentsStatus>;
39
+ workspaces?: () => Promise<ReturnType<typeof buildWorkspaceDoctorStatus>>;
40
+ tools?: () => Promise<ToolsHealth>;
41
+ account?: () => Promise<AccountHealth>;
42
+ }
43
+ interface DoctorOptions {
44
+ json?: boolean;
45
+ section?: string;
46
+ providers?: DoctorProviders;
47
+ cwd?: string;
48
+ }
49
+ export declare function showDoctor(options?: DoctorOptions): Promise<DoctorReport | null>;
50
+ declare function buildWorkspaceDoctorStatus(cwd?: string): WorkspaceCacheView;
51
+ export {};
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.showDoctor = showDoctor;
4
+ const node_fs_1 = require("node:fs");
5
+ const clientInfo_1 = require("../clientInfo");
6
+ const config_1 = require("../config");
7
+ const output_1 = require("../output");
8
+ const shellProbe_1 = require("../shellProbe");
9
+ const versionCompare_1 = require("../versionCompare");
10
+ const clients_1 = require("./clients");
11
+ const agents_1 = require("./agents");
12
+ const workspaces_1 = require("./workspaces");
13
+ async function showDoctor(options = {}) {
14
+ const section = normalizeSection(options.section);
15
+ if (options.section && !section) {
16
+ console.error(`Unknown doctor section "${options.section}". Expected clients, agents, or workspaces.`);
17
+ process.exitCode = 1;
18
+ return null;
19
+ }
20
+ const providers = options.providers ?? {};
21
+ const includeClients = !section || section === 'clients';
22
+ const includeAgents = !section || section === 'agents';
23
+ const includeWorkspaces = !section || section === 'workspaces';
24
+ const [cli, account, tools, clients, agents, workspaces] = await Promise.all([
25
+ providers.cli ? providers.cli() : getCliHealth(),
26
+ providers.account ? providers.account() : getAccountHealth(),
27
+ providers.tools ? providers.tools() : getToolsHealth(),
28
+ includeClients ? (providers.clients ? providers.clients() : (0, clients_1.getClientStatus)()) : Promise.resolve(undefined),
29
+ includeAgents ? (providers.agents ? providers.agents() : (0, agents_1.getAgentsStatus)()) : Promise.resolve(undefined),
30
+ includeWorkspaces ? (providers.workspaces ? providers.workspaces() : Promise.resolve(buildWorkspaceDoctorStatus(options.cwd))) : Promise.resolve(undefined),
31
+ ]);
32
+ const report = {
33
+ cli,
34
+ account,
35
+ tools,
36
+ clients,
37
+ agents,
38
+ workspaces,
39
+ nextActions: collectNextActions(cli, clients, agents, workspaces),
40
+ };
41
+ if (options.json) {
42
+ (0, output_1.printJson)(report);
43
+ }
44
+ else {
45
+ printDoctorReport(report);
46
+ }
47
+ return report;
48
+ }
49
+ function normalizeSection(value) {
50
+ if (!value) {
51
+ return null;
52
+ }
53
+ return value === 'clients' || value === 'agents' || value === 'workspaces' ? value : null;
54
+ }
55
+ async function getAccountHealth() {
56
+ const session = (0, config_1.getCurrentAccountSession)((0, config_1.loadConfig)());
57
+ if (!session) {
58
+ return { loggedIn: false };
59
+ }
60
+ return { loggedIn: true, identity: `${session.username} (${session.env})` };
61
+ }
62
+ async function getCliHealth() {
63
+ const currentVersion = (0, clientInfo_1.getCliVersion)();
64
+ const result = (0, shellProbe_1.runShell)('npm view @playdrop/playdrop-cli version --json');
65
+ if (result.exitCode !== 0 || !result.output.trim()) {
66
+ return {
67
+ version: (0, clientInfo_1.getCliVersionLabel)(),
68
+ updateStatus: 'unknown',
69
+ };
70
+ }
71
+ const latestVersion = parseNpmVersion(result.output);
72
+ if (!latestVersion) {
73
+ return {
74
+ version: (0, clientInfo_1.getCliVersionLabel)(),
75
+ updateStatus: 'unknown',
76
+ };
77
+ }
78
+ const updateAvailable = (0, versionCompare_1.firstVersionIsNewer)(latestVersion, currentVersion);
79
+ return {
80
+ version: (0, clientInfo_1.getCliVersionLabel)(),
81
+ latestVersion,
82
+ updateStatus: updateAvailable ? 'update-available' : 'current',
83
+ updateAvailable,
84
+ nextAction: updateAvailable ? 'playdrop update' : undefined,
85
+ };
86
+ }
87
+ function parseNpmVersion(output) {
88
+ const trimmed = output.trim();
89
+ try {
90
+ const parsed = JSON.parse(trimmed);
91
+ return typeof parsed === 'string' && parsed.trim().length > 0 ? parsed.trim() : undefined;
92
+ }
93
+ catch {
94
+ return trimmed.length > 0 ? trimmed.split('\n')[0]?.trim() : undefined;
95
+ }
96
+ }
97
+ async function getToolsHealth() {
98
+ const [node, npm, git] = await Promise.all([
99
+ detectTool('node', ['--version']),
100
+ detectTool('npm', ['--version']),
101
+ detectGit(),
102
+ ]);
103
+ return { node, npm, git };
104
+ }
105
+ async function detectTool(command, versionArgs) {
106
+ const pathProbe = (0, shellProbe_1.runShell)((0, shellProbe_1.buildCommandPathProbe)(command));
107
+ if (pathProbe.exitCode !== 0 || !pathProbe.output) {
108
+ return { installed: false };
109
+ }
110
+ const version = (0, shellProbe_1.runShell)([command, ...versionArgs].join(' '));
111
+ return {
112
+ installed: true,
113
+ path: pathProbe.output.split('\n')[0]?.trim(),
114
+ version: version.exitCode === 0 ? version.output.split('\n')[0]?.trim() : undefined,
115
+ };
116
+ }
117
+ async function detectGit() {
118
+ const status = await detectTool('git', ['--version']);
119
+ if (!status.installed) {
120
+ return status;
121
+ }
122
+ const email = (0, shellProbe_1.runShell)('git config --global --get user.email').output.trim();
123
+ const name = (0, shellProbe_1.runShell)('git config --global --get user.name').output.trim();
124
+ const identity = email ? (name ? `${name} <${email}>` : email) : name || undefined;
125
+ return { ...status, identity };
126
+ }
127
+ function buildWorkspaceDoctorStatus(cwd) {
128
+ const cache = (0, workspaces_1.loadWorkspaceCacheWithCurrentContext)({ cwd });
129
+ return {
130
+ ...cache,
131
+ defaultWorkspacePath: cache.defaultWorkspacePath,
132
+ workspaces: cache.workspaces.map((workspace) => ({
133
+ ...workspace,
134
+ exists: (0, node_fs_1.existsSync)(workspace.path),
135
+ })),
136
+ };
137
+ }
138
+ function collectNextActions(cli, clients, agents, workspaces) {
139
+ const actions = [];
140
+ if (cli.nextAction) {
141
+ actions.push(cli.nextAction);
142
+ }
143
+ if (clients?.nextAction) {
144
+ actions.push(clients.nextAction);
145
+ }
146
+ for (const agent of agents?.agents ?? []) {
147
+ if (agent.nextAction?.command) {
148
+ actions.push(agent.nextAction.command);
149
+ }
150
+ }
151
+ if (workspaces && workspaces.workspaces.length === 0) {
152
+ actions.push(recommendedWorkspaceRefreshCommand());
153
+ actions.push('playdrop workspaces init');
154
+ }
155
+ return [...new Set(actions)];
156
+ }
157
+ function recommendedWorkspaceRefreshCommand() {
158
+ return (0, workspaces_1.getWorkspaceRefreshExample)();
159
+ }
160
+ function printDoctorReport(report) {
161
+ const cliUpdate = report.cli.updateStatus === 'update-available'
162
+ ? `, update available: ${report.cli.latestVersion}`
163
+ : report.cli.updateStatus === 'current'
164
+ ? ', current'
165
+ : ', update status unknown';
166
+ console.log(`PlayDrop CLI: ${report.cli.version}${cliUpdate}`);
167
+ console.log(`Account: ${report.account.loggedIn ? report.account.identity ?? 'logged in' : 'not logged in'}`);
168
+ console.log(`Node: ${report.tools.node.installed ? report.tools.node.version ?? 'installed' : 'missing'}`);
169
+ console.log(`npm: ${report.tools.npm.installed ? report.tools.npm.version ?? 'installed' : 'missing'}`);
170
+ console.log(`Git: ${report.tools.git.installed ? report.tools.git.identity ?? report.tools.git.version ?? 'installed' : 'missing'}`);
171
+ if (report.clients) {
172
+ printNativeClientDoctorStatus(report.clients);
173
+ }
174
+ if (report.agents) {
175
+ printAgentsDoctorStatus(report.agents);
176
+ }
177
+ if (report.workspaces) {
178
+ if (report.workspaces.workspaces.length === 0) {
179
+ (0, workspaces_1.printNoCachedWorkspacesGuidance)(report.workspaces.defaultWorkspacePath);
180
+ }
181
+ else {
182
+ console.log(`Cached workspaces: ${report.workspaces.workspaces.length}`);
183
+ if (report.workspaces.currentWorkspacePath) {
184
+ console.log(`Current workspace: ${report.workspaces.currentWorkspacePath}`);
185
+ }
186
+ }
187
+ }
188
+ if (report.nextActions.length > 0) {
189
+ console.log('Next actions:');
190
+ for (const action of report.nextActions) {
191
+ console.log(` Run: ${action}`);
192
+ }
193
+ }
194
+ }
195
+ function printNativeClientDoctorStatus(status) {
196
+ if (!status.supported) {
197
+ console.log(`Native client: unavailable on ${status.platform}`);
198
+ return;
199
+ }
200
+ if (!status.installed) {
201
+ console.log('Native client: missing');
202
+ return;
203
+ }
204
+ const statusLabel = status.updateAvailable ? 'update available' : 'up to date';
205
+ console.log(`Native client: ${statusLabel}`);
206
+ console.log(` Installed: ${formatVersionBuild(status.version, status.build)}`);
207
+ if (status.latestVersion || status.latestBuild) {
208
+ console.log(` Latest: ${formatVersionBuild(status.latestVersion, status.latestBuild)}`);
209
+ }
210
+ if (status.appPath) {
211
+ console.log(` Location: ${status.appPath}`);
212
+ }
213
+ }
214
+ function printAgentsDoctorStatus(status) {
215
+ console.log('Agents:');
216
+ if (status.agents.length === 0) {
217
+ console.log(' none detected');
218
+ return;
219
+ }
220
+ for (const agent of status.agents) {
221
+ console.log(` ${agent.name}`);
222
+ console.log(` App: ${agent.app.installed ? agent.app.path ? `installed at ${agent.app.path}` : 'installed' : 'missing'}`);
223
+ console.log(` CLI: ${formatAgentCli(agent.cli)}`);
224
+ console.log(` Account: ${formatAgentAccount(agent.account)}`);
225
+ console.log(` Plugin: ${formatAgentPlugin(agent.plugin)}`);
226
+ }
227
+ }
228
+ function formatVersionBuild(version, build) {
229
+ if (version && build) {
230
+ return `${version} (build ${build})`;
231
+ }
232
+ return version ?? build ?? 'unknown';
233
+ }
234
+ function formatAgentCli(cli) {
235
+ if (!cli.installed) {
236
+ return 'missing';
237
+ }
238
+ const label = cli.version ?? 'installed';
239
+ return cli.path ? `${label} at ${cli.path}` : label;
240
+ }
241
+ function formatAgentAccount(account) {
242
+ if (account?.identity) {
243
+ return account.subscription ? `${account.identity} (${account.subscription})` : account.identity;
244
+ }
245
+ if (account?.signedIn === false) {
246
+ return 'not signed in';
247
+ }
248
+ return 'unknown';
249
+ }
250
+ function formatAgentPlugin(plugin) {
251
+ if (plugin.status === 'manual-verification-required') {
252
+ return plugin.latestVersion ? `manual verification required, latest: ${plugin.latestVersion}` : 'manual verification required';
253
+ }
254
+ if (plugin.status === 'unknown') {
255
+ return plugin.message ? `unknown (${plugin.message})` : 'unknown';
256
+ }
257
+ if (!plugin.installed) {
258
+ return plugin.latestVersion ? `missing, latest: ${plugin.latestVersion}` : 'missing';
259
+ }
260
+ const installedVersion = plugin.version ?? 'installed';
261
+ const enabled = plugin.status === 'activation-required' || plugin.enabled === false ? ', activation required' : '';
262
+ if (plugin.updateAvailable) {
263
+ return `${installedVersion}${enabled}, update available: ${plugin.latestVersion ?? 'unknown'}`;
264
+ }
265
+ return `${installedVersion}${enabled}, up to date`;
266
+ }