create-openagent 0.1.0
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/CHANGELOG.md +14 -0
- package/LICENSE-APACHE +100 -0
- package/LICENSE-MIT +21 -0
- package/README.md +40 -0
- package/dist/index.js +1412 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1412 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { intro, outro, isCancel as isCancel2 } from "@clack/prompts";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
|
|
7
|
+
// src/prompts.ts
|
|
8
|
+
import * as p from "@clack/prompts";
|
|
9
|
+
function parseFlagValue(args, flag) {
|
|
10
|
+
const idx = args.indexOf(flag);
|
|
11
|
+
if (idx >= 0 && idx + 1 < args.length) {
|
|
12
|
+
return args[idx + 1];
|
|
13
|
+
}
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
function hasFlag(args, flag) {
|
|
17
|
+
return args.includes(flag);
|
|
18
|
+
}
|
|
19
|
+
async function collectOptions(projectNameArg, args) {
|
|
20
|
+
const projectName = projectNameArg ?? await p.text({
|
|
21
|
+
message: "Project name:",
|
|
22
|
+
placeholder: "my-agent-app",
|
|
23
|
+
validate: (value) => {
|
|
24
|
+
if (!value.trim()) return "Project name is required.";
|
|
25
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(value)) {
|
|
26
|
+
return "Invalid project name (use alphanumeric, dashes, dots, underscores).";
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
if (p.isCancel(projectName)) throw new Error("Cancelled");
|
|
32
|
+
const templateFlag = parseFlagValue(args, "--template");
|
|
33
|
+
const template = templateFlag ?? await p.select({
|
|
34
|
+
message: "What type of agent app?",
|
|
35
|
+
options: [
|
|
36
|
+
{ value: "agent", label: "Agent", hint: "Standalone agent with identity and tools" },
|
|
37
|
+
{ value: "mcp-server", label: "MCP Server", hint: "Model Context Protocol server with auth" },
|
|
38
|
+
{ value: "web-dashboard", label: "Web Dashboard", hint: "Next.js dashboard for agent management" },
|
|
39
|
+
{ value: "full-stack", label: "Full Stack", hint: "Agent + MCP + Dashboard combined" }
|
|
40
|
+
]
|
|
41
|
+
});
|
|
42
|
+
if (p.isCancel(template)) throw new Error("Cancelled");
|
|
43
|
+
const adapterFlag = parseFlagValue(args, "--adapter");
|
|
44
|
+
const adapters = adapterFlag ? [adapterFlag] : await p.multiselect({
|
|
45
|
+
message: "Authentication adapters?",
|
|
46
|
+
options: [
|
|
47
|
+
{ value: "http", label: "HTTP", hint: "Bearer token / challenge-response" },
|
|
48
|
+
{ value: "websocket", label: "WebSocket", hint: "Persistent connection auth" },
|
|
49
|
+
{ value: "grpc", label: "gRPC", hint: "Protobuf-based auth" }
|
|
50
|
+
],
|
|
51
|
+
initialValues: ["http"],
|
|
52
|
+
required: true
|
|
53
|
+
});
|
|
54
|
+
if (p.isCancel(adapters)) throw new Error("Cancelled");
|
|
55
|
+
const bridgeFlag = parseFlagValue(args, "--bridge");
|
|
56
|
+
const bridge = bridgeFlag ?? await p.select({
|
|
57
|
+
message: "Enterprise bridge?",
|
|
58
|
+
options: [
|
|
59
|
+
{ value: "none", label: "None", hint: "Skip enterprise integration" },
|
|
60
|
+
{ value: "oidc", label: "OIDC", hint: "OpenID Connect bridge for human SSO" },
|
|
61
|
+
{ value: "scim", label: "SCIM", hint: "User provisioning bridge" }
|
|
62
|
+
]
|
|
63
|
+
});
|
|
64
|
+
if (p.isCancel(bridge)) throw new Error("Cancelled");
|
|
65
|
+
const deployFlag = parseFlagValue(args, "--deploy");
|
|
66
|
+
const deploy = deployFlag ?? await p.select({
|
|
67
|
+
message: "Deploy target?",
|
|
68
|
+
options: [
|
|
69
|
+
{ value: "local", label: "Local", hint: "Run locally with Node.js" },
|
|
70
|
+
{ value: "cloudflare", label: "Cloudflare Workers", hint: "Edge-first deployment" },
|
|
71
|
+
{ value: "vercel", label: "Vercel Edge", hint: "Next.js-optimized deployment" },
|
|
72
|
+
{ value: "aws", label: "AWS Lambda", hint: "Serverless with API Gateway" },
|
|
73
|
+
{ value: "docker", label: "Docker", hint: "Container deployment" }
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
if (p.isCancel(deploy)) throw new Error("Cancelled");
|
|
77
|
+
const bioagentic = hasFlag(args, "--bioagentic") ? true : await p.confirm({
|
|
78
|
+
message: "Include Bioagentic entity classification?",
|
|
79
|
+
initialValue: false
|
|
80
|
+
});
|
|
81
|
+
if (p.isCancel(bioagentic)) throw new Error("Cancelled");
|
|
82
|
+
const fabrics = hasFlag(args, "--fabrics") ? true : await p.confirm({
|
|
83
|
+
message: "Include Fabrics routing?",
|
|
84
|
+
initialValue: false
|
|
85
|
+
});
|
|
86
|
+
if (p.isCancel(fabrics)) throw new Error("Cancelled");
|
|
87
|
+
return {
|
|
88
|
+
projectName: typeof projectName === "string" ? projectName : "my-agent-app",
|
|
89
|
+
template,
|
|
90
|
+
adapters,
|
|
91
|
+
bridge,
|
|
92
|
+
deploy,
|
|
93
|
+
bioagentic,
|
|
94
|
+
fabrics
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/scaffold.ts
|
|
99
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
100
|
+
import { join, dirname } from "path";
|
|
101
|
+
import { spinner } from "@clack/prompts";
|
|
102
|
+
|
|
103
|
+
// src/templates/agent.ts
|
|
104
|
+
function generateAgentTemplate(options) {
|
|
105
|
+
return [
|
|
106
|
+
{ path: "package.json", content: packageJson(options) },
|
|
107
|
+
{ path: "src/index.ts", content: indexTs() },
|
|
108
|
+
{ path: "src/agent.ts", content: agentTs(options) },
|
|
109
|
+
{ path: "src/tools.ts", content: toolsTs() },
|
|
110
|
+
{ path: "README.md", content: readme(options) }
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
function packageJson(options) {
|
|
114
|
+
const deps = {
|
|
115
|
+
"@openagentid/sdk": "^0.1.0"
|
|
116
|
+
};
|
|
117
|
+
if (options.bioagentic) {
|
|
118
|
+
deps["@openagentid/bioagentic"] = "^0.1.0";
|
|
119
|
+
}
|
|
120
|
+
if (options.fabrics) {
|
|
121
|
+
deps["@openagentid/fabrics"] = "^0.1.0";
|
|
122
|
+
}
|
|
123
|
+
return JSON.stringify(
|
|
124
|
+
{
|
|
125
|
+
name: options.projectName,
|
|
126
|
+
version: "0.1.0",
|
|
127
|
+
private: true,
|
|
128
|
+
type: "module",
|
|
129
|
+
scripts: {
|
|
130
|
+
build: "tsup src/index.ts --format esm --target node20",
|
|
131
|
+
start: "node dist/index.js",
|
|
132
|
+
dev: "tsx src/index.ts",
|
|
133
|
+
typecheck: "tsc --noEmit"
|
|
134
|
+
},
|
|
135
|
+
dependencies: deps,
|
|
136
|
+
devDependencies: {
|
|
137
|
+
"@types/node": "^20.11.30",
|
|
138
|
+
tsup: "^8.0.2",
|
|
139
|
+
tsx: "^4.7.0",
|
|
140
|
+
typescript: "^5.4.5"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
null,
|
|
144
|
+
2
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
function indexTs() {
|
|
148
|
+
return `/**
|
|
149
|
+
* Entry point \u2014 creates the agent and runs it.
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
import { createAgent } from './agent.js';
|
|
153
|
+
import { tools } from './tools.js';
|
|
154
|
+
|
|
155
|
+
async function main(): Promise<void> {
|
|
156
|
+
const agent = await createAgent();
|
|
157
|
+
|
|
158
|
+
process.stdout.write(\`Agent created: \${agent.did}\\n\`);
|
|
159
|
+
process.stdout.write(\`Available tools: \${tools.map((t) => t.name).join(', ')}\\n\`);
|
|
160
|
+
|
|
161
|
+
// Example: fetch credentials for a provider
|
|
162
|
+
// const creds = await agent.credentialsFor('openai');
|
|
163
|
+
// const res = await creds.fetch('https://api.openai.com/v1/chat/completions', { ... });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
main().catch((err) => {
|
|
167
|
+
process.stderr.write(\`Fatal: \${err instanceof Error ? err.message : String(err)}\\n\`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
});
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
function agentTs(options) {
|
|
173
|
+
return `/**
|
|
174
|
+
* Agent identity and authentication setup.
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
import { OpenAgent, type OpenAgentInstance } from '@openagentid/sdk';
|
|
178
|
+
|
|
179
|
+
/** Configure the OpenAgent runtime. */
|
|
180
|
+
OpenAgent.configure({
|
|
181
|
+
endpoints: {
|
|
182
|
+
oasResolver: process.env.OAS_RESOLVER_URL,
|
|
183
|
+
arsenalBroker: process.env.ARSENAL_BROKER_URL,
|
|
184
|
+
aegisVerifier: process.env.AEGIS_VERIFIER_URL,
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create a new agent identity.
|
|
190
|
+
*
|
|
191
|
+
* In production, replace the parent DID and scopes with your own.
|
|
192
|
+
*/
|
|
193
|
+
export async function createAgent(): Promise<OpenAgentInstance> {
|
|
194
|
+
const parentDid = process.env.OPENAGENT_PARENT_DID ?? 'did:oas:l1fe:hmr:alice';
|
|
195
|
+
const agentName = process.env.OPENAGENT_AGENT_NAME ?? '${options.projectName}';
|
|
196
|
+
|
|
197
|
+
return OpenAgent.createAgent({
|
|
198
|
+
parent: parentDid,
|
|
199
|
+
name: agentName,
|
|
200
|
+
scopes: ['openai:chat:completions', 'github:repo:read'],
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
`;
|
|
204
|
+
}
|
|
205
|
+
function toolsTs() {
|
|
206
|
+
return `/**
|
|
207
|
+
* Example MCP tools \u2014 define your agent's capabilities here.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
export interface Tool {
|
|
211
|
+
readonly name: string;
|
|
212
|
+
readonly description: string;
|
|
213
|
+
execute(input: Record<string, unknown>): Promise<unknown>;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Echo tool \u2014 returns the input as-is. Useful for testing. */
|
|
217
|
+
const echoTool: Tool = {
|
|
218
|
+
name: 'echo',
|
|
219
|
+
description: 'Returns the input unchanged.',
|
|
220
|
+
async execute(input) {
|
|
221
|
+
return { echoed: input };
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/** Timestamp tool \u2014 returns the current ISO timestamp. */
|
|
226
|
+
const timestampTool: Tool = {
|
|
227
|
+
name: 'timestamp',
|
|
228
|
+
description: 'Returns the current UTC timestamp.',
|
|
229
|
+
async execute() {
|
|
230
|
+
return { timestamp: new Date().toISOString() };
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export const tools: readonly Tool[] = [echoTool, timestampTool];
|
|
235
|
+
`;
|
|
236
|
+
}
|
|
237
|
+
function readme(options) {
|
|
238
|
+
return `# ${options.projectName}
|
|
239
|
+
|
|
240
|
+
An OpenAgent-powered agent application.
|
|
241
|
+
|
|
242
|
+
## Setup
|
|
243
|
+
|
|
244
|
+
\`\`\`bash
|
|
245
|
+
npm install
|
|
246
|
+
cp .env.example .env
|
|
247
|
+
# Edit .env with your agent identity
|
|
248
|
+
\`\`\`
|
|
249
|
+
|
|
250
|
+
## Development
|
|
251
|
+
|
|
252
|
+
\`\`\`bash
|
|
253
|
+
npm run dev
|
|
254
|
+
\`\`\`
|
|
255
|
+
|
|
256
|
+
## Build & Run
|
|
257
|
+
|
|
258
|
+
\`\`\`bash
|
|
259
|
+
npm run build
|
|
260
|
+
npm start
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
## Configuration
|
|
264
|
+
|
|
265
|
+
Set environment variables in \`.env\`:
|
|
266
|
+
|
|
267
|
+
- \`OPENAGENT_PARENT_DID\` \u2014 Your human root DID
|
|
268
|
+
- \`OPENAGENT_AGENT_NAME\` \u2014 Agent name
|
|
269
|
+
- \`OAS_RESOLVER_URL\` \u2014 OAS identity resolver (optional for dev)
|
|
270
|
+
- \`ARSENAL_BROKER_URL\` \u2014 Arsenal credential broker (optional for dev)
|
|
271
|
+
- \`AEGIS_VERIFIER_URL\` \u2014 AEGIS verifier (optional for dev)
|
|
272
|
+
|
|
273
|
+
## Learn More
|
|
274
|
+
|
|
275
|
+
- [OpenAgent Docs](https://openagent.id/docs)
|
|
276
|
+
- [OAS Specification](https://openagent.id/spec)
|
|
277
|
+
`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/templates/mcp-server.ts
|
|
281
|
+
function generateMcpServerTemplate(options) {
|
|
282
|
+
return [
|
|
283
|
+
{ path: "package.json", content: packageJson2(options) },
|
|
284
|
+
{ path: "src/index.ts", content: indexTs2() },
|
|
285
|
+
{ path: "src/server.ts", content: serverTs(options) },
|
|
286
|
+
{ path: "src/tools/echo.ts", content: echoToolTs() },
|
|
287
|
+
{ path: "src/tools/index.ts", content: toolsIndexTs() },
|
|
288
|
+
{ path: "README.md", content: readme2(options) }
|
|
289
|
+
];
|
|
290
|
+
}
|
|
291
|
+
function packageJson2(options) {
|
|
292
|
+
const deps = {
|
|
293
|
+
"@openagentid/sdk": "^0.1.0",
|
|
294
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
295
|
+
};
|
|
296
|
+
if (options.bioagentic) {
|
|
297
|
+
deps["@openagentid/bioagentic"] = "^0.1.0";
|
|
298
|
+
}
|
|
299
|
+
if (options.fabrics) {
|
|
300
|
+
deps["@openagentid/fabrics"] = "^0.1.0";
|
|
301
|
+
}
|
|
302
|
+
return JSON.stringify(
|
|
303
|
+
{
|
|
304
|
+
name: options.projectName,
|
|
305
|
+
version: "0.1.0",
|
|
306
|
+
private: true,
|
|
307
|
+
type: "module",
|
|
308
|
+
scripts: {
|
|
309
|
+
build: "tsup src/index.ts --format esm --target node20",
|
|
310
|
+
start: "node dist/index.js",
|
|
311
|
+
dev: "tsx src/index.ts",
|
|
312
|
+
typecheck: "tsc --noEmit"
|
|
313
|
+
},
|
|
314
|
+
dependencies: deps,
|
|
315
|
+
devDependencies: {
|
|
316
|
+
"@types/node": "^20.11.30",
|
|
317
|
+
tsup: "^8.0.2",
|
|
318
|
+
tsx: "^4.7.0",
|
|
319
|
+
typescript: "^5.4.5"
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
null,
|
|
323
|
+
2
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
function indexTs2() {
|
|
327
|
+
return `/**
|
|
328
|
+
* Entry point \u2014 starts the MCP server.
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
import { startServer } from './server.js';
|
|
332
|
+
|
|
333
|
+
startServer().catch((err) => {
|
|
334
|
+
process.stderr.write(\`Fatal: \${err instanceof Error ? err.message : String(err)}\\n\`);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
});
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
function serverTs(options) {
|
|
340
|
+
return `/**
|
|
341
|
+
* MCP Server with OpenAgent authentication middleware.
|
|
342
|
+
*
|
|
343
|
+
* Authenticates incoming tool calls using the OpenAgent SDK before
|
|
344
|
+
* dispatching to tool handlers.
|
|
345
|
+
*/
|
|
346
|
+
|
|
347
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
348
|
+
import { OpenAgent, type AuthContext } from '@openagentid/sdk';
|
|
349
|
+
import { tools } from './tools/index.js';
|
|
350
|
+
|
|
351
|
+
const PORT = parseInt(process.env.PORT ?? '3200', 10);
|
|
352
|
+
const HOST = process.env.HOST ?? '0.0.0.0';
|
|
353
|
+
|
|
354
|
+
/** Configure OpenAgent runtime. */
|
|
355
|
+
OpenAgent.configure({
|
|
356
|
+
endpoints: {
|
|
357
|
+
oasResolver: process.env.OAS_RESOLVER_URL,
|
|
358
|
+
arsenalBroker: process.env.ARSENAL_BROKER_URL,
|
|
359
|
+
aegisVerifier: process.env.AEGIS_VERIFIER_URL,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
function sendJson(res: ServerResponse, body: unknown, status = 200): void {
|
|
364
|
+
const payload = JSON.stringify(body);
|
|
365
|
+
res.writeHead(status, {
|
|
366
|
+
'Content-Type': 'application/json',
|
|
367
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
368
|
+
});
|
|
369
|
+
res.end(payload);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function readBody(req: IncomingMessage): Promise<string> {
|
|
373
|
+
const chunks: Buffer[] = [];
|
|
374
|
+
for await (const chunk of req) {
|
|
375
|
+
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
|
376
|
+
}
|
|
377
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/** Convert IncomingMessage to WHATWG Request. */
|
|
381
|
+
function toRequest(req: IncomingMessage, body: string): Request {
|
|
382
|
+
const host = req.headers.host ?? \`\${HOST}:\${PORT}\`;
|
|
383
|
+
const url = \`http://\${host}\${req.url ?? '/'}\`;
|
|
384
|
+
const headers = new Headers();
|
|
385
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
386
|
+
if (value !== undefined) {
|
|
387
|
+
headers.set(key, Array.isArray(value) ? value.join(', ') : value);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const method = req.method ?? 'GET';
|
|
391
|
+
const hasBody = method !== 'GET' && method !== 'HEAD' && body.length > 0;
|
|
392
|
+
return new Request(url, { method, headers, body: hasBody ? body : undefined });
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Authenticate the request and return the auth context, or null. */
|
|
396
|
+
async function authenticate(
|
|
397
|
+
req: IncomingMessage,
|
|
398
|
+
body: string,
|
|
399
|
+
): Promise<AuthContext | null> {
|
|
400
|
+
const request = toRequest(req, body);
|
|
401
|
+
try {
|
|
402
|
+
return await OpenAgent.authenticate(request);
|
|
403
|
+
} catch {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
interface McpRequest {
|
|
409
|
+
jsonrpc: '2.0';
|
|
410
|
+
id: string | number;
|
|
411
|
+
method: string;
|
|
412
|
+
params?: Record<string, unknown>;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function handleMcpRequest(
|
|
416
|
+
mcpReq: McpRequest,
|
|
417
|
+
_authCtx: AuthContext,
|
|
418
|
+
): Promise<unknown> {
|
|
419
|
+
if (mcpReq.method === 'tools/list') {
|
|
420
|
+
return {
|
|
421
|
+
jsonrpc: '2.0',
|
|
422
|
+
id: mcpReq.id,
|
|
423
|
+
result: {
|
|
424
|
+
tools: tools.map((t) => ({
|
|
425
|
+
name: t.name,
|
|
426
|
+
description: t.description,
|
|
427
|
+
inputSchema: t.inputSchema,
|
|
428
|
+
})),
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (mcpReq.method === 'tools/call') {
|
|
434
|
+
const toolName = (mcpReq.params?.name as string) ?? '';
|
|
435
|
+
const args = (mcpReq.params?.arguments as Record<string, unknown>) ?? {};
|
|
436
|
+
const tool = tools.find((t) => t.name === toolName);
|
|
437
|
+
|
|
438
|
+
if (!tool) {
|
|
439
|
+
return {
|
|
440
|
+
jsonrpc: '2.0',
|
|
441
|
+
id: mcpReq.id,
|
|
442
|
+
error: { code: -32601, message: \`Tool not found: \${toolName}\` },
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const result = await tool.execute(args);
|
|
447
|
+
return {
|
|
448
|
+
jsonrpc: '2.0',
|
|
449
|
+
id: mcpReq.id,
|
|
450
|
+
result: { content: [{ type: 'text', text: JSON.stringify(result) }] },
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
jsonrpc: '2.0',
|
|
456
|
+
id: mcpReq.id,
|
|
457
|
+
error: { code: -32601, message: \`Method not found: \${mcpReq.method}\` },
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function startServer(): Promise<void> {
|
|
462
|
+
const server = createServer(async (req, res) => {
|
|
463
|
+
const url = new URL(req.url ?? '/', \`http://\${req.headers.host ?? 'localhost'}\`);
|
|
464
|
+
|
|
465
|
+
if (url.pathname === '/health') {
|
|
466
|
+
sendJson(res, { status: 'ok', service: '${options.projectName}' });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (url.pathname === '/.well-known/openagent/agent.json') {
|
|
471
|
+
sendJson(res, {
|
|
472
|
+
did: \`did:oas:l1fe:agent:\${process.env.OPENAGENT_AGENT_NAME ?? '${options.projectName}'}\`,
|
|
473
|
+
name: process.env.OPENAGENT_AGENT_NAME ?? '${options.projectName}',
|
|
474
|
+
parent: process.env.OPENAGENT_PARENT_DID ?? 'did:oas:l1fe:hmr:unconfigured',
|
|
475
|
+
capabilities: { tools: tools.map((t) => t.name) },
|
|
476
|
+
});
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (req.method !== 'POST') {
|
|
481
|
+
sendJson(res, { error: 'Method not allowed' }, 405);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const body = await readBody(req);
|
|
486
|
+
const authCtx = await authenticate(req, body);
|
|
487
|
+
|
|
488
|
+
if (!authCtx) {
|
|
489
|
+
sendJson(res, { error: 'Authentication required' }, 401);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const mcpReq = JSON.parse(body) as McpRequest;
|
|
495
|
+
const result = await handleMcpRequest(mcpReq, authCtx);
|
|
496
|
+
sendJson(res, result);
|
|
497
|
+
} catch (err) {
|
|
498
|
+
const message = err instanceof Error ? err.message : 'Internal error';
|
|
499
|
+
sendJson(res, { jsonrpc: '2.0', id: null, error: { code: -32700, message } }, 400);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
server.listen(PORT, HOST, () => {
|
|
504
|
+
process.stdout.write(\`MCP server listening on http://\${HOST}:\${PORT}\\n\`);
|
|
505
|
+
process.stdout.write(\`Tools: \${tools.map((t) => t.name).join(', ')}\\n\`);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
`;
|
|
509
|
+
}
|
|
510
|
+
function echoToolTs() {
|
|
511
|
+
return `/**
|
|
512
|
+
* Echo tool \u2014 returns the input unchanged. Useful for testing.
|
|
513
|
+
*/
|
|
514
|
+
|
|
515
|
+
import type { McpTool } from './index.js';
|
|
516
|
+
|
|
517
|
+
export const echoTool: McpTool = {
|
|
518
|
+
name: 'echo',
|
|
519
|
+
description: 'Returns the input unchanged.',
|
|
520
|
+
inputSchema: {
|
|
521
|
+
type: 'object',
|
|
522
|
+
properties: {
|
|
523
|
+
message: { type: 'string', description: 'Message to echo back.' },
|
|
524
|
+
},
|
|
525
|
+
required: ['message'],
|
|
526
|
+
},
|
|
527
|
+
async execute(input) {
|
|
528
|
+
return { echoed: input };
|
|
529
|
+
},
|
|
530
|
+
};
|
|
531
|
+
`;
|
|
532
|
+
}
|
|
533
|
+
function toolsIndexTs() {
|
|
534
|
+
return `/**
|
|
535
|
+
* Tool registry \u2014 add your tools here.
|
|
536
|
+
*/
|
|
537
|
+
|
|
538
|
+
import { echoTool } from './echo.js';
|
|
539
|
+
|
|
540
|
+
export interface McpTool {
|
|
541
|
+
readonly name: string;
|
|
542
|
+
readonly description: string;
|
|
543
|
+
readonly inputSchema: Record<string, unknown>;
|
|
544
|
+
execute(input: Record<string, unknown>): Promise<unknown>;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export const tools: readonly McpTool[] = [echoTool];
|
|
548
|
+
`;
|
|
549
|
+
}
|
|
550
|
+
function readme2(options) {
|
|
551
|
+
return `# ${options.projectName}
|
|
552
|
+
|
|
553
|
+
An MCP server with OpenAgent authentication.
|
|
554
|
+
|
|
555
|
+
## Setup
|
|
556
|
+
|
|
557
|
+
\`\`\`bash
|
|
558
|
+
npm install
|
|
559
|
+
cp .env.example .env
|
|
560
|
+
\`\`\`
|
|
561
|
+
|
|
562
|
+
## Development
|
|
563
|
+
|
|
564
|
+
\`\`\`bash
|
|
565
|
+
npm run dev
|
|
566
|
+
\`\`\`
|
|
567
|
+
|
|
568
|
+
## Adding Tools
|
|
569
|
+
|
|
570
|
+
1. Create a new file in \`src/tools/\`
|
|
571
|
+
2. Export an \`McpTool\` object
|
|
572
|
+
3. Add it to the \`tools\` array in \`src/tools/index.ts\`
|
|
573
|
+
|
|
574
|
+
## Learn More
|
|
575
|
+
|
|
576
|
+
- [OpenAgent Docs](https://openagent.id/docs)
|
|
577
|
+
- [MCP Specification](https://modelcontextprotocol.io)
|
|
578
|
+
`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/templates/web-dashboard.ts
|
|
582
|
+
function generateWebDashboardTemplate(options) {
|
|
583
|
+
return [
|
|
584
|
+
{ path: "package.json", content: packageJson3(options) },
|
|
585
|
+
{ path: "next.config.ts", content: nextConfig() },
|
|
586
|
+
{ path: "src/app/layout.tsx", content: layoutTsx(options) },
|
|
587
|
+
{ path: "src/app/page.tsx", content: pageTsx(options) },
|
|
588
|
+
{ path: "src/app/api/auth/route.ts", content: authRoute() },
|
|
589
|
+
{ path: "src/app/api/agents/route.ts", content: agentsRoute() },
|
|
590
|
+
{ path: "src/lib/openagent.ts", content: openagentLib() },
|
|
591
|
+
{ path: "src/lib/auth.ts", content: authLib(options) },
|
|
592
|
+
{ path: "README.md", content: readme3(options) }
|
|
593
|
+
];
|
|
594
|
+
}
|
|
595
|
+
function packageJson3(options) {
|
|
596
|
+
const deps = {
|
|
597
|
+
"@openagentid/sdk": "^0.1.0",
|
|
598
|
+
next: "^15.0.0",
|
|
599
|
+
react: "^19.0.0",
|
|
600
|
+
"react-dom": "^19.0.0"
|
|
601
|
+
};
|
|
602
|
+
if (options.bioagentic) {
|
|
603
|
+
deps["@openagentid/bioagentic"] = "^0.1.0";
|
|
604
|
+
}
|
|
605
|
+
return JSON.stringify(
|
|
606
|
+
{
|
|
607
|
+
name: options.projectName,
|
|
608
|
+
version: "0.1.0",
|
|
609
|
+
private: true,
|
|
610
|
+
scripts: {
|
|
611
|
+
dev: "next dev",
|
|
612
|
+
build: "next build",
|
|
613
|
+
start: "next start",
|
|
614
|
+
typecheck: "tsc --noEmit"
|
|
615
|
+
},
|
|
616
|
+
dependencies: deps,
|
|
617
|
+
devDependencies: {
|
|
618
|
+
"@types/node": "^20.11.30",
|
|
619
|
+
"@types/react": "^19.0.0",
|
|
620
|
+
"@types/react-dom": "^19.0.0",
|
|
621
|
+
typescript: "^5.4.5"
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
null,
|
|
625
|
+
2
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
function nextConfig() {
|
|
629
|
+
return `import type { NextConfig } from 'next';
|
|
630
|
+
|
|
631
|
+
const config: NextConfig = {
|
|
632
|
+
reactStrictMode: true,
|
|
633
|
+
experimental: {
|
|
634
|
+
serverActions: {
|
|
635
|
+
bodySizeLimit: '2mb',
|
|
636
|
+
},
|
|
637
|
+
},
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
export default config;
|
|
641
|
+
`;
|
|
642
|
+
}
|
|
643
|
+
function layoutTsx(options) {
|
|
644
|
+
return `import type { Metadata } from 'next';
|
|
645
|
+
import type { ReactNode } from 'react';
|
|
646
|
+
|
|
647
|
+
export const metadata: Metadata = {
|
|
648
|
+
title: '${options.projectName} \u2014 OpenAgent Dashboard',
|
|
649
|
+
description: 'Agent management dashboard powered by OpenAgent.',
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
653
|
+
return (
|
|
654
|
+
<html lang="en">
|
|
655
|
+
<body style={{ fontFamily: 'system-ui, sans-serif', margin: 0, padding: '2rem' }}>
|
|
656
|
+
<header style={{ borderBottom: '1px solid #eee', paddingBottom: '1rem', marginBottom: '2rem' }}>
|
|
657
|
+
<h1 style={{ fontSize: '1.5rem', margin: 0 }}>
|
|
658
|
+
${options.projectName}
|
|
659
|
+
</h1>
|
|
660
|
+
<p style={{ color: '#666', margin: '0.25rem 0 0' }}>OpenAgent Dashboard</p>
|
|
661
|
+
</header>
|
|
662
|
+
<main>{children}</main>
|
|
663
|
+
</body>
|
|
664
|
+
</html>
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
`;
|
|
668
|
+
}
|
|
669
|
+
function pageTsx(options) {
|
|
670
|
+
return `/**
|
|
671
|
+
* Dashboard home \u2014 shows agent status and recent activity.
|
|
672
|
+
*/
|
|
673
|
+
|
|
674
|
+
export default async function HomePage() {
|
|
675
|
+
const agentName = process.env.OPENAGENT_AGENT_NAME ?? '${options.projectName}';
|
|
676
|
+
const parentDid = process.env.OPENAGENT_PARENT_DID ?? 'did:oas:l1fe:hmr:unconfigured';
|
|
677
|
+
|
|
678
|
+
return (
|
|
679
|
+
<div>
|
|
680
|
+
<section style={{ marginBottom: '2rem' }}>
|
|
681
|
+
<h2>Agent Status</h2>
|
|
682
|
+
<table style={{ borderCollapse: 'collapse', width: '100%', maxWidth: '600px' }}>
|
|
683
|
+
<tbody>
|
|
684
|
+
<tr>
|
|
685
|
+
<td style={{ padding: '0.5rem', fontWeight: 'bold' }}>Name</td>
|
|
686
|
+
<td style={{ padding: '0.5rem' }}>{agentName}</td>
|
|
687
|
+
</tr>
|
|
688
|
+
<tr>
|
|
689
|
+
<td style={{ padding: '0.5rem', fontWeight: 'bold' }}>Parent DID</td>
|
|
690
|
+
<td style={{ padding: '0.5rem', fontFamily: 'monospace', fontSize: '0.85rem' }}>
|
|
691
|
+
{parentDid}
|
|
692
|
+
</td>
|
|
693
|
+
</tr>
|
|
694
|
+
<tr>
|
|
695
|
+
<td style={{ padding: '0.5rem', fontWeight: 'bold' }}>Status</td>
|
|
696
|
+
<td style={{ padding: '0.5rem', color: '#22c55e' }}>Active</td>
|
|
697
|
+
</tr>
|
|
698
|
+
</tbody>
|
|
699
|
+
</table>
|
|
700
|
+
</section>
|
|
701
|
+
|
|
702
|
+
<section>
|
|
703
|
+
<h2>API Endpoints</h2>
|
|
704
|
+
<ul style={{ lineHeight: 2 }}>
|
|
705
|
+
<li>
|
|
706
|
+
<code>GET /api/agents</code> \u2014 List managed agents
|
|
707
|
+
</li>
|
|
708
|
+
<li>
|
|
709
|
+
<code>POST /api/auth</code> \u2014 Authenticate agent request
|
|
710
|
+
</li>
|
|
711
|
+
<li>
|
|
712
|
+
<code>GET /.well-known/openagent/agent.json</code> \u2014 Agent discovery
|
|
713
|
+
</li>
|
|
714
|
+
</ul>
|
|
715
|
+
</section>
|
|
716
|
+
</div>
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
`;
|
|
720
|
+
}
|
|
721
|
+
function authRoute() {
|
|
722
|
+
return `/**
|
|
723
|
+
* POST /api/auth \u2014 Authenticate an incoming agent request.
|
|
724
|
+
*/
|
|
725
|
+
|
|
726
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
727
|
+
import { getRuntime } from '@/lib/openagent';
|
|
728
|
+
|
|
729
|
+
export async function POST(request: NextRequest): Promise<NextResponse> {
|
|
730
|
+
const runtime = getRuntime();
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
const ctx = await runtime.authenticate(request as unknown as Request);
|
|
734
|
+
return NextResponse.json({
|
|
735
|
+
success: true,
|
|
736
|
+
data: {
|
|
737
|
+
did: ctx.did,
|
|
738
|
+
root: ctx.root,
|
|
739
|
+
scopes: ctx.scopes,
|
|
740
|
+
expiresAt: ctx.expiresAt,
|
|
741
|
+
},
|
|
742
|
+
});
|
|
743
|
+
} catch (err) {
|
|
744
|
+
const message = err instanceof Error ? err.message : 'Authentication failed';
|
|
745
|
+
return NextResponse.json({ success: false, error: message }, { status: 401 });
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
`;
|
|
749
|
+
}
|
|
750
|
+
function agentsRoute() {
|
|
751
|
+
return `/**
|
|
752
|
+
* GET /api/agents \u2014 List managed agents.
|
|
753
|
+
* POST /api/agents \u2014 Create a new agent.
|
|
754
|
+
*/
|
|
755
|
+
|
|
756
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
757
|
+
import { getRuntime } from '@/lib/openagent';
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* In-memory agent registry for demonstration.
|
|
761
|
+
* Replace with a persistent store in production.
|
|
762
|
+
*/
|
|
763
|
+
const agents: Array<{ did: string; name: string; createdAt: string }> = [];
|
|
764
|
+
|
|
765
|
+
export async function GET(): Promise<NextResponse> {
|
|
766
|
+
return NextResponse.json({ success: true, data: agents });
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export async function POST(request: NextRequest): Promise<NextResponse> {
|
|
770
|
+
const runtime = getRuntime();
|
|
771
|
+
|
|
772
|
+
try {
|
|
773
|
+
const body = (await request.json()) as { name: string; scopes: string[] };
|
|
774
|
+
|
|
775
|
+
if (!body.name || !body.scopes?.length) {
|
|
776
|
+
return NextResponse.json(
|
|
777
|
+
{ success: false, error: 'name and scopes are required' },
|
|
778
|
+
{ status: 400 },
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
const parentDid = process.env.OPENAGENT_PARENT_DID ?? 'did:oas:l1fe:hmr:unconfigured';
|
|
783
|
+
const agent = await runtime.createAgent({
|
|
784
|
+
parent: parentDid,
|
|
785
|
+
name: body.name,
|
|
786
|
+
scopes: body.scopes,
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
const record = {
|
|
790
|
+
did: agent.did,
|
|
791
|
+
name: body.name,
|
|
792
|
+
createdAt: new Date().toISOString(),
|
|
793
|
+
};
|
|
794
|
+
agents.push(record);
|
|
795
|
+
|
|
796
|
+
return NextResponse.json({ success: true, data: record }, { status: 201 });
|
|
797
|
+
} catch (err) {
|
|
798
|
+
const message = err instanceof Error ? err.message : 'Agent creation failed';
|
|
799
|
+
return NextResponse.json({ success: false, error: message }, { status: 500 });
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
`;
|
|
803
|
+
}
|
|
804
|
+
function openagentLib() {
|
|
805
|
+
return `/**
|
|
806
|
+
* OpenAgent SDK setup \u2014 shared runtime instance.
|
|
807
|
+
*/
|
|
808
|
+
|
|
809
|
+
import { OpenAgent, type OpenAgentRuntime } from '@openagentid/sdk';
|
|
810
|
+
|
|
811
|
+
let runtime: OpenAgentRuntime | undefined;
|
|
812
|
+
|
|
813
|
+
export function getRuntime(): OpenAgentRuntime {
|
|
814
|
+
if (!runtime) {
|
|
815
|
+
runtime = OpenAgent.configure({
|
|
816
|
+
endpoints: {
|
|
817
|
+
oasResolver: process.env.OAS_RESOLVER_URL,
|
|
818
|
+
arsenalBroker: process.env.ARSENAL_BROKER_URL,
|
|
819
|
+
aegisVerifier: process.env.AEGIS_VERIFIER_URL,
|
|
820
|
+
},
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return runtime;
|
|
824
|
+
}
|
|
825
|
+
`;
|
|
826
|
+
}
|
|
827
|
+
function authLib(options) {
|
|
828
|
+
const oidcSetup = options.bridge === "oidc" ? `
|
|
829
|
+
/**
|
|
830
|
+
* OIDC configuration for human authentication.
|
|
831
|
+
* Set OIDC_* env vars to enable the bridge.
|
|
832
|
+
*/
|
|
833
|
+
export const oidcConfig = {
|
|
834
|
+
issuerUrl: process.env.OIDC_ISSUER_URL,
|
|
835
|
+
clientId: process.env.OIDC_CLIENT_ID,
|
|
836
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
|
837
|
+
redirectUri: process.env.OIDC_REDIRECT_URI ?? 'http://localhost:3000/api/auth/callback',
|
|
838
|
+
} as const;
|
|
839
|
+
|
|
840
|
+
export function isOidcConfigured(): boolean {
|
|
841
|
+
return Boolean(oidcConfig.issuerUrl && oidcConfig.clientId);
|
|
842
|
+
}
|
|
843
|
+
` : "";
|
|
844
|
+
return `/**
|
|
845
|
+
* Authentication configuration \u2014 human + agent auth.
|
|
846
|
+
*/
|
|
847
|
+
|
|
848
|
+
import { getRuntime } from './openagent.js';
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Authenticate an incoming request. Returns the agent's auth context
|
|
852
|
+
* or null if authentication fails.
|
|
853
|
+
*/
|
|
854
|
+
export async function authenticateRequest(request: Request) {
|
|
855
|
+
const runtime = getRuntime();
|
|
856
|
+
try {
|
|
857
|
+
return await runtime.authenticate(request);
|
|
858
|
+
} catch {
|
|
859
|
+
return null;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
${oidcSetup}`;
|
|
863
|
+
}
|
|
864
|
+
function readme3(options) {
|
|
865
|
+
return `# ${options.projectName}
|
|
866
|
+
|
|
867
|
+
A Next.js dashboard for managing OpenAgent-powered agents.
|
|
868
|
+
|
|
869
|
+
## Setup
|
|
870
|
+
|
|
871
|
+
\`\`\`bash
|
|
872
|
+
npm install
|
|
873
|
+
cp .env.example .env
|
|
874
|
+
\`\`\`
|
|
875
|
+
|
|
876
|
+
## Development
|
|
877
|
+
|
|
878
|
+
\`\`\`bash
|
|
879
|
+
npm run dev
|
|
880
|
+
\`\`\`
|
|
881
|
+
|
|
882
|
+
Open [http://localhost:3000](http://localhost:3000).
|
|
883
|
+
|
|
884
|
+
## API Routes
|
|
885
|
+
|
|
886
|
+
- \`GET /api/agents\` \u2014 List agents
|
|
887
|
+
- \`POST /api/agents\` \u2014 Create agent
|
|
888
|
+
- \`POST /api/auth\` \u2014 Authenticate request
|
|
889
|
+
|
|
890
|
+
## Learn More
|
|
891
|
+
|
|
892
|
+
- [OpenAgent Docs](https://openagent.id/docs)
|
|
893
|
+
- [Next.js Docs](https://nextjs.org/docs)
|
|
894
|
+
`;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/templates/full-stack.ts
|
|
898
|
+
function generateFullStackTemplate(options) {
|
|
899
|
+
return [
|
|
900
|
+
{ path: "package.json", content: packageJson4(options) },
|
|
901
|
+
{ path: "next.config.ts", content: nextConfig2() },
|
|
902
|
+
{ path: "src/app/layout.tsx", content: layoutTsx2(options) },
|
|
903
|
+
{ path: "src/app/page.tsx", content: pageTsx2(options) },
|
|
904
|
+
{ path: "src/app/api/auth/route.ts", content: authRoute2() },
|
|
905
|
+
{ path: "src/app/api/agents/route.ts", content: agentsRoute2() },
|
|
906
|
+
{ path: "src/lib/openagent.ts", content: openagentLib2() },
|
|
907
|
+
{ path: "src/lib/auth.ts", content: authLib2(options) },
|
|
908
|
+
{ path: "src/tools/echo.ts", content: echoToolTs2() },
|
|
909
|
+
{ path: "src/tools/index.ts", content: toolsIndexTs2() },
|
|
910
|
+
{ path: "README.md", content: readme4(options) }
|
|
911
|
+
];
|
|
912
|
+
}
|
|
913
|
+
function packageJson4(options) {
|
|
914
|
+
const deps = {
|
|
915
|
+
"@openagentid/sdk": "^0.1.0",
|
|
916
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
917
|
+
next: "^15.0.0",
|
|
918
|
+
react: "^19.0.0",
|
|
919
|
+
"react-dom": "^19.0.0"
|
|
920
|
+
};
|
|
921
|
+
if (options.bioagentic) {
|
|
922
|
+
deps["@openagentid/bioagentic"] = "^0.1.0";
|
|
923
|
+
}
|
|
924
|
+
if (options.fabrics) {
|
|
925
|
+
deps["@openagentid/fabrics"] = "^0.1.0";
|
|
926
|
+
}
|
|
927
|
+
return JSON.stringify(
|
|
928
|
+
{
|
|
929
|
+
name: options.projectName,
|
|
930
|
+
version: "0.1.0",
|
|
931
|
+
private: true,
|
|
932
|
+
scripts: {
|
|
933
|
+
dev: "next dev",
|
|
934
|
+
build: "next build",
|
|
935
|
+
start: "next start",
|
|
936
|
+
typecheck: "tsc --noEmit"
|
|
937
|
+
},
|
|
938
|
+
dependencies: deps,
|
|
939
|
+
devDependencies: {
|
|
940
|
+
"@types/node": "^20.11.30",
|
|
941
|
+
"@types/react": "^19.0.0",
|
|
942
|
+
"@types/react-dom": "^19.0.0",
|
|
943
|
+
typescript: "^5.4.5"
|
|
944
|
+
}
|
|
945
|
+
},
|
|
946
|
+
null,
|
|
947
|
+
2
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
function nextConfig2() {
|
|
951
|
+
return `import type { NextConfig } from 'next';
|
|
952
|
+
|
|
953
|
+
const config: NextConfig = {
|
|
954
|
+
reactStrictMode: true,
|
|
955
|
+
experimental: {
|
|
956
|
+
serverActions: {
|
|
957
|
+
bodySizeLimit: '2mb',
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
};
|
|
961
|
+
|
|
962
|
+
export default config;
|
|
963
|
+
`;
|
|
964
|
+
}
|
|
965
|
+
function layoutTsx2(options) {
|
|
966
|
+
return `import type { Metadata } from 'next';
|
|
967
|
+
import type { ReactNode } from 'react';
|
|
968
|
+
|
|
969
|
+
export const metadata: Metadata = {
|
|
970
|
+
title: '${options.projectName} \u2014 OpenAgent Full Stack',
|
|
971
|
+
description: 'Full-stack agent platform powered by OpenAgent.',
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
975
|
+
return (
|
|
976
|
+
<html lang="en">
|
|
977
|
+
<body style={{ fontFamily: 'system-ui, sans-serif', margin: 0, padding: '2rem' }}>
|
|
978
|
+
<header style={{ borderBottom: '1px solid #eee', paddingBottom: '1rem', marginBottom: '2rem' }}>
|
|
979
|
+
<h1 style={{ fontSize: '1.5rem', margin: 0 }}>
|
|
980
|
+
${options.projectName}
|
|
981
|
+
</h1>
|
|
982
|
+
<p style={{ color: '#666', margin: '0.25rem 0 0' }}>OpenAgent Full Stack</p>
|
|
983
|
+
</header>
|
|
984
|
+
<main>{children}</main>
|
|
985
|
+
</body>
|
|
986
|
+
</html>
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
`;
|
|
990
|
+
}
|
|
991
|
+
function pageTsx2(options) {
|
|
992
|
+
return `/**
|
|
993
|
+
* Dashboard home \u2014 agent status, tools, and management.
|
|
994
|
+
*/
|
|
995
|
+
|
|
996
|
+
import { tools } from '@/tools';
|
|
997
|
+
|
|
998
|
+
export default async function HomePage() {
|
|
999
|
+
const agentName = process.env.OPENAGENT_AGENT_NAME ?? '${options.projectName}';
|
|
1000
|
+
const parentDid = process.env.OPENAGENT_PARENT_DID ?? 'did:oas:l1fe:hmr:unconfigured';
|
|
1001
|
+
|
|
1002
|
+
return (
|
|
1003
|
+
<div>
|
|
1004
|
+
<section style={{ marginBottom: '2rem' }}>
|
|
1005
|
+
<h2>Agent Status</h2>
|
|
1006
|
+
<table style={{ borderCollapse: 'collapse', width: '100%', maxWidth: '600px' }}>
|
|
1007
|
+
<tbody>
|
|
1008
|
+
<tr>
|
|
1009
|
+
<td style={{ padding: '0.5rem', fontWeight: 'bold' }}>Name</td>
|
|
1010
|
+
<td style={{ padding: '0.5rem' }}>{agentName}</td>
|
|
1011
|
+
</tr>
|
|
1012
|
+
<tr>
|
|
1013
|
+
<td style={{ padding: '0.5rem', fontWeight: 'bold' }}>Parent DID</td>
|
|
1014
|
+
<td style={{ padding: '0.5rem', fontFamily: 'monospace', fontSize: '0.85rem' }}>
|
|
1015
|
+
{parentDid}
|
|
1016
|
+
</td>
|
|
1017
|
+
</tr>
|
|
1018
|
+
<tr>
|
|
1019
|
+
<td style={{ padding: '0.5rem', fontWeight: 'bold' }}>Status</td>
|
|
1020
|
+
<td style={{ padding: '0.5rem', color: '#22c55e' }}>Active</td>
|
|
1021
|
+
</tr>
|
|
1022
|
+
</tbody>
|
|
1023
|
+
</table>
|
|
1024
|
+
</section>
|
|
1025
|
+
|
|
1026
|
+
<section style={{ marginBottom: '2rem' }}>
|
|
1027
|
+
<h2>Available Tools</h2>
|
|
1028
|
+
<ul style={{ lineHeight: 2 }}>
|
|
1029
|
+
{tools.map((tool) => (
|
|
1030
|
+
<li key={tool.name}>
|
|
1031
|
+
<code>{tool.name}</code> \u2014 {tool.description}
|
|
1032
|
+
</li>
|
|
1033
|
+
))}
|
|
1034
|
+
</ul>
|
|
1035
|
+
</section>
|
|
1036
|
+
|
|
1037
|
+
<section>
|
|
1038
|
+
<h2>API Endpoints</h2>
|
|
1039
|
+
<ul style={{ lineHeight: 2 }}>
|
|
1040
|
+
<li><code>GET /api/agents</code> \u2014 List managed agents</li>
|
|
1041
|
+
<li><code>POST /api/agents</code> \u2014 Create a new agent</li>
|
|
1042
|
+
<li><code>POST /api/auth</code> \u2014 Authenticate request</li>
|
|
1043
|
+
</ul>
|
|
1044
|
+
</section>
|
|
1045
|
+
</div>
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
`;
|
|
1049
|
+
}
|
|
1050
|
+
function authRoute2() {
|
|
1051
|
+
return `/**
|
|
1052
|
+
* POST /api/auth \u2014 Authenticate an incoming agent request.
|
|
1053
|
+
*/
|
|
1054
|
+
|
|
1055
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
1056
|
+
import { getRuntime } from '@/lib/openagent';
|
|
1057
|
+
|
|
1058
|
+
export async function POST(request: NextRequest): Promise<NextResponse> {
|
|
1059
|
+
const runtime = getRuntime();
|
|
1060
|
+
|
|
1061
|
+
try {
|
|
1062
|
+
const ctx = await runtime.authenticate(request as unknown as Request);
|
|
1063
|
+
return NextResponse.json({
|
|
1064
|
+
success: true,
|
|
1065
|
+
data: {
|
|
1066
|
+
did: ctx.did,
|
|
1067
|
+
root: ctx.root,
|
|
1068
|
+
scopes: ctx.scopes,
|
|
1069
|
+
expiresAt: ctx.expiresAt,
|
|
1070
|
+
},
|
|
1071
|
+
});
|
|
1072
|
+
} catch (err) {
|
|
1073
|
+
const message = err instanceof Error ? err.message : 'Authentication failed';
|
|
1074
|
+
return NextResponse.json({ success: false, error: message }, { status: 401 });
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
`;
|
|
1078
|
+
}
|
|
1079
|
+
function agentsRoute2() {
|
|
1080
|
+
return `/**
|
|
1081
|
+
* GET /api/agents \u2014 List managed agents.
|
|
1082
|
+
* POST /api/agents \u2014 Create a new agent.
|
|
1083
|
+
*/
|
|
1084
|
+
|
|
1085
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
1086
|
+
import { getRuntime } from '@/lib/openagent';
|
|
1087
|
+
|
|
1088
|
+
const agents: Array<{ did: string; name: string; createdAt: string }> = [];
|
|
1089
|
+
|
|
1090
|
+
export async function GET(): Promise<NextResponse> {
|
|
1091
|
+
return NextResponse.json({ success: true, data: agents });
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export async function POST(request: NextRequest): Promise<NextResponse> {
|
|
1095
|
+
const runtime = getRuntime();
|
|
1096
|
+
|
|
1097
|
+
try {
|
|
1098
|
+
const body = (await request.json()) as { name: string; scopes: string[] };
|
|
1099
|
+
|
|
1100
|
+
if (!body.name || !body.scopes?.length) {
|
|
1101
|
+
return NextResponse.json(
|
|
1102
|
+
{ success: false, error: 'name and scopes are required' },
|
|
1103
|
+
{ status: 400 },
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
const parentDid = process.env.OPENAGENT_PARENT_DID ?? 'did:oas:l1fe:hmr:unconfigured';
|
|
1108
|
+
const agent = await runtime.createAgent({
|
|
1109
|
+
parent: parentDid,
|
|
1110
|
+
name: body.name,
|
|
1111
|
+
scopes: body.scopes,
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
const record = {
|
|
1115
|
+
did: agent.did,
|
|
1116
|
+
name: body.name,
|
|
1117
|
+
createdAt: new Date().toISOString(),
|
|
1118
|
+
};
|
|
1119
|
+
agents.push(record);
|
|
1120
|
+
|
|
1121
|
+
return NextResponse.json({ success: true, data: record }, { status: 201 });
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
const message = err instanceof Error ? err.message : 'Agent creation failed';
|
|
1124
|
+
return NextResponse.json({ success: false, error: message }, { status: 500 });
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
`;
|
|
1128
|
+
}
|
|
1129
|
+
function openagentLib2() {
|
|
1130
|
+
return `/**
|
|
1131
|
+
* OpenAgent SDK setup \u2014 shared runtime instance.
|
|
1132
|
+
*/
|
|
1133
|
+
|
|
1134
|
+
import { OpenAgent, type OpenAgentRuntime } from '@openagentid/sdk';
|
|
1135
|
+
|
|
1136
|
+
let runtime: OpenAgentRuntime | undefined;
|
|
1137
|
+
|
|
1138
|
+
export function getRuntime(): OpenAgentRuntime {
|
|
1139
|
+
if (!runtime) {
|
|
1140
|
+
runtime = OpenAgent.configure({
|
|
1141
|
+
endpoints: {
|
|
1142
|
+
oasResolver: process.env.OAS_RESOLVER_URL,
|
|
1143
|
+
arsenalBroker: process.env.ARSENAL_BROKER_URL,
|
|
1144
|
+
aegisVerifier: process.env.AEGIS_VERIFIER_URL,
|
|
1145
|
+
},
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
return runtime;
|
|
1149
|
+
}
|
|
1150
|
+
`;
|
|
1151
|
+
}
|
|
1152
|
+
function authLib2(options) {
|
|
1153
|
+
const oidcPart = options.bridge === "oidc" ? `
|
|
1154
|
+
|
|
1155
|
+
export const oidcConfig = {
|
|
1156
|
+
issuerUrl: process.env.OIDC_ISSUER_URL,
|
|
1157
|
+
clientId: process.env.OIDC_CLIENT_ID,
|
|
1158
|
+
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
|
1159
|
+
redirectUri: process.env.OIDC_REDIRECT_URI ?? 'http://localhost:3000/api/auth/callback',
|
|
1160
|
+
} as const;
|
|
1161
|
+
|
|
1162
|
+
export function isOidcConfigured(): boolean {
|
|
1163
|
+
return Boolean(oidcConfig.issuerUrl && oidcConfig.clientId);
|
|
1164
|
+
}
|
|
1165
|
+
` : "";
|
|
1166
|
+
return `/**
|
|
1167
|
+
* Authentication configuration \u2014 human + agent auth.
|
|
1168
|
+
*/
|
|
1169
|
+
|
|
1170
|
+
import { getRuntime } from './openagent.js';
|
|
1171
|
+
|
|
1172
|
+
export async function authenticateRequest(request: Request) {
|
|
1173
|
+
const runtime = getRuntime();
|
|
1174
|
+
try {
|
|
1175
|
+
return await runtime.authenticate(request);
|
|
1176
|
+
} catch {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
${oidcPart}`;
|
|
1181
|
+
}
|
|
1182
|
+
function echoToolTs2() {
|
|
1183
|
+
return `/**
|
|
1184
|
+
* Echo tool \u2014 returns the input unchanged. Useful for testing.
|
|
1185
|
+
*/
|
|
1186
|
+
|
|
1187
|
+
import type { McpTool } from './index.js';
|
|
1188
|
+
|
|
1189
|
+
export const echoTool: McpTool = {
|
|
1190
|
+
name: 'echo',
|
|
1191
|
+
description: 'Returns the input unchanged.',
|
|
1192
|
+
inputSchema: {
|
|
1193
|
+
type: 'object',
|
|
1194
|
+
properties: {
|
|
1195
|
+
message: { type: 'string', description: 'Message to echo back.' },
|
|
1196
|
+
},
|
|
1197
|
+
required: ['message'],
|
|
1198
|
+
},
|
|
1199
|
+
async execute(input) {
|
|
1200
|
+
return { echoed: input };
|
|
1201
|
+
},
|
|
1202
|
+
};
|
|
1203
|
+
`;
|
|
1204
|
+
}
|
|
1205
|
+
function toolsIndexTs2() {
|
|
1206
|
+
return `/**
|
|
1207
|
+
* Tool registry \u2014 add your tools here.
|
|
1208
|
+
*/
|
|
1209
|
+
|
|
1210
|
+
import { echoTool } from './echo.js';
|
|
1211
|
+
|
|
1212
|
+
export interface McpTool {
|
|
1213
|
+
readonly name: string;
|
|
1214
|
+
readonly description: string;
|
|
1215
|
+
readonly inputSchema: Record<string, unknown>;
|
|
1216
|
+
execute(input: Record<string, unknown>): Promise<unknown>;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
export const tools: readonly McpTool[] = [echoTool];
|
|
1220
|
+
`;
|
|
1221
|
+
}
|
|
1222
|
+
function readme4(options) {
|
|
1223
|
+
return `# ${options.projectName}
|
|
1224
|
+
|
|
1225
|
+
A full-stack OpenAgent application: agent identity, MCP tools, and web dashboard.
|
|
1226
|
+
|
|
1227
|
+
## Setup
|
|
1228
|
+
|
|
1229
|
+
\`\`\`bash
|
|
1230
|
+
npm install
|
|
1231
|
+
cp .env.example .env
|
|
1232
|
+
\`\`\`
|
|
1233
|
+
|
|
1234
|
+
## Development
|
|
1235
|
+
|
|
1236
|
+
\`\`\`bash
|
|
1237
|
+
npm run dev
|
|
1238
|
+
\`\`\`
|
|
1239
|
+
|
|
1240
|
+
Open [http://localhost:3000](http://localhost:3000).
|
|
1241
|
+
|
|
1242
|
+
## Structure
|
|
1243
|
+
|
|
1244
|
+
- \`src/app/\` \u2014 Next.js app directory (dashboard + API routes)
|
|
1245
|
+
- \`src/lib/\` \u2014 OpenAgent SDK setup and auth configuration
|
|
1246
|
+
- \`src/tools/\` \u2014 MCP tool implementations
|
|
1247
|
+
|
|
1248
|
+
## Adding Tools
|
|
1249
|
+
|
|
1250
|
+
1. Create a new file in \`src/tools/\`
|
|
1251
|
+
2. Export an \`McpTool\` object
|
|
1252
|
+
3. Add it to the \`tools\` array in \`src/tools/index.ts\`
|
|
1253
|
+
|
|
1254
|
+
## Learn More
|
|
1255
|
+
|
|
1256
|
+
- [OpenAgent Docs](https://openagent.id/docs)
|
|
1257
|
+
- [MCP Specification](https://modelcontextprotocol.io)
|
|
1258
|
+
- [Next.js Docs](https://nextjs.org/docs)
|
|
1259
|
+
`;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// src/templates/shared.ts
|
|
1263
|
+
function generateSharedFiles(options) {
|
|
1264
|
+
return [
|
|
1265
|
+
{ path: "tsconfig.json", content: tsconfig(options) },
|
|
1266
|
+
{ path: ".gitignore", content: gitignore() },
|
|
1267
|
+
{ path: ".env.example", content: envExample(options) }
|
|
1268
|
+
];
|
|
1269
|
+
}
|
|
1270
|
+
function tsconfig(options) {
|
|
1271
|
+
const jsx = options.template === "web-dashboard" || options.template === "full-stack";
|
|
1272
|
+
return JSON.stringify(
|
|
1273
|
+
{
|
|
1274
|
+
compilerOptions: {
|
|
1275
|
+
target: "ES2022",
|
|
1276
|
+
module: "ESNext",
|
|
1277
|
+
moduleResolution: "bundler",
|
|
1278
|
+
lib: jsx ? ["ES2022", "DOM", "DOM.Iterable"] : ["ES2022"],
|
|
1279
|
+
strict: true,
|
|
1280
|
+
noEmit: true,
|
|
1281
|
+
skipLibCheck: true,
|
|
1282
|
+
forceConsistentCasingInFileNames: true,
|
|
1283
|
+
isolatedModules: true,
|
|
1284
|
+
resolveJsonModule: true,
|
|
1285
|
+
esModuleInterop: true,
|
|
1286
|
+
...jsx ? { jsx: "preserve" } : {},
|
|
1287
|
+
...jsx ? { plugins: [{ name: "next" }] } : {},
|
|
1288
|
+
...jsx ? { paths: { "@/*": ["./src/*"] } } : {}
|
|
1289
|
+
},
|
|
1290
|
+
include: jsx ? ["src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts"] : ["src/**/*.ts"],
|
|
1291
|
+
exclude: ["node_modules", "dist"]
|
|
1292
|
+
},
|
|
1293
|
+
null,
|
|
1294
|
+
2
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
function gitignore() {
|
|
1298
|
+
return `node_modules/
|
|
1299
|
+
dist/
|
|
1300
|
+
.next/
|
|
1301
|
+
.env
|
|
1302
|
+
.env.local
|
|
1303
|
+
*.tsbuildinfo
|
|
1304
|
+
coverage/
|
|
1305
|
+
`;
|
|
1306
|
+
}
|
|
1307
|
+
function envExample(options) {
|
|
1308
|
+
const lines = [
|
|
1309
|
+
"# OpenAgent Configuration",
|
|
1310
|
+
"OPENAGENT_PARENT_DID=did:oas:l1fe:hmr:your-human-root",
|
|
1311
|
+
"OPENAGENT_AGENT_NAME=my-agent",
|
|
1312
|
+
"",
|
|
1313
|
+
"# Endpoints (optional \u2014 SDK uses stubs when omitted)",
|
|
1314
|
+
"# OAS_RESOLVER_URL=https://resolver.openagent.id",
|
|
1315
|
+
"# ARSENAL_BROKER_URL=https://broker.arsenal.id",
|
|
1316
|
+
"# AEGIS_VERIFIER_URL=https://verify.aegis.id"
|
|
1317
|
+
];
|
|
1318
|
+
if (options.bridge === "oidc") {
|
|
1319
|
+
lines.push(
|
|
1320
|
+
"",
|
|
1321
|
+
"# OIDC Bridge",
|
|
1322
|
+
"# OIDC_ISSUER_URL=https://your-idp.example.com",
|
|
1323
|
+
"# OIDC_CLIENT_ID=your-client-id",
|
|
1324
|
+
"# OIDC_CLIENT_SECRET=your-client-secret"
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
if (options.bioagentic) {
|
|
1328
|
+
lines.push("", "# Bioagentic", "# BIOAGENTIC_THRESHOLD=0.8");
|
|
1329
|
+
}
|
|
1330
|
+
lines.push("");
|
|
1331
|
+
return lines.join("\n");
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// src/scaffold.ts
|
|
1335
|
+
async function scaffold(options) {
|
|
1336
|
+
const s = spinner();
|
|
1337
|
+
s.start("Scaffolding project...");
|
|
1338
|
+
const root = join(process.cwd(), options.projectName);
|
|
1339
|
+
if (existsSync(root)) {
|
|
1340
|
+
s.stop("Directory already exists.");
|
|
1341
|
+
throw new Error(`Directory "${options.projectName}" already exists.`);
|
|
1342
|
+
}
|
|
1343
|
+
const files = [];
|
|
1344
|
+
switch (options.template) {
|
|
1345
|
+
case "agent":
|
|
1346
|
+
files.push(...generateAgentTemplate(options));
|
|
1347
|
+
break;
|
|
1348
|
+
case "mcp-server":
|
|
1349
|
+
files.push(...generateMcpServerTemplate(options));
|
|
1350
|
+
break;
|
|
1351
|
+
case "web-dashboard":
|
|
1352
|
+
files.push(...generateWebDashboardTemplate(options));
|
|
1353
|
+
break;
|
|
1354
|
+
case "full-stack":
|
|
1355
|
+
files.push(...generateFullStackTemplate(options));
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
files.push(...generateSharedFiles(options));
|
|
1359
|
+
for (const file of files) {
|
|
1360
|
+
const fullPath = join(root, file.path);
|
|
1361
|
+
const dir = dirname(fullPath);
|
|
1362
|
+
mkdirSync(dir, { recursive: true });
|
|
1363
|
+
writeFileSync(fullPath, file.content, "utf-8");
|
|
1364
|
+
}
|
|
1365
|
+
s.stop(`Created ${files.length} files in ${options.projectName}/`);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
// src/index.ts
|
|
1369
|
+
async function main() {
|
|
1370
|
+
const args = process.argv.slice(2);
|
|
1371
|
+
const projectName = args.find((arg) => !arg.startsWith("-"));
|
|
1372
|
+
intro(pc.bgCyan(pc.black(" create-openagent ")));
|
|
1373
|
+
let options;
|
|
1374
|
+
try {
|
|
1375
|
+
options = await collectOptions(projectName, args);
|
|
1376
|
+
} catch {
|
|
1377
|
+
outro(pc.red("Cancelled."));
|
|
1378
|
+
process.exit(1);
|
|
1379
|
+
}
|
|
1380
|
+
if (isCancel2(options)) {
|
|
1381
|
+
outro(pc.red("Cancelled."));
|
|
1382
|
+
process.exit(1);
|
|
1383
|
+
}
|
|
1384
|
+
try {
|
|
1385
|
+
await scaffold(options);
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
const message = err instanceof Error ? err.message : "Unknown error";
|
|
1388
|
+
outro(pc.red(`Failed to scaffold project: ${message}`));
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
outro(pc.green("Project created successfully!"));
|
|
1392
|
+
const relativePath = options.projectName;
|
|
1393
|
+
process.stdout.write("\n");
|
|
1394
|
+
process.stdout.write(pc.bold("Next steps:\n"));
|
|
1395
|
+
process.stdout.write(` cd ${relativePath}
|
|
1396
|
+
`);
|
|
1397
|
+
process.stdout.write(" npm install\n");
|
|
1398
|
+
if (options.template === "web-dashboard" || options.template === "full-stack") {
|
|
1399
|
+
process.stdout.write(" npm run dev\n");
|
|
1400
|
+
} else {
|
|
1401
|
+
process.stdout.write(" npm run build\n");
|
|
1402
|
+
process.stdout.write(" npm start\n");
|
|
1403
|
+
}
|
|
1404
|
+
process.stdout.write("\n");
|
|
1405
|
+
process.stdout.write(pc.dim(`Docs: https://openagent.id/docs
|
|
1406
|
+
`));
|
|
1407
|
+
}
|
|
1408
|
+
main().catch((err) => {
|
|
1409
|
+
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}
|
|
1410
|
+
`);
|
|
1411
|
+
process.exit(1);
|
|
1412
|
+
});
|