@web42/w42 0.1.4 → 0.1.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/README.md +75 -47
- package/dist/commands/send.js +107 -33
- package/dist/commands/serve.js +54 -117
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -6
- package/dist/generated/embedded-skills.d.ts +0 -9
- package/dist/generated/embedded-skills.js +0 -40
- package/skills/web42-publish-prep/SKILL.md +0 -181
- package/skills/web42-publish-prep/_meta.json +0 -17
- package/skills/web42-publish-prep/assets/readme-template.md +0 -61
- package/skills/web42-publish-prep/references/file-hygiene.md +0 -109
- package/skills/web42-publish-prep/references/manifest-fields.md +0 -142
- package/skills/web42-publish-prep/references/marketplace-config.md +0 -99
- package/skills/web42-publish-prep/references/resources-guide.md +0 -136
- package/skills/web42-publish-prep/references/web42-folder.md +0 -120
package/README.md
CHANGED
|
@@ -1,73 +1,101 @@
|
|
|
1
1
|
# @web42/cli
|
|
2
2
|
|
|
3
|
-
CLI for the Web42 Agent
|
|
3
|
+
CLI for the Web42 Agent Network — authenticate, publish, discover, and interact with A2A agents.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```
|
|
7
|
+
```bash
|
|
10
8
|
npm install -g @web42/cli
|
|
11
9
|
```
|
|
12
10
|
|
|
13
11
|
## Authentication
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
```bash
|
|
14
|
+
web42 auth login # Sign in via GitHub OAuth
|
|
15
|
+
web42 auth logout # Sign out
|
|
16
|
+
web42 auth whoami # Show current user
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
16
20
|
|
|
21
|
+
### `web42 search <query>`
|
|
22
|
+
|
|
23
|
+
Search the network for agents.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
web42 search "data analysis"
|
|
27
|
+
web42 search "image processing" --limit 20
|
|
17
28
|
```
|
|
18
|
-
|
|
29
|
+
|
|
30
|
+
| Option | Description |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `-l, --limit <number>` | Max results to show (default: 10) |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
### `web42 send <agent> <message>`
|
|
37
|
+
|
|
38
|
+
Send a message to an A2A agent. `<agent>` can be a slug (`@user/agent`) or a direct URL (`http://localhost:3001`).
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
web42 send @alice/summarizer "Summarize this document"
|
|
42
|
+
web42 send http://localhost:3001 "Hello"
|
|
19
43
|
```
|
|
20
44
|
|
|
21
|
-
|
|
45
|
+
| Option | Description |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `--new` | Start a new conversation (clears saved context) |
|
|
48
|
+
| `--context <id>` | Use a specific context ID |
|
|
49
|
+
| `--task-id <id>` | Reply to a specific task (e.g. one in `input-required` state) |
|
|
22
50
|
|
|
23
|
-
|
|
24
|
-
|-----------|--------------|
|
|
25
|
-
| openclaw | Fully Supported |
|
|
26
|
-
| claude | Fully Supported |
|
|
51
|
+
---
|
|
27
52
|
|
|
28
|
-
|
|
53
|
+
### `web42 serve`
|
|
29
54
|
|
|
30
|
-
|
|
55
|
+
Start a local A2A server for your agent, bridging to an OpenClaw gateway.
|
|
31
56
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 |
|
|
57
|
+
```bash
|
|
58
|
+
web42 serve
|
|
59
|
+
web42 serve --port 3001 --url https://my-agent.ngrok.io --verbose
|
|
60
|
+
```
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
| Option | Description |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `--port <port>` | Port to listen on (default: 4000) |
|
|
65
|
+
| `--url <url>` | Public URL for registration and AgentCard (e.g. from ngrok) |
|
|
66
|
+
| `--openclaw-port <port>` | OpenClaw gateway port (default: 18789) |
|
|
67
|
+
| `--openclaw-token <token>` | OpenClaw gateway auth token (or `OPENCLAW_GATEWAY_TOKEN`) |
|
|
68
|
+
| `--openclaw-agent <id>` | OpenClaw agent ID to target (default: `main`) |
|
|
69
|
+
| `--client-id <id>` | Developer app client ID (or `W42_CLIENT_ID`) |
|
|
70
|
+
| `--client-secret <secret>` | Developer app client secret (or `W42_CLIENT_SECRET`) |
|
|
71
|
+
| `--visibility <vis>` | Marketplace visibility: `public` or `private` |
|
|
72
|
+
| `--verbose` | Enable verbose request/response logging |
|
|
45
73
|
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
web42 init
|
|
49
|
-
```
|
|
74
|
+
---
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
web42 pack --agent <name>
|
|
54
|
-
```
|
|
76
|
+
### `web42 register <url>`
|
|
55
77
|
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
web42 push --agent <name>
|
|
59
|
-
```
|
|
78
|
+
Register an agent with the Web42 Network. The URL must serve `/.well-known/agent-card.json`.
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
```bash
|
|
81
|
+
web42 register https://my-agent.example.com
|
|
82
|
+
web42 register https://my-agent.example.com --visibility private --tags "nlp,summarization"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
| Option | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `--price <cents>` | Price in cents (default: 0 = free) |
|
|
88
|
+
| `--license <license>` | License (e.g. `MIT`, `Apache-2.0`) |
|
|
89
|
+
| `--visibility <vis>` | `public` or `private` (default: `public`) |
|
|
90
|
+
| `--tags <tags>` | Comma-separated tags |
|
|
91
|
+
| `--categories <cats>` | Comma-separated categories |
|
|
65
92
|
|
|
66
|
-
|
|
67
|
-
```
|
|
68
|
-
web42 claude install -g @user/agent
|
|
69
|
-
```
|
|
93
|
+
---
|
|
70
94
|
|
|
71
|
-
##
|
|
95
|
+
## Environment variables
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
```bash
|
|
98
|
+
W42_CLIENT_ID=your-client-id
|
|
99
|
+
W42_CLIENT_SECRET=your-client-secret
|
|
100
|
+
OPENCLAW_GATEWAY_TOKEN=your-openclaw-token
|
|
101
|
+
```
|
package/dist/commands/send.js
CHANGED
|
@@ -1,18 +1,41 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClientFactory, ClientFactoryOptions, JsonRpcTransportFactory, } from "@a2a-js/sdk/client";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { Command } from "commander";
|
|
3
4
|
import ora from "ora";
|
|
4
5
|
import { v4 as uuidv4 } from "uuid";
|
|
5
|
-
import { requireAuth, setConfigValue, getConfigValue } from "../utils/config.js";
|
|
6
6
|
import { apiPost } from "../utils/api.js";
|
|
7
|
+
import { getConfigValue, requireAuth, setConfigValue } from "../utils/config.js";
|
|
7
8
|
function isUrl(s) {
|
|
8
9
|
return s.startsWith("http://") || s.startsWith("https://");
|
|
9
10
|
}
|
|
11
|
+
function printPart(part) {
|
|
12
|
+
if (part.kind === "text") {
|
|
13
|
+
if (part.text)
|
|
14
|
+
process.stdout.write(part.text);
|
|
15
|
+
}
|
|
16
|
+
else if (part.kind === "file") {
|
|
17
|
+
const f = part.file;
|
|
18
|
+
const label = [f.name, f.mimeType].filter(Boolean).join(" ");
|
|
19
|
+
process.stdout.write(`\n[file${label ? `: ${label}` : ""}]\n`);
|
|
20
|
+
if ("uri" in f) {
|
|
21
|
+
process.stdout.write(f.uri + "\n");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
process.stdout.write(`<${f.bytes.length} base64 chars>\n`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else if (part.kind === "data") {
|
|
28
|
+
process.stdout.write("\n" + JSON.stringify(part.data, null, 2) + "\n");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
10
31
|
function getCachedToken(slug) {
|
|
11
32
|
const raw = getConfigValue(`agentTokens.${slug}`);
|
|
12
33
|
if (!raw)
|
|
13
34
|
return null;
|
|
14
35
|
try {
|
|
15
|
-
const cached = typeof raw === "string"
|
|
36
|
+
const cached = typeof raw === "string"
|
|
37
|
+
? JSON.parse(raw)
|
|
38
|
+
: raw;
|
|
16
39
|
if (new Date(cached.expiresAt) <= new Date())
|
|
17
40
|
return null;
|
|
18
41
|
return cached;
|
|
@@ -27,6 +50,7 @@ export const sendCommand = new Command("send")
|
|
|
27
50
|
.argument("<message>", "Message to send")
|
|
28
51
|
.option("--new", "Start a new conversation (clears saved context)")
|
|
29
52
|
.option("--context <id>", "Use a specific context ID")
|
|
53
|
+
.option("--task-id <id>", "Reply to a specific task (e.g. one in input-required state)")
|
|
30
54
|
.action(async (rawAgent, userMessage, opts) => {
|
|
31
55
|
// Normalize slug: @user/name → @user~name (DB format)
|
|
32
56
|
const agent = rawAgent.includes("/") && !isUrl(rawAgent)
|
|
@@ -37,10 +61,30 @@ export const sendCommand = new Command("send")
|
|
|
37
61
|
let bearerToken;
|
|
38
62
|
let agentKey;
|
|
39
63
|
if (isUrl(agent)) {
|
|
40
|
-
// Direct URL mode — local development, no handshake needed
|
|
41
64
|
agentUrl = agent;
|
|
42
|
-
bearerToken = config.token;
|
|
43
65
|
agentKey = new URL(agent).host.replace(/[.:]/g, "-");
|
|
66
|
+
const cached = getCachedToken(agentKey);
|
|
67
|
+
if (cached) {
|
|
68
|
+
bearerToken = cached.token;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const spinner = ora("Getting auth token...").start();
|
|
72
|
+
try {
|
|
73
|
+
const res = await apiPost("/api/auth/token", {});
|
|
74
|
+
bearerToken = res.token;
|
|
75
|
+
setConfigValue(`agentTokens.${agentKey}`, JSON.stringify({
|
|
76
|
+
token: res.token,
|
|
77
|
+
agentUrl,
|
|
78
|
+
expiresAt: res.expiresAt,
|
|
79
|
+
}));
|
|
80
|
+
spinner.stop();
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
spinner.fail("Failed to get auth token");
|
|
84
|
+
console.error(chalk.red(String(err)));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
44
88
|
}
|
|
45
89
|
else {
|
|
46
90
|
// Slug mode — handshake with Web42 Network platform
|
|
@@ -85,20 +129,6 @@ export const sendCommand = new Command("send")
|
|
|
85
129
|
contextId = getConfigValue(contextKey) ?? uuidv4();
|
|
86
130
|
}
|
|
87
131
|
setConfigValue(contextKey, contextId);
|
|
88
|
-
// Dynamically import @a2a-js/sdk client
|
|
89
|
-
let ClientFactory;
|
|
90
|
-
let JsonRpcTransportFactory;
|
|
91
|
-
let ClientFactoryOptions;
|
|
92
|
-
try {
|
|
93
|
-
const clientModule = await import("@a2a-js/sdk/client");
|
|
94
|
-
ClientFactory = clientModule.ClientFactory;
|
|
95
|
-
JsonRpcTransportFactory = clientModule.JsonRpcTransportFactory;
|
|
96
|
-
ClientFactoryOptions = clientModule.ClientFactoryOptions;
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
console.error(chalk.red("Failed to load @a2a-js/sdk. Run: pnpm add @a2a-js/sdk"));
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
132
|
const bearerInterceptor = {
|
|
103
133
|
before: async (args) => {
|
|
104
134
|
if (!args.options)
|
|
@@ -119,8 +149,7 @@ export const sendCommand = new Command("send")
|
|
|
119
149
|
interceptors: [bearerInterceptor],
|
|
120
150
|
},
|
|
121
151
|
}));
|
|
122
|
-
|
|
123
|
-
client = await factory.createFromUrl(a2aBaseUrl);
|
|
152
|
+
client = await factory.createFromUrl(agentUrl);
|
|
124
153
|
connectSpinner.stop();
|
|
125
154
|
}
|
|
126
155
|
catch {
|
|
@@ -136,22 +165,67 @@ export const sendCommand = new Command("send")
|
|
|
136
165
|
parts: [{ kind: "text", text: userMessage }],
|
|
137
166
|
kind: "message",
|
|
138
167
|
contextId,
|
|
168
|
+
...(opts.taskId ? { taskId: opts.taskId } : {}),
|
|
139
169
|
},
|
|
140
170
|
});
|
|
141
171
|
for await (const event of stream) {
|
|
142
|
-
if (event.kind === "
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
172
|
+
if (event.kind === "message" && event.role === "agent") {
|
|
173
|
+
for (const part of event.parts)
|
|
174
|
+
printPart(part);
|
|
175
|
+
}
|
|
176
|
+
else if (event.kind === "artifact-update") {
|
|
177
|
+
// When append is true we're receiving streaming chunks — write inline.
|
|
178
|
+
// When it's a new artifact (append falsy), separate from prior output.
|
|
179
|
+
if (!event.append)
|
|
180
|
+
process.stdout.write("\n");
|
|
181
|
+
for (const part of event.artifact.parts ?? [])
|
|
182
|
+
printPart(part);
|
|
150
183
|
}
|
|
151
|
-
if (event.kind === "status-update") {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
184
|
+
else if (event.kind === "status-update") {
|
|
185
|
+
if (event.status?.message) {
|
|
186
|
+
for (const part of event.status.message.parts ?? [])
|
|
187
|
+
printPart(part);
|
|
188
|
+
}
|
|
189
|
+
const state = event.status?.state;
|
|
190
|
+
if (state === "input-required") {
|
|
191
|
+
process.stdout.write("\n");
|
|
192
|
+
console.log(chalk.yellow(`[task ${event.taskId} is awaiting input — reply with --task-id ${event.taskId}]`));
|
|
193
|
+
}
|
|
194
|
+
else if (state === "auth-required") {
|
|
195
|
+
console.error(chalk.red("\nAgent requires authentication."));
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
else if (state === "failed" ||
|
|
199
|
+
state === "canceled" ||
|
|
200
|
+
state === "rejected") {
|
|
201
|
+
console.error(chalk.red(`\nAgent ${state}.`));
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
else if (event.kind === "task") {
|
|
206
|
+
// Non-streaming fallback: server returned the full task object.
|
|
207
|
+
// Print accumulated artifacts and handle terminal state.
|
|
208
|
+
const task = event;
|
|
209
|
+
for (const artifact of task.artifacts ?? []) {
|
|
210
|
+
process.stdout.write("\n");
|
|
211
|
+
for (const part of artifact.parts ?? [])
|
|
212
|
+
printPart(part);
|
|
213
|
+
}
|
|
214
|
+
if (task.status?.message) {
|
|
215
|
+
for (const part of task.status.message.parts ?? [])
|
|
216
|
+
printPart(part);
|
|
217
|
+
}
|
|
218
|
+
const taskState = task.status?.state;
|
|
219
|
+
if (taskState === "input-required") {
|
|
220
|
+
process.stdout.write("\n");
|
|
221
|
+
console.log(chalk.yellow(`[task ${task.id} is awaiting input — reply with --task-id ${task.id}]`));
|
|
222
|
+
}
|
|
223
|
+
else if (taskState === "auth-required") {
|
|
224
|
+
console.error(chalk.red("\nAgent requires authentication."));
|
|
225
|
+
process.exit(1);
|
|
226
|
+
}
|
|
227
|
+
else if (taskState === "failed" || taskState === "canceled" || taskState === "rejected") {
|
|
228
|
+
console.error(chalk.red(`\nAgent ${taskState}.`));
|
|
155
229
|
process.exit(1);
|
|
156
230
|
}
|
|
157
231
|
}
|
package/dist/commands/serve.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
|
-
import {
|
|
3
|
+
import { Web42Client, createA2AServer, } from "@web42/auth";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { Command } from "commander";
|
|
5
6
|
import ora from "ora";
|
|
6
|
-
import express from "express";
|
|
7
|
-
import { agentCardHandler, jsonRpcHandler, } from "@a2a-js/sdk/server/express";
|
|
8
|
-
import { DefaultRequestHandler, InMemoryTaskStore, } from "@a2a-js/sdk/server";
|
|
9
7
|
import { printBanner } from "../utils/banner.js";
|
|
10
|
-
import {
|
|
8
|
+
import { getConfig, requireAuth } from "../utils/config.js";
|
|
11
9
|
class OpenClawAgentExecutor {
|
|
12
10
|
opts;
|
|
13
11
|
verbose;
|
|
@@ -17,8 +15,7 @@ class OpenClawAgentExecutor {
|
|
|
17
15
|
}
|
|
18
16
|
async execute(requestContext, eventBus) {
|
|
19
17
|
const { taskId, contextId, userMessage } = requestContext;
|
|
20
|
-
const userText = userMessage.parts
|
|
21
|
-
.find((p) => p.kind === "text")?.text ?? "";
|
|
18
|
+
const userText = userMessage.parts.find((p) => p.kind === "text")?.text ?? "";
|
|
22
19
|
if (this.verbose) {
|
|
23
20
|
console.log(chalk.gray(`[verbose] → OpenClaw request: agent=${this.opts.openClawAgent} session=${contextId} port=${this.opts.openClawPort}`));
|
|
24
21
|
console.log(chalk.gray(`[verbose] → message text: "${userText.slice(0, 100)}"`));
|
|
@@ -35,7 +32,7 @@ class OpenClawAgentExecutor {
|
|
|
35
32
|
},
|
|
36
33
|
body: JSON.stringify({
|
|
37
34
|
model: "openclaw",
|
|
38
|
-
stream:
|
|
35
|
+
stream: false,
|
|
39
36
|
messages: [{ role: "user", content: userText }],
|
|
40
37
|
}),
|
|
41
38
|
});
|
|
@@ -54,53 +51,15 @@ class OpenClawAgentExecutor {
|
|
|
54
51
|
}
|
|
55
52
|
throw new Error(`OpenClaw error: ${response.status} ${response.statusText}`);
|
|
56
53
|
}
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
let buffer = "";
|
|
60
|
-
let tokenCount = 0;
|
|
61
|
-
while (true) {
|
|
62
|
-
const { done, value } = await reader.read();
|
|
63
|
-
if (done)
|
|
64
|
-
break;
|
|
65
|
-
buffer += decoder.decode(value, { stream: true });
|
|
66
|
-
const lines = buffer.split("\n");
|
|
67
|
-
buffer = lines.pop() ?? "";
|
|
68
|
-
for (const line of lines) {
|
|
69
|
-
if (!line.startsWith("data: "))
|
|
70
|
-
continue;
|
|
71
|
-
const data = line.slice(6).trim();
|
|
72
|
-
if (data === "[DONE]")
|
|
73
|
-
continue;
|
|
74
|
-
try {
|
|
75
|
-
const chunk = JSON.parse(data);
|
|
76
|
-
const token = chunk.choices?.[0]?.delta?.content;
|
|
77
|
-
if (token) {
|
|
78
|
-
tokenCount++;
|
|
79
|
-
eventBus.publish({
|
|
80
|
-
kind: "artifact-update",
|
|
81
|
-
taskId,
|
|
82
|
-
contextId,
|
|
83
|
-
artifact: {
|
|
84
|
-
artifactId: "response",
|
|
85
|
-
parts: [{ kind: "text", text: token }],
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
// ignore malformed SSE lines
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (this.verbose) {
|
|
96
|
-
console.log(chalk.gray(`[verbose] ← stream complete: ${tokenCount} tokens received`));
|
|
97
|
-
}
|
|
54
|
+
const data = (await response.json());
|
|
55
|
+
const text = data.choices?.[0]?.message?.content ?? "";
|
|
98
56
|
eventBus.publish({
|
|
99
|
-
kind: "
|
|
100
|
-
|
|
57
|
+
kind: "message",
|
|
58
|
+
messageId: crypto.randomUUID(),
|
|
59
|
+
role: "agent",
|
|
60
|
+
parts: [{ kind: "text", text }],
|
|
101
61
|
contextId,
|
|
102
|
-
|
|
103
|
-
final: true,
|
|
62
|
+
taskId,
|
|
104
63
|
});
|
|
105
64
|
eventBus.finished();
|
|
106
65
|
}
|
|
@@ -117,10 +76,14 @@ async function publishLiveUrl({ apiUrl, token, slug, a2aUrl, enabled, gatewaySta
|
|
|
117
76
|
Authorization: `Bearer ${token}`,
|
|
118
77
|
"Content-Type": "application/json",
|
|
119
78
|
},
|
|
120
|
-
body: JSON.stringify({
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
a2a_url: a2aUrl,
|
|
81
|
+
a2a_enabled: enabled,
|
|
82
|
+
gateway_status: gatewayStatus,
|
|
83
|
+
}),
|
|
121
84
|
});
|
|
122
85
|
if (!res.ok) {
|
|
123
|
-
const errBody = await res.json().catch(() => ({}));
|
|
86
|
+
const errBody = (await res.json().catch(() => ({})));
|
|
124
87
|
console.warn(chalk.yellow(` Could not register URL with marketplace: ${errBody.error ?? res.status}`));
|
|
125
88
|
}
|
|
126
89
|
else {
|
|
@@ -195,71 +158,42 @@ export const serveCommand = new Command("serve")
|
|
|
195
158
|
process.exit(1);
|
|
196
159
|
}
|
|
197
160
|
const spinner = ora("Starting A2A server...").start();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
version: cardData.version ?? "1.0.0",
|
|
204
|
-
url: `${publicUrl ?? `http://localhost:${port}`}/a2a/jsonrpc`,
|
|
205
|
-
skills: cardData.skills ?? [],
|
|
206
|
-
capabilities: {
|
|
207
|
-
streaming: true,
|
|
208
|
-
pushNotifications: false,
|
|
209
|
-
...(cardData.capabilities ?? {}),
|
|
210
|
-
},
|
|
211
|
-
defaultInputModes: cardData.defaultInputModes ?? ["text"],
|
|
212
|
-
defaultOutputModes: cardData.defaultOutputModes ?? ["text"],
|
|
213
|
-
securitySchemes: {
|
|
214
|
-
W42Bearer: { type: "http", scheme: "bearer" },
|
|
215
|
-
},
|
|
216
|
-
security: [{ W42Bearer: [] }],
|
|
217
|
-
};
|
|
218
|
-
// 4. Start Express server
|
|
219
|
-
const app = express();
|
|
220
|
-
// Auth: validate caller's Bearer token via Web42 introspection with Basic auth
|
|
221
|
-
const basicAuth = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
|
|
222
|
-
const userBuilder = async (req) => {
|
|
223
|
-
const callerToken = req.headers.authorization?.split(" ")[1];
|
|
224
|
-
if (!callerToken)
|
|
225
|
-
throw new Error("Missing token");
|
|
226
|
-
if (verbose) {
|
|
227
|
-
console.log(chalk.gray(`[verbose] Introspecting token ${callerToken.slice(0, 8)}...`));
|
|
228
|
-
}
|
|
229
|
-
const res = await fetch(`${web42ApiUrl}/api/auth/introspect`, {
|
|
230
|
-
method: "POST",
|
|
231
|
-
headers: {
|
|
232
|
-
Authorization: basicAuth,
|
|
233
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
234
|
-
},
|
|
235
|
-
body: new URLSearchParams({ token: callerToken }),
|
|
236
|
-
});
|
|
237
|
-
if (!res.ok)
|
|
238
|
-
throw new Error("Introspect call failed");
|
|
239
|
-
const result = (await res.json());
|
|
240
|
-
if (verbose) {
|
|
241
|
-
console.log(chalk.gray(`[verbose] active=${result.active} sub=${result.sub ?? "(none)"}`));
|
|
242
|
-
}
|
|
243
|
-
if (!result.active)
|
|
244
|
-
throw new Error("Unauthorized");
|
|
245
|
-
return {
|
|
246
|
-
get isAuthenticated() { return true; },
|
|
247
|
-
get userName() { return result.sub ?? ""; },
|
|
248
|
-
};
|
|
249
|
-
};
|
|
161
|
+
const web42Client = new Web42Client({
|
|
162
|
+
baseUrl: web42ApiUrl,
|
|
163
|
+
clientId,
|
|
164
|
+
clientSecret,
|
|
165
|
+
});
|
|
250
166
|
const executor = new OpenClawAgentExecutor({
|
|
251
167
|
openClawPort,
|
|
252
168
|
openClawToken,
|
|
253
169
|
openClawAgent,
|
|
254
170
|
verbose,
|
|
255
171
|
});
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
172
|
+
const { listen } = createA2AServer({
|
|
173
|
+
web42: web42Client,
|
|
174
|
+
card: {
|
|
175
|
+
name: agentName,
|
|
176
|
+
description: cardData.description ?? "",
|
|
177
|
+
version: cardData.version ?? "1.0.0",
|
|
178
|
+
skills: cardData.skills ?? [],
|
|
179
|
+
capabilities: {
|
|
180
|
+
streaming: true,
|
|
181
|
+
pushNotifications: false,
|
|
182
|
+
...(cardData.capabilities ?? {}),
|
|
183
|
+
},
|
|
184
|
+
defaultInputModes: cardData.defaultInputModes ?? [
|
|
185
|
+
"text",
|
|
186
|
+
],
|
|
187
|
+
defaultOutputModes: cardData.defaultOutputModes ?? [
|
|
188
|
+
"text",
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
executor,
|
|
192
|
+
port,
|
|
193
|
+
baseUrl: publicUrl ?? `http://localhost:${port}`,
|
|
194
|
+
});
|
|
195
|
+
const a2aUrl = publicUrl ?? `http://localhost:${port}`;
|
|
196
|
+
listen(async () => {
|
|
263
197
|
spinner.stop();
|
|
264
198
|
printBanner();
|
|
265
199
|
console.log(chalk.green(` Agent "${agentName}" is live`));
|
|
@@ -281,7 +215,10 @@ export const serveCommand = new Command("serve")
|
|
|
281
215
|
Authorization: `Bearer ${token}`,
|
|
282
216
|
"Content-Type": "application/json",
|
|
283
217
|
},
|
|
284
|
-
body: JSON.stringify({
|
|
218
|
+
body: JSON.stringify({
|
|
219
|
+
url: registrationUrl,
|
|
220
|
+
visibility: opts.visibility,
|
|
221
|
+
}),
|
|
285
222
|
});
|
|
286
223
|
if (regRes.ok) {
|
|
287
224
|
const regData = (await regRes.json());
|
|
@@ -289,7 +226,7 @@ export const serveCommand = new Command("serve")
|
|
|
289
226
|
console.log(chalk.dim(` Registered with marketplace (slug: ${registeredSlug})`));
|
|
290
227
|
}
|
|
291
228
|
else {
|
|
292
|
-
const errBody = await regRes.json().catch(() => ({}));
|
|
229
|
+
const errBody = (await regRes.json().catch(() => ({})));
|
|
293
230
|
console.warn(chalk.yellow(` Could not register with marketplace: ${errBody.error ?? regRes.status}`));
|
|
294
231
|
}
|
|
295
232
|
}
|
|
@@ -311,7 +248,7 @@ export const serveCommand = new Command("serve")
|
|
|
311
248
|
console.log(chalk.yellow(" No --url provided. Registered localhost URL is not publicly reachable."));
|
|
312
249
|
}
|
|
313
250
|
console.log(chalk.dim("\nWaiting for requests... (Ctrl+C to stop)\n"));
|
|
314
|
-
//
|
|
251
|
+
// Graceful shutdown
|
|
315
252
|
process.on("SIGINT", async () => {
|
|
316
253
|
console.log(chalk.dim("\nShutting down..."));
|
|
317
254
|
if (registeredSlug) {
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.1.
|
|
1
|
+
export declare const CLI_VERSION = "0.1.8";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.1.
|
|
1
|
+
export const CLI_VERSION = "0.1.8";
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@web42/w42",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "CLI for the Web42 Agent Network — discover, register, and communicate with A2A agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"w42": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"
|
|
11
|
-
"build": "
|
|
12
|
-
"build:binary": "bash scripts/sync-version.sh && npm run embed-skills && bash scripts/build-binaries.sh",
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"build:binary": "bash scripts/sync-version.sh && bash scripts/build-binaries.sh",
|
|
13
12
|
"dev": "tsc --watch",
|
|
14
13
|
"start": "node dist/index.js"
|
|
15
14
|
},
|
|
16
15
|
"dependencies": {
|
|
17
16
|
"@a2a-js/sdk": "^0.3.13",
|
|
17
|
+
"@web42/auth": "workspace:*",
|
|
18
18
|
"chalk": "^5.3.0",
|
|
19
19
|
"commander": "^12.1.0",
|
|
20
20
|
"conf": "^13.0.1",
|
|
@@ -39,8 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
41
|
"dist/**/*.js",
|
|
42
|
-
"dist/**/*.d.ts"
|
|
43
|
-
"skills/**"
|
|
42
|
+
"dist/**/*.d.ts"
|
|
44
43
|
],
|
|
45
44
|
"keywords": [
|
|
46
45
|
"web42",
|