@web42/cli 0.1.16 → 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 +73 -0
- package/dist/commands/init.js +246 -53
- package/dist/commands/install.js +39 -6
- package/dist/commands/pack.js +179 -67
- package/dist/commands/pull.js +176 -108
- package/dist/commands/push.js +264 -124
- package/dist/commands/send.d.ts +2 -0
- package/dist/commands/send.js +124 -0
- package/dist/commands/serve.d.ts +2 -0
- package/dist/commands/serve.js +206 -0
- package/dist/index.js +4 -0
- package/dist/platforms/base.d.ts +18 -0
- package/dist/platforms/claude/__tests__/adapter.test.d.ts +1 -0
- package/dist/platforms/claude/__tests__/adapter.test.js +257 -0
- package/dist/platforms/claude/__tests__/security.test.d.ts +1 -0
- package/dist/platforms/claude/__tests__/security.test.js +166 -0
- package/dist/platforms/claude/adapter.d.ts +34 -0
- package/dist/platforms/claude/adapter.js +525 -0
- package/dist/platforms/claude/security.d.ts +15 -0
- package/dist/platforms/claude/security.js +67 -0
- package/dist/platforms/claude/templates.d.ts +5 -0
- package/dist/platforms/claude/templates.js +22 -0
- package/dist/platforms/registry.js +2 -0
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +10 -0
- package/dist/utils/sync.d.ts +1 -1
- package/dist/utils/sync.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -2
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.
|
package/dist/commands/init.js
CHANGED
|
@@ -35,12 +35,222 @@ function detectWorkspaceSkills(cwd) {
|
|
|
35
35
|
}
|
|
36
36
|
return skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
37
37
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
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
|
});
|
package/dist/commands/install.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { Command } from "commander";
|
|
4
4
|
import chalk from "chalk";
|
|
@@ -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 =
|
|
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}`,
|
|
@@ -169,11 +175,28 @@ export function makeInstallCommand(adapter) {
|
|
|
169
175
|
const configPath = join(workspacePath, ".web42.config.json");
|
|
170
176
|
writeFileSync(configPath, JSON.stringify(web42Config, null, 2) + "\n");
|
|
171
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
|
+
}
|
|
172
194
|
console.log();
|
|
173
195
|
console.log(chalk.green(`Installed ${chalk.bold(`@${username}/${agentSlug}`)} as agent "${localName}"`));
|
|
174
196
|
console.log(chalk.dim(` Workspace: ${workspacePath}`));
|
|
175
197
|
if (manifest.skills && manifest.skills.length > 0) {
|
|
176
|
-
|
|
198
|
+
const skillNames = manifest.skills.map((s) => typeof s === "string" ? s : s.name);
|
|
199
|
+
console.log(chalk.dim(` Skills: ${skillNames.join(", ")}`));
|
|
177
200
|
}
|
|
178
201
|
console.log(chalk.dim(` ${installResult.filesWritten} files written`));
|
|
179
202
|
const pendingVars = (manifest.configVariables ?? []).filter((v) => v.required && !configAnswers[v.key]);
|
|
@@ -185,9 +208,19 @@ export function makeInstallCommand(adapter) {
|
|
|
185
208
|
}
|
|
186
209
|
}
|
|
187
210
|
console.log();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
+
}
|
|
191
224
|
}
|
|
192
225
|
catch (error) {
|
|
193
226
|
spinner.fail("Install failed");
|