@web42/cli 0.2.7 → 0.2.8

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.
Files changed (58) hide show
  1. package/dist/commands/search.js +20 -15
  2. package/dist/commands/send.js +75 -41
  3. package/dist/commands/serve.d.ts +1 -1
  4. package/dist/commands/serve.js +116 -213
  5. package/dist/index.js +1 -19
  6. package/dist/version.d.ts +1 -1
  7. package/dist/version.js +1 -1
  8. package/package.json +1 -1
  9. package/dist/commands/config.d.ts +0 -2
  10. package/dist/commands/config.js +0 -27
  11. package/dist/commands/init.d.ts +0 -2
  12. package/dist/commands/init.js +0 -451
  13. package/dist/commands/install.d.ts +0 -3
  14. package/dist/commands/install.js +0 -231
  15. package/dist/commands/list.d.ts +0 -3
  16. package/dist/commands/list.js +0 -22
  17. package/dist/commands/pack.d.ts +0 -2
  18. package/dist/commands/pack.js +0 -210
  19. package/dist/commands/pull.d.ts +0 -2
  20. package/dist/commands/pull.js +0 -202
  21. package/dist/commands/push.d.ts +0 -2
  22. package/dist/commands/push.js +0 -374
  23. package/dist/commands/remix.d.ts +0 -2
  24. package/dist/commands/remix.js +0 -49
  25. package/dist/commands/sync.d.ts +0 -2
  26. package/dist/commands/sync.js +0 -98
  27. package/dist/commands/uninstall.d.ts +0 -3
  28. package/dist/commands/uninstall.js +0 -54
  29. package/dist/commands/update.d.ts +0 -3
  30. package/dist/commands/update.js +0 -59
  31. package/dist/platforms/base.d.ts +0 -82
  32. package/dist/platforms/base.js +0 -1
  33. package/dist/platforms/claude/__tests__/adapter.test.d.ts +0 -1
  34. package/dist/platforms/claude/__tests__/adapter.test.js +0 -257
  35. package/dist/platforms/claude/__tests__/security.test.d.ts +0 -1
  36. package/dist/platforms/claude/__tests__/security.test.js +0 -166
  37. package/dist/platforms/claude/adapter.d.ts +0 -34
  38. package/dist/platforms/claude/adapter.js +0 -525
  39. package/dist/platforms/claude/security.d.ts +0 -15
  40. package/dist/platforms/claude/security.js +0 -67
  41. package/dist/platforms/claude/templates.d.ts +0 -5
  42. package/dist/platforms/claude/templates.js +0 -22
  43. package/dist/platforms/openclaw/adapter.d.ts +0 -12
  44. package/dist/platforms/openclaw/adapter.js +0 -476
  45. package/dist/platforms/openclaw/templates.d.ts +0 -7
  46. package/dist/platforms/openclaw/templates.js +0 -369
  47. package/dist/platforms/registry.d.ts +0 -6
  48. package/dist/platforms/registry.js +0 -32
  49. package/dist/types/sync.d.ts +0 -74
  50. package/dist/types/sync.js +0 -7
  51. package/dist/utils/bundled-skills.d.ts +0 -6
  52. package/dist/utils/bundled-skills.js +0 -29
  53. package/dist/utils/secrets.d.ts +0 -32
  54. package/dist/utils/secrets.js +0 -118
  55. package/dist/utils/skill.d.ts +0 -6
  56. package/dist/utils/skill.js +0 -42
  57. package/dist/utils/sync.d.ts +0 -14
  58. package/dist/utils/sync.js +0 -242
@@ -1,374 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync, readdirSync } from "fs";
2
- import path, { join } from "path";
3
- import { Command } from "commander";
4
- import chalk from "chalk";
5
- import ora from "ora";
6
- import { apiPost, apiFormData } from "../utils/api.js";
7
- import { requireAuth } from "../utils/config.js";
8
- import { resolvePlatform } from "../platforms/registry.js";
9
- import { parseSkillMd } from "../utils/skill.js";
10
- import { buildLocalSnapshot, computeHashFromSnapshot, findLocalAvatar, findAgentAvatar, discoverResources, readResourcesMeta, readSyncState, writeSyncState, } from "../utils/sync.js";
11
- function mimeFromExtension(ext) {
12
- const map = {
13
- png: "image/png",
14
- jpg: "image/jpeg",
15
- jpeg: "image/jpeg",
16
- webp: "image/webp",
17
- svg: "image/svg+xml",
18
- mp4: "video/mp4",
19
- webm: "video/webm",
20
- pdf: "application/pdf",
21
- };
22
- return map[ext.toLowerCase()] ?? "application/octet-stream";
23
- }
24
- /**
25
- * Push a single agent to the marketplace.
26
- * This encapsulates steps 1-8 of the push flow.
27
- */
28
- async function pushSingleAgent(opts) {
29
- const { cwd, config, spinner, distDir, syncDir } = opts;
30
- let manifest = opts.manifest;
31
- // -----------------------------------------------------------------------
32
- // Step 1: Pack into dist/ (if not already packed)
33
- // -----------------------------------------------------------------------
34
- if (existsSync(distDir)) {
35
- const packedManifestPath = join(distDir, "manifest.json");
36
- if (existsSync(packedManifestPath)) {
37
- manifest = JSON.parse(readFileSync(packedManifestPath, "utf-8"));
38
- }
39
- }
40
- else {
41
- spinner.text = `Packing ${opts.agentName ?? "agent"}...`;
42
- const result = await opts.adapter.pack({
43
- cwd,
44
- outputDir: distDir.startsWith(cwd) ? distDir.slice(cwd.length + 1) : distDir,
45
- agentName: opts.agentName,
46
- });
47
- const internalPrefixes = [];
48
- for (const f of result.files) {
49
- const skillMatch = f.path.match(/^skills\/([^/]+)\/SKILL\.md$/);
50
- if (skillMatch) {
51
- const parsed = parseSkillMd(f.content, skillMatch[1]);
52
- if (parsed.internal)
53
- internalPrefixes.push(`skills/${skillMatch[1]}/`);
54
- }
55
- }
56
- if (internalPrefixes.length > 0) {
57
- result.files = result.files.filter((f) => !internalPrefixes.some((p) => f.path.startsWith(p)));
58
- }
59
- const configVars = Array.isArray(manifest.configVariables) ? manifest.configVariables : [];
60
- const existingKeys = new Set(configVars.map((v) => v.key));
61
- for (const cv of result.configVariables) {
62
- if (!existingKeys.has(cv.key)) {
63
- if (!manifest.configVariables)
64
- manifest.configVariables = [];
65
- manifest.configVariables.push(cv);
66
- existingKeys.add(cv.key);
67
- }
68
- }
69
- mkdirSync(distDir, { recursive: true });
70
- for (const file of result.files) {
71
- const filePath = join(distDir, file.path);
72
- mkdirSync(join(filePath, ".."), { recursive: true });
73
- writeFileSync(filePath, file.content, "utf-8");
74
- }
75
- writeFileSync(join(distDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
76
- }
77
- // -----------------------------------------------------------------------
78
- // Step 2: Resolve agent ID (create if first push)
79
- // -----------------------------------------------------------------------
80
- spinner.text = `Resolving ${opts.agentName ?? "agent"}...`;
81
- let syncState = readSyncState(syncDir);
82
- let agentId = syncState?.agent_id ?? null;
83
- let isCreated = false;
84
- // Always resolve agent metadata (README, marketplace config, avatar)
85
- // and upsert the agent record so metadata updates propagate on every push.
86
- {
87
- let readme = "";
88
- // Check per-agent README first (syncDir/README.md), then cwd/README.md
89
- const agentReadmePath = join(syncDir, "README.md");
90
- const cwdReadmePath = join(cwd, "README.md");
91
- if (existsSync(agentReadmePath)) {
92
- readme = readFileSync(agentReadmePath, "utf-8");
93
- }
94
- else if (existsSync(cwdReadmePath)) {
95
- readme = readFileSync(cwdReadmePath, "utf-8");
96
- }
97
- // Note: visibility, license, price, and tags are managed exclusively
98
- // through the dashboard UI — the CLI never sends these fields.
99
- let profile_image_data = undefined;
100
- if (!agentId) {
101
- // Only upload avatar on first push (subsequent avatar updates use step 6)
102
- const avatarSearchPaths = [
103
- join(cwd, "avatar/avatar.png"),
104
- join(cwd, "avatars/avatar.png"),
105
- join(cwd, "avatar.png"),
106
- join(syncDir, "avatar.png"),
107
- ];
108
- for (const ap of avatarSearchPaths) {
109
- if (existsSync(ap)) {
110
- try {
111
- const stats = statSync(ap);
112
- if (stats.size <= 5 * 1024 * 1024) {
113
- profile_image_data = readFileSync(ap).toString("base64");
114
- break;
115
- }
116
- }
117
- catch {
118
- // Skip
119
- }
120
- }
121
- }
122
- }
123
- const name = manifest.name ?? "";
124
- const agentResult = await apiPost("/api/agents", {
125
- slug: name,
126
- name,
127
- description: manifest.description ?? "",
128
- readme,
129
- manifest,
130
- demo_video_url: manifest.demoVideoUrl,
131
- profile_image_data,
132
- });
133
- agentId = agentResult.agent.id;
134
- isCreated = !!agentResult.created;
135
- }
136
- // -----------------------------------------------------------------------
137
- // Step 3: Build local snapshot and compute hash
138
- // -----------------------------------------------------------------------
139
- spinner.text = "Building snapshot...";
140
- // Determine whether syncDir is a .web42 subdirectory using path semantics,
141
- // not string matching (avoids false positives when project path itself contains ".web42").
142
- const relSyncDir = path.relative(cwd, syncDir);
143
- const syncDirIsWeb42Subdir = Boolean(relSyncDir) &&
144
- !relSyncDir.startsWith("..") &&
145
- !path.isAbsolute(relSyncDir) &&
146
- relSyncDir.split(path.sep).includes(".web42");
147
- const snapshot = buildLocalSnapshot(syncDirIsWeb42Subdir ? syncDir : cwd, distDir);
148
- const localHash = computeHashFromSnapshot(snapshot);
149
- // -----------------------------------------------------------------------
150
- // Step 4: Compare hashes (unless --force)
151
- // -----------------------------------------------------------------------
152
- const name = manifest.name ?? "";
153
- if (!opts.force && !opts.forceAvatar && !isCreated && syncState?.last_local_hash) {
154
- if (localHash === syncState.last_local_hash) {
155
- spinner.succeed(`${chalk.bold(`@${config.username}/${name}`)} has no local changes since last sync.`);
156
- return;
157
- }
158
- }
159
- // -----------------------------------------------------------------------
160
- // Step 5: Push snapshot
161
- // -----------------------------------------------------------------------
162
- spinner.text = `Pushing ${snapshot.files.length} files...`;
163
- const pushResult = await apiPost(`/api/agents/${agentId}/sync/push`, snapshot);
164
- let finalHash = pushResult.hash;
165
- // -----------------------------------------------------------------------
166
- // Step 6: Upload avatar if present
167
- // -----------------------------------------------------------------------
168
- const avatarPath = findLocalAvatar(syncDir) || findAgentAvatar(cwd);
169
- if (avatarPath) {
170
- spinner.text = "Uploading avatar...";
171
- const ext = avatarPath.split(".").pop() ?? "png";
172
- const avatarBuffer = readFileSync(avatarPath);
173
- const avatarBlob = new Blob([avatarBuffer], {
174
- type: mimeFromExtension(ext),
175
- });
176
- const avatarForm = new FormData();
177
- avatarForm.append("avatar", avatarBlob, `avatar.${ext}`);
178
- const avatarResult = await apiFormData(`/api/agents/${agentId}/sync/avatar`, avatarForm);
179
- finalHash = avatarResult.hash;
180
- }
181
- // -----------------------------------------------------------------------
182
- // Step 7: Upload resources if present
183
- // -----------------------------------------------------------------------
184
- const resourcesMeta = readResourcesMeta(syncDir);
185
- const discoveredResources = discoverResources(cwd);
186
- const allResources = [...resourcesMeta, ...discoveredResources];
187
- if (allResources.length > 0) {
188
- spinner.text = "Uploading resources...";
189
- const resForm = new FormData();
190
- const metadataForApi = allResources.map((meta, i) => ({
191
- file_key: `resource_${i}`,
192
- title: meta.title,
193
- description: meta.description,
194
- type: meta.type,
195
- sort_order: meta.sort_order,
196
- }));
197
- resForm.append("metadata", JSON.stringify(metadataForApi));
198
- for (let i = 0; i < allResources.length; i++) {
199
- const meta = allResources[i];
200
- let resFilePath = join(syncDir, "resources", meta.file);
201
- if (!existsSync(resFilePath)) {
202
- resFilePath = join(cwd, ".web42", "resources", meta.file);
203
- }
204
- if (!existsSync(resFilePath)) {
205
- resFilePath = join(cwd, "resources", meta.file);
206
- }
207
- if (existsSync(resFilePath)) {
208
- const resBuffer = readFileSync(resFilePath);
209
- const ext = meta.file.split(".").pop() ?? "";
210
- const blob = new Blob([resBuffer], {
211
- type: mimeFromExtension(ext),
212
- });
213
- resForm.append(`resource_${i}`, blob, meta.file);
214
- }
215
- }
216
- const resResult = await apiFormData(`/api/agents/${agentId}/sync/resources`, resForm);
217
- finalHash = resResult.hash;
218
- }
219
- // -----------------------------------------------------------------------
220
- // Step 8: Save sync state
221
- // -----------------------------------------------------------------------
222
- writeSyncState(syncDir, {
223
- agent_id: agentId,
224
- last_remote_hash: finalHash,
225
- last_local_hash: localHash,
226
- synced_at: new Date().toISOString(),
227
- });
228
- const siteUrl = config.apiUrl ? config.apiUrl.replace("https://", "") : "web42.ai";
229
- if (isCreated) {
230
- console.log(chalk.green(` New agent created: @${config.username}/${name}`));
231
- }
232
- else {
233
- console.log(chalk.green(` Updated: @${config.username}/${name}`));
234
- }
235
- console.log(chalk.dim(` View at: ${siteUrl}/${config.username}/${name}`));
236
- console.log(chalk.dim(` Sync hash: ${finalHash.slice(0, 12)}...`));
237
- }
238
- // ---------------------------------------------------------------------------
239
- // Command
240
- // ---------------------------------------------------------------------------
241
- export const pushCommand = new Command("push")
242
- .description("Push your agent package to the Web42 marketplace")
243
- .option("--force", "Skip hash comparison and always push")
244
- .option("--force-avatar", "Explicitly upload avatar even if no other changes")
245
- .option("--agent <name>", "Push a specific agent (for multi-agent workspaces)")
246
- .option("--url <url>", "Register as a live A2A agent at this public URL (e.g. ngrok URL)")
247
- .action(async (opts) => {
248
- const config = requireAuth();
249
- const cwd = process.cwd();
250
- // Detect multi-agent workspace (per-agent manifests in .web42/{name}/)
251
- const web42Dir = join(cwd, ".web42");
252
- const agentManifests = new Map();
253
- let platform = "openclaw";
254
- if (existsSync(web42Dir)) {
255
- try {
256
- const entries = readdirSync(web42Dir, { withFileTypes: true });
257
- for (const entry of entries) {
258
- if (!entry.isDirectory())
259
- continue;
260
- const agentManifestPath = join(web42Dir, entry.name, "manifest.json");
261
- if (existsSync(agentManifestPath)) {
262
- try {
263
- const m = JSON.parse(readFileSync(agentManifestPath, "utf-8"));
264
- agentManifests.set(entry.name, m);
265
- if (m.platform)
266
- platform = m.platform;
267
- }
268
- catch {
269
- // skip
270
- }
271
- }
272
- }
273
- }
274
- catch {
275
- // .web42 not readable
276
- }
277
- }
278
- const isMultiAgent = agentManifests.size > 0;
279
- // Single-agent mode (e.g., OpenClaw)
280
- if (!isMultiAgent) {
281
- const manifestPath = join(cwd, "manifest.json");
282
- if (!existsSync(manifestPath)) {
283
- console.log(chalk.red("No manifest.json found. Run `web42 init` first."));
284
- process.exit(1);
285
- }
286
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
287
- if (!manifest.name || !manifest.version || !manifest.author) {
288
- console.log(chalk.red("Invalid manifest.json. Must have name, version, and author."));
289
- process.exit(1);
290
- }
291
- if (manifest.platform)
292
- platform = manifest.platform;
293
- const adapter = resolvePlatform(platform);
294
- const spinner = ora("Preparing agent package...").start();
295
- try {
296
- await pushSingleAgent({
297
- cwd,
298
- manifest,
299
- distDir: join(cwd, ".web42", "dist"),
300
- syncDir: cwd,
301
- config,
302
- force: opts.force,
303
- forceAvatar: opts.forceAvatar,
304
- spinner,
305
- adapter,
306
- });
307
- spinner.succeed(`Pushed ${chalk.bold(`@${config.username}/${manifest.name}`)}`);
308
- // Register as live A2A agent if --url provided
309
- if (opts.url) {
310
- const a2aSpinner = ora("Registering live agent URL...").start();
311
- try {
312
- await apiPost(`/api/agents/${manifest.name}/a2a`, {
313
- a2a_url: opts.url,
314
- a2a_enabled: true,
315
- gateway_status: "live",
316
- });
317
- a2aSpinner.succeed(`Agent live at ${chalk.cyan(opts.url)}`);
318
- }
319
- catch (err) {
320
- a2aSpinner.fail("Failed to register live URL");
321
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
322
- }
323
- }
324
- }
325
- catch (error) {
326
- spinner.fail("Push failed");
327
- console.error(chalk.red(error instanceof Error ? error.message : String(error)));
328
- process.exit(1);
329
- }
330
- return;
331
- }
332
- // Multi-agent mode
333
- const adapter = resolvePlatform(platform);
334
- let agentsToPush;
335
- if (opts.agent) {
336
- const manifest = agentManifests.get(opts.agent);
337
- if (!manifest) {
338
- console.log(chalk.red(`Agent "${opts.agent}" not found. Available: ${[...agentManifests.keys()].join(", ")}`));
339
- process.exit(1);
340
- }
341
- agentsToPush = [[opts.agent, manifest]];
342
- }
343
- else {
344
- agentsToPush = [...agentManifests.entries()];
345
- }
346
- const spinner = ora(`Pushing ${agentsToPush.length} agent(s)...`).start();
347
- try {
348
- let successCount = 0;
349
- for (const [agentName, manifest] of agentsToPush) {
350
- spinner.text = `Pushing ${agentName}...`;
351
- const agentWeb42Dir = join(web42Dir, agentName);
352
- const distDir = join(agentWeb42Dir, "dist");
353
- await pushSingleAgent({
354
- cwd,
355
- manifest,
356
- distDir,
357
- syncDir: agentWeb42Dir,
358
- config,
359
- force: opts.force,
360
- forceAvatar: opts.forceAvatar,
361
- spinner,
362
- adapter,
363
- agentName,
364
- });
365
- successCount++;
366
- }
367
- spinner.succeed(`Pushed ${successCount} agent(s)`);
368
- }
369
- catch (error) {
370
- spinner.fail("Push failed");
371
- console.error(chalk.red(error.message));
372
- process.exit(1);
373
- }
374
- });
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare const remixCommand: Command;
@@ -1,49 +0,0 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import inquirer from "inquirer";
4
- import ora from "ora";
5
- import { apiGet, apiPost } from "../utils/api.js";
6
- import { requireAuth } from "../utils/config.js";
7
- export const remixCommand = new Command("remix")
8
- .description("Remix an agent package to your account")
9
- .argument("<agent>", "Agent to remix (e.g. @user/agent-name)")
10
- .action(async (agentRef) => {
11
- const config = requireAuth();
12
- const match = agentRef.match(/^@?([^/]+)\/(.+)$/);
13
- if (!match) {
14
- console.log(chalk.red("Invalid agent reference. Use @user/agent-name format."));
15
- process.exit(1);
16
- }
17
- const [, username, agentSlug] = match;
18
- const { confirm } = await inquirer.prompt([
19
- {
20
- type: "confirm",
21
- name: "confirm",
22
- message: `Remix @${username}/${agentSlug} to @${config.username}/${agentSlug}?`,
23
- default: true,
24
- },
25
- ]);
26
- if (!confirm) {
27
- console.log(chalk.yellow("Aborted."));
28
- return;
29
- }
30
- const spinner = ora("Remixing...").start();
31
- try {
32
- // Find the agent
33
- const agents = await apiGet(`/api/agents?username=${username}`);
34
- const agent = agents.find((a) => a.slug === agentSlug);
35
- if (!agent) {
36
- spinner.fail(`Agent @${username}/${agentSlug} not found`);
37
- process.exit(1);
38
- }
39
- await apiPost(`/api/agents/${agent.id}/remix`, {});
40
- spinner.succeed(`Remixed to ${chalk.bold(`@${config.username}/${agentSlug}`)}`);
41
- console.log();
42
- console.log(chalk.dim(`Run \`web42 install @${config.username}/${agentSlug}\` to install locally.`));
43
- }
44
- catch (error) {
45
- spinner.fail("Remix failed");
46
- console.error(chalk.red(error.message));
47
- process.exit(1);
48
- }
49
- });
@@ -1,2 +0,0 @@
1
- import { Command } from "commander";
2
- export declare const syncCommand: Command;
@@ -1,98 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- import { Command } from "commander";
4
- import chalk from "chalk";
5
- import ora from "ora";
6
- import { apiGet } from "../utils/api.js";
7
- import { requireAuth } from "../utils/config.js";
8
- import { buildLocalSnapshot, computeHashFromSnapshot, readSyncState, } from "../utils/sync.js";
9
- export const syncCommand = new Command("sync")
10
- .description("Check sync status between local workspace and the marketplace")
11
- .action(async () => {
12
- const config = requireAuth();
13
- const cwd = process.cwd();
14
- const manifestPath = join(cwd, "manifest.json");
15
- if (!existsSync(manifestPath)) {
16
- console.log(chalk.red("No manifest.json found. Are you in an agent directory?"));
17
- process.exit(1);
18
- }
19
- const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
20
- if (!manifest.name) {
21
- console.log(chalk.red("manifest.json is missing a name field."));
22
- process.exit(1);
23
- }
24
- const spinner = ora("Checking sync status...").start();
25
- try {
26
- // Resolve agent ID
27
- const syncState = readSyncState(cwd);
28
- let agentId = syncState?.agent_id ?? null;
29
- if (!agentId) {
30
- const agents = await apiGet(`/api/agents?username=${config.username}`);
31
- const agent = agents.find((a) => a.slug === manifest.name);
32
- if (!agent) {
33
- spinner.fail(`Agent @${config.username}/${manifest.name} not found on the marketplace. Run \`web42 push\` first.`);
34
- process.exit(1);
35
- }
36
- agentId = agent.id;
37
- }
38
- // Compute local hash
39
- const distDir = join(cwd, ".web42", "dist");
40
- let localHash = null;
41
- if (existsSync(distDir)) {
42
- const snapshot = buildLocalSnapshot(cwd);
43
- localHash = computeHashFromSnapshot(snapshot);
44
- }
45
- // Fetch remote hash
46
- const remote = await apiGet(`/api/agents/${agentId}/sync`);
47
- const remoteHash = remote.hash;
48
- const lastRemoteHash = syncState?.last_remote_hash ?? null;
49
- const lastLocalHash = syncState?.last_local_hash ?? null;
50
- spinner.stop();
51
- console.log();
52
- console.log(chalk.bold(` Sync status for @${config.username}/${manifest.name}`));
53
- console.log();
54
- console.log(` Remote hash: ${chalk.cyan(remoteHash.slice(0, 12))}...`);
55
- if (localHash) {
56
- console.log(` Local hash: ${chalk.cyan(localHash.slice(0, 12))}...`);
57
- }
58
- else {
59
- console.log(` Local hash: ${chalk.dim("(not packed yet — run web42 pack first)")}`);
60
- }
61
- if (lastRemoteHash) {
62
- console.log(` Last synced: ${chalk.dim(syncState.synced_at)}`);
63
- }
64
- else {
65
- console.log(` Last synced: ${chalk.dim("never")}`);
66
- }
67
- console.log();
68
- // Determine sync status by comparing each side against its last-known hash
69
- if (!lastRemoteHash || !lastLocalHash) {
70
- console.log(chalk.yellow(" Status: Never synced — run `web42 push` or `web42 pull`"));
71
- }
72
- else {
73
- const localChanged = localHash !== null && localHash !== lastLocalHash;
74
- const remoteChanged = remoteHash !== lastRemoteHash;
75
- if (localChanged && remoteChanged) {
76
- console.log(chalk.red(" Status: Both local and remote have changed since last sync"));
77
- console.log(chalk.dim(" Use `web42 push --force` or `web42 pull --force` to resolve"));
78
- }
79
- else if (localChanged) {
80
- console.log(chalk.yellow(" Status: Local changes (push to sync)"));
81
- console.log(chalk.dim(" Run `web42 push` to update the marketplace"));
82
- }
83
- else if (remoteChanged) {
84
- console.log(chalk.yellow(" Status: Remote changes (pull to sync)"));
85
- console.log(chalk.dim(" Run `web42 pull` to update local files"));
86
- }
87
- else {
88
- console.log(chalk.green(" Status: In sync"));
89
- }
90
- }
91
- console.log();
92
- }
93
- catch (error) {
94
- spinner.fail("Sync status check failed");
95
- console.error(chalk.red(error.message));
96
- process.exit(1);
97
- }
98
- });
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- import type { PlatformAdapter } from "../platforms/base.js";
3
- export declare function makeUninstallCommand(adapter: PlatformAdapter): Command;
@@ -1,54 +0,0 @@
1
- import { Command } from "commander";
2
- import chalk from "chalk";
3
- import inquirer from "inquirer";
4
- import ora from "ora";
5
- export function makeUninstallCommand(adapter) {
6
- return new Command("uninstall")
7
- .description("Uninstall an agent")
8
- .argument("<agent>", "Local agent name to uninstall")
9
- .option("-f, --force", "Skip confirmation prompt")
10
- .action(async (agentName, opts) => {
11
- const installed = await adapter.listInstalled();
12
- const found = installed.find((a) => a.name === agentName);
13
- if (!found) {
14
- console.log(chalk.red(`Agent "${agentName}" is not installed on ${adapter.name}.`));
15
- const available = installed.map((a) => a.name);
16
- if (available.length > 0) {
17
- console.log(chalk.dim(` Installed agents: ${available.join(", ")}`));
18
- }
19
- process.exit(1);
20
- }
21
- if (!opts.force) {
22
- const { confirm } = await inquirer.prompt([
23
- {
24
- type: "confirm",
25
- name: "confirm",
26
- message: `Remove agent "${agentName}" and all its workspace files?`,
27
- default: false,
28
- },
29
- ]);
30
- if (!confirm) {
31
- console.log(chalk.dim("Aborted."));
32
- return;
33
- }
34
- }
35
- const spinner = ora(`Uninstalling "${agentName}"...`).start();
36
- try {
37
- const result = await adapter.uninstall({ agentName });
38
- if (result.removed) {
39
- spinner.succeed(`Uninstalled "${agentName}"`);
40
- for (const p of result.paths) {
41
- console.log(chalk.dim(` Removed: ${p}`));
42
- }
43
- }
44
- else {
45
- spinner.warn(`Nothing to remove for "${agentName}"`);
46
- }
47
- }
48
- catch (error) {
49
- spinner.fail("Uninstall failed");
50
- console.error(chalk.red(error.message));
51
- process.exit(1);
52
- }
53
- });
54
- }
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- import type { PlatformAdapter } from "../platforms/base.js";
3
- export declare function makeUpdateCommand(adapter: PlatformAdapter): Command;
@@ -1,59 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "fs";
2
- import { join, dirname } from "path";
3
- import { Command } from "commander";
4
- import chalk from "chalk";
5
- import ora from "ora";
6
- import { apiGet, apiPost } from "../utils/api.js";
7
- function resolveTemplateVars(content, platformHome, workspacePath) {
8
- return content
9
- .replace(/\{\{OPENCLAW_HOME\}\}/g, platformHome)
10
- .replace(/\{\{WORKSPACE\}\}/g, workspacePath);
11
- }
12
- export function makeUpdateCommand(adapter) {
13
- return new Command("update")
14
- .description("Update an installed agent to the latest version")
15
- .argument("<agent>", "Agent to update (e.g. @user/agent-name)")
16
- .action(async (agentRef) => {
17
- const match = agentRef.match(/^@?([^/]+)\/(.+)$/);
18
- if (!match) {
19
- console.log(chalk.red("Invalid agent reference. Use @user/agent-name format."));
20
- process.exit(1);
21
- }
22
- const [, username, agentSlug] = match;
23
- const spinner = ora(`Checking for updates to @${username}/${agentSlug}...`).start();
24
- try {
25
- const agents = await apiGet(`/api/agents?username=${username}`);
26
- const agent = agents.find((a) => a.slug === agentSlug);
27
- if (!agent) {
28
- spinner.fail(`Agent @${username}/${agentSlug} not found`);
29
- process.exit(1);
30
- }
31
- const result = await apiPost(`/api/agents/${agent.id}/install`, {});
32
- spinner.text = "Applying updates...";
33
- const workspacePath = join(adapter.home, `workspace-${agentSlug}`);
34
- mkdirSync(workspacePath, { recursive: true });
35
- let updated = 0;
36
- for (const file of result.files) {
37
- if (file.path === ".openclaw/config.json")
38
- continue;
39
- const filePath = join(workspacePath, file.path);
40
- mkdirSync(dirname(filePath), { recursive: true });
41
- if (file.content !== null && file.content !== undefined) {
42
- const resolved = resolveTemplateVars(file.content, adapter.home, workspacePath);
43
- writeFileSync(filePath, resolved, "utf-8");
44
- updated++;
45
- }
46
- }
47
- if (result.agent.manifest) {
48
- const manifestPath = join(workspacePath, "manifest.json");
49
- writeFileSync(manifestPath, JSON.stringify(result.agent.manifest, null, 2) + "\n");
50
- }
51
- spinner.succeed(`Updated ${chalk.bold(`@${username}/${agentSlug}`)} (${updated} files)`);
52
- }
53
- catch (error) {
54
- spinner.fail("Update failed");
55
- console.error(chalk.red(error.message));
56
- process.exit(1);
57
- }
58
- });
59
- }