wave-agent-sdk 0.11.5 → 0.11.7
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/builtin/skills/init/SKILL.md +2 -0
- package/builtin/skills/settings/SKILLS.md +3 -2
- package/builtin/skills/settings/SUBAGENTS.md +1 -3
- package/dist/agent.d.ts +6 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +18 -1
- package/dist/constants/tools.d.ts +1 -1
- package/dist/constants/tools.d.ts.map +1 -1
- package/dist/constants/tools.js +1 -1
- package/dist/managers/MemoryRuleManager.d.ts.map +1 -1
- package/dist/managers/MemoryRuleManager.js +1 -9
- package/dist/managers/aiManager.d.ts.map +1 -1
- package/dist/managers/aiManager.js +22 -3
- package/dist/managers/mcpManager.d.ts.map +1 -1
- package/dist/managers/mcpManager.js +32 -13
- package/dist/managers/messageManager.d.ts +13 -5
- package/dist/managers/messageManager.d.ts.map +1 -1
- package/dist/managers/messageManager.js +62 -34
- package/dist/managers/permissionManager.js +4 -4
- package/dist/managers/pluginManager.d.ts.map +1 -1
- package/dist/managers/pluginManager.js +4 -2
- package/dist/managers/slashCommandManager.d.ts +2 -0
- package/dist/managers/slashCommandManager.d.ts.map +1 -1
- package/dist/managers/slashCommandManager.js +98 -4
- package/dist/managers/toolManager.d.ts.map +1 -1
- package/dist/managers/toolManager.js +8 -2
- package/dist/prompts/index.d.ts +2 -0
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +5 -0
- package/dist/services/GitService.d.ts +1 -0
- package/dist/services/GitService.d.ts.map +1 -1
- package/dist/services/GitService.js +16 -0
- package/dist/services/MarketplaceService.d.ts +7 -0
- package/dist/services/MarketplaceService.d.ts.map +1 -1
- package/dist/services/MarketplaceService.js +321 -252
- package/dist/services/aiService.d.ts +34 -0
- package/dist/services/aiService.d.ts.map +1 -1
- package/dist/services/aiService.js +124 -1
- package/dist/services/initializationService.d.ts.map +1 -1
- package/dist/services/initializationService.js +18 -0
- package/dist/tools/agentTool.js +3 -3
- package/dist/tools/bashTool.d.ts.map +1 -1
- package/dist/tools/bashTool.js +4 -4
- package/dist/tools/editTool.d.ts.map +1 -1
- package/dist/tools/editTool.js +2 -0
- package/dist/tools/globTool.d.ts.map +1 -1
- package/dist/tools/globTool.js +15 -3
- package/dist/tools/grepTool.d.ts.map +1 -1
- package/dist/tools/grepTool.js +38 -12
- package/dist/tools/readTool.d.ts.map +1 -1
- package/dist/tools/readTool.js +61 -0
- package/dist/tools/skillTool.js +2 -2
- package/dist/tools/types.d.ts +16 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/webFetchTool.d.ts +3 -0
- package/dist/tools/webFetchTool.d.ts.map +1 -0
- package/dist/tools/webFetchTool.js +171 -0
- package/dist/tools/writeTool.d.ts.map +1 -1
- package/dist/tools/writeTool.js +2 -0
- package/dist/types/commands.d.ts +1 -1
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/types/messaging.d.ts +1 -0
- package/dist/types/messaging.d.ts.map +1 -1
- package/dist/utils/bashParser.d.ts +20 -2
- package/dist/utils/bashParser.d.ts.map +1 -1
- package/dist/utils/bashParser.js +281 -146
- package/dist/utils/convertMessagesForAPI.d.ts.map +1 -1
- package/dist/utils/convertMessagesForAPI.js +7 -0
- package/dist/utils/fileUtils.d.ts +8 -0
- package/dist/utils/fileUtils.d.ts.map +1 -1
- package/dist/utils/fileUtils.js +52 -0
- package/dist/utils/messageOperations.d.ts +12 -3
- package/dist/utils/messageOperations.d.ts.map +1 -1
- package/dist/utils/messageOperations.js +77 -9
- package/package.json +4 -2
- package/src/agent.ts +19 -1
- package/src/constants/tools.ts +1 -1
- package/src/managers/MemoryRuleManager.ts +1 -10
- package/src/managers/aiManager.ts +23 -3
- package/src/managers/mcpManager.ts +37 -16
- package/src/managers/messageManager.ts +76 -38
- package/src/managers/permissionManager.ts +4 -4
- package/src/managers/pluginManager.ts +4 -2
- package/src/managers/slashCommandManager.ts +130 -4
- package/src/managers/toolManager.ts +11 -2
- package/src/prompts/index.ts +6 -0
- package/src/services/GitService.ts +20 -0
- package/src/services/MarketplaceService.ts +397 -324
- package/src/services/aiService.ts +197 -1
- package/src/services/initializationService.ts +38 -0
- package/src/tools/agentTool.ts +3 -3
- package/src/tools/bashTool.ts +3 -4
- package/src/tools/editTool.ts +3 -0
- package/src/tools/globTool.ts +16 -3
- package/src/tools/grepTool.ts +41 -13
- package/src/tools/readTool.ts +69 -0
- package/src/tools/skillTool.ts +2 -2
- package/src/tools/types.ts +13 -0
- package/src/tools/webFetchTool.ts +194 -0
- package/src/tools/writeTool.ts +3 -0
- package/src/types/commands.ts +1 -1
- package/src/types/messaging.ts +1 -0
- package/src/utils/bashParser.ts +316 -161
- package/src/utils/convertMessagesForAPI.ts +8 -0
- package/src/utils/fileUtils.ts +69 -0
- package/src/utils/messageOperations.ts +84 -9
- package/dist/tools/taskOutputTool.d.ts +0 -3
- package/dist/tools/taskOutputTool.d.ts.map +0 -1
- package/dist/tools/taskOutputTool.js +0 -198
- package/src/tools/taskOutputTool.ts +0 -222
|
@@ -18,9 +18,11 @@ import { GitService } from "./GitService.js";
|
|
|
18
18
|
* and state management for installed plugins.
|
|
19
19
|
*/
|
|
20
20
|
export class MarketplaceService {
|
|
21
|
+
private static isLockedInProcess = false;
|
|
21
22
|
private pluginsDir: string;
|
|
22
23
|
private knownMarketplacesPath: string;
|
|
23
24
|
private installedPluginsPath: string;
|
|
25
|
+
private lockPath: string;
|
|
24
26
|
private tmpDir: string;
|
|
25
27
|
private cacheDir: string;
|
|
26
28
|
private marketplacesDir: string;
|
|
@@ -44,6 +46,7 @@ export class MarketplaceService {
|
|
|
44
46
|
this.pluginsDir,
|
|
45
47
|
"installed_plugins.json",
|
|
46
48
|
);
|
|
49
|
+
this.lockPath = path.join(this.pluginsDir, ".lock");
|
|
47
50
|
this.tmpDir = path.join(this.pluginsDir, "tmp");
|
|
48
51
|
this.cacheDir = path.join(this.pluginsDir, "cache");
|
|
49
52
|
this.marketplacesDir = path.join(this.pluginsDir, "marketplaces");
|
|
@@ -65,6 +68,53 @@ export class MarketplaceService {
|
|
|
65
68
|
);
|
|
66
69
|
}
|
|
67
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Acquires a file-based lock and executes the provided function.
|
|
73
|
+
* Supports re-entrancy within the same process.
|
|
74
|
+
*/
|
|
75
|
+
private async withLock<T>(fn: () => Promise<T>): Promise<T> {
|
|
76
|
+
if (MarketplaceService.isLockedInProcess) {
|
|
77
|
+
return await fn();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let lockFd: Awaited<ReturnType<typeof fs.open>> | undefined;
|
|
81
|
+
const maxRetries = 600; // 60 seconds total
|
|
82
|
+
const retryDelay = 100;
|
|
83
|
+
|
|
84
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
85
|
+
try {
|
|
86
|
+
lockFd = await fs.open(this.lockPath, "wx");
|
|
87
|
+
break;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (
|
|
90
|
+
error &&
|
|
91
|
+
typeof error === "object" &&
|
|
92
|
+
"code" in error &&
|
|
93
|
+
error.code === "EEXIST"
|
|
94
|
+
) {
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!lockFd) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Failed to acquire marketplace lock after ${maxRetries} retries. If no other wave-agent process is running, please delete ${this.lockPath}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
MarketplaceService.isLockedInProcess = true;
|
|
109
|
+
try {
|
|
110
|
+
return await fn();
|
|
111
|
+
} finally {
|
|
112
|
+
MarketplaceService.isLockedInProcess = false;
|
|
113
|
+
await lockFd.close();
|
|
114
|
+
await fs.unlink(this.lockPath).catch(() => {});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
68
118
|
/**
|
|
69
119
|
* Loads the known marketplaces registry
|
|
70
120
|
*/
|
|
@@ -123,10 +173,9 @@ export class MarketplaceService {
|
|
|
123
173
|
async saveKnownMarketplaces(
|
|
124
174
|
registry: KnownMarketplacesRegistry,
|
|
125
175
|
): Promise<void> {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
176
|
+
const tmpPath = `${this.knownMarketplacesPath}.tmp`;
|
|
177
|
+
await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
|
|
178
|
+
await fs.rename(tmpPath, this.knownMarketplacesPath);
|
|
130
179
|
}
|
|
131
180
|
|
|
132
181
|
/**
|
|
@@ -135,10 +184,9 @@ export class MarketplaceService {
|
|
|
135
184
|
async saveInstalledPlugins(
|
|
136
185
|
registry: InstalledPluginsRegistry,
|
|
137
186
|
): Promise<void> {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
);
|
|
187
|
+
const tmpPath = `${this.installedPluginsPath}.tmp`;
|
|
188
|
+
await fs.writeFile(tmpPath, JSON.stringify(registry, null, 2));
|
|
189
|
+
await fs.rename(tmpPath, this.installedPluginsPath);
|
|
142
190
|
}
|
|
143
191
|
|
|
144
192
|
/**
|
|
@@ -192,97 +240,101 @@ export class MarketplaceService {
|
|
|
192
240
|
* Adds a new marketplace (local directory, GitHub repo, or Git URL)
|
|
193
241
|
*/
|
|
194
242
|
async addMarketplace(input: string): Promise<KnownMarketplace> {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
243
|
+
return this.withLock(async () => {
|
|
244
|
+
let marketplace: KnownMarketplace;
|
|
245
|
+
|
|
246
|
+
const isFullUrl =
|
|
247
|
+
input.startsWith("http://") ||
|
|
248
|
+
input.startsWith("https://") ||
|
|
249
|
+
input.startsWith("git@") ||
|
|
250
|
+
input.startsWith("ssh://");
|
|
251
|
+
|
|
252
|
+
if (
|
|
253
|
+
isFullUrl ||
|
|
254
|
+
(input.includes("/") &&
|
|
255
|
+
!path.isAbsolute(input) &&
|
|
256
|
+
!input.startsWith("."))
|
|
257
|
+
) {
|
|
258
|
+
// Git or GitHub repo
|
|
259
|
+
let urlOrRepo = input;
|
|
260
|
+
let ref: string | undefined;
|
|
261
|
+
|
|
262
|
+
if (input.includes("#")) {
|
|
263
|
+
[urlOrRepo, ref] = input.split("#");
|
|
264
|
+
}
|
|
210
265
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
266
|
+
const tempMarketplace: KnownMarketplace = isFullUrl
|
|
267
|
+
? { name: "", source: { source: "git", url: urlOrRepo, ref } }
|
|
268
|
+
: { name: "", source: { source: "github", repo: urlOrRepo, ref } };
|
|
214
269
|
|
|
215
|
-
|
|
216
|
-
? { name: "", source: { source: "git", url: urlOrRepo, ref } }
|
|
217
|
-
: { name: "", source: { source: "github", repo: urlOrRepo, ref } };
|
|
270
|
+
const targetPath = this.getMarketplacePath(tempMarketplace);
|
|
218
271
|
|
|
219
|
-
|
|
272
|
+
if (!existsSync(targetPath)) {
|
|
273
|
+
try {
|
|
274
|
+
await this.gitService.clone(urlOrRepo, targetPath, ref);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Failed to add marketplace from Git: ${error instanceof Error ? error.message : String(error)}`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
220
281
|
|
|
221
|
-
|
|
282
|
+
let manifest: MarketplaceManifest;
|
|
222
283
|
try {
|
|
223
|
-
await this.
|
|
284
|
+
manifest = await this.loadMarketplaceManifest(targetPath);
|
|
224
285
|
} catch (error) {
|
|
225
286
|
throw new Error(
|
|
226
|
-
`Failed to
|
|
287
|
+
`Failed to load manifest from cloned repository: ${error instanceof Error ? error.message : String(error)}`,
|
|
227
288
|
);
|
|
228
289
|
}
|
|
229
|
-
}
|
|
230
290
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
291
|
+
marketplace = {
|
|
292
|
+
name: manifest.name,
|
|
293
|
+
source: isFullUrl
|
|
294
|
+
? { source: "git", url: urlOrRepo, ref }
|
|
295
|
+
: { source: "github", repo: urlOrRepo, ref },
|
|
296
|
+
autoUpdate: false,
|
|
297
|
+
lastUpdated: new Date().toISOString(),
|
|
298
|
+
};
|
|
299
|
+
} else {
|
|
300
|
+
// Local directory format
|
|
301
|
+
const absolutePath = path.resolve(input);
|
|
302
|
+
let manifest: MarketplaceManifest;
|
|
303
|
+
try {
|
|
304
|
+
manifest = await this.loadMarketplaceManifest(absolutePath);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`Failed to load manifest from directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
308
|
+
);
|
|
309
|
+
}
|
|
239
310
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
:
|
|
245
|
-
|
|
246
|
-
lastUpdated: new Date().toISOString(),
|
|
247
|
-
};
|
|
248
|
-
} else {
|
|
249
|
-
// Local directory format
|
|
250
|
-
const absolutePath = path.resolve(input);
|
|
251
|
-
let manifest: MarketplaceManifest;
|
|
252
|
-
try {
|
|
253
|
-
manifest = await this.loadMarketplaceManifest(absolutePath);
|
|
254
|
-
} catch (error) {
|
|
255
|
-
throw new Error(
|
|
256
|
-
`Failed to load manifest from directory: ${error instanceof Error ? error.message : String(error)}`,
|
|
257
|
-
);
|
|
311
|
+
marketplace = {
|
|
312
|
+
name: manifest.name,
|
|
313
|
+
source: { source: "directory", path: absolutePath },
|
|
314
|
+
autoUpdate: false,
|
|
315
|
+
lastUpdated: new Date().toISOString(),
|
|
316
|
+
};
|
|
258
317
|
}
|
|
259
318
|
|
|
260
|
-
|
|
261
|
-
name: manifest.name,
|
|
262
|
-
source: { source: "directory", path: absolutePath },
|
|
263
|
-
autoUpdate: false,
|
|
264
|
-
lastUpdated: new Date().toISOString(),
|
|
265
|
-
};
|
|
266
|
-
}
|
|
319
|
+
const registry = await this.getKnownMarketplaces();
|
|
267
320
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
registry.marketplaces.push(marketplace);
|
|
278
|
-
}
|
|
321
|
+
// Check if already exists
|
|
322
|
+
const existingIndex = registry.marketplaces.findIndex(
|
|
323
|
+
(m) => m.name === marketplace.name,
|
|
324
|
+
);
|
|
325
|
+
if (existingIndex >= 0) {
|
|
326
|
+
registry.marketplaces[existingIndex] = marketplace;
|
|
327
|
+
} else {
|
|
328
|
+
registry.marketplaces.push(marketplace);
|
|
329
|
+
}
|
|
279
330
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
331
|
+
// Ensure builtin is included if we are creating the file for the first time
|
|
332
|
+
// and it hasn't been explicitly removed yet.
|
|
333
|
+
// (getKnownMarketplaces already handles the default injection)
|
|
283
334
|
|
|
284
|
-
|
|
285
|
-
|
|
335
|
+
await this.saveKnownMarketplaces(registry);
|
|
336
|
+
return marketplace;
|
|
337
|
+
});
|
|
286
338
|
}
|
|
287
339
|
|
|
288
340
|
/**
|
|
@@ -300,17 +352,19 @@ export class MarketplaceService {
|
|
|
300
352
|
* Removes a marketplace by name
|
|
301
353
|
*/
|
|
302
354
|
async removeMarketplace(name: string): Promise<void> {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
355
|
+
return this.withLock(async () => {
|
|
356
|
+
const registry = await this.getKnownMarketplaces();
|
|
357
|
+
const initialCount = registry.marketplaces.length;
|
|
358
|
+
registry.marketplaces = registry.marketplaces.filter(
|
|
359
|
+
(m) => m.name !== name,
|
|
360
|
+
);
|
|
308
361
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
362
|
+
if (registry.marketplaces.length === initialCount) {
|
|
363
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
364
|
+
}
|
|
312
365
|
|
|
313
|
-
|
|
366
|
+
await this.saveKnownMarketplaces(registry);
|
|
367
|
+
});
|
|
314
368
|
}
|
|
315
369
|
|
|
316
370
|
/**
|
|
@@ -320,138 +374,146 @@ export class MarketplaceService {
|
|
|
320
374
|
name?: string,
|
|
321
375
|
options?: { updatePlugins?: boolean },
|
|
322
376
|
): Promise<void> {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
377
|
+
return this.withLock(async () => {
|
|
378
|
+
const registry = await this.getKnownMarketplaces();
|
|
379
|
+
const toUpdate = name
|
|
380
|
+
? registry.marketplaces.filter((m) => m.name === name)
|
|
381
|
+
: registry.marketplaces;
|
|
382
|
+
|
|
383
|
+
if (name && toUpdate.length === 0) {
|
|
384
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
385
|
+
}
|
|
331
386
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
} else {
|
|
350
|
-
let url: string;
|
|
351
|
-
if (marketplace.source.source === "github") {
|
|
352
|
-
url = marketplace.source.repo;
|
|
387
|
+
const isGitAvailable = await this.gitService.isGitAvailable();
|
|
388
|
+
const errors: string[] = [];
|
|
389
|
+
for (const marketplace of toUpdate) {
|
|
390
|
+
try {
|
|
391
|
+
if (
|
|
392
|
+
marketplace.source.source === "github" ||
|
|
393
|
+
marketplace.source.source === "git"
|
|
394
|
+
) {
|
|
395
|
+
if (!isGitAvailable) {
|
|
396
|
+
console.warn(
|
|
397
|
+
`Skipping update for Git/GitHub marketplace "${marketplace.name}" because Git is not installed.`,
|
|
398
|
+
);
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
const targetPath = this.getMarketplacePath(marketplace);
|
|
402
|
+
if (existsSync(targetPath)) {
|
|
403
|
+
await this.gitService.pull(targetPath);
|
|
353
404
|
} else {
|
|
354
|
-
url
|
|
405
|
+
let url: string;
|
|
406
|
+
if (marketplace.source.source === "github") {
|
|
407
|
+
url = marketplace.source.repo;
|
|
408
|
+
} else {
|
|
409
|
+
url = marketplace.source.url;
|
|
410
|
+
}
|
|
411
|
+
await this.gitService.clone(
|
|
412
|
+
url,
|
|
413
|
+
targetPath,
|
|
414
|
+
marketplace.source.ref,
|
|
415
|
+
);
|
|
355
416
|
}
|
|
356
|
-
await this.gitService.clone(
|
|
357
|
-
url,
|
|
358
|
-
targetPath,
|
|
359
|
-
marketplace.source.ref,
|
|
360
|
-
);
|
|
361
417
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
);
|
|
418
|
+
// For directory source, we just re-validate the manifest
|
|
419
|
+
const manifest = await this.loadMarketplaceManifest(
|
|
420
|
+
this.getMarketplacePath(marketplace),
|
|
421
|
+
);
|
|
367
422
|
|
|
368
|
-
|
|
423
|
+
marketplace.lastUpdated = new Date().toISOString();
|
|
369
424
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
);
|
|
375
|
-
for (const plugin of pluginsToUpdate) {
|
|
376
|
-
const pluginEntry = manifest.plugins.find(
|
|
377
|
-
(p) => p.name === plugin.name,
|
|
425
|
+
if (options?.updatePlugins) {
|
|
426
|
+
const installedRegistry = await this.getInstalledPlugins();
|
|
427
|
+
const pluginsToUpdate = installedRegistry.plugins.filter(
|
|
428
|
+
(p) => p.marketplace === marketplace.name,
|
|
378
429
|
);
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
430
|
+
for (const plugin of pluginsToUpdate) {
|
|
431
|
+
const pluginEntry = manifest.plugins.find(
|
|
432
|
+
(p) => p.name === plugin.name,
|
|
382
433
|
);
|
|
434
|
+
if (!pluginEntry) {
|
|
435
|
+
console.warn(
|
|
436
|
+
`Plugin "${plugin.name}" no longer found in marketplace "${marketplace.name}". Uninstalling...`,
|
|
437
|
+
);
|
|
438
|
+
try {
|
|
439
|
+
await this.uninstallPlugin(
|
|
440
|
+
`${plugin.name}@${plugin.marketplace}`,
|
|
441
|
+
plugin.projectPath,
|
|
442
|
+
);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
console.error(
|
|
445
|
+
`Failed to uninstall orphaned plugin "${plugin.name}" from marketplace "${marketplace.name}":`,
|
|
446
|
+
error,
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
383
451
|
try {
|
|
384
|
-
await this.
|
|
452
|
+
await this.installPlugin(
|
|
385
453
|
`${plugin.name}@${plugin.marketplace}`,
|
|
386
454
|
plugin.projectPath,
|
|
387
455
|
);
|
|
388
456
|
} catch (error) {
|
|
389
457
|
console.error(
|
|
390
|
-
`Failed to
|
|
458
|
+
`Failed to update plugin "${plugin.name}" from marketplace "${marketplace.name}":`,
|
|
391
459
|
error,
|
|
392
460
|
);
|
|
393
461
|
}
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
try {
|
|
397
|
-
await this.installPlugin(
|
|
398
|
-
`${plugin.name}@${plugin.marketplace}`,
|
|
399
|
-
plugin.projectPath,
|
|
400
|
-
);
|
|
401
|
-
} catch (error) {
|
|
402
|
-
console.error(
|
|
403
|
-
`Failed to update plugin "${plugin.name}" from marketplace "${marketplace.name}":`,
|
|
404
|
-
error,
|
|
405
|
-
);
|
|
406
462
|
}
|
|
407
463
|
}
|
|
464
|
+
} catch (error) {
|
|
465
|
+
const msg = `Failed to update marketplace "${marketplace.name}": ${error instanceof Error ? error.message : String(error)}`;
|
|
466
|
+
console.error(msg);
|
|
467
|
+
errors.push(msg);
|
|
408
468
|
}
|
|
409
|
-
} catch (error) {
|
|
410
|
-
const msg = `Failed to update marketplace "${marketplace.name}": ${error instanceof Error ? error.message : String(error)}`;
|
|
411
|
-
console.error(msg);
|
|
412
|
-
errors.push(msg);
|
|
413
469
|
}
|
|
414
|
-
}
|
|
415
470
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
471
|
+
if (errors.length > 0) {
|
|
472
|
+
throw new Error(
|
|
473
|
+
`Some marketplaces failed to update:\n${errors.join("\n")}`,
|
|
474
|
+
);
|
|
475
|
+
}
|
|
421
476
|
|
|
422
|
-
|
|
477
|
+
await this.saveKnownMarketplaces(registry);
|
|
478
|
+
});
|
|
423
479
|
}
|
|
424
480
|
|
|
425
481
|
/**
|
|
426
482
|
* Automatically updates all marketplaces that have auto-update enabled
|
|
427
483
|
*/
|
|
428
484
|
async autoUpdateAll(): Promise<void> {
|
|
429
|
-
|
|
430
|
-
|
|
485
|
+
return this.withLock(async () => {
|
|
486
|
+
const registry = await this.getKnownMarketplaces();
|
|
487
|
+
const toAutoUpdate = registry.marketplaces.filter((m) => m.autoUpdate);
|
|
431
488
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
error
|
|
439
|
-
|
|
489
|
+
for (const marketplace of toAutoUpdate) {
|
|
490
|
+
try {
|
|
491
|
+
await this.updateMarketplace(marketplace.name, {
|
|
492
|
+
updatePlugins: true,
|
|
493
|
+
});
|
|
494
|
+
} catch (error) {
|
|
495
|
+
console.error(
|
|
496
|
+
`Auto-update failed for marketplace "${marketplace.name}":`,
|
|
497
|
+
error,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
440
500
|
}
|
|
441
|
-
}
|
|
501
|
+
});
|
|
442
502
|
}
|
|
443
503
|
|
|
444
504
|
/**
|
|
445
505
|
* Toggles auto-update for a marketplace
|
|
446
506
|
*/
|
|
447
507
|
async toggleAutoUpdate(name: string, enabled: boolean): Promise<void> {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
508
|
+
return this.withLock(async () => {
|
|
509
|
+
const registry = await this.getKnownMarketplaces();
|
|
510
|
+
const marketplace = registry.marketplaces.find((m) => m.name === name);
|
|
511
|
+
if (!marketplace) {
|
|
512
|
+
throw new Error(`Marketplace ${name} not found`);
|
|
513
|
+
}
|
|
514
|
+
marketplace.autoUpdate = enabled;
|
|
515
|
+
await this.saveKnownMarketplaces(registry);
|
|
516
|
+
});
|
|
455
517
|
}
|
|
456
518
|
|
|
457
519
|
/**
|
|
@@ -461,125 +523,132 @@ export class MarketplaceService {
|
|
|
461
523
|
pluginAtMarketplace: string,
|
|
462
524
|
projectPath?: string,
|
|
463
525
|
): Promise<InstalledPlugin> {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
const marketplaces = await this.listMarketplaces();
|
|
470
|
-
const marketplace = marketplaces.find((m) => m.name === marketplaceName);
|
|
471
|
-
if (!marketplace) {
|
|
472
|
-
throw new Error(`Marketplace ${marketplaceName} not found`);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const marketplacePath = this.getMarketplacePath(marketplace);
|
|
476
|
-
const manifest = await this.loadMarketplaceManifest(marketplacePath);
|
|
477
|
-
const pluginEntry = manifest.plugins.find((p) => p.name === pluginName);
|
|
478
|
-
if (!pluginEntry) {
|
|
479
|
-
throw new Error(
|
|
480
|
-
`Plugin ${pluginName} not found in marketplace ${marketplaceName}`,
|
|
481
|
-
);
|
|
482
|
-
}
|
|
526
|
+
return this.withLock(async () => {
|
|
527
|
+
const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
|
|
528
|
+
if (!pluginName || !marketplaceName) {
|
|
529
|
+
throw new Error("Invalid plugin format. Use name@marketplace");
|
|
530
|
+
}
|
|
483
531
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
pluginEntry.source.startsWith("ssh://");
|
|
489
|
-
|
|
490
|
-
let pluginSrcPath: string;
|
|
491
|
-
let tempCloneDir: string | undefined;
|
|
492
|
-
|
|
493
|
-
if (isGitSource) {
|
|
494
|
-
tempCloneDir = path.join(this.tmpDir, `clone-${Date.now()}`);
|
|
495
|
-
let url = pluginEntry.source;
|
|
496
|
-
let ref: string | undefined;
|
|
497
|
-
if (url.includes("#")) {
|
|
498
|
-
[url, ref] = url.split("#");
|
|
532
|
+
const marketplaces = await this.listMarketplaces();
|
|
533
|
+
const marketplace = marketplaces.find((m) => m.name === marketplaceName);
|
|
534
|
+
if (!marketplace) {
|
|
535
|
+
throw new Error(`Marketplace ${marketplaceName} not found`);
|
|
499
536
|
}
|
|
500
|
-
await this.gitService.clone(url, tempCloneDir, ref);
|
|
501
|
-
pluginSrcPath = tempCloneDir;
|
|
502
|
-
} else {
|
|
503
|
-
pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
|
|
504
|
-
}
|
|
505
537
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
await fs.rm(tempCloneDir, { recursive: true, force: true });
|
|
538
|
+
const marketplacePath = this.getMarketplacePath(marketplace);
|
|
539
|
+
const manifest = await this.loadMarketplaceManifest(marketplacePath);
|
|
540
|
+
const pluginEntry = manifest.plugins.find((p) => p.name === pluginName);
|
|
541
|
+
if (!pluginEntry) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`Plugin ${pluginName} not found in marketplace ${marketplaceName}`,
|
|
544
|
+
);
|
|
514
545
|
}
|
|
515
|
-
throw new Error(`Plugin manifest not found at ${pluginManifestPath}`);
|
|
516
|
-
}
|
|
517
546
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const version = pluginManifest.version || "1.0.0";
|
|
547
|
+
const isGitSource =
|
|
548
|
+
pluginEntry.source.startsWith("http://") ||
|
|
549
|
+
pluginEntry.source.startsWith("https://") ||
|
|
550
|
+
pluginEntry.source.startsWith("git@") ||
|
|
551
|
+
pluginEntry.source.startsWith("ssh://");
|
|
524
552
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
if (isGitSource) {
|
|
529
|
-
await fs.rename(pluginSrcPath, tmpPluginDir);
|
|
530
|
-
tempCloneDir = undefined; // Already moved
|
|
531
|
-
} else {
|
|
532
|
-
await fs.cp(pluginSrcPath, tmpPluginDir, { recursive: true });
|
|
533
|
-
}
|
|
553
|
+
let pluginSrcPath: string;
|
|
554
|
+
let tempCloneDir: string | undefined;
|
|
534
555
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
556
|
+
try {
|
|
557
|
+
if (isGitSource) {
|
|
558
|
+
tempCloneDir = path.join(this.tmpDir, `clone-${Date.now()}`);
|
|
559
|
+
let url = pluginEntry.source;
|
|
560
|
+
let ref: string | undefined;
|
|
561
|
+
if (url.includes("#")) {
|
|
562
|
+
[url, ref] = url.split("#");
|
|
563
|
+
}
|
|
564
|
+
await this.gitService.clone(url, tempCloneDir, ref);
|
|
565
|
+
pluginSrcPath = tempCloneDir;
|
|
566
|
+
} else {
|
|
567
|
+
pluginSrcPath = path.resolve(marketplacePath, pluginEntry.source);
|
|
568
|
+
}
|
|
546
569
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
570
|
+
const pluginManifestPath = path.join(
|
|
571
|
+
pluginSrcPath,
|
|
572
|
+
".wave-plugin",
|
|
573
|
+
"plugin.json",
|
|
574
|
+
);
|
|
575
|
+
if (!existsSync(pluginManifestPath)) {
|
|
576
|
+
throw new Error(`Plugin manifest not found at ${pluginManifestPath}`);
|
|
577
|
+
}
|
|
554
578
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
};
|
|
579
|
+
const pluginManifestContent = await fs.readFile(
|
|
580
|
+
pluginManifestPath,
|
|
581
|
+
"utf-8",
|
|
582
|
+
);
|
|
583
|
+
const pluginManifest = JSON.parse(pluginManifestContent);
|
|
584
|
+
const version = pluginManifest.version || "1.0.0";
|
|
562
585
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
586
|
+
// Atomic installation
|
|
587
|
+
const tmpPluginDir = path.join(
|
|
588
|
+
this.tmpDir,
|
|
589
|
+
`${pluginName}-${Date.now()}`,
|
|
590
|
+
);
|
|
591
|
+
try {
|
|
592
|
+
if (isGitSource) {
|
|
593
|
+
await fs.rename(pluginSrcPath, tmpPluginDir);
|
|
594
|
+
tempCloneDir = undefined; // Already moved
|
|
595
|
+
} else {
|
|
596
|
+
await fs.cp(pluginSrcPath, tmpPluginDir, { recursive: true });
|
|
597
|
+
}
|
|
568
598
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
599
|
+
const cachePath = path.join(
|
|
600
|
+
this.cacheDir,
|
|
601
|
+
marketplaceName,
|
|
602
|
+
pluginName,
|
|
603
|
+
version,
|
|
604
|
+
);
|
|
605
|
+
if (existsSync(cachePath)) {
|
|
606
|
+
await fs.rm(cachePath, { recursive: true, force: true });
|
|
607
|
+
}
|
|
608
|
+
await fs.mkdir(path.dirname(cachePath), { recursive: true });
|
|
609
|
+
await fs.rename(tmpPluginDir, cachePath);
|
|
610
|
+
|
|
611
|
+
const installedRegistry = await this.getInstalledPlugins();
|
|
612
|
+
const existingIndex = installedRegistry.plugins.findIndex(
|
|
613
|
+
(p) =>
|
|
614
|
+
p.name === pluginName &&
|
|
615
|
+
p.marketplace === marketplaceName &&
|
|
616
|
+
p.projectPath === projectPath,
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
const installedPlugin: InstalledPlugin = {
|
|
620
|
+
name: pluginName,
|
|
621
|
+
marketplace: marketplaceName,
|
|
622
|
+
version,
|
|
623
|
+
cachePath,
|
|
624
|
+
projectPath,
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
if (existingIndex >= 0) {
|
|
628
|
+
installedRegistry.plugins[existingIndex] = installedPlugin;
|
|
629
|
+
} else {
|
|
630
|
+
installedRegistry.plugins.push(installedPlugin);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
await this.saveInstalledPlugins(installedRegistry);
|
|
634
|
+
return installedPlugin;
|
|
635
|
+
} catch (error) {
|
|
636
|
+
// Cleanup tmp dir if it exists
|
|
637
|
+
if (existsSync(tmpPluginDir)) {
|
|
638
|
+
await fs.rm(tmpPluginDir, { recursive: true, force: true });
|
|
639
|
+
}
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
} catch (error) {
|
|
643
|
+
// Cleanup temp clone dir if it exists
|
|
644
|
+
if (tempCloneDir && existsSync(tempCloneDir)) {
|
|
645
|
+
await fs.rm(tempCloneDir, { recursive: true, force: true });
|
|
646
|
+
}
|
|
647
|
+
throw new Error(
|
|
648
|
+
`Failed to install plugin ${pluginName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
649
|
+
);
|
|
578
650
|
}
|
|
579
|
-
|
|
580
|
-
`Failed to install plugin ${pluginName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
581
|
-
);
|
|
582
|
-
}
|
|
651
|
+
});
|
|
583
652
|
}
|
|
584
653
|
|
|
585
654
|
/**
|
|
@@ -589,47 +658,51 @@ export class MarketplaceService {
|
|
|
589
658
|
pluginAtMarketplace: string,
|
|
590
659
|
projectPath?: string,
|
|
591
660
|
): Promise<void> {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const installedRegistry = await this.getInstalledPlugins();
|
|
598
|
-
const pluginIndex = installedRegistry.plugins.findIndex(
|
|
599
|
-
(p) =>
|
|
600
|
-
p.name === pluginName &&
|
|
601
|
-
p.marketplace === marketplaceName &&
|
|
602
|
-
p.projectPath === projectPath,
|
|
603
|
-
);
|
|
661
|
+
return this.withLock(async () => {
|
|
662
|
+
const [pluginName, marketplaceName] = pluginAtMarketplace.split("@");
|
|
663
|
+
if (!pluginName || !marketplaceName) {
|
|
664
|
+
throw new Error("Invalid plugin format. Use name@marketplace");
|
|
665
|
+
}
|
|
604
666
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
667
|
+
const installedRegistry = await this.getInstalledPlugins();
|
|
668
|
+
const pluginIndex = installedRegistry.plugins.findIndex(
|
|
669
|
+
(p) =>
|
|
670
|
+
p.name === pluginName &&
|
|
671
|
+
p.marketplace === marketplaceName &&
|
|
672
|
+
p.projectPath === projectPath,
|
|
608
673
|
);
|
|
609
|
-
}
|
|
610
674
|
|
|
611
|
-
|
|
675
|
+
if (pluginIndex === -1) {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`Plugin ${pluginName}@${marketplaceName} is not installed${projectPath ? ` for project ${projectPath}` : ""}`,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const pluginToRemove = installedRegistry.plugins[pluginIndex];
|
|
612
682
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
683
|
+
// Remove from registry first
|
|
684
|
+
installedRegistry.plugins.splice(pluginIndex, 1);
|
|
685
|
+
await this.saveInstalledPlugins(installedRegistry);
|
|
616
686
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
687
|
+
// Check if any other project is still using this same cache path
|
|
688
|
+
const isStillReferenced = installedRegistry.plugins.some(
|
|
689
|
+
(p) => p.cachePath === pluginToRemove.cachePath,
|
|
690
|
+
);
|
|
621
691
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
692
|
+
// Only remove cached files if no other references exist
|
|
693
|
+
if (!isStillReferenced && existsSync(pluginToRemove.cachePath)) {
|
|
694
|
+
await fs.rm(pluginToRemove.cachePath, { recursive: true, force: true });
|
|
695
|
+
}
|
|
696
|
+
});
|
|
626
697
|
}
|
|
627
698
|
|
|
628
699
|
/**
|
|
629
700
|
* Updates a plugin (uninstall followed by install)
|
|
630
701
|
*/
|
|
631
702
|
async updatePlugin(pluginAtMarketplace: string): Promise<InstalledPlugin> {
|
|
632
|
-
|
|
633
|
-
|
|
703
|
+
return this.withLock(async () => {
|
|
704
|
+
await this.uninstallPlugin(pluginAtMarketplace);
|
|
705
|
+
return this.installPlugin(pluginAtMarketplace);
|
|
706
|
+
});
|
|
634
707
|
}
|
|
635
708
|
}
|