@web42/cli 0.1.17 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @web42/cli
2
+
3
+ CLI for the Web42 Agent Marketplace - push, install, and remix OpenClaw agent packages.
4
+
5
+ ## Installation
6
+
7
+ To install the CLI globally, run:
8
+
9
+ ```
10
+ npm install -g @web42/cli
11
+ ```
12
+
13
+ ## Authentication
14
+
15
+ Authenticate with the marketplace by running:
16
+
17
+ ```
18
+ web42 login
19
+ ```
20
+
21
+ ## Supported Platforms
22
+
23
+ | Platform | Status |
24
+ |-----------|--------------|
25
+ | openclaw | Fully Supported |
26
+ | claude | Fully Supported |
27
+
28
+ ## CLI Commands Reference
29
+
30
+ ### General Commands
31
+
32
+ | Command | Description |
33
+ |--------------|-----------------------------------------|
34
+ | `web42 install <agent>` | Install an agent package from the marketplace |
35
+ | `web42 push` | Push your agent package to the marketplace |
36
+ | `web42 pull` | Pull the latest agent state from the marketplace |
37
+ | `web42 list` | List installed agents |
38
+ | `web42 update <agent>` | Update an installed agent to the latest version |
39
+ | `web42 uninstall <agent>` | Uninstall an agent |
40
+ | `web42 search <query>` | Search the marketplace for agents |
41
+ | `web42 remix <agent>` | Remix an agent package to your account |
42
+ | `web42 sync` | Check sync status between local workspace and the marketplace |
43
+
44
+ ### Claude-Specific Examples
45
+
46
+ - **Initialize a Project:**
47
+ ```
48
+ web42 init
49
+ ```
50
+
51
+ - **Pack an Agent:**
52
+ ```
53
+ web42 pack --agent <name>
54
+ ```
55
+
56
+ - **Push an Agent:**
57
+ ```
58
+ web42 push --agent <name>
59
+ ```
60
+
61
+ - **Install an Agent Globally:**
62
+ ```
63
+ web42 claude install @user/agent
64
+ ```
65
+
66
+ - **Install an Agent Locally:**
67
+ ```
68
+ web42 claude install -g @user/agent
69
+ ```
70
+
71
+ ## Versioning
72
+
73
+ Version `0.2.0` introduces Claude Code support.
@@ -35,12 +35,222 @@ function detectWorkspaceSkills(cwd) {
35
35
  }
36
36
  return skills.sort((a, b) => a.name.localeCompare(b.name));
37
37
  }
38
- export const initCommand = new Command("init")
39
- .description("Create a manifest.json for your agent package")
40
- .option("--with-skills [names...]", "Add bundled starter skills (omit names to install all)")
41
- .action(async (opts) => {
42
- const config = requireAuth();
43
- const cwd = process.cwd();
38
+ const WEB42_IGNORE_CONTENT = [
39
+ "# .web42ignore files excluded from web42 pack / push",
40
+ "# Syntax: glob patterns, one per line. Lines starting with # are comments.",
41
+ "# NOTE: .git, node_modules, .web42/, manifest.json, and other internals",
42
+ "# are always excluded automatically.",
43
+ "",
44
+ "# Working notes & drafts",
45
+ "TODO.md",
46
+ "NOTES.md",
47
+ "drafts/**",
48
+ "",
49
+ "# Environment & secrets",
50
+ ".env",
51
+ ".env.*",
52
+ "",
53
+ "# IDE / editor",
54
+ ".vscode/**",
55
+ ".idea/**",
56
+ ".cursor/**",
57
+ "",
58
+ "# Test & CI",
59
+ "tests/**",
60
+ "__tests__/**",
61
+ ".github/**",
62
+ "",
63
+ "# Build artifacts",
64
+ "dist/**",
65
+ "build/**",
66
+ "",
67
+ "# Large media not needed at runtime",
68
+ "# *.mp4",
69
+ "# *.mov",
70
+ "",
71
+ ].join("\n");
72
+ // ---------------------------------------------------------------------------
73
+ // Claude-specific init flow
74
+ // ---------------------------------------------------------------------------
75
+ async function initClaude(cwd, config, adapter) {
76
+ // Discover agents
77
+ if (!adapter.discoverAgents) {
78
+ console.log(chalk.red("Claude adapter missing discoverAgents method."));
79
+ process.exit(1);
80
+ }
81
+ const agents = adapter.discoverAgents(cwd);
82
+ if (agents.length === 0) {
83
+ console.log(chalk.red("No agents found.\n" +
84
+ "Create an agent in ~/.claude/agents/ or ./agents/ first."));
85
+ process.exit(1);
86
+ }
87
+ // Agent picker (multi-select)
88
+ let selectedAgents = agents;
89
+ if (agents.length > 1) {
90
+ const { chosen } = await inquirer.prompt([
91
+ {
92
+ type: "checkbox",
93
+ name: "chosen",
94
+ message: "Which agents do you want to init for the marketplace?",
95
+ choices: agents.map((a) => ({
96
+ name: `${a.name}${a.description ? ` — ${a.description}` : ""}`,
97
+ value: a.name,
98
+ checked: true,
99
+ })),
100
+ validate: (val) => val.length > 0 || "Select at least one agent",
101
+ },
102
+ ]);
103
+ selectedAgents = agents.filter((a) => chosen.includes(a.name));
104
+ }
105
+ else {
106
+ console.log();
107
+ console.log(chalk.dim(` Found agent: ${chalk.bold(agents[0].name)}${agents[0].description ? ` — ${agents[0].description}` : ""}`));
108
+ }
109
+ // Check for existing .web42/ agents that are already init'd
110
+ const web42Dir = join(cwd, ".web42");
111
+ const existingInits = new Set();
112
+ if (existsSync(web42Dir)) {
113
+ try {
114
+ const entries = readdirSync(web42Dir, { withFileTypes: true });
115
+ for (const entry of entries) {
116
+ if (entry.isDirectory() && existsSync(join(web42Dir, entry.name, "manifest.json"))) {
117
+ existingInits.add(entry.name);
118
+ }
119
+ }
120
+ }
121
+ catch {
122
+ // skip
123
+ }
124
+ }
125
+ const toInit = selectedAgents.filter((a) => !existingInits.has(a.name));
126
+ const alreadyInit = selectedAgents.filter((a) => existingInits.has(a.name));
127
+ if (alreadyInit.length > 0) {
128
+ console.log(chalk.dim(` Already initialized: ${alreadyInit.map((a) => a.name).join(", ")}`));
129
+ }
130
+ if (toInit.length === 0 && alreadyInit.length > 0) {
131
+ // Ask if they want to re-init
132
+ const { reinit } = await inquirer.prompt([
133
+ {
134
+ type: "confirm",
135
+ name: "reinit",
136
+ message: "All selected agents are already initialized. Re-initialize?",
137
+ default: false,
138
+ },
139
+ ]);
140
+ if (!reinit) {
141
+ console.log(chalk.yellow("Aborted."));
142
+ return;
143
+ }
144
+ toInit.push(...alreadyInit);
145
+ }
146
+ // For each agent: resolve skills, prompt, create .web42/{name}/ metadata
147
+ for (const agent of toInit.length > 0 ? toInit : selectedAgents) {
148
+ console.log();
149
+ console.log(chalk.bold(`Initializing ${agent.name}...`));
150
+ // Resolve skills
151
+ let resolvedSkills = [];
152
+ if (agent.skills.length > 0 && adapter.resolveSkills) {
153
+ const resolved = adapter.resolveSkills(agent.skills, cwd);
154
+ const found = resolved.filter((s) => s.found);
155
+ const missing = resolved.filter((s) => !s.found);
156
+ if (found.length > 0) {
157
+ // Read SKILL.md for descriptions
158
+ resolvedSkills = found.map((s) => {
159
+ const skillMd = join(s.sourcePath, "SKILL.md");
160
+ if (existsSync(skillMd)) {
161
+ const content = readFileSync(skillMd, "utf-8");
162
+ const parsed = parseSkillMd(content, s.name);
163
+ return { name: parsed.name, description: parsed.description };
164
+ }
165
+ return { name: s.name, description: `Skill: ${s.name}` };
166
+ });
167
+ console.log(chalk.dim(` Resolved ${found.length} skill(s): ${found.map((s) => s.name).join(", ")}`));
168
+ }
169
+ if (missing.length > 0) {
170
+ console.log(chalk.yellow(` Skills not found: ${missing.map((s) => s.name).join(", ")}`));
171
+ }
172
+ }
173
+ // Also detect workspace skills (in case agent references skills in cwd/skills/)
174
+ const workspaceSkills = detectWorkspaceSkills(cwd);
175
+ const existingNames = new Set(resolvedSkills.map((s) => s.name));
176
+ for (const ws of workspaceSkills) {
177
+ if (!existingNames.has(ws.name)) {
178
+ resolvedSkills.push(ws);
179
+ }
180
+ }
181
+ // Prompt for description and version
182
+ const defaults = {
183
+ description: agent.description ?? "",
184
+ version: "1.0.0",
185
+ };
186
+ // Check if manifest already exists for this agent
187
+ const agentWeb42Dir = join(web42Dir, agent.name);
188
+ const existingManifestPath = join(agentWeb42Dir, "manifest.json");
189
+ let existingManifest = null;
190
+ if (existsSync(existingManifestPath)) {
191
+ try {
192
+ existingManifest = JSON.parse(readFileSync(existingManifestPath, "utf-8"));
193
+ }
194
+ catch {
195
+ // ignore
196
+ }
197
+ }
198
+ const answers = await inquirer.prompt([
199
+ {
200
+ type: "input",
201
+ name: "description",
202
+ message: ` Description for ${agent.name}:`,
203
+ default: existingManifest?.description ?? defaults.description,
204
+ validate: (val) => (val.length > 0 && val.length <= 500) || "1-500 characters",
205
+ },
206
+ {
207
+ type: "input",
208
+ name: "version",
209
+ message: " Version:",
210
+ default: existingManifest?.version ?? defaults.version,
211
+ validate: (val) => /^\d+\.\d+\.\d+$/.test(val) || "Must follow semver (e.g. 1.0.0)",
212
+ },
213
+ ]);
214
+ // Create per-agent .web42/{name}/ directory
215
+ mkdirSync(agentWeb42Dir, { recursive: true });
216
+ // Write manifest
217
+ const manifest = {
218
+ format: "agentpkg/1",
219
+ platform: "claude",
220
+ name: agent.name,
221
+ description: answers.description,
222
+ version: answers.version,
223
+ author: config.username ?? "",
224
+ skills: resolvedSkills,
225
+ plugins: [],
226
+ modelPreferences: agent.model
227
+ ? { primary: agent.model }
228
+ : undefined,
229
+ configVariables: [],
230
+ };
231
+ writeFileSync(join(agentWeb42Dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
232
+ console.log(chalk.green(` Created .web42/${agent.name}/manifest.json`));
233
+ // Write marketplace.json
234
+ const marketplacePath = join(agentWeb42Dir, "marketplace.json");
235
+ if (!existsSync(marketplacePath)) {
236
+ writeFileSync(marketplacePath, JSON.stringify(DEFAULT_MARKETPLACE, null, 2) + "\n");
237
+ console.log(chalk.green(` Created .web42/${agent.name}/marketplace.json`));
238
+ }
239
+ // Write .web42ignore
240
+ const ignorePath = join(agentWeb42Dir, ".web42ignore");
241
+ if (!existsSync(ignorePath)) {
242
+ writeFileSync(ignorePath, WEB42_IGNORE_CONTENT, "utf-8");
243
+ console.log(chalk.green(` Created .web42/${agent.name}/.web42ignore`));
244
+ }
245
+ }
246
+ console.log();
247
+ console.log(chalk.dim("Edit .web42/{agent}/marketplace.json to set price, tags, license, and visibility."));
248
+ console.log(chalk.dim("Run `web42 pack` to bundle your agents, or `web42 push` to pack and publish."));
249
+ }
250
+ // ---------------------------------------------------------------------------
251
+ // OpenClaw init flow (existing behavior)
252
+ // ---------------------------------------------------------------------------
253
+ async function initOpenclaw(cwd, config, adapter, platform, opts) {
44
254
  const manifestPath = join(cwd, "manifest.json");
45
255
  let existingManifest = null;
46
256
  if (existsSync(manifestPath)) {
@@ -60,20 +270,9 @@ export const initCommand = new Command("init")
60
270
  existingManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
61
271
  }
62
272
  catch {
63
- // ignore parse errors from existing manifest
273
+ // ignore
64
274
  }
65
275
  }
66
- const platforms = listPlatforms();
67
- const { platform } = await inquirer.prompt([
68
- {
69
- type: "list",
70
- name: "platform",
71
- message: "Platform:",
72
- choices: platforms,
73
- default: platforms[0],
74
- },
75
- ]);
76
- const adapter = resolvePlatform(platform);
77
276
  const initConfig = adapter.extractInitConfig(cwd);
78
277
  if (!initConfig) {
79
278
  console.log(chalk.red(`No agent entry found in ${adapter.name} config for this directory.\n` +
@@ -112,7 +311,7 @@ export const initCommand = new Command("init")
112
311
  name: initConfig.name,
113
312
  description: answers.description,
114
313
  version: answers.version,
115
- author: config.username,
314
+ author: config.username ?? "",
116
315
  skills: detectedSkills,
117
316
  plugins: [],
118
317
  modelPreferences: initConfig.model
@@ -171,40 +370,7 @@ export const initCommand = new Command("init")
171
370
  mkdirSync(join(web42Dir, "resources"), { recursive: true });
172
371
  const ignorePath = join(cwd, ".web42ignore");
173
372
  if (!existsSync(ignorePath)) {
174
- writeFileSync(ignorePath, [
175
- "# .web42ignore — files excluded from web42 pack / push",
176
- "# Syntax: glob patterns, one per line. Lines starting with # are comments.",
177
- "# NOTE: .git, node_modules, .web42/, manifest.json, and other internals",
178
- "# are always excluded automatically.",
179
- "",
180
- "# Working notes & drafts",
181
- "TODO.md",
182
- "NOTES.md",
183
- "drafts/**",
184
- "",
185
- "# Environment & secrets",
186
- ".env",
187
- ".env.*",
188
- "",
189
- "# IDE / editor",
190
- ".vscode/**",
191
- ".idea/**",
192
- ".cursor/**",
193
- "",
194
- "# Test & CI",
195
- "tests/**",
196
- "__tests__/**",
197
- ".github/**",
198
- "",
199
- "# Build artifacts",
200
- "dist/**",
201
- "build/**",
202
- "",
203
- "# Large media not needed at runtime",
204
- "# *.mp4",
205
- "# *.mov",
206
- "",
207
- ].join("\n"), "utf-8");
373
+ writeFileSync(ignorePath, WEB42_IGNORE_CONTENT, "utf-8");
208
374
  console.log(chalk.green(` Created ${chalk.bold(".web42ignore")}`));
209
375
  }
210
376
  else {
@@ -255,4 +421,31 @@ export const initCommand = new Command("init")
255
421
  console.log();
256
422
  console.log(chalk.dim("Edit .web42/marketplace.json to set price, tags, license, and visibility."));
257
423
  console.log(chalk.dim("Run `web42 pack` to bundle your agent, or `web42 push` to pack and publish."));
424
+ }
425
+ // ---------------------------------------------------------------------------
426
+ // Command
427
+ // ---------------------------------------------------------------------------
428
+ export const initCommand = new Command("init")
429
+ .description("Create a manifest.json for your agent package")
430
+ .option("--with-skills [names...]", "Add bundled starter skills (omit names to install all)")
431
+ .action(async (opts) => {
432
+ const config = requireAuth();
433
+ const cwd = process.cwd();
434
+ const platforms = listPlatforms();
435
+ const { platform } = await inquirer.prompt([
436
+ {
437
+ type: "list",
438
+ name: "platform",
439
+ message: "Platform:",
440
+ choices: platforms,
441
+ default: platforms[0],
442
+ },
443
+ ]);
444
+ const adapter = resolvePlatform(platform);
445
+ if (platform === "claude") {
446
+ await initClaude(cwd, config, adapter);
447
+ }
448
+ else {
449
+ await initOpenclaw(cwd, config, adapter, platform, opts);
450
+ }
258
451
  });
@@ -44,6 +44,7 @@ export function makeInstallCommand(adapter) {
44
44
  .argument("<agent>", "Agent to install (e.g. @user/agent-name)")
45
45
  .option("--as <name>", "Install under a different local agent name")
46
46
  .option("--no-prompt", "Skip config variable prompts, use defaults")
47
+ .option("-g, --global", "Install globally to ~/.claude/ instead of project-local .claude/")
47
48
  .action(async (agentRef, opts) => {
48
49
  const match = agentRef.match(/^@?([^/]+)\/(.+)$/);
49
50
  if (!match) {
@@ -153,7 +154,11 @@ export function makeInstallCommand(adapter) {
153
154
  }
154
155
  }
155
156
  const localName = opts.as ?? agentSlug;
156
- const workspacePath = join(adapter.home, `workspace-${localName}`);
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");
157
162
  const installResult = await adapter.install({
158
163
  agentSlug: localName,
159
164
  username,
@@ -161,6 +166,7 @@ export function makeInstallCommand(adapter) {
161
166
  files: result.files,
162
167
  configTemplate,
163
168
  configAnswers,
169
+ version: result.agent.manifest.version,
164
170
  });
165
171
  const web42Config = {
166
172
  source: `@${username}/${agentSlug}`,
@@ -189,7 +195,8 @@ export function makeInstallCommand(adapter) {
189
195
  console.log(chalk.green(`Installed ${chalk.bold(`@${username}/${agentSlug}`)} as agent "${localName}"`));
190
196
  console.log(chalk.dim(` Workspace: ${workspacePath}`));
191
197
  if (manifest.skills && manifest.skills.length > 0) {
192
- console.log(chalk.dim(` Skills: ${manifest.skills.join(", ")}`));
198
+ const skillNames = manifest.skills.map((s) => typeof s === "string" ? s : s.name);
199
+ console.log(chalk.dim(` Skills: ${skillNames.join(", ")}`));
193
200
  }
194
201
  console.log(chalk.dim(` ${installResult.filesWritten} files written`));
195
202
  const pendingVars = (manifest.configVariables ?? []).filter((v) => v.required && !configAnswers[v.key]);
@@ -201,9 +208,19 @@ export function makeInstallCommand(adapter) {
201
208
  }
202
209
  }
203
210
  console.log();
204
- console.log(chalk.dim(" Next steps:"));
205
- console.log(chalk.dim(` 1. Set up channel bindings: ${adapter.name} config`));
206
- console.log(chalk.dim(` 2. Restart the gateway: ${adapter.name} gateway restart`));
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
+ }
207
224
  }
208
225
  catch (error) {
209
226
  spinner.fail("Install failed");