@evo-dev/evodev 0.0.1-alpha → 0.0.1-alpha.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/dist/.claude-plugin/marketplace.json +2 -2
- package/dist/assets/agents/review/code-reviewer/examples.md +1 -1
- package/dist/assets/agents/review/code-reviewer/prompt.md +1 -1
- package/dist/assets/agents/review/code-reviewer/verification.md +1 -1
- package/dist/assets/skills/coding/knowledge-distillation/SKILL.md +248 -0
- package/dist/assets/skills/coding/knowledge-distillation/manifest.json +10 -0
- package/dist/assets/skills/coding/knowledge-distillation/references/knowledge-distillation-methods.md +122 -0
- package/dist/assets/workflows/rd-bug-fix/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-code-review/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-docs-update/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-feature-implementation/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-refactor/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-release-readiness/WORKFLOW.json +1 -1
- package/dist/assets/workflows/rd-security-boundary-review/WORKFLOW.json +2 -2
- package/dist/assets/workflows/rd-test-generation/WORKFLOW.json +1 -1
- package/dist/index.js +13114 -6432
- package/dist/plugins/evodev/.claude-plugin/plugin.json +2 -2
- package/dist/plugins/evodev/.codex-plugin/plugin.json +8 -6
- package/dist/plugins/evodev/.mcp.json +6 -0
- package/dist/plugins/evodev/hooks/codex-hooks.json +10 -10
- package/dist/plugins/evodev/hooks/codex.ts +393 -34
- package/dist/plugins/evodev/hooks/hooks.json +18 -18
- package/dist/plugins/evodev/hooks/hooks.ts +268 -10
- package/dist/plugins/evodev/hooks/index.ts +15 -0
- package/dist/plugins/evodev/hooks/paths.ts +44 -1
- package/dist/plugins/evodev/hooks/plugin.ts +160 -9
- package/dist/plugins/evodev/hooks/runtime.ts +146 -31
- package/dist/plugins/evodev/hooks/transform-agent.ts +30 -0
- package/dist/plugins/evodev/hooks/workspace-core.ts +181 -0
- package/dist/plugins/evodev/package.json +3 -3
- package/dist/plugins/evodev/skills/engineering-discipline/SKILL.md +63 -0
- package/dist/plugins/evodev/skills/engineering-discipline/anti-patterns.md +21 -0
- package/dist/plugins/evodev/skills/engineering-discipline/examples.md +19 -0
- package/dist/plugins/evodev/skills/engineering-discipline/verification.md +11 -0
- package/dist/plugins/evodev/skills/knowledge-distillation/SKILL.md +248 -0
- package/dist/plugins/evodev/skills/knowledge-distillation/references/knowledge-distillation-methods.md +122 -0
- package/package.json +3 -6
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
1
3
|
import type {
|
|
2
4
|
CodeAgentCapabilities,
|
|
3
5
|
CodeAgentPlugin,
|
|
4
6
|
DoctorCheck,
|
|
5
7
|
DoctorContext,
|
|
8
|
+
HookEventType,
|
|
6
9
|
PluginDetectionResult,
|
|
7
10
|
PluginInstallInput,
|
|
8
11
|
PluginInstallResult,
|
|
@@ -11,9 +14,28 @@ import type {
|
|
|
11
14
|
SyncSkillsInput,
|
|
12
15
|
} from "@evo-dev/core/plugins";
|
|
13
16
|
import { runNodeCommand } from "./command-runner.ts";
|
|
17
|
+
import { type CodexPaths, resolveCodexAgentTargetPath, resolveCodexPaths } from "./paths.ts";
|
|
18
|
+
import { transformCodexAgent } from "./transform-agent.ts";
|
|
19
|
+
import {
|
|
20
|
+
type WorkspaceCoreHydrationResult,
|
|
21
|
+
hydrateWorkspaceCoreDependency,
|
|
22
|
+
isSafePluginCacheSegment,
|
|
23
|
+
} from "./workspace-core.ts";
|
|
14
24
|
|
|
15
|
-
const
|
|
16
|
-
"Codex
|
|
25
|
+
const CODEX_SKILL_BUNDLE_MESSAGE =
|
|
26
|
+
"Codex skills are bundled in the EvoDev Codex plugin; direct skill sync is skipped.";
|
|
27
|
+
const CODEX_CAPABILITY_HOOK_EVENTS: HookEventType[] = [
|
|
28
|
+
"SessionStart",
|
|
29
|
+
"UserPromptSubmit",
|
|
30
|
+
"PreToolUse",
|
|
31
|
+
"PermissionRequest",
|
|
32
|
+
"PostToolUse",
|
|
33
|
+
"PreCompact",
|
|
34
|
+
"PostCompact",
|
|
35
|
+
"SubagentStart",
|
|
36
|
+
"SubagentStop",
|
|
37
|
+
"Stop",
|
|
38
|
+
];
|
|
17
39
|
|
|
18
40
|
export interface CodexCommandResult {
|
|
19
41
|
exitCode: number;
|
|
@@ -29,9 +51,55 @@ export interface CodexPluginOptions {
|
|
|
29
51
|
commandRunner?: CodexCommandRunner;
|
|
30
52
|
pluginSelector?: string;
|
|
31
53
|
pluginMarketplaceSource?: string;
|
|
54
|
+
pluginMarketplaceFallbackSource?: string;
|
|
55
|
+
paths?: CodexPaths;
|
|
56
|
+
syncFileSystem?: CodexSyncFileSystem;
|
|
32
57
|
}
|
|
33
58
|
|
|
59
|
+
export interface CodexSyncFileSystem {
|
|
60
|
+
readFile(path: string): Promise<string>;
|
|
61
|
+
writeFile(path: string, content: string, options?: { overwrite?: boolean }): Promise<void>;
|
|
62
|
+
fileExists(path: string): Promise<boolean>;
|
|
63
|
+
ensureDir(path: string): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nodeSyncFileSystem: CodexSyncFileSystem = {
|
|
67
|
+
async readFile(path: string): Promise<string> {
|
|
68
|
+
return readFile(path, "utf8");
|
|
69
|
+
},
|
|
70
|
+
async writeFile(
|
|
71
|
+
path: string,
|
|
72
|
+
content: string,
|
|
73
|
+
options: { overwrite?: boolean } = {},
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
await writeFile(path, content, {
|
|
76
|
+
encoding: "utf8",
|
|
77
|
+
flag: options.overwrite === true ? "w" : "wx",
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
async fileExists(path: string): Promise<boolean> {
|
|
81
|
+
try {
|
|
82
|
+
const fileStat = await stat(path);
|
|
83
|
+
return fileStat.isFile();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (isNotFoundError(error)) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
async ensureDir(path: string): Promise<void> {
|
|
93
|
+
await mkdir(path, { recursive: true });
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
34
97
|
export function createCodexPlugin(options: CodexPluginOptions = {}): CodeAgentPlugin {
|
|
98
|
+
const syncFileSystem = options.syncFileSystem ?? nodeSyncFileSystem;
|
|
99
|
+
const getSyncPaths = (homeDir?: string) =>
|
|
100
|
+
options.paths ??
|
|
101
|
+
(homeDir === undefined ? resolveCodexPathsFromHome() : resolveCodexPaths({ homeDir }));
|
|
102
|
+
|
|
35
103
|
return {
|
|
36
104
|
id: "codex",
|
|
37
105
|
name: "Codex",
|
|
@@ -40,9 +108,9 @@ export function createCodexPlugin(options: CodexPluginOptions = {}): CodeAgentPl
|
|
|
40
108
|
},
|
|
41
109
|
async getCapabilities(): Promise<CodeAgentCapabilities> {
|
|
42
110
|
return {
|
|
43
|
-
skills: { supported:
|
|
44
|
-
agents: { supported:
|
|
45
|
-
hooks: { supported:
|
|
111
|
+
skills: { supported: true, format: "codex-skill" },
|
|
112
|
+
agents: { supported: true, format: "codex-agent" },
|
|
113
|
+
hooks: { supported: true, events: [...CODEX_CAPABILITY_HOOK_EVENTS] },
|
|
46
114
|
};
|
|
47
115
|
},
|
|
48
116
|
async doctor(context: DoctorContext): Promise<DoctorCheck[]> {
|
|
@@ -52,29 +120,29 @@ export function createCodexPlugin(options: CodexPluginOptions = {}): CodeAgentPl
|
|
|
52
120
|
return [
|
|
53
121
|
detectionToDoctorCheck(detection),
|
|
54
122
|
{
|
|
55
|
-
id: "codex.
|
|
56
|
-
status: enabled ? "
|
|
123
|
+
id: "codex.skills.plugin-bundled",
|
|
124
|
+
status: enabled ? "pass" : "skip",
|
|
57
125
|
message: enabled
|
|
58
|
-
?
|
|
59
|
-
: `${
|
|
126
|
+
? "Codex skills are packaged in the EvoDev plugin and loaded through Codex plugin installation."
|
|
127
|
+
: `${CODEX_SKILL_BUNDLE_MESSAGE} Codex is disabled in settings.`,
|
|
60
128
|
},
|
|
129
|
+
codexAgentsDoctorCheck(enabled, getSyncPaths(context.homeDir)),
|
|
61
130
|
];
|
|
62
131
|
},
|
|
63
132
|
async syncSkills(input: SyncSkillsInput): Promise<SyncResult> {
|
|
64
|
-
const result =
|
|
133
|
+
const result = createEmptySyncResult(input.targetPlugin);
|
|
65
134
|
for (const skill of input.skills) {
|
|
66
135
|
result.skipped.push(skill.registryKey);
|
|
136
|
+
if (skill.manifest.targets.includes("codex")) {
|
|
137
|
+
result.warnings.push(
|
|
138
|
+
`${CODEX_SKILL_BUNDLE_MESSAGE} Refresh or reinstall the Codex plugin to update ${skill.registryKey}.`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
67
141
|
}
|
|
68
|
-
addCodexSyncDiagnostic(result, input.dryRun, "skill");
|
|
69
142
|
return result;
|
|
70
143
|
},
|
|
71
144
|
async syncAgents(input: SyncAgentsInput): Promise<SyncResult> {
|
|
72
|
-
|
|
73
|
-
for (const agent of input.agents) {
|
|
74
|
-
result.skipped.push(agent.registryKey);
|
|
75
|
-
}
|
|
76
|
-
addCodexSyncDiagnostic(result, input.dryRun, "agent");
|
|
77
|
-
return result;
|
|
145
|
+
return syncCodexAgents(input, getSyncPaths(), syncFileSystem);
|
|
78
146
|
},
|
|
79
147
|
async installPlugin(input: PluginInstallInput): Promise<PluginInstallResult> {
|
|
80
148
|
return installCodexPlugin(input, options);
|
|
@@ -118,24 +186,116 @@ async function detectCodex(
|
|
|
118
186
|
|
|
119
187
|
async function installCodexPlugin(
|
|
120
188
|
input: PluginInstallInput,
|
|
121
|
-
options: Pick<
|
|
189
|
+
options: Pick<
|
|
190
|
+
CodexPluginOptions,
|
|
191
|
+
| "commandRunner"
|
|
192
|
+
| "pluginSelector"
|
|
193
|
+
| "pluginMarketplaceSource"
|
|
194
|
+
| "pluginMarketplaceFallbackSource"
|
|
195
|
+
| "paths"
|
|
196
|
+
>,
|
|
122
197
|
): Promise<PluginInstallResult> {
|
|
123
198
|
const command = "codex";
|
|
124
199
|
const selector = options.pluginSelector ?? "evodev@evodev";
|
|
125
200
|
const args = ["plugin", "add", selector];
|
|
126
201
|
const runner = options.commandRunner ?? new NodeCodexCommandRunner();
|
|
202
|
+
const primaryResult = await installCodexPluginOnce({
|
|
203
|
+
input,
|
|
204
|
+
command,
|
|
205
|
+
selector,
|
|
206
|
+
args,
|
|
207
|
+
runner,
|
|
208
|
+
marketplaceSource: options.pluginMarketplaceSource,
|
|
209
|
+
paths: options.paths,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const fallbackSource = options.pluginMarketplaceFallbackSource;
|
|
213
|
+
if (
|
|
214
|
+
primaryResult.status !== "failed" ||
|
|
215
|
+
options.pluginMarketplaceSource === undefined ||
|
|
216
|
+
fallbackSource === undefined ||
|
|
217
|
+
fallbackSource === options.pluginMarketplaceSource ||
|
|
218
|
+
!isSshGitMarketplaceSource(options.pluginMarketplaceSource)
|
|
219
|
+
) {
|
|
220
|
+
return primaryResult;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const fallbackResult = await installCodexPluginOnce({
|
|
224
|
+
input,
|
|
225
|
+
command,
|
|
226
|
+
selector,
|
|
227
|
+
args,
|
|
228
|
+
runner,
|
|
229
|
+
marketplaceSource: fallbackSource,
|
|
230
|
+
removeMarketplaceBeforeAdd: true,
|
|
231
|
+
paths: options.paths,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
...fallbackResult,
|
|
236
|
+
warnings: [
|
|
237
|
+
`Codex SSH marketplace source failed (${primaryResult.message}); retried with fallback source: ${fallbackSource}.`,
|
|
238
|
+
...fallbackResult.warnings,
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function installCodexPluginOnce(input: {
|
|
244
|
+
input: PluginInstallInput;
|
|
245
|
+
command: string;
|
|
246
|
+
selector: string;
|
|
247
|
+
args: string[];
|
|
248
|
+
runner: CodexCommandRunner;
|
|
249
|
+
marketplaceSource?: string;
|
|
250
|
+
removeMarketplaceBeforeAdd?: boolean;
|
|
251
|
+
paths?: CodexPaths;
|
|
252
|
+
}): Promise<PluginInstallResult> {
|
|
253
|
+
const { command, selector, args, runner } = input;
|
|
254
|
+
const warnings: string[] = [];
|
|
127
255
|
|
|
128
256
|
try {
|
|
129
|
-
if (
|
|
257
|
+
if (input.marketplaceSource !== undefined) {
|
|
258
|
+
if (
|
|
259
|
+
input.removeMarketplaceBeforeAdd === true ||
|
|
260
|
+
(input.input.force === true && isGitMarketplaceSource(input.marketplaceSource))
|
|
261
|
+
) {
|
|
262
|
+
const marketplaceName = extractMarketplaceNameFromSelector(selector);
|
|
263
|
+
if (marketplaceName === undefined) {
|
|
264
|
+
warnings.push(
|
|
265
|
+
`Skipped Codex marketplace remove before refresh because selector does not include a marketplace name: ${selector}`,
|
|
266
|
+
);
|
|
267
|
+
} else {
|
|
268
|
+
const removeMarketplaceResult = await runner.run(command, [
|
|
269
|
+
"plugin",
|
|
270
|
+
"marketplace",
|
|
271
|
+
"remove",
|
|
272
|
+
marketplaceName,
|
|
273
|
+
]);
|
|
274
|
+
const removeMarketplaceMessage =
|
|
275
|
+
removeMarketplaceResult.stderr ?? removeMarketplaceResult.stdout;
|
|
276
|
+
if (
|
|
277
|
+
removeMarketplaceResult.exitCode !== 0 &&
|
|
278
|
+
!isNotInstalledMessage(removeMarketplaceMessage)
|
|
279
|
+
) {
|
|
280
|
+
warnings.push(
|
|
281
|
+
`Codex marketplace refresh remove did not complete: ${
|
|
282
|
+
removeMarketplaceMessage ??
|
|
283
|
+
`Codex marketplace remove exited with code ${removeMarketplaceResult.exitCode}`
|
|
284
|
+
}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
130
290
|
const marketplaceResult = await runner.run(command, [
|
|
131
291
|
"plugin",
|
|
132
292
|
"marketplace",
|
|
133
293
|
"add",
|
|
134
|
-
|
|
294
|
+
input.marketplaceSource,
|
|
135
295
|
]);
|
|
136
296
|
if (marketplaceResult.exitCode !== 0) {
|
|
137
297
|
return createPluginInstallResult(
|
|
138
|
-
input.targetPlugin,
|
|
298
|
+
input.input.targetPlugin,
|
|
139
299
|
"failed",
|
|
140
300
|
command,
|
|
141
301
|
args,
|
|
@@ -144,21 +304,78 @@ async function installCodexPlugin(
|
|
|
144
304
|
`Codex marketplace add exited with code ${marketplaceResult.exitCode}`,
|
|
145
305
|
);
|
|
146
306
|
}
|
|
307
|
+
|
|
308
|
+
if (isGitMarketplaceSource(input.marketplaceSource)) {
|
|
309
|
+
const marketplaceName = extractMarketplaceNameFromSelector(selector);
|
|
310
|
+
if (marketplaceName === undefined) {
|
|
311
|
+
warnings.push(
|
|
312
|
+
`Skipped Codex marketplace upgrade because selector does not include a marketplace name: ${selector}`,
|
|
313
|
+
);
|
|
314
|
+
} else {
|
|
315
|
+
const upgradeResult = await runner.run(command, [
|
|
316
|
+
"plugin",
|
|
317
|
+
"marketplace",
|
|
318
|
+
"upgrade",
|
|
319
|
+
marketplaceName,
|
|
320
|
+
]);
|
|
321
|
+
if (upgradeResult.exitCode !== 0) {
|
|
322
|
+
return createPluginInstallResult(
|
|
323
|
+
input.input.targetPlugin,
|
|
324
|
+
"failed",
|
|
325
|
+
command,
|
|
326
|
+
args,
|
|
327
|
+
upgradeResult.stderr ??
|
|
328
|
+
upgradeResult.stdout ??
|
|
329
|
+
`Codex marketplace upgrade exited with code ${upgradeResult.exitCode}`,
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (input.input.force === true) {
|
|
337
|
+
const removeArgs = ["plugin", "remove", selector];
|
|
338
|
+
const removeResult = await runner.run(command, removeArgs);
|
|
339
|
+
const removeMessage = removeResult.stderr ?? removeResult.stdout;
|
|
340
|
+
if (removeResult.exitCode !== 0 && !isNotInstalledMessage(removeMessage)) {
|
|
341
|
+
warnings.push(
|
|
342
|
+
`Codex plugin refresh remove did not complete: ${
|
|
343
|
+
removeMessage ?? `Codex plugin remove exited with code ${removeResult.exitCode}`
|
|
344
|
+
}`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
147
347
|
}
|
|
148
348
|
|
|
149
349
|
const result = await runner.run(command, args);
|
|
150
350
|
if (result.exitCode === 0) {
|
|
351
|
+
const hydration = await hydrateCodexWorkspaceCoreDependency({
|
|
352
|
+
paths: input.paths,
|
|
353
|
+
selector,
|
|
354
|
+
});
|
|
355
|
+
warnings.push(...hydration.warnings);
|
|
356
|
+
if (hydration.errors.length > 0) {
|
|
357
|
+
return createPluginInstallResult(
|
|
358
|
+
input.input.targetPlugin,
|
|
359
|
+
"failed",
|
|
360
|
+
command,
|
|
361
|
+
args,
|
|
362
|
+
hydration.errors.join("\n"),
|
|
363
|
+
warnings,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
151
367
|
return createPluginInstallResult(
|
|
152
|
-
input.targetPlugin,
|
|
368
|
+
input.input.targetPlugin,
|
|
153
369
|
"installed",
|
|
154
370
|
command,
|
|
155
371
|
args,
|
|
156
372
|
result.stdout,
|
|
373
|
+
warnings,
|
|
157
374
|
);
|
|
158
375
|
}
|
|
159
376
|
|
|
160
377
|
return createPluginInstallResult(
|
|
161
|
-
input.targetPlugin,
|
|
378
|
+
input.input.targetPlugin,
|
|
162
379
|
"failed",
|
|
163
380
|
command,
|
|
164
381
|
args,
|
|
@@ -166,7 +383,7 @@ async function installCodexPlugin(
|
|
|
166
383
|
);
|
|
167
384
|
} catch (error) {
|
|
168
385
|
return createPluginInstallResult(
|
|
169
|
-
input.targetPlugin,
|
|
386
|
+
input.input.targetPlugin,
|
|
170
387
|
"failed",
|
|
171
388
|
command,
|
|
172
389
|
args,
|
|
@@ -175,6 +392,45 @@ async function installCodexPlugin(
|
|
|
175
392
|
}
|
|
176
393
|
}
|
|
177
394
|
|
|
395
|
+
async function hydrateCodexWorkspaceCoreDependency(input: {
|
|
396
|
+
paths?: CodexPaths;
|
|
397
|
+
selector: string;
|
|
398
|
+
}): Promise<WorkspaceCoreHydrationResult> {
|
|
399
|
+
const warnings: string[] = [];
|
|
400
|
+
const errors: string[] = [];
|
|
401
|
+
|
|
402
|
+
if (input.paths === undefined) {
|
|
403
|
+
return { warnings, errors };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const marketplaceName = extractMarketplaceNameFromSelector(input.selector);
|
|
407
|
+
const pluginName = extractPluginNameFromSelector(input.selector);
|
|
408
|
+
if (marketplaceName === undefined || pluginName === undefined) {
|
|
409
|
+
warnings.push(
|
|
410
|
+
`Skipped @evo-dev/core hydration because Codex plugin selector is not marketplace-qualified: ${input.selector}`,
|
|
411
|
+
);
|
|
412
|
+
return { warnings, errors };
|
|
413
|
+
}
|
|
414
|
+
if (!isSafePluginCacheSegment(marketplaceName) || !isSafePluginCacheSegment(pluginName)) {
|
|
415
|
+
warnings.push(
|
|
416
|
+
`Skipped @evo-dev/core hydration because Codex plugin selector contains an unsafe cache segment: ${input.selector}`,
|
|
417
|
+
);
|
|
418
|
+
return { warnings, errors };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const cachePluginRoot = `${input.paths.codexDir}/plugins/cache/${marketplaceName}/${pluginName}`;
|
|
422
|
+
const marketplaceCoreRoot = `${input.paths.codexDir}/.tmp/marketplaces/${marketplaceName}/packages/core`;
|
|
423
|
+
const hydration = await hydrateWorkspaceCoreDependency({
|
|
424
|
+
sourceCoreRoot: marketplaceCoreRoot,
|
|
425
|
+
cachePluginRoot,
|
|
426
|
+
runtimeLabel: "Codex",
|
|
427
|
+
});
|
|
428
|
+
return {
|
|
429
|
+
warnings: [...warnings, ...hydration.warnings],
|
|
430
|
+
errors: [...errors, ...hydration.errors],
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
178
434
|
class NodeCodexCommandRunner implements CodexCommandRunner {
|
|
179
435
|
async run(command: string, args: string[]): Promise<CodexCommandResult> {
|
|
180
436
|
return runNodeCommand(command, args);
|
|
@@ -193,7 +449,55 @@ function detectionToDoctorCheck(detection: PluginDetectionResult): DoctorCheck {
|
|
|
193
449
|
return { id: "codex.available", status: "warn", message: detection.message };
|
|
194
450
|
}
|
|
195
451
|
|
|
196
|
-
function
|
|
452
|
+
async function syncCodexAgents(
|
|
453
|
+
input: SyncAgentsInput,
|
|
454
|
+
paths: CodexPaths,
|
|
455
|
+
fileSystem: CodexSyncFileSystem,
|
|
456
|
+
): Promise<SyncResult> {
|
|
457
|
+
const result = createEmptySyncResult(input.targetPlugin);
|
|
458
|
+
|
|
459
|
+
for (const asset of input.agents) {
|
|
460
|
+
if (!asset.manifest.targets.includes("codex")) {
|
|
461
|
+
result.skipped.push(asset.registryKey);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
const source = await fileSystem.readFile(asset.entryPath);
|
|
467
|
+
const transformed = transformCodexAgent({ asset, source });
|
|
468
|
+
const targetPath = resolveCodexAgentTargetPath(transformed.name, paths);
|
|
469
|
+
const targetExists = await fileSystem.fileExists(targetPath);
|
|
470
|
+
|
|
471
|
+
if (targetExists && input.force !== true) {
|
|
472
|
+
result.skipped.push(asset.registryKey);
|
|
473
|
+
result.warnings.push(`Skipped existing Codex agent target: ${targetPath}`);
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (!input.dryRun) {
|
|
478
|
+
await fileSystem.ensureDir(dirname(targetPath));
|
|
479
|
+
await fileSystem.writeFile(targetPath, ensureTrailingNewline(transformed.content), {
|
|
480
|
+
overwrite: input.force === true,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (targetExists) {
|
|
485
|
+
result.warnings.push(
|
|
486
|
+
`${input.dryRun ? "Would update" : "Updated"} existing Codex agent target: ${targetPath}`,
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
result.syncedAgents.push(asset.registryKey);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
result.errors.push(
|
|
492
|
+
`Failed to sync Codex agent ${asset.registryKey}: ${describeError(error)}`,
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return result;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function createEmptySyncResult(targetPlugin: string): SyncResult {
|
|
197
501
|
return {
|
|
198
502
|
targetPlugin,
|
|
199
503
|
syncedSkills: [],
|
|
@@ -204,14 +508,20 @@ function createSkippedSyncResult(targetPlugin: string): SyncResult {
|
|
|
204
508
|
};
|
|
205
509
|
}
|
|
206
510
|
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
511
|
+
function codexAgentsDoctorCheck(enabled: boolean, paths: CodexPaths): DoctorCheck {
|
|
512
|
+
if (!enabled) {
|
|
513
|
+
return {
|
|
514
|
+
id: "codex.paths.agents",
|
|
515
|
+
status: "skip",
|
|
516
|
+
message: `Codex agent sync is disabled in settings. Planned user-level path: ${paths.agentsDir}`,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
id: "codex.paths.agents",
|
|
522
|
+
status: "pass",
|
|
523
|
+
message: `Codex agents will sync to user-level TOML files under: ${paths.agentsDir}`,
|
|
524
|
+
};
|
|
215
525
|
}
|
|
216
526
|
|
|
217
527
|
function createPluginInstallResult(
|
|
@@ -220,6 +530,7 @@ function createPluginInstallResult(
|
|
|
220
530
|
command: string,
|
|
221
531
|
args: string[],
|
|
222
532
|
message?: string,
|
|
533
|
+
warnings: string[] = [],
|
|
223
534
|
): PluginInstallResult {
|
|
224
535
|
const normalizedMessage = message?.trim() || `${command} ${args.join(" ")}`;
|
|
225
536
|
return {
|
|
@@ -228,11 +539,39 @@ function createPluginInstallResult(
|
|
|
228
539
|
command,
|
|
229
540
|
args,
|
|
230
541
|
message: normalizedMessage,
|
|
231
|
-
warnings
|
|
542
|
+
warnings,
|
|
232
543
|
errors: status === "failed" ? [normalizedMessage] : [],
|
|
233
544
|
};
|
|
234
545
|
}
|
|
235
546
|
|
|
547
|
+
function isNotInstalledMessage(message: string | undefined): boolean {
|
|
548
|
+
return message !== undefined && /(not\s+installed|not\s+found|no\s+installed)/i.test(message);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function extractMarketplaceNameFromSelector(selector: string): string | undefined {
|
|
552
|
+
const separator = selector.lastIndexOf("@");
|
|
553
|
+
if (separator <= 0 || separator === selector.length - 1) return undefined;
|
|
554
|
+
return selector.slice(separator + 1);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function extractPluginNameFromSelector(selector: string): string | undefined {
|
|
558
|
+
const separator = selector.lastIndexOf("@");
|
|
559
|
+
const pluginName = separator <= 0 ? selector : selector.slice(0, separator);
|
|
560
|
+
return pluginName.trim() === "" ? undefined : pluginName;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function isGitMarketplaceSource(source: string): boolean {
|
|
564
|
+
const trimmed = source.trim();
|
|
565
|
+
if (/^(https?|ssh|git):\/\//i.test(trimmed)) return true;
|
|
566
|
+
if (/^git@[^:]+:.+/i.test(trimmed)) return true;
|
|
567
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:\.git)?$/.test(trimmed);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function isSshGitMarketplaceSource(source: string): boolean {
|
|
571
|
+
const trimmed = source.trim();
|
|
572
|
+
return /^ssh:\/\//i.test(trimmed) || /^git@[^:]+:.+/i.test(trimmed);
|
|
573
|
+
}
|
|
574
|
+
|
|
236
575
|
function isMissingExit(exitCode: number, stderr?: string): boolean {
|
|
237
576
|
if (exitCode === 127) return true;
|
|
238
577
|
const normalized = stderr?.toLowerCase() ?? "";
|
|
@@ -248,6 +587,12 @@ function isCommandMissingError(error: unknown): boolean {
|
|
|
248
587
|
);
|
|
249
588
|
}
|
|
250
589
|
|
|
590
|
+
function isNotFoundError(error: unknown): boolean {
|
|
591
|
+
return (
|
|
592
|
+
error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT"
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
251
596
|
function describeError(error: unknown): string {
|
|
252
597
|
if (error instanceof Error) {
|
|
253
598
|
return error.message;
|
|
@@ -255,3 +600,17 @@ function describeError(error: unknown): string {
|
|
|
255
600
|
|
|
256
601
|
return String(error);
|
|
257
602
|
}
|
|
603
|
+
|
|
604
|
+
function resolveCodexPathsFromHome(): CodexPaths {
|
|
605
|
+
const home = process.env.HOME;
|
|
606
|
+
|
|
607
|
+
if (home === undefined || home.trim() === "") {
|
|
608
|
+
throw new Error("Cannot resolve Codex paths: HOME is not set");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
return resolveCodexPaths({ homeDir: home });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function ensureTrailingNewline(content: string): string {
|
|
615
|
+
return content.endsWith("\n") ? content : `${content}\n`;
|
|
616
|
+
}
|