@playdrop/playdrop-cli 0.8.8 → 0.9.1
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 +20 -0
- package/config/client-meta.json +1 -1
- package/dist/commands/agents.d.ts +62 -0
- package/dist/commands/agents.js +470 -0
- package/dist/commands/clients.d.ts +44 -0
- package/dist/commands/clients.js +257 -0
- package/dist/commands/doctor.d.ts +51 -0
- package/dist/commands/doctor.js +266 -0
- package/dist/commands/marketing.d.ts +137 -0
- package/dist/commands/marketing.js +1102 -0
- package/dist/commands/workspaces.d.ts +47 -0
- package/dist/commands/workspaces.js +498 -0
- package/dist/index.js +153 -10
- package/dist/shellProbe.d.ts +6 -0
- package/dist/shellProbe.js +17 -0
- package/dist/versionCompare.d.ts +2 -0
- package/dist/versionCompare.js +30 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +6 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +3 -0
- package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/admin.js +11 -0
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/payments.js +22 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +12 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +15 -0
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +28 -1
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,6 +51,10 @@ playdrop project publish --env dev .
|
|
|
51
51
|
## Main Commands
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
+
playdrop doctor
|
|
55
|
+
playdrop clients status
|
|
56
|
+
playdrop agents status
|
|
57
|
+
playdrop workspaces list
|
|
54
58
|
playdrop auth login
|
|
55
59
|
playdrop auth whoami --env dev
|
|
56
60
|
playdrop browse
|
|
@@ -72,6 +76,22 @@ playdrop feedback send --title "Bug report" --comment "Details here."
|
|
|
72
76
|
playdrop getting-started
|
|
73
77
|
```
|
|
74
78
|
|
|
79
|
+
## Machine Setup
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
playdrop doctor
|
|
83
|
+
playdrop clients install
|
|
84
|
+
playdrop agents install-plugin codex
|
|
85
|
+
playdrop agents install-plugin claude
|
|
86
|
+
playdrop workspaces init
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`playdrop agents install-plugin claude` adds the PlayDrop marketplace, installs the PlayDrop plugin through the Claude Code CLI, and tells you to reload plugins.
|
|
90
|
+
|
|
91
|
+
`playdrop agents install-plugin codex` configures the PlayDrop plugin marketplace and uses Codex's plugin install command when the local Codex CLI exposes one. If the installed Codex CLI only supports marketplace management, finish activation from Codex with `/plugins`.
|
|
92
|
+
|
|
93
|
+
Cursor does not currently expose documented plugin installation through its CLI. `playdrop agents install-plugin cursor` prints the manual steps instead: open Cursor and use `/add-plugin` or the Cursor Marketplace for PlayDrop.
|
|
94
|
+
|
|
75
95
|
## Ref Format
|
|
76
96
|
|
|
77
97
|
Public content refs are always:
|
package/config/client-meta.json
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
type AgentID = 'codex' | 'claude' | 'cursor';
|
|
2
|
+
interface ShellResult {
|
|
3
|
+
exitCode: number;
|
|
4
|
+
output: string;
|
|
5
|
+
}
|
|
6
|
+
interface ToolStatus {
|
|
7
|
+
installed: boolean;
|
|
8
|
+
path?: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
}
|
|
11
|
+
interface PluginStatus {
|
|
12
|
+
installed: boolean;
|
|
13
|
+
status?: 'installed' | 'missing' | 'activation-required' | 'manual-verification-required' | 'unknown';
|
|
14
|
+
id?: string;
|
|
15
|
+
path?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
latestVersion?: string;
|
|
18
|
+
updateAvailable?: boolean;
|
|
19
|
+
enabled?: boolean;
|
|
20
|
+
scope?: string;
|
|
21
|
+
installableFromCli?: boolean;
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface AgentStatus {
|
|
25
|
+
id: AgentID;
|
|
26
|
+
name: string;
|
|
27
|
+
app: ToolStatus;
|
|
28
|
+
cli: ToolStatus;
|
|
29
|
+
account?: {
|
|
30
|
+
identity?: string;
|
|
31
|
+
subscription?: string;
|
|
32
|
+
signedIn: boolean | null;
|
|
33
|
+
};
|
|
34
|
+
plugin: PluginStatus;
|
|
35
|
+
nextAction: {
|
|
36
|
+
command: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
} | null;
|
|
39
|
+
}
|
|
40
|
+
export interface AgentsStatus {
|
|
41
|
+
agents: AgentStatus[];
|
|
42
|
+
}
|
|
43
|
+
interface AgentsStatusOptions {
|
|
44
|
+
homeDir?: string;
|
|
45
|
+
latestPluginVersion?: string;
|
|
46
|
+
runShell?: (command: string) => Promise<ShellResult>;
|
|
47
|
+
appExists?: (path: string) => boolean;
|
|
48
|
+
json?: boolean;
|
|
49
|
+
}
|
|
50
|
+
interface AgentActionOptions {
|
|
51
|
+
runCommand?: (command: string, args: string[]) => {
|
|
52
|
+
status: number | null;
|
|
53
|
+
stdout?: string;
|
|
54
|
+
stderr?: string;
|
|
55
|
+
};
|
|
56
|
+
homeDir?: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function showAgentsStatus(options?: AgentsStatusOptions): Promise<AgentsStatus>;
|
|
59
|
+
export declare function getAgentsStatus(options?: AgentsStatusOptions): Promise<AgentsStatus>;
|
|
60
|
+
export declare function installPlugin(agentID: string, options?: AgentActionOptions): Promise<void>;
|
|
61
|
+
export declare function updatePlugin(agentID: string, options?: AgentActionOptions): Promise<void>;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,470 @@
|
|
|
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.showAgentsStatus = showAgentsStatus;
|
|
7
|
+
exports.getAgentsStatus = getAgentsStatus;
|
|
8
|
+
exports.installPlugin = installPlugin;
|
|
9
|
+
exports.updatePlugin = updatePlugin;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
const node_child_process_1 = require("node:child_process");
|
|
14
|
+
const output_1 = require("../output");
|
|
15
|
+
const shellProbe_1 = require("../shellProbe");
|
|
16
|
+
const versionCompare_1 = require("../versionCompare");
|
|
17
|
+
const AGENTS = [
|
|
18
|
+
{ id: 'codex', name: 'Codex', cliCommand: 'codex', appNames: ['Codex', 'OpenAI Codex', 'ChatGPT'] },
|
|
19
|
+
{ id: 'claude', name: 'Claude', cliCommand: 'claude', appNames: ['Claude'] },
|
|
20
|
+
{ id: 'cursor', name: 'Cursor', cliCommand: 'cursor', appNames: ['Cursor'] },
|
|
21
|
+
];
|
|
22
|
+
const PUBLIC_PLUGIN_MANIFEST_URL = 'https://raw.githubusercontent.com/playdrop-ai/playdrop-plugin/main/.codex-plugin/plugin.json';
|
|
23
|
+
const PLAYDROP_PLUGIN_MARKETPLACE = 'playdrop-ai/playdrop-plugin';
|
|
24
|
+
const PLAYDROP_PLUGIN_SPEC = 'playdrop@playdrop';
|
|
25
|
+
async function showAgentsStatus(options = {}) {
|
|
26
|
+
const status = await getAgentsStatus(options);
|
|
27
|
+
if (options.json) {
|
|
28
|
+
(0, output_1.printJson)(status);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
printAgentsStatus(status);
|
|
32
|
+
}
|
|
33
|
+
return status;
|
|
34
|
+
}
|
|
35
|
+
async function getAgentsStatus(options = {}) {
|
|
36
|
+
const homeDir = options.homeDir ?? node_os_1.default.homedir();
|
|
37
|
+
const runShell = options.runShell ?? runLoginShell;
|
|
38
|
+
const appExists = options.appExists ?? node_fs_1.existsSync;
|
|
39
|
+
const latestPluginVersion = options.latestPluginVersion ?? await fetchLatestPublicPluginVersion();
|
|
40
|
+
const agents = [];
|
|
41
|
+
for (const agent of AGENTS) {
|
|
42
|
+
const cli = await detectCli(agent.cliCommand, runShell);
|
|
43
|
+
const app = detectApp(agent.appNames, homeDir, appExists);
|
|
44
|
+
const plugin = await detectPlugin(agent.id, cli, runShell, latestPluginVersion);
|
|
45
|
+
const account = await detectAccount(agent.id, runShell, homeDir);
|
|
46
|
+
agents.push({
|
|
47
|
+
id: agent.id,
|
|
48
|
+
name: agent.name,
|
|
49
|
+
app,
|
|
50
|
+
cli,
|
|
51
|
+
account,
|
|
52
|
+
plugin,
|
|
53
|
+
nextAction: buildNextAction(agent.id, plugin),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { agents };
|
|
57
|
+
}
|
|
58
|
+
async function installPlugin(agentID, options = {}) {
|
|
59
|
+
await runPluginAction('install-plugin', agentID, options);
|
|
60
|
+
}
|
|
61
|
+
async function updatePlugin(agentID, options = {}) {
|
|
62
|
+
await runPluginAction('update-plugin', agentID, options);
|
|
63
|
+
}
|
|
64
|
+
async function runPluginAction(action, rawAgentID, options) {
|
|
65
|
+
const agentID = normalizeAgentID(rawAgentID);
|
|
66
|
+
if (!agentID) {
|
|
67
|
+
console.error(`Unknown agent "${rawAgentID}". Expected codex, claude, or cursor.`);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const runCommand = options.runCommand ?? runCommandSync;
|
|
72
|
+
if (agentID === 'codex') {
|
|
73
|
+
runCodexPluginAction(action, runCommand);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (agentID === 'claude') {
|
|
77
|
+
runClaudePluginAction(action, runCommand);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
printCursorManualInstall();
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
function normalizeAgentID(value) {
|
|
84
|
+
return value === 'codex' || value === 'claude' || value === 'cursor' ? value : null;
|
|
85
|
+
}
|
|
86
|
+
async function detectCli(command, runShell) {
|
|
87
|
+
const pathProbe = await runShell((0, shellProbe_1.buildCommandPathProbe)(command));
|
|
88
|
+
if (pathProbe.exitCode !== 0 || pathProbe.output.trim().length === 0) {
|
|
89
|
+
return { installed: false };
|
|
90
|
+
}
|
|
91
|
+
const versionProbe = await runShell(`${command} --version`);
|
|
92
|
+
return {
|
|
93
|
+
installed: true,
|
|
94
|
+
path: pathProbe.output.split('\n')[0]?.trim(),
|
|
95
|
+
version: versionProbe.exitCode === 0 ? versionProbe.output.split('\n')[0]?.trim() : undefined,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function detectApp(appNames, homeDir, appExists) {
|
|
99
|
+
for (const root of ['/Applications', (0, node_path_1.join)(homeDir, 'Applications')]) {
|
|
100
|
+
for (const appName of appNames) {
|
|
101
|
+
const candidate = (0, node_path_1.join)(root, `${appName}.app`);
|
|
102
|
+
if (appExists(candidate)) {
|
|
103
|
+
return { installed: true, path: candidate };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return { installed: false };
|
|
108
|
+
}
|
|
109
|
+
async function detectPlugin(agentID, cli, runShell, latestVersion) {
|
|
110
|
+
if (agentID === 'cursor') {
|
|
111
|
+
return {
|
|
112
|
+
installed: false,
|
|
113
|
+
status: 'manual-verification-required',
|
|
114
|
+
latestVersion,
|
|
115
|
+
updateAvailable: false,
|
|
116
|
+
installableFromCli: false,
|
|
117
|
+
message: 'Cursor CLI cannot verify plugin installation. Open Cursor and use /add-plugin or the Cursor Marketplace.',
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (!cli.installed) {
|
|
121
|
+
return {
|
|
122
|
+
installed: false,
|
|
123
|
+
status: 'unknown',
|
|
124
|
+
latestVersion,
|
|
125
|
+
updateAvailable: false,
|
|
126
|
+
installableFromCli: true,
|
|
127
|
+
message: `${agentID === 'codex' ? 'Codex' : 'Claude'} CLI is missing, so installed plugins cannot be checked.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const list = await runShell(`${agentID} plugin list --json 2>&1`);
|
|
131
|
+
if (list.exitCode !== 0) {
|
|
132
|
+
return {
|
|
133
|
+
installed: false,
|
|
134
|
+
status: agentID === 'codex' ? 'manual-verification-required' : 'unknown',
|
|
135
|
+
latestVersion,
|
|
136
|
+
updateAvailable: false,
|
|
137
|
+
installableFromCli: true,
|
|
138
|
+
message: agentID === 'codex'
|
|
139
|
+
? 'Codex CLI cannot verify installed plugins. Open Codex and run /plugins to install, enable, or verify PlayDrop.'
|
|
140
|
+
: 'Could not read Claude installed plugins through claude plugin list --json.',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const plugin = selectPlayDropPlugin(parseInstalledPlugins(list.output));
|
|
144
|
+
if (!plugin) {
|
|
145
|
+
return {
|
|
146
|
+
installed: false,
|
|
147
|
+
status: 'missing',
|
|
148
|
+
latestVersion,
|
|
149
|
+
updateAvailable: false,
|
|
150
|
+
installableFromCli: true,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const version = typeof plugin.version === 'string' && plugin.version.trim() ? plugin.version.trim() : undefined;
|
|
154
|
+
const enabled = typeof plugin.enabled === 'boolean' ? plugin.enabled : undefined;
|
|
155
|
+
return {
|
|
156
|
+
installed: true,
|
|
157
|
+
status: enabled === false ? 'activation-required' : 'installed',
|
|
158
|
+
id: normalizePluginID(plugin),
|
|
159
|
+
path: typeof plugin.installPath === 'string' ? plugin.installPath : plugin.path,
|
|
160
|
+
version,
|
|
161
|
+
latestVersion,
|
|
162
|
+
updateAvailable: (0, versionCompare_1.firstVersionIsNewer)(latestVersion, version),
|
|
163
|
+
enabled,
|
|
164
|
+
scope: typeof plugin.scope === 'string' ? plugin.scope : undefined,
|
|
165
|
+
installableFromCli: true,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function fetchLatestPublicPluginVersion() {
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(PUBLIC_PLUGIN_MANIFEST_URL, { headers: { Accept: 'application/json' } });
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
const parsed = await response.json();
|
|
175
|
+
return typeof parsed.version === 'string' && parsed.version.trim().length > 0 ? parsed.version.trim() : undefined;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function detectAccount(agentID, runShell, homeDir) {
|
|
182
|
+
if (agentID === 'codex') {
|
|
183
|
+
return readCodexAuthAccount(homeDir);
|
|
184
|
+
}
|
|
185
|
+
if (agentID === 'claude') {
|
|
186
|
+
const result = await runShell('claude auth status 2>&1');
|
|
187
|
+
if (result.exitCode !== 0 || !result.output.trim()) {
|
|
188
|
+
return { signedIn: null };
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const parsed = JSON.parse(result.output);
|
|
192
|
+
if (!parsed.loggedIn) {
|
|
193
|
+
return { signedIn: false };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
identity: parsed.email || parsed.authMethod || 'Signed in',
|
|
197
|
+
subscription: parsed.subscriptionType,
|
|
198
|
+
signedIn: true,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return { identity: result.output.split('\n')[0]?.trim(), signedIn: null };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { signedIn: null };
|
|
206
|
+
}
|
|
207
|
+
function readCodexAuthAccount(homeDir) {
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(homeDir, '.codex', 'auth.json'), 'utf8'));
|
|
210
|
+
const tokens = parsed.tokens;
|
|
211
|
+
if (tokens && typeof tokens.id_token === 'string') {
|
|
212
|
+
const payload = decodeJWTPayload(tokens.id_token);
|
|
213
|
+
const email = typeof payload?.email === 'string' ? payload.email : undefined;
|
|
214
|
+
const plan = payload?.['https://api.openai.com/auth']?.chatgpt_plan_type;
|
|
215
|
+
if (email) {
|
|
216
|
+
return {
|
|
217
|
+
identity: email,
|
|
218
|
+
subscription: typeof plan === 'string' && plan ? plan : undefined,
|
|
219
|
+
signedIn: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (typeof parsed.auth_mode === 'string') {
|
|
224
|
+
return {
|
|
225
|
+
identity: parsed.auth_mode === 'apikey' ? 'API key' : parsed.auth_mode,
|
|
226
|
+
signedIn: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return { signedIn: false };
|
|
232
|
+
}
|
|
233
|
+
return { signedIn: false };
|
|
234
|
+
}
|
|
235
|
+
function decodeJWTPayload(token) {
|
|
236
|
+
const parts = token.split('.');
|
|
237
|
+
const payload = parts[1];
|
|
238
|
+
if (!payload) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const padded = payload.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - payload.length % 4) % 4);
|
|
242
|
+
try {
|
|
243
|
+
return JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
function buildNextAction(agentID, plugin) {
|
|
250
|
+
if (plugin.status === 'manual-verification-required') {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
if (plugin.status === 'unknown') {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
if (!plugin.installed) {
|
|
257
|
+
return { command: `playdrop agents install-plugin ${agentID}`, reason: 'PlayDrop plugin is not installed.' };
|
|
258
|
+
}
|
|
259
|
+
if (plugin.status === 'activation-required' || plugin.enabled === false) {
|
|
260
|
+
const command = agentID === 'claude' && plugin.id ? `claude plugin enable ${plugin.id}` : `playdrop agents install-plugin ${agentID}`;
|
|
261
|
+
return { command, reason: 'PlayDrop plugin is installed but disabled.' };
|
|
262
|
+
}
|
|
263
|
+
if (plugin.updateAvailable) {
|
|
264
|
+
return { command: `playdrop agents update-plugin ${agentID}`, reason: 'PlayDrop plugin update is available.' };
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function printAgentsStatus(status) {
|
|
269
|
+
for (const agent of status.agents) {
|
|
270
|
+
console.log(agent.name);
|
|
271
|
+
console.log(` CLI: ${agent.cli.installed ? agent.cli.version ?? agent.cli.path ?? 'installed' : 'missing'}`);
|
|
272
|
+
console.log(` App: ${agent.app.installed ? agent.app.path ?? 'installed' : 'missing'}`);
|
|
273
|
+
const account = agent.account?.identity
|
|
274
|
+
? agent.account.subscription
|
|
275
|
+
? `${agent.account.identity} (${agent.account.subscription})`
|
|
276
|
+
: agent.account.identity
|
|
277
|
+
: agent.account?.signedIn === false
|
|
278
|
+
? 'not signed in'
|
|
279
|
+
: 'unknown';
|
|
280
|
+
console.log(` Account: ${account}`);
|
|
281
|
+
console.log(` PlayDrop plugin: ${formatPluginForAgentsStatus(agent.plugin)}`);
|
|
282
|
+
if (agent.nextAction) {
|
|
283
|
+
console.log(` Run: ${agent.nextAction.command}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function runCodexPluginAction(action, runCommand) {
|
|
288
|
+
const marketplaceCommand = action === 'install-plugin'
|
|
289
|
+
? ['plugin', 'marketplace', 'add', PLAYDROP_PLUGIN_MARKETPLACE]
|
|
290
|
+
: ['plugin', 'marketplace', 'upgrade', 'playdrop'];
|
|
291
|
+
const marketplace = runCommand('codex', marketplaceCommand);
|
|
292
|
+
if (marketplace.status !== 0) {
|
|
293
|
+
console.error(`Codex PlayDrop plugin ${action} failed.`);
|
|
294
|
+
process.exitCode = 1;
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const help = runCommand('codex', ['plugin', '--help']);
|
|
298
|
+
const helpText = commandOutput(help);
|
|
299
|
+
if (action === 'install-plugin' && commandHelpIncludes(helpText, 'install')) {
|
|
300
|
+
const install = runCommand('codex', ['plugin', 'install', PLAYDROP_PLUGIN_SPEC]);
|
|
301
|
+
if (install.status !== 0) {
|
|
302
|
+
console.error('Codex PlayDrop plugin install failed.');
|
|
303
|
+
process.exitCode = 1;
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (commandHelpIncludes(helpText, 'list') && !readInstalledPlayDropPlugin('codex', runCommand)) {
|
|
307
|
+
console.error('Codex PlayDrop plugin install could not be verified from codex plugin list --json.');
|
|
308
|
+
process.exitCode = 1;
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
console.log('Installed the PlayDrop plugin for Codex.');
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (action === 'update-plugin' && commandHelpIncludes(helpText, 'update')) {
|
|
315
|
+
const update = runCommand('codex', ['plugin', 'update', PLAYDROP_PLUGIN_SPEC]);
|
|
316
|
+
if (update.status !== 0) {
|
|
317
|
+
console.error('Codex PlayDrop plugin update failed.');
|
|
318
|
+
process.exitCode = 1;
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
console.log('Updated the PlayDrop plugin for Codex.');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
console.log('Codex PlayDrop plugin marketplace is configured.');
|
|
325
|
+
console.log('Open Codex and run /plugins, then install or enable PlayDrop.');
|
|
326
|
+
}
|
|
327
|
+
function runClaudePluginAction(action, runCommand) {
|
|
328
|
+
if (action === 'install-plugin') {
|
|
329
|
+
const marketplace = runCommand('claude', ['plugin', 'marketplace', 'add', PLAYDROP_PLUGIN_MARKETPLACE]);
|
|
330
|
+
if (marketplace.status !== 0) {
|
|
331
|
+
console.error('Claude PlayDrop marketplace install failed.');
|
|
332
|
+
process.exitCode = 1;
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const install = runCommand('claude', ['plugin', 'install', PLAYDROP_PLUGIN_SPEC]);
|
|
336
|
+
if (install.status !== 0) {
|
|
337
|
+
console.error('Claude PlayDrop plugin install failed.');
|
|
338
|
+
process.exitCode = 1;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
if (!readInstalledPlayDropPlugin('claude', runCommand)) {
|
|
342
|
+
console.error('Claude PlayDrop plugin install could not be verified from claude plugin list --json.');
|
|
343
|
+
process.exitCode = 1;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
console.log('Installed the PlayDrop plugin for Claude.');
|
|
347
|
+
console.log('Run /reload-plugins in Claude Code or restart Claude Code to apply it.');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const installed = readInstalledPlayDropPlugin('claude', runCommand);
|
|
351
|
+
if (!installed) {
|
|
352
|
+
console.error('PlayDrop plugin is not installed for Claude.');
|
|
353
|
+
console.error('Run "playdrop agents install-plugin claude" first.');
|
|
354
|
+
process.exitCode = 1;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const updateTarget = normalizePluginID(installed) ?? 'playdrop';
|
|
358
|
+
const update = runCommand('claude', ['plugin', 'update', updateTarget]);
|
|
359
|
+
if (update.status !== 0) {
|
|
360
|
+
console.error('Claude PlayDrop plugin update failed.');
|
|
361
|
+
process.exitCode = 1;
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
console.log('Updated the PlayDrop plugin for Claude.');
|
|
365
|
+
console.log('Run /reload-plugins in Claude Code or restart Claude Code to apply it.');
|
|
366
|
+
}
|
|
367
|
+
function readInstalledPlayDropPlugin(command, runCommand) {
|
|
368
|
+
const list = runCommand(command, ['plugin', 'list', '--json']);
|
|
369
|
+
if (list.status !== 0) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
return selectPlayDropPlugin(parseInstalledPlugins(list.stdout ?? ''));
|
|
373
|
+
}
|
|
374
|
+
function parseInstalledPlugins(output) {
|
|
375
|
+
try {
|
|
376
|
+
const parsed = JSON.parse(output);
|
|
377
|
+
if (Array.isArray(parsed)) {
|
|
378
|
+
return parsed.filter(isInstalledPluginRecord);
|
|
379
|
+
}
|
|
380
|
+
if (isRecord(parsed)) {
|
|
381
|
+
for (const key of ['plugins', 'installed', 'items']) {
|
|
382
|
+
const value = parsed[key];
|
|
383
|
+
if (Array.isArray(value)) {
|
|
384
|
+
return value.filter(isInstalledPluginRecord);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
return [];
|
|
393
|
+
}
|
|
394
|
+
function selectPlayDropPlugin(plugins) {
|
|
395
|
+
const candidates = plugins.filter((plugin) => isPlayDropPluginID(normalizePluginID(plugin)));
|
|
396
|
+
if (candidates.length === 0) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
return candidates.sort((left, right) => pluginPriority(right) - pluginPriority(left))[0] ?? null;
|
|
400
|
+
}
|
|
401
|
+
function isInstalledPluginRecord(value) {
|
|
402
|
+
return isRecord(value);
|
|
403
|
+
}
|
|
404
|
+
function normalizePluginID(plugin) {
|
|
405
|
+
if (typeof plugin.id === 'string' && plugin.id.trim()) {
|
|
406
|
+
return plugin.id.trim();
|
|
407
|
+
}
|
|
408
|
+
if (typeof plugin.name === 'string' && plugin.name.trim()) {
|
|
409
|
+
return plugin.name.trim();
|
|
410
|
+
}
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
function isPlayDropPluginID(id) {
|
|
414
|
+
if (!id) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
return id === 'playdrop' || id.startsWith('playdrop@');
|
|
418
|
+
}
|
|
419
|
+
function pluginPriority(plugin) {
|
|
420
|
+
const id = normalizePluginID(plugin);
|
|
421
|
+
if (id === PLAYDROP_PLUGIN_SPEC) {
|
|
422
|
+
return 3;
|
|
423
|
+
}
|
|
424
|
+
if (id === 'playdrop') {
|
|
425
|
+
return 2;
|
|
426
|
+
}
|
|
427
|
+
return isPlayDropPluginID(id) ? 1 : 0;
|
|
428
|
+
}
|
|
429
|
+
function commandHelpIncludes(helpText, command) {
|
|
430
|
+
return new RegExp(`(^|\\n)\\s*${command}(\\s|$)`).test(helpText);
|
|
431
|
+
}
|
|
432
|
+
function commandOutput(result) {
|
|
433
|
+
return [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
434
|
+
}
|
|
435
|
+
function printCursorManualInstall() {
|
|
436
|
+
console.error('Cursor does not expose plugin installation through its CLI.');
|
|
437
|
+
console.error('Open Cursor and use /add-plugin or the Cursor Marketplace to install PlayDrop.');
|
|
438
|
+
console.error('Plugin source: https://github.com/playdrop-ai/playdrop-plugin');
|
|
439
|
+
}
|
|
440
|
+
function formatPluginForAgentsStatus(plugin) {
|
|
441
|
+
if (plugin.status === 'manual-verification-required') {
|
|
442
|
+
return plugin.latestVersion ? `manual verification required, latest: ${plugin.latestVersion}` : 'manual verification required';
|
|
443
|
+
}
|
|
444
|
+
if (plugin.status === 'unknown') {
|
|
445
|
+
return plugin.message ? `unknown (${plugin.message})` : 'unknown';
|
|
446
|
+
}
|
|
447
|
+
if (!plugin.installed) {
|
|
448
|
+
return plugin.latestVersion ? `missing, latest: ${plugin.latestVersion}` : 'missing';
|
|
449
|
+
}
|
|
450
|
+
const installedVersion = plugin.version ?? 'installed';
|
|
451
|
+
const enabled = plugin.status === 'activation-required' || plugin.enabled === false ? ', activation required' : '';
|
|
452
|
+
if (plugin.updateAvailable) {
|
|
453
|
+
return `${installedVersion}${enabled}, update available: ${plugin.latestVersion ?? 'unknown'}`;
|
|
454
|
+
}
|
|
455
|
+
return `${installedVersion}${enabled}, up to date`;
|
|
456
|
+
}
|
|
457
|
+
function isRecord(value) {
|
|
458
|
+
return typeof value === 'object' && value !== null;
|
|
459
|
+
}
|
|
460
|
+
async function runLoginShell(command) {
|
|
461
|
+
return (0, shellProbe_1.runShell)(command);
|
|
462
|
+
}
|
|
463
|
+
function runCommandSync(command, args) {
|
|
464
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
465
|
+
return {
|
|
466
|
+
status: result.status,
|
|
467
|
+
stdout: result.stdout ?? '',
|
|
468
|
+
stderr: result.stderr ?? '',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface MacClientManifest {
|
|
2
|
+
platform: 'mac';
|
|
3
|
+
version: string;
|
|
4
|
+
build: string;
|
|
5
|
+
versionCode: string;
|
|
6
|
+
filename: string;
|
|
7
|
+
dmgFilename: string;
|
|
8
|
+
downloadUrl: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ClientStatus {
|
|
11
|
+
platform: NodeJS.Platform | string;
|
|
12
|
+
supported: boolean;
|
|
13
|
+
status: 'unavailable' | 'missing' | 'installed' | 'update-available';
|
|
14
|
+
installed: boolean;
|
|
15
|
+
appPath?: string;
|
|
16
|
+
version?: string;
|
|
17
|
+
build?: string;
|
|
18
|
+
latestVersion?: string;
|
|
19
|
+
latestBuild?: string;
|
|
20
|
+
updateAvailable?: boolean;
|
|
21
|
+
reason?: string;
|
|
22
|
+
nextAction?: string;
|
|
23
|
+
}
|
|
24
|
+
interface ClientStatusOptions {
|
|
25
|
+
platform?: NodeJS.Platform | string;
|
|
26
|
+
appPath?: string;
|
|
27
|
+
fetchLatestManifest?: () => Promise<MacClientManifest | null>;
|
|
28
|
+
}
|
|
29
|
+
interface ClientActionOptions extends ClientStatusOptions {
|
|
30
|
+
runCommand?: (command: string, args: string[]) => {
|
|
31
|
+
status: number | null;
|
|
32
|
+
stdout?: string;
|
|
33
|
+
stderr?: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export declare function showClientStatus(options?: ClientStatusOptions & {
|
|
37
|
+
json?: boolean;
|
|
38
|
+
}): Promise<ClientStatus>;
|
|
39
|
+
export declare function getClientStatus(options?: ClientStatusOptions): Promise<ClientStatus>;
|
|
40
|
+
export declare function installClient(options?: ClientActionOptions): Promise<void>;
|
|
41
|
+
export declare function updateClient(options?: ClientActionOptions): Promise<void>;
|
|
42
|
+
export declare function launchClient(options?: ClientActionOptions): Promise<void>;
|
|
43
|
+
export declare function installMacDmg(manifest: MacClientManifest, appPath: string, runCommand: NonNullable<ClientActionOptions['runCommand']>): Promise<boolean>;
|
|
44
|
+
export {};
|