@web42/cli 0.2.6 → 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.
Files changed (58) hide show
  1. package/dist/commands/search.js +20 -15
  2. package/dist/commands/send.js +75 -41
  3. package/dist/commands/serve.d.ts +1 -1
  4. package/dist/commands/serve.js +116 -213
  5. package/dist/index.js +1 -19
  6. package/dist/version.d.ts +1 -1
  7. package/dist/version.js +1 -1
  8. package/package.json +1 -1
  9. package/dist/commands/config.d.ts +0 -2
  10. package/dist/commands/config.js +0 -27
  11. package/dist/commands/init.d.ts +0 -2
  12. package/dist/commands/init.js +0 -451
  13. package/dist/commands/install.d.ts +0 -3
  14. package/dist/commands/install.js +0 -231
  15. package/dist/commands/list.d.ts +0 -3
  16. package/dist/commands/list.js +0 -22
  17. package/dist/commands/pack.d.ts +0 -2
  18. package/dist/commands/pack.js +0 -210
  19. package/dist/commands/pull.d.ts +0 -2
  20. package/dist/commands/pull.js +0 -202
  21. package/dist/commands/push.d.ts +0 -2
  22. package/dist/commands/push.js +0 -374
  23. package/dist/commands/remix.d.ts +0 -2
  24. package/dist/commands/remix.js +0 -49
  25. package/dist/commands/sync.d.ts +0 -2
  26. package/dist/commands/sync.js +0 -98
  27. package/dist/commands/uninstall.d.ts +0 -3
  28. package/dist/commands/uninstall.js +0 -54
  29. package/dist/commands/update.d.ts +0 -3
  30. package/dist/commands/update.js +0 -59
  31. package/dist/platforms/base.d.ts +0 -82
  32. package/dist/platforms/base.js +0 -1
  33. package/dist/platforms/claude/__tests__/adapter.test.d.ts +0 -1
  34. package/dist/platforms/claude/__tests__/adapter.test.js +0 -257
  35. package/dist/platforms/claude/__tests__/security.test.d.ts +0 -1
  36. package/dist/platforms/claude/__tests__/security.test.js +0 -166
  37. package/dist/platforms/claude/adapter.d.ts +0 -34
  38. package/dist/platforms/claude/adapter.js +0 -525
  39. package/dist/platforms/claude/security.d.ts +0 -15
  40. package/dist/platforms/claude/security.js +0 -67
  41. package/dist/platforms/claude/templates.d.ts +0 -5
  42. package/dist/platforms/claude/templates.js +0 -22
  43. package/dist/platforms/openclaw/adapter.d.ts +0 -12
  44. package/dist/platforms/openclaw/adapter.js +0 -476
  45. package/dist/platforms/openclaw/templates.d.ts +0 -7
  46. package/dist/platforms/openclaw/templates.js +0 -369
  47. package/dist/platforms/registry.d.ts +0 -6
  48. package/dist/platforms/registry.js +0 -32
  49. package/dist/types/sync.d.ts +0 -74
  50. package/dist/types/sync.js +0 -7
  51. package/dist/utils/bundled-skills.d.ts +0 -6
  52. package/dist/utils/bundled-skills.js +0 -29
  53. package/dist/utils/secrets.d.ts +0 -32
  54. package/dist/utils/secrets.js +0 -118
  55. package/dist/utils/skill.d.ts +0 -6
  56. package/dist/utils/skill.js +0 -42
  57. package/dist/utils/sync.d.ts +0 -14
  58. package/dist/utils/sync.js +0 -242
@@ -2,6 +2,16 @@ import { Command } from "commander";
2
2
  import chalk from "chalk";
3
3
  import ora from "ora";
4
4
  import { apiGet } from "../utils/api.js";
5
+ function getCardName(card) {
6
+ return card?.name ?? "Untitled Agent";
7
+ }
8
+ function getCardDescription(card) {
9
+ return card?.description ?? "";
10
+ }
11
+ function getMarketplacePrice(card) {
12
+ const ext = card?.capabilities?.extensions?.find((e) => e.uri === "https://web42.ai/ext/marketplace/v1");
13
+ return ext?.params?.price_cents ?? 0;
14
+ }
5
15
  function truncate(str, max) {
6
16
  if (str.length <= max)
7
17
  return str;
@@ -10,7 +20,6 @@ function truncate(str, max) {
10
20
  export const searchCommand = new Command("search")
11
21
  .description("Search the marketplace for agents")
12
22
  .argument("<query>", "Search query")
13
- .option("-p, --platform <platform>", "Filter results by platform (e.g. openclaw)")
14
23
  .option("-l, --limit <number>", "Max results to show", "10")
15
24
  .action(async (query, opts) => {
16
25
  const spinner = ora(`Searching for "${query}"...`).start();
@@ -26,24 +35,20 @@ export const searchCommand = new Command("search")
26
35
  console.log(chalk.bold(`Found ${agents.length} agent(s) for "${query}":`));
27
36
  console.log();
28
37
  for (const agent of results) {
38
+ const name = getCardName(agent.agent_card);
39
+ const description = getCardDescription(agent.agent_card);
40
+ const priceCents = getMarketplacePrice(agent.agent_card);
29
41
  const ref = `@${agent.owner.username}/${agent.slug}`;
30
42
  const stars = agent.stars_count > 0 ? chalk.yellow(` \u2605 ${agent.stars_count}`) : "";
31
- const price = agent.price_cents > 0
32
- ? chalk.green(` $${(agent.price_cents / 100).toFixed(2)}`)
43
+ const price = priceCents > 0
44
+ ? chalk.green(` $${(priceCents / 100).toFixed(2)}`)
33
45
  : chalk.dim(" Free");
34
- console.log(` ${chalk.cyan.bold(agent.name)}${stars}${price}`);
46
+ console.log(` ${chalk.cyan.bold(name)}${stars}${price}`);
35
47
  console.log(` ${chalk.dim(ref)}`);
36
- if (agent.description) {
37
- console.log(` ${truncate(agent.description, 80)}`);
38
- }
39
- const platform = agent.manifest?.platform ?? "openclaw";
40
- if (agent.price_cents > 0) {
41
- const siteUrl = process.env.WEB42_API_URL ?? "https://web42.ai";
42
- console.log(chalk.dim(` Purchase: ${siteUrl}/${agent.owner.username}/${agent.slug}`));
43
- }
44
- else {
45
- console.log(chalk.dim(` Install: web42 ${platform} install ${ref}`));
48
+ if (description) {
49
+ console.log(` ${truncate(description, 80)}`);
46
50
  }
51
+ console.log(chalk.dim(` Send: web42 send ${agent.slug} "hello"`));
47
52
  console.log();
48
53
  }
49
54
  if (agents.length > limit) {
@@ -52,7 +57,7 @@ export const searchCommand = new Command("search")
52
57
  }
53
58
  catch (error) {
54
59
  spinner.fail("Search failed");
55
- console.error(chalk.red(error.message));
60
+ console.error(chalk.red(String(error)));
56
61
  process.exit(1);
57
62
  }
58
63
  });
@@ -3,44 +3,85 @@ import chalk from "chalk";
3
3
  import ora from "ora";
4
4
  import { v4 as uuidv4 } from "uuid";
5
5
  import { requireAuth, setConfigValue, getConfigValue } from "../utils/config.js";
6
- import { apiGet } from "../utils/api.js";
6
+ import { apiPost } from "../utils/api.js";
7
+ function isUrl(s) {
8
+ return s.startsWith("http://") || s.startsWith("https://");
9
+ }
10
+ function getCachedToken(slug) {
11
+ const raw = getConfigValue(`agentTokens.${slug}`);
12
+ if (!raw)
13
+ return null;
14
+ try {
15
+ const cached = typeof raw === "string" ? JSON.parse(raw) : raw;
16
+ if (new Date(cached.expiresAt) <= new Date())
17
+ return null;
18
+ return cached;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
7
24
  export const sendCommand = new Command("send")
8
- .description("Send a message to a live web42 agent")
9
- .argument("<agent>", "Agent handle, e.g. @javier/gilfoyle")
25
+ .description("Send a message to an A2A agent")
26
+ .argument("<agent>", "Agent slug (e.g. my-agent) or direct URL (http://localhost:3001)")
10
27
  .argument("<message>", "Message to send")
11
28
  .option("--new", "Start a new conversation (clears saved context)")
12
- .action(async (agentHandle, userMessage, opts) => {
29
+ .option("--context <id>", "Use a specific context ID")
30
+ .action(async (agent, userMessage, opts) => {
13
31
  const config = requireAuth();
14
- // 1. Parse @user/slug
15
- const match = agentHandle.match(/^@?([\w-]+)\/([\w-]+)$/);
16
- if (!match) {
17
- console.error(chalk.red("Invalid agent handle. Expected format: @user/agent-slug"));
18
- process.exit(1);
19
- }
20
- const [, username, slug] = match;
21
- // 2. Look up agent A2A URL from marketplace
22
- const spinner = ora(`Looking up @${username}/${slug}...`).start();
23
- let a2aData;
24
- try {
25
- a2aData = await apiGet(`/api/agents/${slug}/a2a`);
32
+ let agentUrl;
33
+ let bearerToken;
34
+ let agentKey;
35
+ if (isUrl(agent)) {
36
+ // Direct URL mode — local development, no handshake needed
37
+ agentUrl = agent;
38
+ bearerToken = config.token;
39
+ agentKey = new URL(agent).host.replace(/[.:]/g, "-");
26
40
  }
27
- catch {
28
- spinner.fail(`Agent @${username}/${slug} not found`);
29
- process.exit(1);
41
+ else {
42
+ // Slug mode — handshake with Web42 platform
43
+ agentKey = agent;
44
+ const cached = getCachedToken(agent);
45
+ if (cached) {
46
+ agentUrl = cached.agentUrl;
47
+ bearerToken = cached.token;
48
+ }
49
+ else {
50
+ const spinner = ora(`Authenticating with ${agent}...`).start();
51
+ try {
52
+ const res = await apiPost("/api/auth/handshake", {
53
+ agentSlug: agent,
54
+ });
55
+ agentUrl = res.agentUrl;
56
+ bearerToken = res.token;
57
+ setConfigValue(`agentTokens.${agent}`, JSON.stringify({
58
+ token: res.token,
59
+ agentUrl: res.agentUrl,
60
+ expiresAt: res.expiresAt,
61
+ }));
62
+ spinner.succeed(`Authenticated with ${agent}`);
63
+ }
64
+ catch (err) {
65
+ spinner.fail(`Failed to authenticate with ${agent}`);
66
+ console.error(chalk.red(String(err)));
67
+ process.exit(1);
68
+ }
69
+ }
30
70
  }
31
- if (!a2aData.a2a_enabled || !a2aData.a2a_url) {
32
- spinner.fail(`@${username}/${slug} is not live. Publisher must run: web42 serve --url <url>`);
33
- process.exit(1);
71
+ // Resolve contextId
72
+ const contextKey = `context.${agentKey}`;
73
+ let contextId;
74
+ if (opts.context) {
75
+ contextId = opts.context;
34
76
  }
35
- spinner.stop();
36
- // 3. Resolve contextId — reuse existing session or start fresh
37
- const contextKey = `context.${username}.${slug}`;
38
- let contextId = getConfigValue(contextKey) ?? uuidv4();
39
- if (opts.new) {
77
+ else if (opts.new) {
40
78
  contextId = uuidv4();
41
79
  }
80
+ else {
81
+ contextId = getConfigValue(contextKey) ?? uuidv4();
82
+ }
42
83
  setConfigValue(contextKey, contextId);
43
- // 4. Dynamically import @a2a-js/sdk client (ESM)
84
+ // Dynamically import @a2a-js/sdk client
44
85
  let ClientFactory;
45
86
  let JsonRpcTransportFactory;
46
87
  let ClientFactoryOptions;
@@ -54,22 +95,18 @@ export const sendCommand = new Command("send")
54
95
  console.error(chalk.red("Failed to load @a2a-js/sdk. Run: pnpm add @a2a-js/sdk"));
55
96
  process.exit(1);
56
97
  }
57
- // 5. Bearer token interceptor
58
- const token = config.token;
59
98
  const bearerInterceptor = {
60
99
  before: async (args) => {
61
- if (!args.options) {
100
+ if (!args.options)
62
101
  args.options = {};
63
- }
64
102
  args.options.serviceParameters = {
65
103
  ...(args.options.serviceParameters ?? {}),
66
- Authorization: `Bearer ${token}`,
104
+ Authorization: `Bearer ${bearerToken}`,
67
105
  };
68
106
  },
69
107
  after: async () => { },
70
108
  };
71
- // 6. Create A2A client
72
- const connectSpinner = ora(`Connecting to @${username}/${slug}...`).start();
109
+ const connectSpinner = ora(`Connecting to ${agentKey}...`).start();
73
110
  let client;
74
111
  try {
75
112
  const factory = new ClientFactory(ClientFactoryOptions.createFrom(ClientFactoryOptions.default, {
@@ -78,18 +115,15 @@ export const sendCommand = new Command("send")
78
115
  interceptors: [bearerInterceptor],
79
116
  },
80
117
  }));
81
- // a2aData.a2a_url is the full JSON-RPC endpoint, e.g. https://foo.ngrok.dev/a2a/jsonrpc
82
- // createFromUrl needs the base URL; it will append /.well-known/agent-card.json itself
83
- const a2aBaseUrl = new URL(a2aData.a2a_url).origin;
118
+ const a2aBaseUrl = new URL(agentUrl).origin;
84
119
  client = await factory.createFromUrl(a2aBaseUrl);
85
120
  connectSpinner.stop();
86
121
  }
87
122
  catch {
88
- connectSpinner.fail(`Could not reach agent at ${a2aData.a2a_url}`);
89
- console.error(chalk.dim("Is the publisher running web42 serve? Is ngrok still active?"));
123
+ connectSpinner.fail(`Could not reach agent at ${agentUrl}`);
124
+ console.error(chalk.dim("Is the agent server running?"));
90
125
  process.exit(1);
91
126
  }
92
- // 7. Stream response to stdout
93
127
  try {
94
128
  const stream = client.sendMessageStream({
95
129
  message: {
@@ -1,2 +1,2 @@
1
- import { Command } from 'commander';
1
+ import { Command } from "commander";
2
2
  export declare const serveCommand: Command;
@@ -1,105 +1,32 @@
1
- import { existsSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
- import { Command } from 'commander';
4
- import chalk from 'chalk';
5
- import ora from 'ora';
6
- import express from 'express';
7
- import { agentCardHandler, jsonRpcHandler, } from '@a2a-js/sdk/server/express';
8
- import { DefaultRequestHandler, InMemoryTaskStore, } from '@a2a-js/sdk/server';
9
- import { requireAuth } from '../utils/config.js';
10
- import { getConfig } from '../utils/config.js';
11
- class OpenClawAgentExecutor {
12
- opts;
13
- verbose;
14
- constructor(opts) {
15
- this.opts = opts;
16
- this.verbose = opts.verbose ?? false;
17
- }
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import express from "express";
5
+ import { agentCardHandler, jsonRpcHandler, } from "@a2a-js/sdk/server/express";
6
+ import { DefaultRequestHandler, InMemoryTaskStore, } from "@a2a-js/sdk/server";
7
+ import { requireAuth, getConfig } from "../utils/config.js";
8
+ // ---------------------------------------------------------------------------
9
+ // StdinExecutor generic executor that pipes user messages to a subprocess
10
+ // ---------------------------------------------------------------------------
11
+ class EchoExecutor {
18
12
  async execute(requestContext, eventBus) {
19
13
  const { taskId, contextId, userMessage } = requestContext;
20
14
  const userText = userMessage.parts
21
- .find((p) => p.kind === 'text')?.text ?? '';
22
- if (this.verbose) {
23
- console.log(chalk.gray(`[verbose] → OpenClaw request: agent=${this.opts.openClawAgent} session=${contextId} port=${this.opts.openClawPort}`));
24
- console.log(chalk.gray(`[verbose] → message text: "${userText.slice(0, 100)}"`));
25
- }
26
- let response;
27
- try {
28
- response = await fetch(`http://localhost:${this.opts.openClawPort}/v1/chat/completions`, {
29
- method: 'POST',
30
- headers: {
31
- Authorization: `Bearer ${this.opts.openClawToken}`,
32
- 'Content-Type': 'application/json',
33
- 'x-openclaw-agent-id': this.opts.openClawAgent,
34
- 'x-openclaw-session-key': contextId,
35
- },
36
- body: JSON.stringify({
37
- model: 'openclaw',
38
- stream: true,
39
- messages: [{ role: 'user', content: userText }],
40
- }),
41
- });
42
- }
43
- catch (err) {
44
- throw new Error(`OpenClaw is not reachable on port ${this.opts.openClawPort}. ` +
45
- `Make sure it is running with chatCompletions enabled. (${String(err)})`);
46
- }
47
- if (this.verbose) {
48
- console.log(chalk.gray(`[verbose] ← OpenClaw response: status=${response.status}`));
49
- }
50
- if (!response.ok) {
51
- if (this.verbose) {
52
- const body = await response.text().catch(() => '(unreadable)');
53
- console.log(chalk.gray(`[verbose] ← response body: ${body}`));
54
- }
55
- throw new Error(`OpenClaw error: ${response.status} ${response.statusText}`);
56
- }
57
- const reader = response.body.getReader();
58
- const decoder = new TextDecoder();
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
- }
15
+ .find((p) => p.kind === "text")?.text ?? "";
98
16
  eventBus.publish({
99
- kind: 'status-update',
17
+ kind: "artifact-update",
100
18
  taskId,
101
19
  contextId,
102
- status: { state: 'completed', timestamp: new Date().toISOString() },
20
+ artifact: {
21
+ artifactId: "response",
22
+ parts: [{ kind: "text", text: `Echo: ${userText}` }],
23
+ },
24
+ });
25
+ eventBus.publish({
26
+ kind: "status-update",
27
+ taskId,
28
+ contextId,
29
+ status: { state: "completed", timestamp: new Date().toISOString() },
103
30
  final: true,
104
31
  });
105
32
  eventBus.finished();
@@ -107,174 +34,150 @@ class OpenClawAgentExecutor {
107
34
  cancelTask = async () => { };
108
35
  }
109
36
  // ---------------------------------------------------------------------------
110
- // Helpers
111
- // ---------------------------------------------------------------------------
112
- async function publishLiveUrl({ apiUrl, token, slug, a2aUrl, enabled, gatewayStatus, }) {
113
- try {
114
- const res = await fetch(`${apiUrl}/api/agents/${slug}/a2a`, {
115
- method: 'POST',
116
- headers: {
117
- Authorization: `Bearer ${token}`,
118
- 'Content-Type': 'application/json',
119
- },
120
- body: JSON.stringify({ a2a_url: a2aUrl, a2a_enabled: enabled, gateway_status: gatewayStatus }),
121
- });
122
- if (!res.ok) {
123
- console.warn(chalk.yellow(`⚠ Could not register URL with marketplace: ${res.status}`));
124
- }
125
- else {
126
- console.log(chalk.dim(' Registered with marketplace ✓'));
127
- }
128
- }
129
- catch (err) {
130
- console.warn(chalk.yellow(`⚠ Could not register URL with marketplace: ${String(err)}`));
131
- }
132
- }
133
- // ---------------------------------------------------------------------------
134
37
  // Command
135
38
  // ---------------------------------------------------------------------------
136
- export const serveCommand = new Command('serve')
137
- .description('Start a local A2A server for your agent')
138
- .option('--port <port>', 'Port to listen on', '4000')
139
- .option('--url <url>', 'Public URL (e.g. from ngrok) shown in logs and AgentCard')
140
- .option('--openclaw-port <port>', 'OpenClaw gateway port', '18789')
141
- .option('--openclaw-token <token>', 'OpenClaw gateway auth token (or set OPENCLAW_GATEWAY_TOKEN)')
142
- .option('--openclaw-agent <id>', 'OpenClaw agent ID to target', 'main')
143
- .option('--verbose', 'Enable verbose request/response logging')
39
+ export const serveCommand = new Command("serve")
40
+ .description("Start a local A2A server for your agent")
41
+ .option("--port <port>", "Port to listen on", "4000")
42
+ .option("--url <url>", "Public URL (e.g. from ngrok) shown in logs and AgentCard")
43
+ .option("--client-id <id>", "Developer app client ID (or set WEB42_CLIENT_ID)")
44
+ .option("--client-secret <secret>", "Developer app client secret (or set WEB42_CLIENT_SECRET)")
45
+ .option("--verbose", "Enable verbose request/response logging")
144
46
  .action(async (opts) => {
145
47
  const verbose = opts.verbose ?? false;
146
- // 1. Must be logged into web42
147
48
  let token;
148
49
  try {
149
50
  const authConfig = requireAuth();
150
51
  token = authConfig.token;
151
52
  }
152
53
  catch {
153
- console.error(chalk.red('Not authenticated. Run `web42 auth login` first.'));
154
- process.exit(1);
155
- }
156
- const cwd = process.cwd();
157
- const port = parseInt(opts.port, 10);
158
- const openClawPort = parseInt(opts.openclawPort, 10);
159
- const openClawToken = opts.openclawToken ?? process.env.OPENCLAW_GATEWAY_TOKEN ?? '';
160
- const openClawAgent = opts.openclawAgent;
161
- const publicUrl = opts.url;
162
- // 2. Read manifest.json
163
- const manifestPath = join(cwd, 'manifest.json');
164
- if (!existsSync(manifestPath)) {
165
- console.error(chalk.red('No manifest.json found in current directory.'));
166
- console.error(chalk.dim('Run `web42 init` to create one.'));
167
- process.exit(1);
168
- }
169
- let manifest;
170
- try {
171
- manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
172
- }
173
- catch {
174
- console.error(chalk.red('Failed to parse manifest.json.'));
54
+ console.error(chalk.red("Not authenticated. Run `web42 auth login` first."));
175
55
  process.exit(1);
176
56
  }
177
- if (!manifest.name) {
178
- console.error(chalk.red('manifest.json must have a "name" field.'));
57
+ const clientId = opts.clientId ?? process.env.WEB42_CLIENT_ID;
58
+ const clientSecret = opts.clientSecret ?? process.env.WEB42_CLIENT_SECRET;
59
+ if (!clientId || !clientSecret) {
60
+ console.error(chalk.red("Developer app credentials required.\n" +
61
+ " Provide --client-id and --client-secret flags,\n" +
62
+ " or set WEB42_CLIENT_ID and WEB42_CLIENT_SECRET env vars.\n" +
63
+ " Create them at: https://web42.ai/settings/developer-apps"));
179
64
  process.exit(1);
180
65
  }
66
+ const port = parseInt(opts.port, 10);
67
+ const publicUrl = opts.url;
181
68
  const config = getConfig();
182
- const web42ApiUrl = config.apiUrl ?? 'https://web42.ai';
183
- const spinner = ora('Starting A2A server...').start();
184
- // 3. Build AgentCard from manifest
69
+ const web42ApiUrl = config.apiUrl ?? "https://web42.ai";
70
+ const agentName = "Local Agent";
71
+ const agentDescription = "";
72
+ const agentVersion = "1.0.0";
73
+ const skills = [];
74
+ const spinner = ora("Starting A2A server...").start();
185
75
  const agentCard = {
186
- name: manifest.name,
187
- description: manifest.description ?? '',
188
- protocolVersion: '0.3.0',
189
- version: manifest.version ?? '1.0.0',
76
+ name: agentName,
77
+ description: agentDescription,
78
+ protocolVersion: "0.3.0",
79
+ version: agentVersion,
190
80
  url: `${publicUrl ?? `http://localhost:${port}`}/a2a/jsonrpc`,
191
- skills: (manifest.skills ?? []).map((s) => ({
192
- id: s.name.toLowerCase().replace(/\s+/g, '-'),
193
- name: s.name,
194
- description: s.description ?? '',
195
- tags: [],
196
- })),
81
+ skills,
197
82
  capabilities: {
198
83
  streaming: true,
199
84
  pushNotifications: false,
200
85
  },
201
- defaultInputModes: ['text'],
202
- defaultOutputModes: ['text'],
86
+ defaultInputModes: ["text"],
87
+ defaultOutputModes: ["text"],
203
88
  securitySchemes: {
204
- Web42Bearer: { type: 'http', scheme: 'bearer' },
89
+ Web42Bearer: { type: "http", scheme: "bearer" },
205
90
  },
206
91
  security: [{ Web42Bearer: [] }],
207
92
  };
208
- // 4. Start Express server
209
93
  const app = express();
210
- // Auth: validate caller's web42 Bearer token against marketplace introspect endpoint
94
+ // Auth: validate caller's Bearer token via Web42 introspection with Basic auth
95
+ const basicAuth = `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString("base64")}`;
211
96
  const userBuilder = async (req) => {
212
- const callerToken = req.headers.authorization?.split(' ')[1];
97
+ const callerToken = req.headers.authorization?.split(" ")[1];
213
98
  if (!callerToken)
214
- throw new Error('Missing token');
99
+ throw new Error("Missing token");
215
100
  if (verbose) {
216
- const masked = `${callerToken.slice(0, 8)}...`;
217
- console.log(chalk.gray(`[verbose] userBuilder: token=${masked} → introspecting...`));
101
+ console.log(chalk.gray(`[verbose] Introspecting token ${callerToken.slice(0, 8)}...`));
218
102
  }
219
103
  const res = await fetch(`${web42ApiUrl}/api/auth/introspect`, {
220
- method: 'POST',
221
- headers: { 'Content-Type': 'application/json' },
222
- body: JSON.stringify({ token: callerToken }),
104
+ method: "POST",
105
+ headers: {
106
+ Authorization: basicAuth,
107
+ "Content-Type": "application/x-www-form-urlencoded",
108
+ },
109
+ body: new URLSearchParams({ token: callerToken }),
223
110
  });
224
111
  if (!res.ok)
225
- throw new Error('Introspect call failed');
112
+ throw new Error("Introspect call failed");
226
113
  const result = (await res.json());
227
114
  if (verbose) {
228
- console.log(chalk.gray(`[verbose] userBuilder: token=${callerToken.slice(0, 8)}... → active=${result.active} sub=${result.sub ?? '(none)'}`));
115
+ console.log(chalk.gray(`[verbose] active=${result.active} sub=${result.sub ?? "(none)"}`));
229
116
  }
230
117
  if (!result.active)
231
- throw new Error('Unauthorized');
232
- const userId = result.sub ?? '';
118
+ throw new Error("Unauthorized");
233
119
  return {
234
120
  get isAuthenticated() { return true; },
235
- get userName() { return userId; },
121
+ get userName() { return result.sub ?? ""; },
236
122
  };
237
123
  };
238
- const executor = new OpenClawAgentExecutor({ openClawPort, openClawToken, openClawAgent, verbose });
124
+ const executor = new EchoExecutor();
239
125
  const requestHandler = new DefaultRequestHandler(agentCard, new InMemoryTaskStore(), executor);
240
- // 5. Mount A2A SDK handlers
241
- app.use('/.well-known/agent-card.json', agentCardHandler({ agentCardProvider: requestHandler }));
242
- app.use('/a2a/jsonrpc', jsonRpcHandler({ requestHandler, userBuilder }));
126
+ app.use("/.well-known/agent-card.json", agentCardHandler({ agentCardProvider: requestHandler }));
127
+ app.use("/a2a/jsonrpc", jsonRpcHandler({ requestHandler, userBuilder }));
243
128
  const a2aUrl = `${publicUrl ?? `http://localhost:${port}`}/a2a/jsonrpc`;
244
- // 6. Start listening
129
+ // Auto-register agent with directory
130
+ const registrationUrl = opts.url ?? `http://localhost:${port}`;
131
+ try {
132
+ await fetch(`${web42ApiUrl}/api/agents`, {
133
+ method: "POST",
134
+ headers: {
135
+ Authorization: `Bearer ${token}`,
136
+ "Content-Type": "application/json",
137
+ },
138
+ body: JSON.stringify({ url: registrationUrl }),
139
+ });
140
+ console.log(chalk.dim(" Pre-registered with marketplace"));
141
+ }
142
+ catch {
143
+ console.warn(chalk.yellow(" Could not pre-register with marketplace"));
144
+ }
245
145
  app.listen(port, async () => {
246
146
  spinner.stop();
247
- console.log(chalk.green(`\n Agent "${manifest.name}" is live`));
248
- console.log(chalk.dim(` Local: http://localhost:${port}`));
147
+ console.log(chalk.green(`\n Agent "${agentName}" is live`));
148
+ console.log(chalk.dim(` Local: http://localhost:${port}`));
249
149
  if (publicUrl)
250
- console.log(chalk.dim(` Public: ${publicUrl}`));
150
+ console.log(chalk.dim(` Public: ${publicUrl}`));
251
151
  console.log(chalk.dim(` Agent card: http://localhost:${port}/.well-known/agent-card.json`));
252
152
  console.log(chalk.dim(` JSON-RPC: http://localhost:${port}/a2a/jsonrpc`));
253
- if (verbose) {
254
- console.log(chalk.gray(`[verbose] agent card url: http://localhost:${port}/.well-known/agent-card.json`));
255
- console.log(chalk.gray(`[verbose] openclaw target: http://localhost:${openClawPort}/v1/chat/completions agent=${openClawAgent}`));
153
+ // Register live URL with marketplace
154
+ try {
155
+ await fetch(`${web42ApiUrl}/api/agents/${agentName}/a2a`, {
156
+ method: "POST",
157
+ headers: {
158
+ Authorization: `Bearer ${token}`,
159
+ "Content-Type": "application/json",
160
+ },
161
+ body: JSON.stringify({
162
+ a2a_url: a2aUrl,
163
+ a2a_enabled: true,
164
+ gateway_status: "live",
165
+ }),
166
+ });
167
+ console.log(chalk.dim(" Registered with marketplace"));
256
168
  }
257
- await publishLiveUrl({
258
- apiUrl: web42ApiUrl,
259
- token,
260
- slug: manifest.name,
261
- a2aUrl,
262
- enabled: true,
263
- gatewayStatus: 'live',
264
- });
265
- if (!publicUrl) {
266
- console.log(chalk.yellow(' ⚠ No --url provided. Registered localhost URL is not publicly reachable; buyers cannot connect.'));
169
+ catch {
170
+ console.warn(chalk.yellow(" Could not register with marketplace"));
267
171
  }
268
- console.log(chalk.dim('\nWaiting for requests... (Ctrl+C to stop)\n'));
172
+ console.log(chalk.dim("\nWaiting for requests... (Ctrl+C to stop)\n"));
269
173
  });
270
- // 7. Keep process alive
271
- process.on('SIGINT', async () => {
272
- console.log(chalk.dim('\nShutting down...'));
273
- await fetch(`${web42ApiUrl}/api/agents/${manifest.name}/a2a`, {
274
- method: 'POST',
275
- headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
276
- body: JSON.stringify({ a2a_url: null, a2a_enabled: false, gateway_status: 'offline' }),
277
- }).catch(() => { }); // best-effort
174
+ process.on("SIGINT", async () => {
175
+ console.log(chalk.dim("\nShutting down..."));
176
+ await fetch(`${web42ApiUrl}/api/agents/${agentName}/a2a`, {
177
+ method: "POST",
178
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
179
+ body: JSON.stringify({ a2a_url: null, a2a_enabled: false, gateway_status: "offline" }),
180
+ }).catch(() => { });
278
181
  process.exit(0);
279
182
  });
280
183
  });