@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/install.js
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
|
-
import ora from "ora";
|
|
7
|
-
import { apiGet, apiPost } from "../utils/api.js";
|
|
8
|
-
function deriveProviderEnvKey(model) {
|
|
9
|
-
const slash = model.indexOf("/");
|
|
10
|
-
if (slash < 1)
|
|
11
|
-
return null;
|
|
12
|
-
const provider = model.slice(0, slash);
|
|
13
|
-
const envKey = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
|
|
14
|
-
return { provider, envKey };
|
|
15
|
-
}
|
|
16
|
-
function isKeyConfigured(envKey, platformHome) {
|
|
17
|
-
if (process.env[envKey])
|
|
18
|
-
return true;
|
|
19
|
-
const dotenvPath = join(platformHome, ".env");
|
|
20
|
-
if (!existsSync(dotenvPath))
|
|
21
|
-
return false;
|
|
22
|
-
try {
|
|
23
|
-
const content = readFileSync(dotenvPath, "utf-8");
|
|
24
|
-
return content.split("\n").some((line) => {
|
|
25
|
-
const trimmed = line.trim();
|
|
26
|
-
if (trimmed.startsWith("#"))
|
|
27
|
-
return false;
|
|
28
|
-
const eqIdx = trimmed.indexOf("=");
|
|
29
|
-
return eqIdx > 0 && trimmed.slice(0, eqIdx).trim() === envKey;
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function appendToEnv(envKey, value, platformHome) {
|
|
37
|
-
const dotenvPath = join(platformHome, ".env");
|
|
38
|
-
const line = `${envKey}=${value}\n`;
|
|
39
|
-
appendFileSync(dotenvPath, line, "utf-8");
|
|
40
|
-
}
|
|
41
|
-
export function makeInstallCommand(adapter) {
|
|
42
|
-
return new Command("install")
|
|
43
|
-
.description("Install an agent package from the marketplace")
|
|
44
|
-
.argument("<agent>", "Agent to install (e.g. @user/agent-name)")
|
|
45
|
-
.option("--as <name>", "Install under a different local agent name")
|
|
46
|
-
.option("--no-prompt", "Skip config variable prompts, use defaults")
|
|
47
|
-
.option("-g, --global", "Install globally to ~/.claude/ instead of project-local .claude/")
|
|
48
|
-
.action(async (agentRef, opts) => {
|
|
49
|
-
const match = agentRef.match(/^@?([^/]+)\/(.+)$/);
|
|
50
|
-
if (!match) {
|
|
51
|
-
console.log(chalk.red("Invalid agent reference. Use @user/agent-name format."));
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
const [, username, agentSlug] = match;
|
|
55
|
-
const spinner = ora(`Fetching @${username}/${agentSlug}...`).start();
|
|
56
|
-
try {
|
|
57
|
-
const agents = await apiGet(`/api/agents?username=${username}`);
|
|
58
|
-
const agent = agents.find((a) => a.slug === agentSlug);
|
|
59
|
-
if (!agent) {
|
|
60
|
-
spinner.fail(`Agent @${username}/${agentSlug} not found`);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
let result;
|
|
64
|
-
try {
|
|
65
|
-
result = await apiPost(`/api/agents/${agent.id}/install`, {});
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
if (err.message?.includes("Access required") &&
|
|
69
|
-
agent.price_cents > 0) {
|
|
70
|
-
const siteUrl = process.env.WEB42_API_URL ?? "https://web42.ai";
|
|
71
|
-
spinner.fail(`This is a paid agent ($${(agent.price_cents / 100).toFixed(2)}). Purchase it on the web first:`);
|
|
72
|
-
console.log(chalk.cyan(` ${siteUrl}/${username}/${agentSlug}`));
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
77
|
-
const manifest = result.agent.manifest;
|
|
78
|
-
let configAnswers = {};
|
|
79
|
-
if (manifest.configVariables && manifest.configVariables.length > 0) {
|
|
80
|
-
if (opts.prompt === false) {
|
|
81
|
-
for (const v of manifest.configVariables) {
|
|
82
|
-
configAnswers[v.key] = v.default ?? "";
|
|
83
|
-
}
|
|
84
|
-
spinner.text = "Installing agent (skipping prompts)...";
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
spinner.stop();
|
|
88
|
-
console.log();
|
|
89
|
-
console.log(chalk.bold("Configure your agent:"));
|
|
90
|
-
console.log();
|
|
91
|
-
configAnswers = await inquirer.prompt(manifest.configVariables.map((v) => ({
|
|
92
|
-
type: "input",
|
|
93
|
-
name: v.key,
|
|
94
|
-
message: `${v.label}${v.description ? ` (${v.description})` : ""}:`,
|
|
95
|
-
default: v.default,
|
|
96
|
-
validate: (val) => !v.required || val.length > 0 || `${v.label} is required`,
|
|
97
|
-
})));
|
|
98
|
-
spinner.start("Installing agent...");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
spinner.text = "Installing agent...";
|
|
103
|
-
}
|
|
104
|
-
const primaryModel = manifest.modelPreferences?.primary;
|
|
105
|
-
if (primaryModel) {
|
|
106
|
-
const providerInfo = deriveProviderEnvKey(primaryModel);
|
|
107
|
-
if (providerInfo) {
|
|
108
|
-
if (isKeyConfigured(providerInfo.envKey, adapter.home)) {
|
|
109
|
-
if (spinner.isSpinning)
|
|
110
|
-
spinner.stop();
|
|
111
|
-
console.log(chalk.dim(` ${providerInfo.envKey} already configured for ${primaryModel}`));
|
|
112
|
-
if (!spinner.isSpinning)
|
|
113
|
-
spinner.start("Installing agent...");
|
|
114
|
-
}
|
|
115
|
-
else if (opts.prompt === false) {
|
|
116
|
-
if (spinner.isSpinning)
|
|
117
|
-
spinner.stop();
|
|
118
|
-
console.log();
|
|
119
|
-
console.log(chalk.yellow(` This agent uses ${chalk.bold(primaryModel)}. You'll need to set ${chalk.bold(providerInfo.envKey)} in ${adapter.home}/.env or as an environment variable.`));
|
|
120
|
-
spinner.start("Installing agent...");
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
if (spinner.isSpinning)
|
|
124
|
-
spinner.stop();
|
|
125
|
-
console.log();
|
|
126
|
-
console.log(chalk.bold(`This agent uses ${chalk.cyan(primaryModel)}.`));
|
|
127
|
-
const { apiKey } = await inquirer.prompt([
|
|
128
|
-
{
|
|
129
|
-
type: "password",
|
|
130
|
-
name: "apiKey",
|
|
131
|
-
message: `Enter your ${providerInfo.envKey} (leave empty to skip):`,
|
|
132
|
-
mask: "*",
|
|
133
|
-
},
|
|
134
|
-
]);
|
|
135
|
-
if (apiKey) {
|
|
136
|
-
appendToEnv(providerInfo.envKey, apiKey, adapter.home);
|
|
137
|
-
console.log(chalk.green(` Saved ${providerInfo.envKey} to ${adapter.home}/.env`));
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
console.log(chalk.yellow(` Skipped. Set ${chalk.bold(providerInfo.envKey)} in ${adapter.home}/.env later.`));
|
|
141
|
-
}
|
|
142
|
-
spinner.start("Installing agent...");
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
let configTemplate = null;
|
|
147
|
-
const configFile = result.files.find((f) => f.path === ".openclaw/config.json" && f.content);
|
|
148
|
-
if (configFile?.content) {
|
|
149
|
-
try {
|
|
150
|
-
configTemplate = JSON.parse(configFile.content);
|
|
151
|
-
}
|
|
152
|
-
catch {
|
|
153
|
-
// No config template available
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
const localName = opts.as ?? agentSlug;
|
|
157
|
-
const workspacePath = adapter.resolveInstallPath
|
|
158
|
-
? adapter.resolveInstallPath(localName, opts.global)
|
|
159
|
-
: opts.global
|
|
160
|
-
? join(adapter.home, `workspace-${localName}`)
|
|
161
|
-
: join(process.cwd(), ".claude");
|
|
162
|
-
const installResult = await adapter.install({
|
|
163
|
-
agentSlug: localName,
|
|
164
|
-
username,
|
|
165
|
-
workspacePath,
|
|
166
|
-
files: result.files,
|
|
167
|
-
configTemplate,
|
|
168
|
-
configAnswers,
|
|
169
|
-
version: result.agent.manifest.version,
|
|
170
|
-
});
|
|
171
|
-
const web42Config = {
|
|
172
|
-
source: `@${username}/${agentSlug}`,
|
|
173
|
-
...configAnswers,
|
|
174
|
-
};
|
|
175
|
-
const configPath = join(workspacePath, ".web42.config.json");
|
|
176
|
-
writeFileSync(configPath, JSON.stringify(web42Config, null, 2) + "\n");
|
|
177
|
-
spinner.stop();
|
|
178
|
-
const profileImageUrl = result.agent.profile_image_url;
|
|
179
|
-
if (profileImageUrl) {
|
|
180
|
-
try {
|
|
181
|
-
const avatarsDir = join(workspacePath, "avatars");
|
|
182
|
-
mkdirSync(avatarsDir, { recursive: true });
|
|
183
|
-
const avatarPath = join(avatarsDir, "avatar.png");
|
|
184
|
-
const avatarResponse = await fetch(profileImageUrl);
|
|
185
|
-
if (avatarResponse.ok) {
|
|
186
|
-
const buffer = Buffer.from(await avatarResponse.arrayBuffer());
|
|
187
|
-
writeFileSync(avatarPath, buffer);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// Non-fatal — avatar download failure shouldn't block install
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
console.log();
|
|
195
|
-
console.log(chalk.green(`Installed ${chalk.bold(`@${username}/${agentSlug}`)} as agent "${localName}"`));
|
|
196
|
-
console.log(chalk.dim(` Workspace: ${workspacePath}`));
|
|
197
|
-
if (manifest.skills && manifest.skills.length > 0) {
|
|
198
|
-
const skillNames = manifest.skills.map((s) => typeof s === "string" ? s : s.name);
|
|
199
|
-
console.log(chalk.dim(` Skills: ${skillNames.join(", ")}`));
|
|
200
|
-
}
|
|
201
|
-
console.log(chalk.dim(` ${installResult.filesWritten} files written`));
|
|
202
|
-
const pendingVars = (manifest.configVariables ?? []).filter((v) => v.required && !configAnswers[v.key]);
|
|
203
|
-
if (pendingVars.length > 0) {
|
|
204
|
-
console.log();
|
|
205
|
-
console.log(chalk.yellow(` ${pendingVars.length} config variable(s) still need setup:`));
|
|
206
|
-
for (const v of pendingVars) {
|
|
207
|
-
console.log(chalk.yellow(` - ${v.label} (${v.key})`));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
console.log();
|
|
211
|
-
if (adapter.name === "claude") {
|
|
212
|
-
if (opts.global) {
|
|
213
|
-
console.log(chalk.dim(" Next: Open Claude Code — the agent is available globally."));
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
console.log(chalk.dim(" Next: Open Claude Code in this project — the agent is available locally."));
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
console.log(chalk.dim(" Next steps:"));
|
|
221
|
-
console.log(chalk.dim(` 1. Set up channel bindings: ${adapter.name} config`));
|
|
222
|
-
console.log(chalk.dim(` 2. Restart the gateway: ${adapter.name} gateway restart`));
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
spinner.fail("Install failed");
|
|
227
|
-
console.error(chalk.red(error.message));
|
|
228
|
-
process.exit(1);
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
}
|
package/dist/commands/list.d.ts
DELETED
package/dist/commands/list.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
export function makeListCommand(adapter) {
|
|
4
|
-
return new Command("list")
|
|
5
|
-
.description("List installed agents")
|
|
6
|
-
.action(async () => {
|
|
7
|
-
const agents = await adapter.listInstalled();
|
|
8
|
-
if (agents.length === 0) {
|
|
9
|
-
console.log(chalk.yellow("No agents installed."));
|
|
10
|
-
console.log(chalk.dim(`Run \`web42 ${adapter.name} install @user/agent\` to install one.`));
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
console.log(chalk.bold(`Installed ${adapter.name} agents:`));
|
|
14
|
-
console.log();
|
|
15
|
-
for (const agent of agents) {
|
|
16
|
-
console.log(` ${chalk.cyan(agent.source ?? agent.name)}`);
|
|
17
|
-
if (agent.workspace) {
|
|
18
|
-
console.log(chalk.dim(` ${agent.workspace}`));
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
}
|
package/dist/commands/pack.d.ts
DELETED
package/dist/commands/pack.js
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import ora from "ora";
|
|
6
|
-
import { resolvePlatform } from "../platforms/registry.js";
|
|
7
|
-
import { parseSkillMd } from "../utils/skill.js";
|
|
8
|
-
/**
|
|
9
|
-
* Pack a single agent: run adapter.pack(), detect skills, write to dist.
|
|
10
|
-
*/
|
|
11
|
-
async function packSingleAgent(opts) {
|
|
12
|
-
const { cwd, adapter, agentName, dryRun } = opts;
|
|
13
|
-
// Clone manifest so per-agent enrichment doesn't leak
|
|
14
|
-
const manifest = JSON.parse(JSON.stringify(opts.manifest));
|
|
15
|
-
const result = await adapter.pack({
|
|
16
|
-
cwd,
|
|
17
|
-
outputDir: opts.outputDir,
|
|
18
|
-
agentName,
|
|
19
|
-
});
|
|
20
|
-
// Detect skills from packed files and strip internal ones
|
|
21
|
-
const internalSkillPrefixes = [];
|
|
22
|
-
const detectedSkills = [];
|
|
23
|
-
for (const f of result.files) {
|
|
24
|
-
const match = f.path.match(/^skills\/([^/]+)\/SKILL\.md$/);
|
|
25
|
-
if (match) {
|
|
26
|
-
const parsed = parseSkillMd(f.content, match[1]);
|
|
27
|
-
if (parsed.internal) {
|
|
28
|
-
internalSkillPrefixes.push(`skills/${match[1]}/`);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
detectedSkills.push({ name: parsed.name, description: parsed.description });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
if (internalSkillPrefixes.length > 0) {
|
|
36
|
-
result.files = result.files.filter((f) => !internalSkillPrefixes.some((prefix) => f.path.startsWith(prefix)));
|
|
37
|
-
}
|
|
38
|
-
if (detectedSkills.length > 0) {
|
|
39
|
-
manifest.skills = detectedSkills.sort((a, b) => a.name.localeCompare(b.name));
|
|
40
|
-
}
|
|
41
|
-
// Merge auto-generated config variables into manifest
|
|
42
|
-
const existingKeys = new Set((manifest.configVariables ?? []).map((v) => v.key));
|
|
43
|
-
for (const cv of result.configVariables) {
|
|
44
|
-
if (!existingKeys.has(cv.key)) {
|
|
45
|
-
if (!manifest.configVariables)
|
|
46
|
-
manifest.configVariables = [];
|
|
47
|
-
manifest.configVariables.push(cv);
|
|
48
|
-
existingKeys.add(cv.key);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (dryRun) {
|
|
52
|
-
const label = agentName ? `Dry run for ${chalk.bold(agentName)}` : "Dry run";
|
|
53
|
-
console.log(chalk.bold(`${label} — would pack:`));
|
|
54
|
-
console.log();
|
|
55
|
-
for (const f of result.files) {
|
|
56
|
-
console.log(chalk.dim(` ${f.path} (${f.content.length} bytes)`));
|
|
57
|
-
}
|
|
58
|
-
console.log();
|
|
59
|
-
console.log(chalk.dim(`${result.files.length} files, ${result.configVariables.length} config variable(s)`));
|
|
60
|
-
return { manifest, result };
|
|
61
|
-
}
|
|
62
|
-
const outputDir = join(cwd, opts.outputDir);
|
|
63
|
-
mkdirSync(outputDir, { recursive: true });
|
|
64
|
-
for (const file of result.files) {
|
|
65
|
-
const filePath = join(outputDir, file.path);
|
|
66
|
-
mkdirSync(join(filePath, ".."), { recursive: true });
|
|
67
|
-
writeFileSync(filePath, file.content, "utf-8");
|
|
68
|
-
}
|
|
69
|
-
writeFileSync(join(outputDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
70
|
-
return { manifest, result };
|
|
71
|
-
}
|
|
72
|
-
export const packCommand = new Command("pack")
|
|
73
|
-
.description("Pack your agent workspace into a distributable artifact")
|
|
74
|
-
.option("-o, --output <dir>", "Output directory", ".web42/dist")
|
|
75
|
-
.option("--dry-run", "Preview what would be packed without writing files")
|
|
76
|
-
.option("--agent <name>", "Pack a specific agent (for multi-agent workspaces)")
|
|
77
|
-
.action(async (opts) => {
|
|
78
|
-
const cwd = process.cwd();
|
|
79
|
-
// Determine platform: check for per-agent manifests first, then root manifest
|
|
80
|
-
let platform = "openclaw"; // default
|
|
81
|
-
let isMultiAgent = false;
|
|
82
|
-
const agentManifests = new Map();
|
|
83
|
-
// Check if this is a multi-agent workspace (per-agent manifests in .web42/{name}/)
|
|
84
|
-
const web42Dir = join(cwd, ".web42");
|
|
85
|
-
if (existsSync(web42Dir)) {
|
|
86
|
-
const { readdirSync } = await import("fs");
|
|
87
|
-
try {
|
|
88
|
-
const entries = readdirSync(web42Dir, { withFileTypes: true });
|
|
89
|
-
for (const entry of entries) {
|
|
90
|
-
if (!entry.isDirectory())
|
|
91
|
-
continue;
|
|
92
|
-
const agentManifestPath = join(web42Dir, entry.name, "manifest.json");
|
|
93
|
-
if (existsSync(agentManifestPath)) {
|
|
94
|
-
try {
|
|
95
|
-
const m = JSON.parse(readFileSync(agentManifestPath, "utf-8"));
|
|
96
|
-
agentManifests.set(entry.name, m);
|
|
97
|
-
if (m.platform)
|
|
98
|
-
platform = m.platform;
|
|
99
|
-
}
|
|
100
|
-
catch {
|
|
101
|
-
// skip invalid manifests
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// .web42 not readable
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
isMultiAgent = agentManifests.size > 0;
|
|
111
|
-
// Fall back to root manifest.json (single-agent, e.g., OpenClaw)
|
|
112
|
-
if (!isMultiAgent) {
|
|
113
|
-
const manifestPath = join(cwd, "manifest.json");
|
|
114
|
-
if (!existsSync(manifestPath)) {
|
|
115
|
-
console.log(chalk.red("No manifest.json found. Run `web42 init` first."));
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
119
|
-
if (!manifest.name || !manifest.version || !manifest.author) {
|
|
120
|
-
console.log(chalk.red("Invalid manifest.json. Must have name, version, and author."));
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
if (manifest.platform)
|
|
124
|
-
platform = manifest.platform;
|
|
125
|
-
const adapter = resolvePlatform(platform);
|
|
126
|
-
const spinner = ora("Packing agent...").start();
|
|
127
|
-
try {
|
|
128
|
-
const { manifest: enriched, result } = await packSingleAgent({
|
|
129
|
-
cwd,
|
|
130
|
-
manifest,
|
|
131
|
-
manifestPath,
|
|
132
|
-
adapter,
|
|
133
|
-
outputDir: opts.output,
|
|
134
|
-
dryRun: opts.dryRun,
|
|
135
|
-
});
|
|
136
|
-
if (opts.dryRun) {
|
|
137
|
-
spinner.stop();
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
spinner.succeed(`Packed ${chalk.bold(enriched.name)} (${result.files.length} files) → ${opts.output}/`);
|
|
141
|
-
if (result.configVariables.length > 0) {
|
|
142
|
-
console.log(chalk.dim(` ${result.configVariables.length} config variable(s) detected`));
|
|
143
|
-
}
|
|
144
|
-
console.log();
|
|
145
|
-
console.log(chalk.dim("Run `web42 push` to publish to the marketplace."));
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
spinner.fail("Pack failed");
|
|
149
|
-
console.error(chalk.red(error.message));
|
|
150
|
-
process.exit(1);
|
|
151
|
-
}
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
// Multi-agent mode
|
|
155
|
-
const adapter = resolvePlatform(platform);
|
|
156
|
-
// Determine which agents to pack
|
|
157
|
-
let agentsToPack;
|
|
158
|
-
if (opts.agent) {
|
|
159
|
-
const manifest = agentManifests.get(opts.agent);
|
|
160
|
-
if (!manifest) {
|
|
161
|
-
console.log(chalk.red(`Agent "${opts.agent}" not found. Available: ${[...agentManifests.keys()].join(", ")}`));
|
|
162
|
-
process.exit(1);
|
|
163
|
-
}
|
|
164
|
-
agentsToPack = [[opts.agent, manifest]];
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
agentsToPack = [...agentManifests.entries()];
|
|
168
|
-
}
|
|
169
|
-
const spinner = ora(`Packing ${agentsToPack.length} agent(s)...`).start();
|
|
170
|
-
try {
|
|
171
|
-
for (const [agentName, manifest] of agentsToPack) {
|
|
172
|
-
const agentOutputDir = join(".web42", agentName, "dist");
|
|
173
|
-
spinner.text = `Packing ${agentName}...`;
|
|
174
|
-
if (opts.dryRun) {
|
|
175
|
-
spinner.stop();
|
|
176
|
-
}
|
|
177
|
-
const { manifest: enriched, result } = await packSingleAgent({
|
|
178
|
-
cwd,
|
|
179
|
-
manifest,
|
|
180
|
-
manifestPath: join(web42Dir, agentName, "manifest.json"),
|
|
181
|
-
adapter,
|
|
182
|
-
agentName,
|
|
183
|
-
outputDir: agentOutputDir,
|
|
184
|
-
dryRun: opts.dryRun,
|
|
185
|
-
});
|
|
186
|
-
if (!opts.dryRun) {
|
|
187
|
-
// Also write the enriched manifest back
|
|
188
|
-
writeFileSync(join(web42Dir, agentName, "manifest.json"), JSON.stringify(enriched, null, 2) + "\n");
|
|
189
|
-
console.log(chalk.green(` Packed ${chalk.bold(agentName)} (${result.files.length} files) → ${agentOutputDir}/`));
|
|
190
|
-
}
|
|
191
|
-
if (opts.dryRun) {
|
|
192
|
-
console.log(); // spacing between agents
|
|
193
|
-
spinner.start();
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (opts.dryRun) {
|
|
197
|
-
spinner.stop();
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
spinner.succeed(`Packed ${agentsToPack.length} agent(s)`);
|
|
201
|
-
console.log();
|
|
202
|
-
console.log(chalk.dim("Run `web42 push` to publish to the marketplace."));
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
spinner.fail("Pack failed");
|
|
207
|
-
console.error(chalk.red(error.message));
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
});
|
package/dist/commands/pull.d.ts
DELETED
package/dist/commands/pull.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
2
|
-
import { dirname, 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, writeSyncState, writeMarketplace, writeResourcesMeta, } from "../utils/sync.js";
|
|
9
|
-
/**
|
|
10
|
-
* Pull a single agent from the marketplace into a local directory.
|
|
11
|
-
*/
|
|
12
|
-
async function pullSingleAgent(opts) {
|
|
13
|
-
const { manifest, manifestPath, syncDir, writeDir, distDir, config, spinner } = opts;
|
|
14
|
-
const name = manifest.name ?? "";
|
|
15
|
-
// Step 1: Resolve agent ID
|
|
16
|
-
let syncState = readSyncState(syncDir);
|
|
17
|
-
let agentId = syncState?.agent_id ?? null;
|
|
18
|
-
if (!agentId) {
|
|
19
|
-
spinner.text = `Looking up ${name}...`;
|
|
20
|
-
const agents = await apiGet(`/api/agents?username=${config.username}`);
|
|
21
|
-
const agent = agents.find((a) => a.slug === name);
|
|
22
|
-
if (!agent) {
|
|
23
|
-
spinner.fail(`Agent @${config.username}/${name} not found on the marketplace. Run \`web42 push\` first.`);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
agentId = agent.id;
|
|
27
|
-
}
|
|
28
|
-
// Step 2: Compare remote hash with last known remote hash (unless --force)
|
|
29
|
-
if (!opts.force && syncState?.last_remote_hash) {
|
|
30
|
-
spinner.text = `Checking remote state for ${name}...`;
|
|
31
|
-
const remote = await apiGet(`/api/agents/${agentId}/sync`);
|
|
32
|
-
if (remote.hash === syncState.last_remote_hash) {
|
|
33
|
-
console.log(chalk.dim(` ${chalk.bold(`@${config.username}/${name}`)} is already in sync.`));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
// Step 3: Pull full snapshot
|
|
38
|
-
spinner.text = `Downloading ${name}...`;
|
|
39
|
-
const pullResult = await apiGet(`/api/agents/${agentId}/sync/pull`);
|
|
40
|
-
const { snapshot } = pullResult;
|
|
41
|
-
let written = 0;
|
|
42
|
-
// Step 4a: Write manifest.json
|
|
43
|
-
const updatedManifest = {
|
|
44
|
-
...manifest,
|
|
45
|
-
...snapshot.manifest,
|
|
46
|
-
name: snapshot.identity.slug,
|
|
47
|
-
description: snapshot.identity.description,
|
|
48
|
-
};
|
|
49
|
-
writeFileSync(manifestPath, JSON.stringify(updatedManifest, null, 2) + "\n");
|
|
50
|
-
written++;
|
|
51
|
-
// Step 4b: Write README.md
|
|
52
|
-
if (snapshot.readme) {
|
|
53
|
-
writeFileSync(join(writeDir, "README.md"), snapshot.readme, "utf-8");
|
|
54
|
-
written++;
|
|
55
|
-
}
|
|
56
|
-
// Step 4c: Write marketplace.json
|
|
57
|
-
writeMarketplace(writeDir, snapshot.marketplace);
|
|
58
|
-
written++;
|
|
59
|
-
// Step 4d: Write agent files into dist/
|
|
60
|
-
let skipped = 0;
|
|
61
|
-
mkdirSync(distDir, { recursive: true });
|
|
62
|
-
for (const file of snapshot.files) {
|
|
63
|
-
if (file.content === null || file.content === undefined) {
|
|
64
|
-
skipped++;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (file.path === ".openclaw/config.json") {
|
|
68
|
-
skipped++;
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
const filePath = join(distDir, file.path);
|
|
72
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
73
|
-
writeFileSync(filePath, file.content, "utf-8");
|
|
74
|
-
written++;
|
|
75
|
-
}
|
|
76
|
-
// Step 4e: Write resources metadata
|
|
77
|
-
if (snapshot.resources.length > 0) {
|
|
78
|
-
const resourcesMeta = snapshot.resources.map((r, i) => ({
|
|
79
|
-
file: `resource-${i}-${r.title.replace(/[^a-zA-Z0-9.-]/g, "_")}`,
|
|
80
|
-
title: r.title,
|
|
81
|
-
description: r.description ?? undefined,
|
|
82
|
-
type: r.type,
|
|
83
|
-
sort_order: r.sort_order,
|
|
84
|
-
}));
|
|
85
|
-
writeResourcesMeta(writeDir, resourcesMeta);
|
|
86
|
-
written++;
|
|
87
|
-
}
|
|
88
|
-
// Step 5: Save sync state
|
|
89
|
-
const localSnapshot = buildLocalSnapshot(syncDir, distDir);
|
|
90
|
-
const localHash = computeHashFromSnapshot(localSnapshot);
|
|
91
|
-
writeSyncState(syncDir, {
|
|
92
|
-
agent_id: agentId,
|
|
93
|
-
last_remote_hash: pullResult.hash,
|
|
94
|
-
last_local_hash: localHash,
|
|
95
|
-
synced_at: new Date().toISOString(),
|
|
96
|
-
});
|
|
97
|
-
console.log(chalk.green(` Pulled @${config.username}/${name} (${written} files${skipped > 0 ? `, ${skipped} skipped` : ""})`));
|
|
98
|
-
console.log(chalk.dim(` Sync hash: ${pullResult.hash.slice(0, 12)}...`));
|
|
99
|
-
}
|
|
100
|
-
export const pullCommand = new Command("pull")
|
|
101
|
-
.description("Pull latest agent state from the Web42 marketplace into the current directory")
|
|
102
|
-
.option("--force", "Skip hash comparison and always pull")
|
|
103
|
-
.option("--agent <name>", "Pull a specific agent (for multi-agent workspaces)")
|
|
104
|
-
.action(async (opts) => {
|
|
105
|
-
const config = requireAuth();
|
|
106
|
-
const cwd = process.cwd();
|
|
107
|
-
// Detect multi-agent workspace (.web42/{name}/manifest.json)
|
|
108
|
-
const web42Dir = join(cwd, ".web42");
|
|
109
|
-
const agentManifests = new Map();
|
|
110
|
-
if (existsSync(web42Dir)) {
|
|
111
|
-
try {
|
|
112
|
-
const entries = readdirSync(web42Dir, { withFileTypes: true });
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
if (!entry.isDirectory())
|
|
115
|
-
continue;
|
|
116
|
-
const agentManifestPath = join(web42Dir, entry.name, "manifest.json");
|
|
117
|
-
if (existsSync(agentManifestPath)) {
|
|
118
|
-
try {
|
|
119
|
-
const m = JSON.parse(readFileSync(agentManifestPath, "utf-8"));
|
|
120
|
-
agentManifests.set(entry.name, m);
|
|
121
|
-
}
|
|
122
|
-
catch {
|
|
123
|
-
// skip
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
// .web42 not readable
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const isMultiAgent = agentManifests.size > 0;
|
|
133
|
-
// Single-agent mode
|
|
134
|
-
if (!isMultiAgent) {
|
|
135
|
-
const manifestPath = join(cwd, "manifest.json");
|
|
136
|
-
if (!existsSync(manifestPath)) {
|
|
137
|
-
console.log(chalk.red("No manifest.json found. Are you in an agent directory?"));
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
141
|
-
if (!manifest.name) {
|
|
142
|
-
console.log(chalk.red("manifest.json is missing a name field."));
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
const spinner = ora(`Pulling @${config.username}/${manifest.name}...`).start();
|
|
146
|
-
try {
|
|
147
|
-
await pullSingleAgent({
|
|
148
|
-
manifest,
|
|
149
|
-
manifestPath,
|
|
150
|
-
syncDir: cwd,
|
|
151
|
-
writeDir: cwd,
|
|
152
|
-
distDir: join(cwd, ".web42", "dist"),
|
|
153
|
-
config,
|
|
154
|
-
force: opts.force,
|
|
155
|
-
spinner,
|
|
156
|
-
});
|
|
157
|
-
spinner.succeed(`Pull complete`);
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
spinner.fail("Pull failed");
|
|
161
|
-
console.error(chalk.red(error.message));
|
|
162
|
-
process.exit(1);
|
|
163
|
-
}
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
// Multi-agent mode
|
|
167
|
-
let agentsToPull;
|
|
168
|
-
if (opts.agent) {
|
|
169
|
-
const manifest = agentManifests.get(opts.agent);
|
|
170
|
-
if (!manifest) {
|
|
171
|
-
console.log(chalk.red(`Agent "${opts.agent}" not found. Available: ${[...agentManifests.keys()].join(", ")}`));
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
agentsToPull = [[opts.agent, manifest]];
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
agentsToPull = [...agentManifests.entries()];
|
|
178
|
-
}
|
|
179
|
-
const spinner = ora(`Pulling ${agentsToPull.length} agent(s)...`).start();
|
|
180
|
-
try {
|
|
181
|
-
for (const [agentName, manifest] of agentsToPull) {
|
|
182
|
-
spinner.text = `Pulling ${agentName}...`;
|
|
183
|
-
const agentWeb42Dir = join(web42Dir, agentName);
|
|
184
|
-
await pullSingleAgent({
|
|
185
|
-
manifest,
|
|
186
|
-
manifestPath: join(agentWeb42Dir, "manifest.json"),
|
|
187
|
-
syncDir: agentWeb42Dir,
|
|
188
|
-
writeDir: agentWeb42Dir,
|
|
189
|
-
distDir: join(agentWeb42Dir, "dist"),
|
|
190
|
-
config,
|
|
191
|
-
force: opts.force,
|
|
192
|
-
spinner,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
spinner.succeed(`Pulled ${agentsToPull.length} agent(s)`);
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
spinner.fail("Pull failed");
|
|
199
|
-
console.error(chalk.red(error.message));
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
});
|
package/dist/commands/push.d.ts
DELETED