@evo-dev/evodev 0.0.1-alpha.18 → 0.0.1-alpha.19
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/dist/.claude-plugin/marketplace.json +2 -2
- package/dist/index.js +308 -182
- package/dist/plugins/evodev/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/evodev/.codex-plugin/plugin.json +1 -1
- package/dist/plugins/evodev/hooks/codex.ts +69 -2
- package/dist/plugins/evodev/hooks/plugin.ts +78 -3
- package/dist/plugins/evodev/package.json +1 -1
- package/dist/ui/app.js +9 -9
- package/dist/ui/styles.css +1 -1
- package/package.json +1 -1
|
@@ -54,6 +54,7 @@ export interface CodexPluginOptions {
|
|
|
54
54
|
pluginSelector?: string;
|
|
55
55
|
pluginMarketplaceSource?: string;
|
|
56
56
|
pluginMarketplaceFallbackSource?: string;
|
|
57
|
+
pluginVersion?: string;
|
|
57
58
|
paths?: CodexPaths;
|
|
58
59
|
syncFileSystem?: CodexSyncFileSystem;
|
|
59
60
|
}
|
|
@@ -195,6 +196,7 @@ async function installCodexPlugin(
|
|
|
195
196
|
| "pluginSelector"
|
|
196
197
|
| "pluginMarketplaceSource"
|
|
197
198
|
| "pluginMarketplaceFallbackSource"
|
|
199
|
+
| "pluginVersion"
|
|
198
200
|
| "paths"
|
|
199
201
|
>,
|
|
200
202
|
): Promise<PluginInstallResult> {
|
|
@@ -202,8 +204,46 @@ async function installCodexPlugin(
|
|
|
202
204
|
const selector = options.pluginSelector ?? "evodev@evodev";
|
|
203
205
|
const args = ["plugin", "add", selector];
|
|
204
206
|
const runner = options.commandRunner ?? new NodeCodexCommandRunner();
|
|
207
|
+
const installedPlugin =
|
|
208
|
+
input.force === true || options.pluginVersion === undefined
|
|
209
|
+
? null
|
|
210
|
+
: await readInstalledCodexPlugin(runner, selector);
|
|
211
|
+
let needsRepair = false;
|
|
212
|
+
|
|
213
|
+
if (
|
|
214
|
+
installedPlugin !== null &&
|
|
215
|
+
installedPlugin.version === options.pluginVersion &&
|
|
216
|
+
installedPlugin.enabled
|
|
217
|
+
) {
|
|
218
|
+
try {
|
|
219
|
+
const hydration = await hydrateCodexWorkspaceCoreDependency({
|
|
220
|
+
paths: options.paths,
|
|
221
|
+
selector,
|
|
222
|
+
});
|
|
223
|
+
if (hydration.warnings.length === 0 && hydration.errors.length === 0) {
|
|
224
|
+
return createPluginInstallResult(
|
|
225
|
+
input.targetPlugin,
|
|
226
|
+
"already-installed",
|
|
227
|
+
command,
|
|
228
|
+
args,
|
|
229
|
+
`Codex plugin ${selector} already matches EvoDev ${options.pluginVersion}; skipped marketplace refresh.`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
// Fall through to the normal repair path, which returns a structured install failure.
|
|
234
|
+
}
|
|
235
|
+
needsRepair = true;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const effectiveInput =
|
|
239
|
+
input.force === true ||
|
|
240
|
+
needsRepair ||
|
|
241
|
+
(installedPlugin !== null &&
|
|
242
|
+
(installedPlugin.version !== options.pluginVersion || !installedPlugin.enabled))
|
|
243
|
+
? { ...input, force: true }
|
|
244
|
+
: input;
|
|
205
245
|
const primaryResult = await installCodexPluginOnce({
|
|
206
|
-
input,
|
|
246
|
+
input: effectiveInput,
|
|
207
247
|
command,
|
|
208
248
|
selector,
|
|
209
249
|
args,
|
|
@@ -224,7 +264,7 @@ async function installCodexPlugin(
|
|
|
224
264
|
}
|
|
225
265
|
|
|
226
266
|
const fallbackResult = await installCodexPluginOnce({
|
|
227
|
-
input,
|
|
267
|
+
input: effectiveInput,
|
|
228
268
|
command,
|
|
229
269
|
selector,
|
|
230
270
|
args,
|
|
@@ -243,6 +283,33 @@ async function installCodexPlugin(
|
|
|
243
283
|
};
|
|
244
284
|
}
|
|
245
285
|
|
|
286
|
+
interface InstalledCodexPlugin {
|
|
287
|
+
version: string | null;
|
|
288
|
+
enabled: boolean;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function readInstalledCodexPlugin(
|
|
292
|
+
runner: CodexCommandRunner,
|
|
293
|
+
selector: string,
|
|
294
|
+
): Promise<InstalledCodexPlugin | null> {
|
|
295
|
+
try {
|
|
296
|
+
const result = await runner.run("codex", ["plugin", "list", "--json"]);
|
|
297
|
+
if (result.exitCode !== 0 || result.stdout === undefined) return null;
|
|
298
|
+
const parsed = JSON.parse(result.stdout) as unknown;
|
|
299
|
+
const plugins = isRecord(parsed) && Array.isArray(parsed.installed) ? parsed.installed : [];
|
|
300
|
+
const plugin = plugins.find(
|
|
301
|
+
(candidate) => isRecord(candidate) && candidate.pluginId === selector,
|
|
302
|
+
);
|
|
303
|
+
if (!isRecord(plugin)) return null;
|
|
304
|
+
return {
|
|
305
|
+
version: typeof plugin.version === "string" ? plugin.version : null,
|
|
306
|
+
enabled: plugin.enabled !== false,
|
|
307
|
+
};
|
|
308
|
+
} catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
246
313
|
async function installCodexPluginOnce(input: {
|
|
247
314
|
input: PluginInstallInput;
|
|
248
315
|
command: string;
|
|
@@ -56,6 +56,7 @@ export interface ClaudePluginOptions {
|
|
|
56
56
|
pluginSelector?: string;
|
|
57
57
|
pluginMarketplaceSource?: string;
|
|
58
58
|
pluginScope?: "user";
|
|
59
|
+
pluginVersion?: string;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export function createClaudePlugin(options: ClaudePluginOptions = {}): CodeAgentPlugin {
|
|
@@ -186,7 +187,12 @@ async function installClaudeCodePlugin(
|
|
|
186
187
|
input: PluginInstallInput,
|
|
187
188
|
options: Pick<
|
|
188
189
|
ClaudePluginOptions,
|
|
189
|
-
|
|
190
|
+
| "commandRunner"
|
|
191
|
+
| "paths"
|
|
192
|
+
| "pluginSelector"
|
|
193
|
+
| "pluginMarketplaceSource"
|
|
194
|
+
| "pluginScope"
|
|
195
|
+
| "pluginVersion"
|
|
190
196
|
>,
|
|
191
197
|
): Promise<PluginInstallResult> {
|
|
192
198
|
const command = "claude";
|
|
@@ -195,6 +201,42 @@ async function installClaudeCodePlugin(
|
|
|
195
201
|
const args = ["plugin", "install", selector, "--scope", scope];
|
|
196
202
|
const runner = options.commandRunner ?? new NodeClaudeCommandRunner();
|
|
197
203
|
const warnings: string[] = [];
|
|
204
|
+
const installedPlugin =
|
|
205
|
+
input.force === true || options.pluginVersion === undefined
|
|
206
|
+
? null
|
|
207
|
+
: await readInstalledClaudePlugin(runner, selector);
|
|
208
|
+
let needsRepair = false;
|
|
209
|
+
|
|
210
|
+
if (
|
|
211
|
+
installedPlugin !== null &&
|
|
212
|
+
installedPlugin.version === options.pluginVersion &&
|
|
213
|
+
installedPlugin.enabled
|
|
214
|
+
) {
|
|
215
|
+
try {
|
|
216
|
+
const hydration = await hydrateClaudeWorkspaceCoreDependency({
|
|
217
|
+
paths: options.paths,
|
|
218
|
+
selector,
|
|
219
|
+
});
|
|
220
|
+
if (hydration.warnings.length === 0 && hydration.errors.length === 0) {
|
|
221
|
+
return createPluginInstallResult(
|
|
222
|
+
input.targetPlugin,
|
|
223
|
+
"already-installed",
|
|
224
|
+
command,
|
|
225
|
+
args,
|
|
226
|
+
`Claude Code plugin ${selector} already matches EvoDev ${options.pluginVersion}; skipped marketplace refresh.`,
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// Fall through to the normal repair path, which returns a structured install failure.
|
|
231
|
+
}
|
|
232
|
+
needsRepair = true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const forceRefresh =
|
|
236
|
+
input.force === true ||
|
|
237
|
+
needsRepair ||
|
|
238
|
+
(installedPlugin !== null &&
|
|
239
|
+
(installedPlugin.version !== options.pluginVersion || !installedPlugin.enabled));
|
|
198
240
|
|
|
199
241
|
try {
|
|
200
242
|
if (options.pluginMarketplaceSource !== undefined) {
|
|
@@ -244,7 +286,7 @@ async function installClaudeCodePlugin(
|
|
|
244
286
|
}
|
|
245
287
|
}
|
|
246
288
|
|
|
247
|
-
if (
|
|
289
|
+
if (forceRefresh) {
|
|
248
290
|
const uninstallArgs = [
|
|
249
291
|
"plugin",
|
|
250
292
|
"uninstall",
|
|
@@ -293,7 +335,7 @@ async function installClaudeCodePlugin(
|
|
|
293
335
|
result.stdout,
|
|
294
336
|
[
|
|
295
337
|
...warnings,
|
|
296
|
-
|
|
338
|
+
forceRefresh
|
|
297
339
|
? "Claude Code reported the plugin is already installed after forced refresh; check the local plugin cache if changes are not visible."
|
|
298
340
|
: "Claude Code reported the plugin is already installed; if local plugin files changed without a version bump, Claude may keep the existing cached copy. Bump the plugin version or uninstall and reinstall to refresh the cache.",
|
|
299
341
|
],
|
|
@@ -330,6 +372,35 @@ async function installClaudeCodePlugin(
|
|
|
330
372
|
}
|
|
331
373
|
}
|
|
332
374
|
|
|
375
|
+
interface InstalledClaudePlugin {
|
|
376
|
+
version: string | null;
|
|
377
|
+
enabled: boolean;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function readInstalledClaudePlugin(
|
|
381
|
+
runner: ClaudeCommandRunner,
|
|
382
|
+
selector: string,
|
|
383
|
+
): Promise<InstalledClaudePlugin | null> {
|
|
384
|
+
try {
|
|
385
|
+
const result = await runner.run("claude", ["plugin", "list", "--json"]);
|
|
386
|
+
if (result.exitCode !== 0 || result.stdout === undefined) return null;
|
|
387
|
+
const parsed = JSON.parse(result.stdout) as unknown;
|
|
388
|
+
const plugins = Array.isArray(parsed)
|
|
389
|
+
? parsed
|
|
390
|
+
: isRecord(parsed) && Array.isArray(parsed.plugins)
|
|
391
|
+
? parsed.plugins
|
|
392
|
+
: [];
|
|
393
|
+
const plugin = plugins.find((candidate) => isRecord(candidate) && candidate.id === selector);
|
|
394
|
+
if (!isRecord(plugin)) return null;
|
|
395
|
+
return {
|
|
396
|
+
version: typeof plugin.version === "string" ? plugin.version : null,
|
|
397
|
+
enabled: plugin.enabled !== false,
|
|
398
|
+
};
|
|
399
|
+
} catch {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
333
404
|
function isAlreadyInstalledMessage(message: string | undefined): boolean {
|
|
334
405
|
return message !== undefined && /already\s+installed/i.test(message);
|
|
335
406
|
}
|
|
@@ -655,6 +726,10 @@ function isNotDirectoryError(error: unknown): boolean {
|
|
|
655
726
|
);
|
|
656
727
|
}
|
|
657
728
|
|
|
729
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
730
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
731
|
+
}
|
|
732
|
+
|
|
658
733
|
function describeError(error: unknown): string {
|
|
659
734
|
if (error instanceof Error) {
|
|
660
735
|
return error.message;
|