@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.
- package/dist/commands/search.js +20 -15
- package/dist/commands/send.js +75 -41
- package/dist/commands/serve.d.ts +1 -1
- package/dist/commands/serve.js +116 -213
- package/dist/index.js +1 -19
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/commands/config.d.ts +0 -2
- package/dist/commands/config.js +0 -27
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.js +0 -451
- package/dist/commands/install.d.ts +0 -3
- package/dist/commands/install.js +0 -231
- package/dist/commands/list.d.ts +0 -3
- package/dist/commands/list.js +0 -22
- package/dist/commands/pack.d.ts +0 -2
- package/dist/commands/pack.js +0 -210
- package/dist/commands/pull.d.ts +0 -2
- package/dist/commands/pull.js +0 -202
- package/dist/commands/push.d.ts +0 -2
- package/dist/commands/push.js +0 -374
- package/dist/commands/remix.d.ts +0 -2
- package/dist/commands/remix.js +0 -49
- package/dist/commands/sync.d.ts +0 -2
- package/dist/commands/sync.js +0 -98
- package/dist/commands/uninstall.d.ts +0 -3
- package/dist/commands/uninstall.js +0 -54
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.js +0 -59
- package/dist/platforms/base.d.ts +0 -82
- package/dist/platforms/base.js +0 -1
- package/dist/platforms/claude/__tests__/adapter.test.d.ts +0 -1
- package/dist/platforms/claude/__tests__/adapter.test.js +0 -257
- package/dist/platforms/claude/__tests__/security.test.d.ts +0 -1
- package/dist/platforms/claude/__tests__/security.test.js +0 -166
- package/dist/platforms/claude/adapter.d.ts +0 -34
- package/dist/platforms/claude/adapter.js +0 -525
- package/dist/platforms/claude/security.d.ts +0 -15
- package/dist/platforms/claude/security.js +0 -67
- package/dist/platforms/claude/templates.d.ts +0 -5
- package/dist/platforms/claude/templates.js +0 -22
- package/dist/platforms/openclaw/adapter.d.ts +0 -12
- package/dist/platforms/openclaw/adapter.js +0 -476
- package/dist/platforms/openclaw/templates.d.ts +0 -7
- package/dist/platforms/openclaw/templates.js +0 -369
- package/dist/platforms/registry.d.ts +0 -6
- package/dist/platforms/registry.js +0 -32
- package/dist/types/sync.d.ts +0 -74
- package/dist/types/sync.js +0 -7
- package/dist/utils/bundled-skills.d.ts +0 -6
- package/dist/utils/bundled-skills.js +0 -29
- package/dist/utils/secrets.d.ts +0 -32
- package/dist/utils/secrets.js +0 -118
- package/dist/utils/skill.d.ts +0 -6
- package/dist/utils/skill.js +0 -42
- package/dist/utils/sync.d.ts +0 -14
- package/dist/utils/sync.js +0 -242
package/dist/commands/push.js
DELETED
|
@@ -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
|
-
});
|
package/dist/commands/remix.d.ts
DELETED
package/dist/commands/remix.js
DELETED
|
@@ -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
|
-
});
|
package/dist/commands/sync.d.ts
DELETED
package/dist/commands/sync.js
DELETED
|
@@ -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,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
|
-
}
|
package/dist/commands/update.js
DELETED
|
@@ -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
|
-
}
|