recoder-code 2.5.2 ā 2.5.3
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/index.js +0 -0
- package/dist/src/commands/context/index.js +2 -2
- package/dist/src/commands/mcp/marketplace.d.ts +6 -0
- package/dist/src/commands/mcp/marketplace.js +448 -0
- package/dist/src/commands/mcp.js +2 -0
- package/dist/src/commands/parallel.d.ts +20 -0
- package/dist/src/commands/parallel.js +133 -0
- package/dist/src/commands/recoderWeb.js +184 -5
- package/dist/src/commands/web/diff.d.ts +13 -0
- package/dist/src/commands/web/diff.js +235 -0
- package/dist/src/commands/web/link.d.ts +11 -0
- package/dist/src/commands/web/link.js +96 -0
- package/dist/src/commands/web/pull.d.ts +13 -0
- package/dist/src/commands/web/pull.js +203 -0
- package/dist/src/commands/web/status.d.ts +10 -0
- package/dist/src/commands/web/status.js +104 -0
- package/dist/src/commands/web/unlink.d.ts +10 -0
- package/dist/src/commands/web/unlink.js +45 -0
- package/dist/src/commands/web/watch.d.ts +14 -0
- package/dist/src/commands/web/watch.js +360 -0
- package/dist/src/commands/web.js +12 -0
- package/dist/src/config/config.js +6 -2
- package/dist/src/config/defaultMcpServers.d.ts +1 -0
- package/dist/src/config/defaultMcpServers.js +46 -0
- package/dist/src/gemini.js +10 -0
- package/dist/src/parallel/git-utils.d.ts +42 -0
- package/dist/src/parallel/git-utils.js +161 -0
- package/dist/src/parallel/index.d.ts +14 -0
- package/dist/src/parallel/index.js +14 -0
- package/dist/src/parallel/parallel-mode.d.ts +48 -0
- package/dist/src/parallel/parallel-mode.js +224 -0
- package/dist/src/services/AgentBridgeService.d.ts +61 -0
- package/dist/src/services/AgentBridgeService.js +253 -0
- package/dist/src/services/BuiltinCommandLoader.js +7 -0
- package/dist/src/services/PlatformSyncService.d.ts +154 -0
- package/dist/src/services/PlatformSyncService.js +588 -0
- package/dist/src/ui/commands/workflowCommands.d.ts +16 -0
- package/dist/src/ui/commands/workflowCommands.js +291 -0
- package/dist/src/ui/commands/workspaceCommand.d.ts +11 -0
- package/dist/src/ui/commands/workspaceCommand.js +329 -0
- package/dist/src/zed-integration/schema.d.ts +30 -30
- package/package.json +29 -10
- package/src/postinstall.cjs +3 -2
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -5,11 +5,21 @@
|
|
|
5
5
|
import open from 'open';
|
|
6
6
|
import { RecoderWebService } from '../services/RecoderWebService.js';
|
|
7
7
|
import { RecoderAuthService } from '../services/RecoderAuthService.js';
|
|
8
|
+
import { platformSync } from '../services/PlatformSyncService.js';
|
|
8
9
|
export async function handleRecoderWebCommand(args) {
|
|
9
10
|
const webService = new RecoderWebService();
|
|
10
11
|
const authService = new RecoderAuthService();
|
|
11
12
|
const command = args[0];
|
|
12
|
-
//
|
|
13
|
+
// Sync command doesn't require auth (uses container context)
|
|
14
|
+
if (command === 'sync') {
|
|
15
|
+
await handleSync(args.slice(1));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (command === 'status') {
|
|
19
|
+
await handleStatus();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
// Check authentication for all other commands
|
|
13
23
|
const isAuth = await authService.isAuthenticated();
|
|
14
24
|
if (!isAuth) {
|
|
15
25
|
console.error('ā Not authenticated');
|
|
@@ -29,6 +39,9 @@ export async function handleRecoderWebCommand(args) {
|
|
|
29
39
|
case 'info':
|
|
30
40
|
await handleInfo(webService, args.slice(1));
|
|
31
41
|
break;
|
|
42
|
+
case 'push':
|
|
43
|
+
await handlePush(webService, args.slice(1));
|
|
44
|
+
break;
|
|
32
45
|
default:
|
|
33
46
|
showHelp();
|
|
34
47
|
break;
|
|
@@ -172,6 +185,160 @@ async function handleInfo(webService, args) {
|
|
|
172
185
|
process.exit(1);
|
|
173
186
|
}
|
|
174
187
|
}
|
|
188
|
+
async function handleSync(args) {
|
|
189
|
+
const subCommand = args[0];
|
|
190
|
+
switch (subCommand) {
|
|
191
|
+
case 'start':
|
|
192
|
+
await startSync();
|
|
193
|
+
break;
|
|
194
|
+
case 'stop':
|
|
195
|
+
stopSync();
|
|
196
|
+
break;
|
|
197
|
+
default:
|
|
198
|
+
await showSyncStatus();
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async function startSync() {
|
|
203
|
+
console.log('š Starting platform sync...\n');
|
|
204
|
+
const platform = await platformSync.detectPlatform();
|
|
205
|
+
if (!platform.isContainer) {
|
|
206
|
+
console.log('š Not running in recoder.xyz container');
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log('š” Platform sync works when you run recoder-code inside:');
|
|
209
|
+
console.log(' - recoder.xyz web terminal');
|
|
210
|
+
console.log(' - recoder.xyz Docker container');
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log('For local development, changes will sync to web when you');
|
|
213
|
+
console.log('push with: recoder web push <urlId>');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
console.log('š³ Detected recoder.xyz container');
|
|
217
|
+
console.log(`š Project ID: ${platform.projectId || 'Unknown'}`);
|
|
218
|
+
const connected = await platformSync.connect();
|
|
219
|
+
if (connected) {
|
|
220
|
+
console.log('ā
Connected to platform sync');
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log('š” File changes will now sync bidirectionally:');
|
|
223
|
+
console.log(' - CLI edits ā Web editor');
|
|
224
|
+
console.log(' - Web edits ā CLI workspace');
|
|
225
|
+
console.log('');
|
|
226
|
+
if (platform.previewUrl) {
|
|
227
|
+
console.log(`š Live Preview: ${platform.previewUrl}`);
|
|
228
|
+
}
|
|
229
|
+
// Start file watcher
|
|
230
|
+
platformSync.startFileWatcher();
|
|
231
|
+
// Listen for remote changes
|
|
232
|
+
platformSync.on('remoteChange', (changes) => {
|
|
233
|
+
console.log(`\nš„ Remote changes: ${changes.length} files updated`);
|
|
234
|
+
for (const change of changes) {
|
|
235
|
+
console.log(` ${change.type}: ${change.path}`);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
platformSync.on('localChange', (change) => {
|
|
239
|
+
console.log(`š¤ Synced: ${change.type} ${change.path}`);
|
|
240
|
+
});
|
|
241
|
+
console.log('');
|
|
242
|
+
console.log('Press Ctrl+C to stop sync');
|
|
243
|
+
// Keep process running
|
|
244
|
+
await new Promise(() => { });
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
console.log('ā Failed to connect to platform');
|
|
248
|
+
console.log('š” Make sure docker-backend is running');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function stopSync() {
|
|
252
|
+
platformSync.disconnect();
|
|
253
|
+
console.log('ā
Platform sync stopped');
|
|
254
|
+
}
|
|
255
|
+
async function showSyncStatus() {
|
|
256
|
+
const platform = await platformSync.detectPlatform();
|
|
257
|
+
platformSync.displayStatus();
|
|
258
|
+
if (platform.isContainer) {
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log('Commands:');
|
|
261
|
+
console.log(' recoder web sync start - Start bidirectional sync');
|
|
262
|
+
console.log(' recoder web sync stop - Stop sync');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function handleStatus() {
|
|
266
|
+
console.log('š Platform Status\n');
|
|
267
|
+
const platform = await platformSync.detectPlatform();
|
|
268
|
+
console.log(`Environment: ${platform.isContainer ? 'š³ Container' : 'š» Local'}`);
|
|
269
|
+
if (platform.isContainer) {
|
|
270
|
+
console.log(`Project ID: ${platform.projectId || 'Not detected'}`);
|
|
271
|
+
console.log(`Backend: ${platform.backendUrl}`);
|
|
272
|
+
console.log(`Web URL: ${platform.webUrl}`);
|
|
273
|
+
console.log(`Sync: ${platformSync.isConnected() ? 'ā
Connected' : 'ā Disconnected'}`);
|
|
274
|
+
if (platform.previewUrl) {
|
|
275
|
+
console.log(`Preview: ${platform.previewUrl}`);
|
|
276
|
+
}
|
|
277
|
+
console.log('');
|
|
278
|
+
console.log('š” Start sync: recoder web sync start');
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log('š” Run recoder-code in recoder.xyz terminal for live sync');
|
|
283
|
+
console.log(' Or use "recoder web push <urlId>" to upload local files');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function handlePush(webService, args) {
|
|
287
|
+
const urlId = args[0];
|
|
288
|
+
const directory = args[1] || process.cwd();
|
|
289
|
+
if (!urlId) {
|
|
290
|
+
console.error('ā Please provide a project URL ID');
|
|
291
|
+
console.log('\nUsage:');
|
|
292
|
+
console.log(' recoder web push <urlId> [directory]');
|
|
293
|
+
console.log('\nExample:');
|
|
294
|
+
console.log(' recoder web push 1762542265823');
|
|
295
|
+
console.log(' recoder web push 1762542265823 ./my-project');
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
console.log(`š¤ Scanning files in: ${directory}\n`);
|
|
300
|
+
const files = await webService.scanDirectory(directory);
|
|
301
|
+
const fileCount = Object.keys(files).length;
|
|
302
|
+
if (fileCount === 0) {
|
|
303
|
+
console.log('ā No files found to upload');
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
console.log(`Found ${fileCount} files to upload`);
|
|
307
|
+
// Get remote files for comparison
|
|
308
|
+
let changes = { added: [], modified: [], deleted: [], unchanged: [] };
|
|
309
|
+
try {
|
|
310
|
+
const project = await webService.getProject(urlId);
|
|
311
|
+
changes = await webService.detectChanges(files, project.fileSnapshot);
|
|
312
|
+
console.log('');
|
|
313
|
+
if (changes.added.length)
|
|
314
|
+
console.log(` + ${changes.added.length} new files`);
|
|
315
|
+
if (changes.modified.length)
|
|
316
|
+
console.log(` ~ ${changes.modified.length} modified`);
|
|
317
|
+
if (changes.deleted.length)
|
|
318
|
+
console.log(` - ${changes.deleted.length} deleted`);
|
|
319
|
+
if (changes.unchanged.length)
|
|
320
|
+
console.log(` = ${changes.unchanged.length} unchanged`);
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
// Project doesn't exist yet
|
|
324
|
+
console.log(' Creating new project...');
|
|
325
|
+
}
|
|
326
|
+
console.log('');
|
|
327
|
+
const result = await webService.uploadProject(urlId, files);
|
|
328
|
+
if (result.success) {
|
|
329
|
+
console.log('ā
Upload complete!');
|
|
330
|
+
console.log(`š ${result.fileCount} files synced`);
|
|
331
|
+
console.log(`š View at: ${webService.getProjectUrl(urlId)}`);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
console.log('ā Upload failed');
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
console.error(`ā ${error.message}`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
175
342
|
function showHelp() {
|
|
176
343
|
console.log('Recoder.xyz Web Platform');
|
|
177
344
|
console.log('\nManage projects from your web IDE');
|
|
@@ -184,19 +351,31 @@ function showHelp() {
|
|
|
184
351
|
console.log(' download <urlId> Download project files to local machine');
|
|
185
352
|
console.log(' [directory] Optional output directory');
|
|
186
353
|
console.log('');
|
|
354
|
+
console.log(' push <urlId> Upload local files to web project');
|
|
355
|
+
console.log(' [directory] Optional source directory (default: cwd)');
|
|
356
|
+
console.log('');
|
|
357
|
+
console.log(' sync Platform sync (when in container)');
|
|
358
|
+
console.log(' start Start bidirectional sync');
|
|
359
|
+
console.log(' stop Stop sync');
|
|
360
|
+
console.log('');
|
|
361
|
+
console.log(' status Show platform connection status');
|
|
187
362
|
console.log(' open <urlId> Open project in web browser');
|
|
188
363
|
console.log(' info <urlId> Show detailed project information');
|
|
189
364
|
console.log('\nExamples:');
|
|
190
365
|
console.log(' recoder web list');
|
|
191
366
|
console.log(' recoder web list --limit 10');
|
|
192
367
|
console.log(' recoder web download 1762542265823');
|
|
193
|
-
console.log(' recoder web
|
|
194
|
-
console.log(' recoder web
|
|
195
|
-
console.log(' recoder web
|
|
368
|
+
console.log(' recoder web push 1762542265823 ./my-project');
|
|
369
|
+
console.log(' recoder web sync start');
|
|
370
|
+
console.log(' recoder web status');
|
|
196
371
|
console.log('\nš” Workflow:');
|
|
197
|
-
console.log(' 1. Create project in web IDE (
|
|
372
|
+
console.log(' 1. Create project in web IDE (https://web.recoder.xyz)');
|
|
198
373
|
console.log(' 2. List projects: recoder web list');
|
|
199
374
|
console.log(' 3. Download locally: recoder web download <urlId>');
|
|
200
375
|
console.log(' 4. Edit with full file system access');
|
|
376
|
+
console.log(' 5. Push changes: recoder web push <urlId>');
|
|
377
|
+
console.log('');
|
|
378
|
+
console.log('š” In recoder.xyz terminal:');
|
|
379
|
+
console.log(' Run "recoder web sync start" for live bidirectional sync');
|
|
201
380
|
console.log('\nFor more information, visit: https://recoder.xyz/docs');
|
|
202
381
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 'recoder web diff' command
|
|
3
|
+
* Show differences between local and web project
|
|
4
|
+
*/
|
|
5
|
+
import type { CommandModule } from 'yargs';
|
|
6
|
+
interface DiffArgs {
|
|
7
|
+
urlId?: string;
|
|
8
|
+
directory?: string;
|
|
9
|
+
verbose?: boolean;
|
|
10
|
+
'show-content'?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const diffCommand: CommandModule<{}, DiffArgs>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 'recoder web diff' command
|
|
3
|
+
* Show differences between local and web project
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import { RecoderWebService } from '../../services/RecoderWebService.js';
|
|
8
|
+
import { RecoderAuthService } from '../../services/RecoderAuthService.js';
|
|
9
|
+
const DOCKER_BACKEND_URL = process.env['RECODER_DOCKER_URL'] || 'https://docker.recoder.xyz';
|
|
10
|
+
// ANSI color codes
|
|
11
|
+
const colors = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
cyan: '\x1b[36m',
|
|
18
|
+
gray: '\x1b[90m',
|
|
19
|
+
};
|
|
20
|
+
export const diffCommand = {
|
|
21
|
+
command: 'diff [urlId]',
|
|
22
|
+
describe: 'Show differences between local and web project',
|
|
23
|
+
builder: (yargs) => yargs
|
|
24
|
+
.positional('urlId', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Project URL ID (optional, will read from .recoder-web if present)',
|
|
27
|
+
})
|
|
28
|
+
.option('directory', {
|
|
29
|
+
type: 'string',
|
|
30
|
+
alias: 'd',
|
|
31
|
+
description: 'Directory to compare (defaults to current directory)',
|
|
32
|
+
default: process.cwd(),
|
|
33
|
+
})
|
|
34
|
+
.option('verbose', {
|
|
35
|
+
type: 'boolean',
|
|
36
|
+
alias: 'v',
|
|
37
|
+
description: 'Show detailed diff for each file',
|
|
38
|
+
default: false,
|
|
39
|
+
})
|
|
40
|
+
.option('show-content', {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Show unified diff content for modified files',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
handler: async (argv) => {
|
|
46
|
+
const webService = new RecoderWebService();
|
|
47
|
+
const authService = new RecoderAuthService();
|
|
48
|
+
try {
|
|
49
|
+
// Check authentication
|
|
50
|
+
const isAuth = await authService.isAuthenticated();
|
|
51
|
+
if (!isAuth) {
|
|
52
|
+
console.error('Not authenticated');
|
|
53
|
+
console.log('Run: recoder auth login');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const targetDir = path.resolve(argv.directory || process.cwd());
|
|
57
|
+
let urlId = argv.urlId;
|
|
58
|
+
// Try to read .recoder-web metadata if no urlId provided
|
|
59
|
+
if (!urlId) {
|
|
60
|
+
try {
|
|
61
|
+
const metadataPath = path.join(targetDir, '.recoder-web');
|
|
62
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
63
|
+
urlId = metadata.urlId || metadata.projectId;
|
|
64
|
+
console.log(`Found project ID from .recoder-web: ${urlId}`);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
console.error('No project ID provided and no .recoder-web file found');
|
|
68
|
+
console.log('Usage: recoder web diff <urlId>');
|
|
69
|
+
console.log(' Or run from a linked directory');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
console.log(`Comparing project: ${urlId}`);
|
|
74
|
+
console.log(`Local directory: ${targetDir}\n`);
|
|
75
|
+
// Scan local files
|
|
76
|
+
console.log('Scanning local files...');
|
|
77
|
+
const localFiles = await webService.scanDirectory(targetDir);
|
|
78
|
+
console.log(`Found ${Object.keys(localFiles).length} local files`);
|
|
79
|
+
// Get token for backend request
|
|
80
|
+
const token = await authService.getAccessToken();
|
|
81
|
+
if (!token) {
|
|
82
|
+
console.error('Failed to get access token');
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
// Try to get diff from docker backend first (has unified diff support)
|
|
86
|
+
let diffResult = null;
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(`${DOCKER_BACKEND_URL}/api/sync/${urlId}/diff`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
'Authorization': `Bearer ${token}`,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({ localFiles }),
|
|
95
|
+
});
|
|
96
|
+
if (response.ok) {
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
if (data?.success) {
|
|
99
|
+
diffResult = data;
|
|
100
|
+
if (data.containerRunning) {
|
|
101
|
+
console.log('Comparing with running container');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Docker backend not available
|
|
108
|
+
}
|
|
109
|
+
// Fall back to local comparison with web API
|
|
110
|
+
if (!diffResult) {
|
|
111
|
+
console.log('Fetching remote files from web API...');
|
|
112
|
+
let remoteFiles = {};
|
|
113
|
+
try {
|
|
114
|
+
const project = await webService.getProject(urlId);
|
|
115
|
+
remoteFiles = project.fileSnapshot || {};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error(`Failed to fetch project: ${error.message}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const changes = await webService.detectChanges(localFiles, remoteFiles);
|
|
122
|
+
diffResult = {
|
|
123
|
+
diff: changes,
|
|
124
|
+
stats: {
|
|
125
|
+
totalChanges: changes.added.length + changes.modified.length + changes.deleted.length,
|
|
126
|
+
addedCount: changes.added.length,
|
|
127
|
+
modifiedCount: changes.modified.length,
|
|
128
|
+
deletedCount: changes.deleted.length,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// Display results
|
|
133
|
+
console.log('\n' + '='.repeat(60));
|
|
134
|
+
console.log(' DIFF SUMMARY');
|
|
135
|
+
console.log('='.repeat(60));
|
|
136
|
+
const { diff, stats } = diffResult;
|
|
137
|
+
if (stats.totalChanges === 0) {
|
|
138
|
+
console.log(`\n${colors.green}No differences - local and remote are in sync!${colors.reset}`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Summary box
|
|
142
|
+
console.log(`
|
|
143
|
+
${colors.gray}+----------------------------------+${colors.reset}
|
|
144
|
+
${colors.gray}|${colors.reset} ${colors.green}+ ${stats.addedCount} added${colors.reset} (local only) ${colors.gray}|${colors.reset}
|
|
145
|
+
${colors.gray}|${colors.reset} ${colors.yellow}~ ${stats.modifiedCount} modified${colors.reset} ${colors.gray}|${colors.reset}
|
|
146
|
+
${colors.gray}|${colors.reset} ${colors.red}- ${stats.deletedCount} deleted${colors.reset} (remote only) ${colors.gray}|${colors.reset}
|
|
147
|
+
${colors.gray}+----------------------------------+${colors.reset}
|
|
148
|
+
`);
|
|
149
|
+
// Added files (local only)
|
|
150
|
+
if (diff.added.length > 0) {
|
|
151
|
+
console.log(`${colors.green}Added (local only):${colors.reset}`);
|
|
152
|
+
const toShow = argv.verbose ? diff.added : diff.added.slice(0, 15);
|
|
153
|
+
for (const file of toShow) {
|
|
154
|
+
console.log(` ${colors.green}+${colors.reset} ${file}`);
|
|
155
|
+
}
|
|
156
|
+
if (!argv.verbose && diff.added.length > 15) {
|
|
157
|
+
console.log(` ${colors.gray}... and ${diff.added.length - 15} more${colors.reset}`);
|
|
158
|
+
}
|
|
159
|
+
console.log('');
|
|
160
|
+
}
|
|
161
|
+
// Modified files
|
|
162
|
+
if (diff.modified.length > 0) {
|
|
163
|
+
console.log(`${colors.yellow}Modified:${colors.reset}`);
|
|
164
|
+
const toShow = argv.verbose ? diff.modified : diff.modified.slice(0, 15);
|
|
165
|
+
for (const file of toShow) {
|
|
166
|
+
console.log(` ${colors.yellow}~${colors.reset} ${file}`);
|
|
167
|
+
}
|
|
168
|
+
if (!argv.verbose && diff.modified.length > 15) {
|
|
169
|
+
console.log(` ${colors.gray}... and ${diff.modified.length - 15} more${colors.reset}`);
|
|
170
|
+
}
|
|
171
|
+
console.log('');
|
|
172
|
+
}
|
|
173
|
+
// Deleted files (remote only)
|
|
174
|
+
if (diff.deleted.length > 0) {
|
|
175
|
+
console.log(`${colors.red}Deleted (remote only):${colors.reset}`);
|
|
176
|
+
const toShow = argv.verbose ? diff.deleted : diff.deleted.slice(0, 15);
|
|
177
|
+
for (const file of toShow) {
|
|
178
|
+
console.log(` ${colors.red}-${colors.reset} ${file}`);
|
|
179
|
+
}
|
|
180
|
+
if (!argv.verbose && diff.deleted.length > 15) {
|
|
181
|
+
console.log(` ${colors.gray}... and ${diff.deleted.length - 15} more${colors.reset}`);
|
|
182
|
+
}
|
|
183
|
+
console.log('');
|
|
184
|
+
}
|
|
185
|
+
// Show unified diffs if requested
|
|
186
|
+
if (argv['show-content'] && diffResult.unifiedDiffs) {
|
|
187
|
+
console.log('='.repeat(60));
|
|
188
|
+
console.log(' UNIFIED DIFFS');
|
|
189
|
+
console.log('='.repeat(60));
|
|
190
|
+
for (const [filePath, unifiedDiff] of Object.entries(diffResult.unifiedDiffs)) {
|
|
191
|
+
console.log(`\n${colors.cyan}--- ${filePath} ---${colors.reset}`);
|
|
192
|
+
// Color the diff output
|
|
193
|
+
for (const line of unifiedDiff.split('\n')) {
|
|
194
|
+
if (line.startsWith('+') && !line.startsWith('+++')) {
|
|
195
|
+
console.log(`${colors.green}${line}${colors.reset}`);
|
|
196
|
+
}
|
|
197
|
+
else if (line.startsWith('-') && !line.startsWith('---')) {
|
|
198
|
+
console.log(`${colors.red}${line}${colors.reset}`);
|
|
199
|
+
}
|
|
200
|
+
else if (line.startsWith('@@')) {
|
|
201
|
+
console.log(`${colors.cyan}${line}${colors.reset}`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
console.log(line);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (diff.modified.length > Object.keys(diffResult.unifiedDiffs).length) {
|
|
209
|
+
console.log(`\n${colors.gray}(Showing first ${Object.keys(diffResult.unifiedDiffs).length} of ${diff.modified.length} modified files)${colors.reset}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Suggestions
|
|
213
|
+
console.log('='.repeat(60));
|
|
214
|
+
console.log('ACTIONS:');
|
|
215
|
+
console.log('-'.repeat(60));
|
|
216
|
+
if (diff.added.length > 0 || diff.modified.length > 0) {
|
|
217
|
+
console.log(` ${colors.blue}recoder web push${colors.reset} - Upload local changes to web`);
|
|
218
|
+
}
|
|
219
|
+
if (diff.deleted.length > 0 || diff.modified.length > 0) {
|
|
220
|
+
console.log(` ${colors.blue}recoder web pull${colors.reset} - Download remote changes to local`);
|
|
221
|
+
}
|
|
222
|
+
console.log(` ${colors.blue}recoder web sync${colors.reset} - Two-way sync`);
|
|
223
|
+
if (!argv['show-content'] && diff.modified.length > 0) {
|
|
224
|
+
console.log(`\n${colors.gray}Tip: Use --show-content to see unified diff of modified files${colors.reset}`);
|
|
225
|
+
}
|
|
226
|
+
if (!argv.verbose && stats.totalChanges > 15) {
|
|
227
|
+
console.log(`${colors.gray}Tip: Use --verbose to see all changed files${colors.reset}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
console.error(`${error.message}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 'recoder web link' command
|
|
3
|
+
* Link current directory to a recoder.xyz web project for real-time sync
|
|
4
|
+
*/
|
|
5
|
+
import type { CommandModule } from 'yargs';
|
|
6
|
+
interface LinkArgs {
|
|
7
|
+
projectId?: string;
|
|
8
|
+
directory?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const linkCommand: CommandModule<{}, LinkArgs>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 'recoder web link' command
|
|
3
|
+
* Link current directory to a recoder.xyz web project for real-time sync
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import { RecoderAuthService } from '../../services/RecoderAuthService.js';
|
|
8
|
+
import { RecoderWebService } from '../../services/RecoderWebService.js';
|
|
9
|
+
export const linkCommand = {
|
|
10
|
+
command: 'link [projectId]',
|
|
11
|
+
describe: 'Link current directory to a recoder.xyz project for real-time sync',
|
|
12
|
+
builder: (yargs) => yargs
|
|
13
|
+
.positional('projectId', {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Project URL ID (e.g., abc123 from web.recoder.xyz/chat/abc123)',
|
|
16
|
+
})
|
|
17
|
+
.option('directory', {
|
|
18
|
+
type: 'string',
|
|
19
|
+
alias: 'd',
|
|
20
|
+
description: 'Directory to link (defaults to current directory)',
|
|
21
|
+
default: process.cwd(),
|
|
22
|
+
}),
|
|
23
|
+
handler: async (argv) => {
|
|
24
|
+
const authService = new RecoderAuthService();
|
|
25
|
+
const webService = new RecoderWebService();
|
|
26
|
+
try {
|
|
27
|
+
// Check authentication
|
|
28
|
+
const isAuth = await authService.isAuthenticated();
|
|
29
|
+
if (!isAuth) {
|
|
30
|
+
console.error('ā Not authenticated');
|
|
31
|
+
console.log('š” Run: recoder auth login');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const targetDir = path.resolve(argv.directory || process.cwd());
|
|
35
|
+
let projectId = argv.projectId;
|
|
36
|
+
// If no projectId, try to read from existing .recoder-web
|
|
37
|
+
if (!projectId) {
|
|
38
|
+
try {
|
|
39
|
+
const metadataPath = path.join(targetDir, '.recoder-web');
|
|
40
|
+
const metadata = JSON.parse(await fs.readFile(metadataPath, 'utf-8'));
|
|
41
|
+
projectId = metadata.urlId || metadata.projectId;
|
|
42
|
+
console.log(`š Found existing link: ${projectId}`);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
console.error('ā No project ID provided');
|
|
46
|
+
console.log('š” Usage: recoder web link <projectId>');
|
|
47
|
+
console.log(' Example: recoder web link abc123');
|
|
48
|
+
console.log('\nš Get project ID from your recoder.xyz URL:');
|
|
49
|
+
console.log(' https://web.recoder.xyz/chat/abc123 ā projectId is "abc123"');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Verify project exists
|
|
54
|
+
console.log(`š Verifying project: ${projectId}...`);
|
|
55
|
+
let project;
|
|
56
|
+
try {
|
|
57
|
+
project = await webService.getProject(projectId);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
console.error(`ā Project not found: ${projectId}`);
|
|
61
|
+
console.log('š” Make sure you have access to this project');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// Create .recoder-web metadata file
|
|
65
|
+
const metadataFile = path.join(targetDir, '.recoder-web');
|
|
66
|
+
const metadata = {
|
|
67
|
+
urlId: projectId,
|
|
68
|
+
projectId: projectId,
|
|
69
|
+
description: project.description || '',
|
|
70
|
+
linkedAt: new Date().toISOString(),
|
|
71
|
+
webUrl: webService.getProjectUrl(projectId),
|
|
72
|
+
previewUrl: `https://docker.recoder.xyz/preview/${projectId}`,
|
|
73
|
+
syncEnabled: true,
|
|
74
|
+
};
|
|
75
|
+
await fs.writeFile(metadataFile, JSON.stringify(metadata, null, 2), 'utf-8');
|
|
76
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
77
|
+
console.log('ā ā
Project Linked Successfully! ā');
|
|
78
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤');
|
|
79
|
+
console.log(`ā š Directory: ${targetDir.slice(-30).padEnd(30)}ā`);
|
|
80
|
+
console.log(`ā š Project: ${projectId.slice(0, 32).padEnd(32)}ā`);
|
|
81
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤');
|
|
82
|
+
console.log('ā Available commands: ā');
|
|
83
|
+
console.log('ā ⢠recoder web sync - Sync files ā');
|
|
84
|
+
console.log('ā ⢠recoder web push - Upload local changes ā');
|
|
85
|
+
console.log('ā ⢠recoder web info - Show project info ā');
|
|
86
|
+
console.log('ā ⢠recoder web unlink - Remove link ā');
|
|
87
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
88
|
+
console.log('\nš Web IDE:', metadata.webUrl);
|
|
89
|
+
console.log('š Preview:', metadata.previewUrl);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
console.error(`ā ${error.message}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 'recoder web pull' command
|
|
3
|
+
* Pull web project files to local directory
|
|
4
|
+
*/
|
|
5
|
+
import type { CommandModule } from 'yargs';
|
|
6
|
+
interface PullArgs {
|
|
7
|
+
urlId?: string;
|
|
8
|
+
directory?: string;
|
|
9
|
+
'dry-run'?: boolean;
|
|
10
|
+
force?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const pullCommand: CommandModule<{}, PullArgs>;
|
|
13
|
+
export {};
|