@elizaos/cli 1.0.14 → 1.0.16
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/assets/{index-WVhZEObe.js → index-BBiLesJT.js} +3 -3
- package/dist/assets/index-BBiLesJT.js.br +0 -0
- package/dist/assets/{index-WVhZEObe.js.map → index-BBiLesJT.js.map} +1 -1
- package/dist/assets/index-DxW_jg81.css +1 -0
- package/dist/assets/index-DxW_jg81.css.br +0 -0
- package/dist/assets/{index-BHRtDt-w.js → index-EfDd1Y35.js} +2105 -1820
- package/dist/assets/index-EfDd1Y35.js.br +0 -0
- package/dist/assets/{index-BHRtDt-w.js.map → index-EfDd1Y35.js.map} +1 -1
- package/dist/assets/{vendor-DSdxb8P-.js → vendor-DS2z8upE.js} +2 -2
- package/dist/assets/vendor-DS2z8upE.js.br +0 -0
- package/dist/assets/{vendor-DSdxb8P-.js.map → vendor-DS2z8upE.js.map} +1 -1
- package/dist/{chunk-IYFVOAOM.js → chunk-OK5O2MHJ.js} +505 -705
- package/dist/{chunk-E2PKE6N5.js → chunk-PRO6QOLZ.js} +188 -56
- package/dist/{chunk-23FI2G4U.js → chunk-UVBNT4WP.js} +19 -6
- package/dist/{chunk-KB3JDWUI.js → chunk-W4NJVJQV.js} +43 -1
- package/dist/commands/agent/actions/index.js +2 -3
- package/dist/commands/agent/index.js +2 -3
- package/dist/commands/create/actions/index.d.ts +3 -0
- package/dist/commands/create/actions/index.js +3 -4
- package/dist/commands/create/index.js +4 -5
- package/dist/elizaos-avatar.png +0 -0
- package/dist/index.html +3 -3
- package/dist/index.js +1462 -381
- package/dist/migration-guides/advanced-migration-guide.md +459 -0
- package/dist/migration-guides/completion-requirements.md +379 -0
- package/dist/migration-guides/integrated-migration-loop.md +392 -0
- package/dist/migration-guides/migration-guide.md +712 -0
- package/dist/migration-guides/prompt-and-generation-guide.md +702 -0
- package/dist/migration-guides/state-and-providers-guide.md +544 -0
- package/dist/migration-guides/testing-guide.md +1021 -0
- package/dist/{plugin-creator-TLQLTQIB.js → plugin-creator-ZCOZ3UCT.js} +1 -2
- package/dist/{registry-3YA2T6KV.js → registry-FBPEGL2T.js} +2 -3
- package/dist/templates/project-starter/package.json +4 -4
- package/dist/templates/project-starter/src/__tests__/character-plugin-ordering.test.ts +440 -0
- package/dist/templates/project-starter/src/character.ts +11 -4
- package/dist/templates/project-tee-starter/package.json +3 -3
- package/dist/templates/project-tee-starter/src/character.ts +13 -2
- package/dist/{utils-QKNGJZLP.js → utils-I3EA43DE.js} +6 -19
- package/package.json +8 -6
- package/templates/project-starter/package.json +4 -4
- package/templates/project-starter/src/__tests__/character-plugin-ordering.test.ts +440 -0
- package/templates/project-starter/src/character.ts +11 -4
- package/templates/project-tee-starter/package.json +3 -3
- package/templates/project-tee-starter/src/character.ts +13 -2
- package/dist/assets/index-DzLbKTxV.css +0 -1
- package/dist/assets/index-DzLbKTxV.css.br +0 -0
- package/dist/assets/index-WVhZEObe.js.br +0 -0
- package/dist/assets/vendor-DSdxb8P-.js.br +0 -0
- package/dist/chunk-RIAWNDYI.js +0 -49
- package/dist/migrator-UQ4XFYE5.js +0 -744
|
@@ -1,744 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
const require = createRequire(import.meta.url);
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
emoji
|
|
7
|
-
} from "./chunk-KB3JDWUI.js";
|
|
8
|
-
import "./chunk-AQ6OMR2A.js";
|
|
9
|
-
|
|
10
|
-
// src/utils/upgrade/migrator.ts
|
|
11
|
-
import Anthropic from "@anthropic-ai/sdk";
|
|
12
|
-
import { logger } from "@elizaos/core";
|
|
13
|
-
import { execa } from "execa";
|
|
14
|
-
import * as fs from "fs-extra";
|
|
15
|
-
import { globby } from "globby";
|
|
16
|
-
import ora from "ora";
|
|
17
|
-
import * as path from "path";
|
|
18
|
-
import { dirname as dirname2 } from "path";
|
|
19
|
-
import simpleGit from "simple-git";
|
|
20
|
-
import { encoding_for_model } from "tiktoken";
|
|
21
|
-
import { fileURLToPath } from "url";
|
|
22
|
-
import * as os from "os";
|
|
23
|
-
var __filename = fileURLToPath(import.meta.url);
|
|
24
|
-
var __dirname = dirname2(__filename);
|
|
25
|
-
var encoder = encoding_for_model("gpt-4");
|
|
26
|
-
var MAX_TOKENS = 1e5;
|
|
27
|
-
var BRANCH_NAME = "1.x-claude";
|
|
28
|
-
var MAX_TEST_ITERATIONS = 5;
|
|
29
|
-
var MAX_REVISION_ITERATIONS = 3;
|
|
30
|
-
var CLAUDE_CODE_TIMEOUT = 5 * 60 * 1e3;
|
|
31
|
-
var MIN_DISK_SPACE_GB = 2;
|
|
32
|
-
var LOCK_FILE_NAME = ".elizaos-migration.lock";
|
|
33
|
-
var PluginMigrator = class {
|
|
34
|
-
git;
|
|
35
|
-
repoPath;
|
|
36
|
-
anthropic;
|
|
37
|
-
changedFiles;
|
|
38
|
-
options;
|
|
39
|
-
lockFilePath = null;
|
|
40
|
-
activeClaudeProcess = null;
|
|
41
|
-
constructor(options = {}) {
|
|
42
|
-
this.git = simpleGit();
|
|
43
|
-
this.repoPath = null;
|
|
44
|
-
this.anthropic = null;
|
|
45
|
-
this.changedFiles = /* @__PURE__ */ new Set();
|
|
46
|
-
this.options = options;
|
|
47
|
-
this.registerCleanupHandlers();
|
|
48
|
-
}
|
|
49
|
-
registerCleanupHandlers() {
|
|
50
|
-
const cleanup = async () => {
|
|
51
|
-
logger.info("Cleaning up migration process...");
|
|
52
|
-
if (this.activeClaudeProcess) {
|
|
53
|
-
try {
|
|
54
|
-
this.activeClaudeProcess.kill();
|
|
55
|
-
logger.info("Terminated active Claude Code process");
|
|
56
|
-
} catch (error) {
|
|
57
|
-
logger.error("Failed to terminate Claude Code process:", error);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
await this.removeLockFile();
|
|
61
|
-
process.exit(1);
|
|
62
|
-
};
|
|
63
|
-
process.on("SIGINT", cleanup);
|
|
64
|
-
process.on("SIGTERM", cleanup);
|
|
65
|
-
process.on("uncaughtException", async (error) => {
|
|
66
|
-
logger.error("Uncaught exception:", error);
|
|
67
|
-
await cleanup();
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
async initializeAnthropic() {
|
|
71
|
-
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
72
|
-
if (!apiKey) {
|
|
73
|
-
logger.error("ANTHROPIC_API_KEY not found in environment.");
|
|
74
|
-
throw new Error("ANTHROPIC_API_KEY is required for migration strategy generation");
|
|
75
|
-
}
|
|
76
|
-
this.anthropic = new Anthropic({ apiKey });
|
|
77
|
-
}
|
|
78
|
-
async migrate(input) {
|
|
79
|
-
const spinner = ora(`Processing ${input}...`).start();
|
|
80
|
-
let originalBranch;
|
|
81
|
-
try {
|
|
82
|
-
await this.initializeAnthropic();
|
|
83
|
-
spinner.text = `Checking disk space...`;
|
|
84
|
-
await this.checkDiskSpace();
|
|
85
|
-
try {
|
|
86
|
-
await execa("claude", ["--version"], { stdio: "pipe" });
|
|
87
|
-
} catch {
|
|
88
|
-
throw new Error(
|
|
89
|
-
"Claude Code is required for migration. Install with: bun install -g @anthropic-ai/claude-code"
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
spinner.text = `Setting up repository for ${input}...`;
|
|
93
|
-
await this.handleInput(input);
|
|
94
|
-
spinner.succeed(`Input validated for ${input}`);
|
|
95
|
-
await this.createLockFile();
|
|
96
|
-
logger.warn(
|
|
97
|
-
`${emoji.warning("SECURITY WARNING: This command will execute code from the repository.")}`
|
|
98
|
-
);
|
|
99
|
-
logger.warn("Only run this on trusted repositories you own or have reviewed.");
|
|
100
|
-
originalBranch = (await this.git.branch()).current;
|
|
101
|
-
logger.info(`Current branch: ${originalBranch}`);
|
|
102
|
-
spinner.text = `Creating branch ${BRANCH_NAME}...`;
|
|
103
|
-
await this.createBranch();
|
|
104
|
-
spinner.succeed(`Branch ${BRANCH_NAME} created`);
|
|
105
|
-
const initialCommit = await this.git.revparse(["HEAD"]);
|
|
106
|
-
const claudeMdPath = path.join(this.repoPath, "CLAUDE.md");
|
|
107
|
-
let skipGeneration = false;
|
|
108
|
-
if (await fs.pathExists(claudeMdPath)) {
|
|
109
|
-
spinner.info(`CLAUDE.md already exists. Skipping generation.`);
|
|
110
|
-
skipGeneration = true;
|
|
111
|
-
}
|
|
112
|
-
if (!skipGeneration) {
|
|
113
|
-
spinner.text = `Analyzing repository structure...`;
|
|
114
|
-
const context = await this.analyzeRepository();
|
|
115
|
-
spinner.succeed(`Repository analyzed`);
|
|
116
|
-
spinner.text = `Generating migration strategy...`;
|
|
117
|
-
const specificStrategy = await this.generateMigrationStrategy(context);
|
|
118
|
-
spinner.succeed(`Migration strategy generated`);
|
|
119
|
-
spinner.text = `Creating migration instructions...`;
|
|
120
|
-
await this.createMigrationInstructions(specificStrategy);
|
|
121
|
-
spinner.succeed(`Migration instructions created`);
|
|
122
|
-
}
|
|
123
|
-
if (!this.options.skipTests) {
|
|
124
|
-
spinner.text = `Running migration with test validation...`;
|
|
125
|
-
const migrationSuccess = await this.runMigrationWithTestLoop();
|
|
126
|
-
if (!migrationSuccess) {
|
|
127
|
-
throw new Error("Migration failed after maximum test iterations");
|
|
128
|
-
}
|
|
129
|
-
} else {
|
|
130
|
-
spinner.text = `Running migration (tests skipped)...`;
|
|
131
|
-
await this.runClaudeCode();
|
|
132
|
-
spinner.succeed(`Migration applied (test validation skipped)`);
|
|
133
|
-
}
|
|
134
|
-
await this.trackChangedFiles(initialCommit);
|
|
135
|
-
if (!this.options.skipValidation) {
|
|
136
|
-
spinner.text = `Validating migration for production readiness...`;
|
|
137
|
-
const validationSuccess = await this.runProductionValidationLoop();
|
|
138
|
-
if (!validationSuccess) {
|
|
139
|
-
throw new Error("Migration not production ready after maximum revision iterations");
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
spinner.info(`Production validation skipped`);
|
|
143
|
-
}
|
|
144
|
-
spinner.text = `Pushing branch ${BRANCH_NAME} to origin...`;
|
|
145
|
-
try {
|
|
146
|
-
await this.git.push("origin", BRANCH_NAME, { "--dry-run": null });
|
|
147
|
-
await this.git.push("origin", BRANCH_NAME, { "--set-upstream": null });
|
|
148
|
-
spinner.succeed(`Branch ${BRANCH_NAME} pushed`);
|
|
149
|
-
} catch (pushError) {
|
|
150
|
-
spinner.warn(`Could not push branch to origin: ${pushError.message}`);
|
|
151
|
-
logger.warn("Branch created locally but not pushed. You may need to push manually.");
|
|
152
|
-
}
|
|
153
|
-
logger.info(`${emoji.success(`Migration complete for ${input}!`)}`);
|
|
154
|
-
return {
|
|
155
|
-
success: true,
|
|
156
|
-
branchName: BRANCH_NAME,
|
|
157
|
-
repoPath: this.repoPath
|
|
158
|
-
};
|
|
159
|
-
} catch (error) {
|
|
160
|
-
spinner.fail(`Migration failed for ${input}`);
|
|
161
|
-
logger.error(`Error processing ${input}:`, error);
|
|
162
|
-
await this.removeLockFile();
|
|
163
|
-
try {
|
|
164
|
-
if (this.git && originalBranch) {
|
|
165
|
-
logger.info(`Attempting to restore original branch: ${originalBranch}`);
|
|
166
|
-
await this.git.checkout(originalBranch);
|
|
167
|
-
}
|
|
168
|
-
} catch (restoreError) {
|
|
169
|
-
logger.error("Failed to restore original branch:", restoreError);
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
success: false,
|
|
173
|
-
branchName: BRANCH_NAME,
|
|
174
|
-
repoPath: this.repoPath || "",
|
|
175
|
-
error
|
|
176
|
-
};
|
|
177
|
-
} finally {
|
|
178
|
-
await this.removeLockFile();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
async runMigrationWithTestLoop() {
|
|
182
|
-
let testIteration = 0;
|
|
183
|
-
let allTestsPass = false;
|
|
184
|
-
while (testIteration < MAX_TEST_ITERATIONS && !allTestsPass) {
|
|
185
|
-
testIteration++;
|
|
186
|
-
logger.info(`Test iteration ${testIteration}/${MAX_TEST_ITERATIONS}`);
|
|
187
|
-
if (testIteration === 1) {
|
|
188
|
-
await this.runClaudeCode();
|
|
189
|
-
} else {
|
|
190
|
-
const testErrors = await this.getTestErrors();
|
|
191
|
-
await this.runClaudeCodeWithContext(testErrors);
|
|
192
|
-
}
|
|
193
|
-
const testResult = await this.runTests();
|
|
194
|
-
allTestsPass = testResult.success;
|
|
195
|
-
if (allTestsPass) {
|
|
196
|
-
logger.info("\u2705 All tests passing!");
|
|
197
|
-
return true;
|
|
198
|
-
} else {
|
|
199
|
-
logger.warn(`Tests failed. ${MAX_TEST_ITERATIONS - testIteration} attempts remaining.`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return allTestsPass;
|
|
203
|
-
}
|
|
204
|
-
async runProductionValidationLoop() {
|
|
205
|
-
let revisionIteration = 0;
|
|
206
|
-
let productionReady = false;
|
|
207
|
-
while (revisionIteration < MAX_REVISION_ITERATIONS && !productionReady) {
|
|
208
|
-
revisionIteration++;
|
|
209
|
-
logger.info(
|
|
210
|
-
`Production validation iteration ${revisionIteration}/${MAX_REVISION_ITERATIONS}`
|
|
211
|
-
);
|
|
212
|
-
const validationResult = await this.validateProductionReadiness();
|
|
213
|
-
productionReady = validationResult.production_ready;
|
|
214
|
-
if (productionReady) {
|
|
215
|
-
logger.info("\u2705 Migration validated as production ready!");
|
|
216
|
-
return true;
|
|
217
|
-
} else if (validationResult.revision_instructions) {
|
|
218
|
-
logger.warn("Migration needs revisions. Applying changes...");
|
|
219
|
-
await this.runClaudeCodeWithContext(validationResult.revision_instructions);
|
|
220
|
-
const testSuccess = await this.runMigrationWithTestLoop();
|
|
221
|
-
if (!testSuccess) {
|
|
222
|
-
return false;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return productionReady;
|
|
227
|
-
}
|
|
228
|
-
async runTests() {
|
|
229
|
-
try {
|
|
230
|
-
const packageJsonPath = path.join(this.repoPath, "package.json");
|
|
231
|
-
if (!await fs.pathExists(packageJsonPath)) {
|
|
232
|
-
return {
|
|
233
|
-
success: false,
|
|
234
|
-
errors: "No package.json found in repository. Cannot run tests."
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
logger.info("Installing dependencies...");
|
|
238
|
-
try {
|
|
239
|
-
await execa("bun", ["install"], {
|
|
240
|
-
cwd: this.repoPath,
|
|
241
|
-
stdio: "pipe",
|
|
242
|
-
timeout: 3e5
|
|
243
|
-
// 5 minute timeout for bun install
|
|
244
|
-
});
|
|
245
|
-
} catch (installError) {
|
|
246
|
-
if (installError.timedOut) {
|
|
247
|
-
return {
|
|
248
|
-
success: false,
|
|
249
|
-
errors: "bun install timed out after 5 minutes. Check network connection."
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
logger.warn(`bun install failed: ${installError.message}`);
|
|
253
|
-
}
|
|
254
|
-
let testCommand;
|
|
255
|
-
let testArgs;
|
|
256
|
-
try {
|
|
257
|
-
await execa("bunx", ["elizaos", "--version"], {
|
|
258
|
-
cwd: this.repoPath,
|
|
259
|
-
stdio: "pipe"
|
|
260
|
-
});
|
|
261
|
-
testCommand = "bunx";
|
|
262
|
-
testArgs = ["elizaos", "test"];
|
|
263
|
-
logger.info("Running tests with elizaos test...");
|
|
264
|
-
} catch {
|
|
265
|
-
const packageJson = JSON.parse(
|
|
266
|
-
await fs.readFile(path.join(this.repoPath, "package.json"), "utf-8")
|
|
267
|
-
);
|
|
268
|
-
if (packageJson.scripts?.test) {
|
|
269
|
-
testCommand = "bun";
|
|
270
|
-
testArgs = ["test"];
|
|
271
|
-
logger.info("Running tests with bun test...");
|
|
272
|
-
} else {
|
|
273
|
-
throw new Error("No test script found in package.json and elizaos not available");
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
logger.info("Running tests...");
|
|
277
|
-
await execa(testCommand, testArgs, {
|
|
278
|
-
cwd: this.repoPath,
|
|
279
|
-
stdio: "inherit"
|
|
280
|
-
});
|
|
281
|
-
return { success: true };
|
|
282
|
-
} catch (error) {
|
|
283
|
-
const errorOutput = (error.stdout || "") + "\n" + (error.stderr || "");
|
|
284
|
-
logger.error("Test execution failed:", errorOutput);
|
|
285
|
-
return { success: false, errors: errorOutput };
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
async getTestErrors() {
|
|
289
|
-
try {
|
|
290
|
-
const result = await this.runTests();
|
|
291
|
-
return result.errors || "Tests failed but no specific errors captured";
|
|
292
|
-
} catch (error) {
|
|
293
|
-
return `Failed to capture test errors: ${error}`;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
async trackChangedFiles(initialCommit) {
|
|
297
|
-
const diff = await this.git.diff(["--name-only", initialCommit, "HEAD"]);
|
|
298
|
-
const files = diff.split("\n").filter((f) => f.trim());
|
|
299
|
-
files.forEach((file) => this.changedFiles.add(file));
|
|
300
|
-
}
|
|
301
|
-
async validateProductionReadiness() {
|
|
302
|
-
const changedFilesContent = await this.getChangedFilesContent();
|
|
303
|
-
const prompt = `You are reviewing a migration from Eliza 0.x to 1.x. Please evaluate if this migration is production-ready.
|
|
304
|
-
|
|
305
|
-
## Changed Files:
|
|
306
|
-
${changedFilesContent}
|
|
307
|
-
|
|
308
|
-
## Evaluation Criteria:
|
|
309
|
-
1. All imports are correctly updated
|
|
310
|
-
2. All types are properly migrated (Account \u2192 Entity, userId \u2192 entityId, etc.)
|
|
311
|
-
3. Services extend the base Service class with lifecycle methods
|
|
312
|
-
4. Event system is properly implemented
|
|
313
|
-
5. Memory operations use the new API
|
|
314
|
-
6. Model usage is converted to runtime.useModel
|
|
315
|
-
7. Templates are migrated to XML format
|
|
316
|
-
8. Comprehensive tests exist and cover the main functionality
|
|
317
|
-
9. No stubs or incomplete code remains
|
|
318
|
-
10. Error handling is robust
|
|
319
|
-
|
|
320
|
-
## Response Format:
|
|
321
|
-
Respond with a JSON object:
|
|
322
|
-
{
|
|
323
|
-
"production_ready": boolean,
|
|
324
|
-
"revision_instructions": "Detailed instructions for what needs to be fixed (only if not production ready)"
|
|
325
|
-
}`;
|
|
326
|
-
const message = await this.anthropic.messages.create({
|
|
327
|
-
model: "claude-opus-4-20250514",
|
|
328
|
-
max_tokens: 8192,
|
|
329
|
-
temperature: 0,
|
|
330
|
-
messages: [
|
|
331
|
-
{
|
|
332
|
-
role: "user",
|
|
333
|
-
content: prompt
|
|
334
|
-
}
|
|
335
|
-
]
|
|
336
|
-
});
|
|
337
|
-
try {
|
|
338
|
-
const responseText = message.content.map((block) => block.type === "text" ? block.text : "").join("");
|
|
339
|
-
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
340
|
-
if (jsonMatch) {
|
|
341
|
-
return JSON.parse(jsonMatch[0]);
|
|
342
|
-
}
|
|
343
|
-
throw new Error("No JSON found in response");
|
|
344
|
-
} catch (error) {
|
|
345
|
-
logger.error("Failed to parse validation response:", error);
|
|
346
|
-
return {
|
|
347
|
-
production_ready: false,
|
|
348
|
-
revision_instructions: "Failed to parse validation response. Please review manually."
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
async getChangedFilesContent() {
|
|
353
|
-
let content = "";
|
|
354
|
-
let totalTokens = 0;
|
|
355
|
-
for (const file of this.changedFiles) {
|
|
356
|
-
const filePath = path.join(this.repoPath, file);
|
|
357
|
-
if (await fs.pathExists(filePath)) {
|
|
358
|
-
let fileContent = await fs.readFile(filePath, "utf-8");
|
|
359
|
-
let fileTokens = encoder.encode(fileContent).length;
|
|
360
|
-
if (fileTokens > MAX_TOKENS) {
|
|
361
|
-
const lines = fileContent.split("\n");
|
|
362
|
-
let truncatedContent = "";
|
|
363
|
-
let truncatedTokens = 0;
|
|
364
|
-
for (const line of lines) {
|
|
365
|
-
const lineTokens = encoder.encode(line + "\n").length;
|
|
366
|
-
if (truncatedTokens + lineTokens > MAX_TOKENS * 0.8) {
|
|
367
|
-
truncatedContent += "\n... (file truncated due to size) ...";
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
truncatedContent += line + "\n";
|
|
371
|
-
truncatedTokens += lineTokens;
|
|
372
|
-
}
|
|
373
|
-
fileContent = truncatedContent;
|
|
374
|
-
fileTokens = truncatedTokens;
|
|
375
|
-
}
|
|
376
|
-
if (totalTokens + fileTokens > MAX_TOKENS) break;
|
|
377
|
-
content += `
|
|
378
|
-
### File: ${file}
|
|
379
|
-
\`\`\`typescript
|
|
380
|
-
${fileContent}
|
|
381
|
-
\`\`\`
|
|
382
|
-
`;
|
|
383
|
-
totalTokens += fileTokens;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return content;
|
|
387
|
-
}
|
|
388
|
-
async runClaudeCodeWithContext(context) {
|
|
389
|
-
const prompt = `Please read the CLAUDE.md file and apply the migration. Additionally, address the following:
|
|
390
|
-
|
|
391
|
-
${context}
|
|
392
|
-
|
|
393
|
-
Make all necessary changes to fix the issues and ensure the migration is complete and correct.`;
|
|
394
|
-
await this.runClaudeCodeWithPrompt(prompt);
|
|
395
|
-
}
|
|
396
|
-
async runClaudeCodeWithPrompt(prompt) {
|
|
397
|
-
process.chdir(this.repoPath);
|
|
398
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
399
|
-
setTimeout(() => {
|
|
400
|
-
reject(
|
|
401
|
-
new Error(`Claude Code execution timed out after ${CLAUDE_CODE_TIMEOUT / 1e3} seconds`)
|
|
402
|
-
);
|
|
403
|
-
}, CLAUDE_CODE_TIMEOUT);
|
|
404
|
-
});
|
|
405
|
-
const executePromise = (async () => {
|
|
406
|
-
try {
|
|
407
|
-
this.activeClaudeProcess = execa(
|
|
408
|
-
"claude",
|
|
409
|
-
[
|
|
410
|
-
"--print",
|
|
411
|
-
"--max-turns",
|
|
412
|
-
"30",
|
|
413
|
-
"--verbose",
|
|
414
|
-
"--model",
|
|
415
|
-
"opus",
|
|
416
|
-
"--dangerously-skip-permissions",
|
|
417
|
-
prompt
|
|
418
|
-
],
|
|
419
|
-
{
|
|
420
|
-
stdio: "inherit",
|
|
421
|
-
cwd: this.repoPath
|
|
422
|
-
}
|
|
423
|
-
);
|
|
424
|
-
await this.activeClaudeProcess;
|
|
425
|
-
this.activeClaudeProcess = null;
|
|
426
|
-
} catch (error) {
|
|
427
|
-
this.activeClaudeProcess = null;
|
|
428
|
-
if (error.code === "ENOENT") {
|
|
429
|
-
throw new Error(
|
|
430
|
-
"Claude Code not found! Install with: bun install -g @anthropic-ai/claude-code"
|
|
431
|
-
);
|
|
432
|
-
}
|
|
433
|
-
throw error;
|
|
434
|
-
}
|
|
435
|
-
})();
|
|
436
|
-
try {
|
|
437
|
-
await Promise.race([executePromise, timeoutPromise]);
|
|
438
|
-
} catch (error) {
|
|
439
|
-
if (this.activeClaudeProcess) {
|
|
440
|
-
try {
|
|
441
|
-
this.activeClaudeProcess.kill();
|
|
442
|
-
this.activeClaudeProcess = null;
|
|
443
|
-
} catch (killError) {
|
|
444
|
-
logger.error("Failed to kill timed-out process:", killError);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
logger.error("Claude Code execution failed:", error);
|
|
448
|
-
throw error;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
// Include all the other methods from the original updater.ts
|
|
452
|
-
// (handleInput, analyzeRepository, generateMigrationStrategy, createMigrationInstructions,
|
|
453
|
-
// createBranch, runClaudeCode, etc.)
|
|
454
|
-
async handleInput(input) {
|
|
455
|
-
if (input.startsWith("https://github.com/")) {
|
|
456
|
-
const repoName = input.split("/").slice(-2).join("/").replace(".git", "");
|
|
457
|
-
const repoFolder = repoName.split("/")[1] || repoName;
|
|
458
|
-
this.repoPath = path.join(process.cwd(), "cloned_repos", repoFolder);
|
|
459
|
-
await fs.ensureDir(path.dirname(this.repoPath));
|
|
460
|
-
if (await fs.pathExists(this.repoPath)) {
|
|
461
|
-
this.git = simpleGit(this.repoPath);
|
|
462
|
-
try {
|
|
463
|
-
await this.git.fetch();
|
|
464
|
-
} catch (fetchError) {
|
|
465
|
-
await fs.remove(this.repoPath);
|
|
466
|
-
await simpleGit().clone(input, this.repoPath);
|
|
467
|
-
this.git = simpleGit(this.repoPath);
|
|
468
|
-
}
|
|
469
|
-
} else {
|
|
470
|
-
await simpleGit().clone(input, this.repoPath);
|
|
471
|
-
this.git = simpleGit(this.repoPath);
|
|
472
|
-
}
|
|
473
|
-
const branches = await this.git.branch();
|
|
474
|
-
if (branches.all.includes("remotes/origin/0.x") || branches.all.includes("0.x")) {
|
|
475
|
-
if (branches.current !== "0.x") await this.git.checkout("0.x");
|
|
476
|
-
} else if (branches.all.includes("remotes/origin/main") || branches.all.includes("main")) {
|
|
477
|
-
if (branches.current !== "main") await this.git.checkout("main");
|
|
478
|
-
}
|
|
479
|
-
} else {
|
|
480
|
-
this.repoPath = path.resolve(input);
|
|
481
|
-
if (!await fs.pathExists(this.repoPath)) {
|
|
482
|
-
throw new Error(`Folder not found: ${this.repoPath}`);
|
|
483
|
-
}
|
|
484
|
-
this.git = simpleGit(this.repoPath);
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
async analyzeRepository() {
|
|
488
|
-
const files = {
|
|
489
|
-
readme: null,
|
|
490
|
-
packageJson: null,
|
|
491
|
-
index: null,
|
|
492
|
-
sourceFiles: []
|
|
493
|
-
};
|
|
494
|
-
const readmePath = path.join(this.repoPath, "README.md");
|
|
495
|
-
if (await fs.pathExists(readmePath)) {
|
|
496
|
-
files.readme = await fs.readFile(readmePath, "utf-8");
|
|
497
|
-
}
|
|
498
|
-
const packagePath = path.join(this.repoPath, "package.json");
|
|
499
|
-
if (await fs.pathExists(packagePath)) {
|
|
500
|
-
files.packageJson = await fs.readFile(packagePath, "utf-8");
|
|
501
|
-
}
|
|
502
|
-
const indexPaths = ["index.ts", "src/index.ts", "index.js", "src/index.js"];
|
|
503
|
-
for (const indexPath of indexPaths) {
|
|
504
|
-
const fullPath = path.join(this.repoPath, indexPath);
|
|
505
|
-
if (await fs.pathExists(fullPath)) {
|
|
506
|
-
files.index = {
|
|
507
|
-
path: indexPath,
|
|
508
|
-
content: await fs.readFile(fullPath, "utf-8")
|
|
509
|
-
};
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
const sourceFiles = await globby(["**/*.ts", "**/*.js"], {
|
|
514
|
-
cwd: this.repoPath,
|
|
515
|
-
ignore: [
|
|
516
|
-
"node_modules/**",
|
|
517
|
-
"dist/**",
|
|
518
|
-
"build/**",
|
|
519
|
-
"*.test.*",
|
|
520
|
-
"*.spec.*",
|
|
521
|
-
"coverage/**",
|
|
522
|
-
"cloned_repos/**",
|
|
523
|
-
"**/*.min.js",
|
|
524
|
-
"**/*.min.ts",
|
|
525
|
-
"**/vendor/**",
|
|
526
|
-
"**/lib/**"
|
|
527
|
-
]
|
|
528
|
-
});
|
|
529
|
-
let totalTokens = 0;
|
|
530
|
-
const readmeTokens = files.readme ? encoder.encode(files.readme).length : 0;
|
|
531
|
-
const packageTokens = files.packageJson ? encoder.encode(files.packageJson).length : 0;
|
|
532
|
-
const indexTokens = files.index ? encoder.encode(files.index.content).length : 0;
|
|
533
|
-
totalTokens = readmeTokens + packageTokens + indexTokens;
|
|
534
|
-
const sortedFiles = sourceFiles.sort((a, b) => {
|
|
535
|
-
const depthA = a.split("/").length;
|
|
536
|
-
const depthB = b.split("/").length;
|
|
537
|
-
if (depthA !== depthB) return depthA - depthB;
|
|
538
|
-
return a.localeCompare(b);
|
|
539
|
-
});
|
|
540
|
-
for (const file of sortedFiles) {
|
|
541
|
-
if (file === files.index?.path) continue;
|
|
542
|
-
const filePath = path.join(this.repoPath, file);
|
|
543
|
-
const stats = await fs.stat(filePath);
|
|
544
|
-
if (stats.size > 1024 * 1024) {
|
|
545
|
-
logger.warn(`Skipping large file: ${file} (${stats.size} bytes)`);
|
|
546
|
-
continue;
|
|
547
|
-
}
|
|
548
|
-
const content = await fs.readFile(filePath, "utf-8");
|
|
549
|
-
if (content.includes("\0")) {
|
|
550
|
-
logger.warn(`Skipping binary file: ${file}`);
|
|
551
|
-
continue;
|
|
552
|
-
}
|
|
553
|
-
const fileTokens = encoder.encode(content).length;
|
|
554
|
-
if (totalTokens + fileTokens > MAX_TOKENS) break;
|
|
555
|
-
files.sourceFiles.push({ path: file, content });
|
|
556
|
-
totalTokens += fileTokens;
|
|
557
|
-
}
|
|
558
|
-
let context = "";
|
|
559
|
-
if (files.readme) context += "# README.md\n\n" + files.readme + "\n\n";
|
|
560
|
-
if (files.packageJson)
|
|
561
|
-
context += "# package.json\n\n```json\n" + files.packageJson + "\n```\n\n";
|
|
562
|
-
if (files.index)
|
|
563
|
-
context += `# ${files.index.path}
|
|
564
|
-
|
|
565
|
-
\`\`\`typescript
|
|
566
|
-
${files.index.content}
|
|
567
|
-
\`\`\`
|
|
568
|
-
|
|
569
|
-
`;
|
|
570
|
-
for (const file of files.sourceFiles) {
|
|
571
|
-
context += `# ${file.path}
|
|
572
|
-
|
|
573
|
-
\`\`\`typescript
|
|
574
|
-
${file.content}
|
|
575
|
-
\`\`\`
|
|
576
|
-
|
|
577
|
-
`;
|
|
578
|
-
}
|
|
579
|
-
return context;
|
|
580
|
-
}
|
|
581
|
-
async generateMigrationStrategy(context) {
|
|
582
|
-
const prompt = `You are migrating an Eliza plugin from version 0.x to 1.x. Analyze the provided codebase and generate a SPECIFIC, DETAILED migration strategy.
|
|
583
|
-
|
|
584
|
-
## Key Migration Requirements:
|
|
585
|
-
|
|
586
|
-
1. **Import Updates**: All @elizaos imports must use new paths (elizaLogger \u2192 logger, etc.)
|
|
587
|
-
2. **Type Migrations**: Account \u2192 Entity, userId \u2192 entityId, room \u2192 world
|
|
588
|
-
3. **Service Architecture**: Services must extend base Service class with lifecycle methods
|
|
589
|
-
4. **Event System**: Implement proper event emission and handling
|
|
590
|
-
5. **Memory Operations**: Update to use new API with table names
|
|
591
|
-
6. **Model Usage**: Convert generateText to runtime.useModel
|
|
592
|
-
7. **Templates**: Migrate from JSON to XML format
|
|
593
|
-
8. **Testing**: Create comprehensive unit and integration tests
|
|
594
|
-
|
|
595
|
-
## Repository Context:
|
|
596
|
-
|
|
597
|
-
${context}
|
|
598
|
-
|
|
599
|
-
## Task:
|
|
600
|
-
|
|
601
|
-
Generate a SPECIFIC migration strategy for THIS plugin. Your response should include:
|
|
602
|
-
|
|
603
|
-
1. **Exact File Changes**: List each file that needs to be modified with specific changes
|
|
604
|
-
2. **Import Mappings**: Exact old import \u2192 new import for this codebase
|
|
605
|
-
3. **Type Updates**: List every type that needs updating with old \u2192 new
|
|
606
|
-
4. **Service Migrations**: Identify services and exactly how to migrate them
|
|
607
|
-
5. **Memory Operation Updates**: Find all memory operations and show exact changes
|
|
608
|
-
6. **Model Usage Updates**: Find all model calls and show exact replacements
|
|
609
|
-
7. **Test Files to Create**: List specific test files with what they should test
|
|
610
|
-
8. **Package.json Updates**: Exact scripts and dependencies to add/update
|
|
611
|
-
|
|
612
|
-
Be extremely specific. Use actual file names, function names, and line references from the codebase.
|
|
613
|
-
Format your response as a clear, actionable migration plan.`;
|
|
614
|
-
let retries = 3;
|
|
615
|
-
let lastError = null;
|
|
616
|
-
while (retries > 0) {
|
|
617
|
-
try {
|
|
618
|
-
const message = await this.anthropic.messages.create({
|
|
619
|
-
model: "claude-opus-4-20250514",
|
|
620
|
-
max_tokens: 8192,
|
|
621
|
-
temperature: 0,
|
|
622
|
-
messages: [
|
|
623
|
-
{
|
|
624
|
-
role: "user",
|
|
625
|
-
content: prompt
|
|
626
|
-
}
|
|
627
|
-
]
|
|
628
|
-
});
|
|
629
|
-
return message.content.map((block) => block.type === "text" ? block.text : "").join("");
|
|
630
|
-
} catch (error) {
|
|
631
|
-
lastError = error;
|
|
632
|
-
retries--;
|
|
633
|
-
if (retries > 0) {
|
|
634
|
-
logger.warn(`API call failed, retrying... (${retries} attempts remaining)`);
|
|
635
|
-
await new Promise((resolve2) => setTimeout(resolve2, 2e3));
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
throw new Error(
|
|
640
|
-
`Failed to generate migration strategy after 3 attempts: ${lastError?.message}`
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
async createMigrationInstructions(specificStrategy) {
|
|
644
|
-
const baseClaude = await fs.readFile(path.join(__dirname, "./CLAUDE.md"), "utf-8");
|
|
645
|
-
const combinedInstructions = baseClaude + `
|
|
646
|
-
|
|
647
|
-
## SPECIFIC MIGRATION STRATEGY FOR THIS PLUGIN
|
|
648
|
-
|
|
649
|
-
${specificStrategy}
|
|
650
|
-
|
|
651
|
-
## MIGRATION EXECUTION INSTRUCTIONS
|
|
652
|
-
|
|
653
|
-
You are now going to apply the above migration strategy to this codebase. Follow these steps:
|
|
654
|
-
|
|
655
|
-
1. **Apply All File Changes**: Go through each file listed in the strategy and apply the exact changes specified
|
|
656
|
-
2. **Create Test Files**: Create all the test files mentioned in the strategy with comprehensive test coverage
|
|
657
|
-
3. **Update package.json**: Add all scripts and dependencies as specified
|
|
658
|
-
4. **Run Tests**: After making changes, run the tests to ensure everything works
|
|
659
|
-
5. **Fix Any Issues**: If tests fail, debug and fix the issues
|
|
660
|
-
6. **Format Code**: Run prettier to format all code
|
|
661
|
-
|
|
662
|
-
Work systematically through the strategy. Make all changes, create all tests, and ensure everything is working before finishing.
|
|
663
|
-
|
|
664
|
-
The goal is a fully migrated, tested, and working 1.x plugin.
|
|
665
|
-
`;
|
|
666
|
-
const outputPath = path.join(this.repoPath, "CLAUDE.md");
|
|
667
|
-
await fs.writeFile(outputPath, combinedInstructions);
|
|
668
|
-
}
|
|
669
|
-
async createBranch() {
|
|
670
|
-
const branches = await this.git.branch();
|
|
671
|
-
const currentBranch = branches.current;
|
|
672
|
-
if (branches.all.includes(BRANCH_NAME) || branches.all.includes(`remotes/origin/${BRANCH_NAME}`)) {
|
|
673
|
-
if (currentBranch !== BRANCH_NAME) {
|
|
674
|
-
try {
|
|
675
|
-
await this.git.checkout(BRANCH_NAME);
|
|
676
|
-
} catch (e) {
|
|
677
|
-
await this.git.fetch("origin", BRANCH_NAME).catch(() => {
|
|
678
|
-
});
|
|
679
|
-
await this.git.deleteLocalBranch(BRANCH_NAME, true).catch(() => {
|
|
680
|
-
});
|
|
681
|
-
await this.git.checkoutBranch(BRANCH_NAME, `origin/${BRANCH_NAME}`).catch(async () => {
|
|
682
|
-
await this.git.checkout(currentBranch);
|
|
683
|
-
await this.git.checkoutLocalBranch(BRANCH_NAME);
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
} else {
|
|
688
|
-
await this.git.checkoutLocalBranch(BRANCH_NAME);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
async runClaudeCode() {
|
|
692
|
-
const migrationPrompt = `Please read the CLAUDE.md file in this repository and execute all the migration steps described there. Apply all changes systematically, create all tests, and ensure everything works.`;
|
|
693
|
-
await this.runClaudeCodeWithPrompt(migrationPrompt);
|
|
694
|
-
}
|
|
695
|
-
async checkDiskSpace() {
|
|
696
|
-
const diskSpace = await this.getAvailableDiskSpace();
|
|
697
|
-
if (diskSpace < MIN_DISK_SPACE_GB) {
|
|
698
|
-
throw new Error(
|
|
699
|
-
`Insufficient disk space. Need at least ${MIN_DISK_SPACE_GB}GB free, but only ${diskSpace.toFixed(2)}GB available.`
|
|
700
|
-
);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
async getAvailableDiskSpace() {
|
|
704
|
-
try {
|
|
705
|
-
const result = await execa("df", ["-k", os.tmpdir()]);
|
|
706
|
-
const lines = result.stdout.split("\n");
|
|
707
|
-
const dataLine = lines[1];
|
|
708
|
-
const parts = dataLine.split(/\s+/);
|
|
709
|
-
const availableKB = parseInt(parts[3]);
|
|
710
|
-
return availableKB / 1024 / 1024;
|
|
711
|
-
} catch (error) {
|
|
712
|
-
logger.warn("Could not check disk space, proceeding anyway");
|
|
713
|
-
return MIN_DISK_SPACE_GB + 1;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
async createLockFile() {
|
|
717
|
-
if (!this.repoPath) return;
|
|
718
|
-
this.lockFilePath = path.join(this.repoPath, LOCK_FILE_NAME);
|
|
719
|
-
if (await fs.pathExists(this.lockFilePath)) {
|
|
720
|
-
const lockData2 = await fs.readFile(this.lockFilePath, "utf-8");
|
|
721
|
-
throw new Error(
|
|
722
|
-
`Another migration is already running on this repository.
|
|
723
|
-
Lock file: ${this.lockFilePath}
|
|
724
|
-
Lock data: ${lockData2}
|
|
725
|
-
If this is an error, manually delete the lock file and try again.`
|
|
726
|
-
);
|
|
727
|
-
}
|
|
728
|
-
const lockData = {
|
|
729
|
-
pid: process.pid,
|
|
730
|
-
startTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
731
|
-
repository: this.repoPath
|
|
732
|
-
};
|
|
733
|
-
await fs.writeFile(this.lockFilePath, JSON.stringify(lockData, null, 2));
|
|
734
|
-
}
|
|
735
|
-
async removeLockFile() {
|
|
736
|
-
if (this.lockFilePath && await fs.pathExists(this.lockFilePath)) {
|
|
737
|
-
await fs.remove(this.lockFilePath);
|
|
738
|
-
this.lockFilePath = null;
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
export {
|
|
743
|
-
PluginMigrator
|
|
744
|
-
};
|