@inneranimalmedia/agentsam-sdk 1.5.1 → 1.7.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/README.md +2 -0
- package/bin/scaffold.mjs +17 -0
- package/package.json +11 -4
- package/src/cli.js +15 -0
- package/src/commands/http-server.js +97 -0
- package/src/commands/tunnel.js +232 -0
- package/src/lib/scaffold/index.js +61 -0
- package/src/lib/scaffold/templates/cms/index.js +472 -0
- package/src/lib/scaffold/templates/worker-api/index.js +204 -0
- package/src/lib/scaffold/wizards/cms.js +175 -0
- package/src/lib/scaffold/wizards/worker-api.js +76 -0
- package/src/lib/scaffold/writer.js +22 -0
- package/src/ui/splash-xterm.js +290 -0
- package/src/ui/splash.js +426 -0
- package/src/ui/terminal-lanes.css +162 -0
- package/src/ui/theme.js +315 -0
- package/test/smoke.mjs +9 -2
package/README.md
CHANGED
|
@@ -35,10 +35,12 @@ cd my-project
|
|
|
35
35
|
npm install
|
|
36
36
|
npm run smoke
|
|
37
37
|
npx agentsam start-local # local PTY on ws://127.0.0.1:3099
|
|
38
|
+
npx agentsam tunnel # cloudflared → register with IAM (dashboard Local lane)
|
|
38
39
|
npm run dev # http://127.0.0.1:8787
|
|
39
40
|
npm run db:migrate # local D1 schema
|
|
40
41
|
```
|
|
41
42
|
|
|
43
|
+
`agentsam tunnel` (default `--quick`) starts a Cloudflare quick tunnel to `:3099` and POSTs the `wss://` URL to `/api/sdk/terminal/register-local` so `agentsam_terminal_local` can reach your machine. Use `--named --tunnel-name … --hostname … --zone-id …` for a stable BYOK named tunnel.
|
|
42
44
|
When you're ready to ship to **your** Cloudflare account:
|
|
43
45
|
|
|
44
46
|
```bash
|
package/bin/scaffold.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agentsam scaffold
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx @inneranimalmedia/agentsam-sdk scaffold
|
|
6
|
+
* npx @inneranimalmedia/agentsam-sdk scaffold cms
|
|
7
|
+
* npx @inneranimalmedia/agentsam-sdk scaffold worker-api
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { runScaffold } from '../src/lib/scaffold/index.js';
|
|
11
|
+
|
|
12
|
+
const type = process.argv[2] ?? null;
|
|
13
|
+
|
|
14
|
+
runScaffold(type).catch((err) => {
|
|
15
|
+
console.error(err);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inneranimalmedia/agentsam-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Agent Sam is a full-stack AI agent SDK for autonomous task execution — covering data management, creative workflows, design commands, and multi-step agentic pipelines.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"./package.json": "./package.json"
|
|
10
10
|
},
|
|
11
11
|
"bin": {
|
|
12
|
-
"agentsam": "src/cli.js"
|
|
12
|
+
"agentsam": "src/cli.js",
|
|
13
|
+
"agentsam-scaffold": "bin/scaffold.mjs"
|
|
13
14
|
},
|
|
14
15
|
"files": [
|
|
15
16
|
"src",
|
|
@@ -19,7 +20,8 @@
|
|
|
19
20
|
"test",
|
|
20
21
|
"README.md",
|
|
21
22
|
"LICENSE",
|
|
22
|
-
"DEVELOPMENT.md"
|
|
23
|
+
"DEVELOPMENT.md",
|
|
24
|
+
"bin"
|
|
23
25
|
],
|
|
24
26
|
"scripts": {
|
|
25
27
|
"test": "node test/smoke.mjs",
|
|
@@ -30,7 +32,9 @@
|
|
|
30
32
|
"node": ">=20"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
35
|
+
"@clack/prompts": "^1.7.0",
|
|
33
36
|
"node-pty": "^1.0.0",
|
|
37
|
+
"picocolors": "^1.1.1",
|
|
34
38
|
"ws": "^8.18.0"
|
|
35
39
|
},
|
|
36
40
|
"publishConfig": {
|
|
@@ -54,5 +58,8 @@
|
|
|
54
58
|
"bugs": {
|
|
55
59
|
"url": "https://github.com/SamPrimeaux/agentsam-sdk/issues"
|
|
56
60
|
},
|
|
57
|
-
"homepage": "https://github.com/SamPrimeaux/agentsam-sdk#readme"
|
|
61
|
+
"homepage": "https://github.com/SamPrimeaux/agentsam-sdk#readme",
|
|
62
|
+
"allowScripts": {
|
|
63
|
+
"node-pty@1.1.0": true
|
|
64
|
+
}
|
|
58
65
|
}
|
package/src/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { copyGorillaTemplate } from './lib/gorilla-template.js';
|
|
|
8
8
|
import { printContextSummary } from './lib/detect-context.js';
|
|
9
9
|
import { promptOptionalByokKeys } from './lib/prompt-byok.js';
|
|
10
10
|
import { runStartLocal } from './commands/start-local.js';
|
|
11
|
+
import { runTunnel } from './commands/tunnel.js';
|
|
11
12
|
import { runDeploy } from './commands/deploy.js';
|
|
12
13
|
import { SLASH_COMMANDS, SHELL_PHASES } from './lib/slash-commands.js';
|
|
13
14
|
|
|
@@ -28,6 +29,7 @@ function printHelp() {
|
|
|
28
29
|
Usage:
|
|
29
30
|
agentsam init Local-first project scaffold (default: localhost, no accounts)
|
|
30
31
|
agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
|
|
32
|
+
agentsam tunnel Expose local PTY to IAM (cloudflared + register)
|
|
31
33
|
agentsam deploy Graduate to Cloudflare / GCP when ready
|
|
32
34
|
agentsam shell Slash commands + shell UX info
|
|
33
35
|
agentsam --version
|
|
@@ -36,6 +38,12 @@ function printHelp() {
|
|
|
36
38
|
Init is completable with Node only — no IAM login, no OAuth, no Cloudflare.
|
|
37
39
|
Prove locally first; deploy prompts for accounts only when you choose to ship.
|
|
38
40
|
|
|
41
|
+
Tunnel options:
|
|
42
|
+
--quick Quick tunnel (default) — trycloudflare.com URL
|
|
43
|
+
--named Named CF tunnel (needs --tunnel-name --hostname --zone-id)
|
|
44
|
+
--port <n> Local PTY port (default 3099)
|
|
45
|
+
--token <sdk_…> Use existing AGENTSAM_SDK_TOKEN (skip browser auth)
|
|
46
|
+
|
|
39
47
|
Init options:
|
|
40
48
|
--name <name> Project directory name
|
|
41
49
|
--lane <fullstack|cms|data|crm|creative>
|
|
@@ -193,6 +201,13 @@ if (command === '--version' || command === '-v') {
|
|
|
193
201
|
await runShellInfo();
|
|
194
202
|
} else if (command === 'start-local') {
|
|
195
203
|
await runStartLocal({});
|
|
204
|
+
} else if (command === 'tunnel') {
|
|
205
|
+
try {
|
|
206
|
+
await runTunnel(rest);
|
|
207
|
+
} catch (e) {
|
|
208
|
+
console.error(`\n ✗ ${e?.message || e}\n`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
196
211
|
} else if (command === 'deploy') {
|
|
197
212
|
try {
|
|
198
213
|
await runDeploy(parseDeployArgs(rest));
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* AgentSam HTTP API server for containerized deployments
|
|
4
|
+
* Exposes the AgentSam SDK as HTTP endpoints (compatible with Cloudflare Workers pattern)
|
|
5
|
+
*/
|
|
6
|
+
import http from 'node:http';
|
|
7
|
+
import { AgentSam } from './AgentSam.js';
|
|
8
|
+
|
|
9
|
+
const PORT = process.env.PORT || 8080;
|
|
10
|
+
const HOST = process.env.HOST || '0.0.0.0';
|
|
11
|
+
|
|
12
|
+
const agentSam = new AgentSam({
|
|
13
|
+
env: process.env,
|
|
14
|
+
agent: process.env.AGENT || 'orchestrator',
|
|
15
|
+
lane: process.env.LANE || 'fullstack',
|
|
16
|
+
project: process.env.PROJECT || 'agentsam-docker',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Adapt Web API Request to Node HTTP
|
|
21
|
+
*/
|
|
22
|
+
class NodeRequest {
|
|
23
|
+
constructor(req, body) {
|
|
24
|
+
this.url = `http://${req.headers.host || 'localhost'}${req.url}`;
|
|
25
|
+
this.method = req.method;
|
|
26
|
+
this.headers = req.headers;
|
|
27
|
+
this.body = body;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async json() {
|
|
31
|
+
if (!this.body) return {};
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(this.body);
|
|
34
|
+
} catch {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const server = http.createServer(async (req, res) => {
|
|
41
|
+
let body = '';
|
|
42
|
+
|
|
43
|
+
// Read request body
|
|
44
|
+
req.on('data', (chunk) => {
|
|
45
|
+
body += chunk.toString();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
req.on('end', async () => {
|
|
49
|
+
try {
|
|
50
|
+
const request = new NodeRequest(req, body);
|
|
51
|
+
const response = await agentSam.handle(request);
|
|
52
|
+
|
|
53
|
+
// Write response headers
|
|
54
|
+
const headers = {};
|
|
55
|
+
if (response.headers && response.headers.entries) {
|
|
56
|
+
for (const [key, value] of response.headers.entries()) {
|
|
57
|
+
headers[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
res.writeHead(response.status, headers);
|
|
61
|
+
|
|
62
|
+
// Write response body
|
|
63
|
+
const text = await response.text();
|
|
64
|
+
res.end(text);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error handling request:', error);
|
|
67
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
68
|
+
res.end(JSON.stringify({ ok: false, error: error.message }));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
server.listen(PORT, HOST, () => {
|
|
74
|
+
console.log(`
|
|
75
|
+
✓ Agent Sam API Server listening on http://${HOST === '0.0.0.0' ? 'localhost' : HOST}:${PORT}
|
|
76
|
+
|
|
77
|
+
Routes:
|
|
78
|
+
GET /api/health → service health
|
|
79
|
+
GET /api/agentsam/info → agent capabilities
|
|
80
|
+
POST /api/agentsam/session → create session
|
|
81
|
+
GET /api/agentsam/session/:id → get session
|
|
82
|
+
POST /api/agentsam/message → route message
|
|
83
|
+
|
|
84
|
+
Environment: AGENT=${process.env.AGENT || 'orchestrator'} LANE=${process.env.LANE || 'fullstack'}
|
|
85
|
+
`);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Graceful shutdown
|
|
89
|
+
const shutdown = async () => {
|
|
90
|
+
console.log('\nShutting down...');
|
|
91
|
+
server.close(() => {
|
|
92
|
+
process.exit(0);
|
|
93
|
+
});
|
|
94
|
+
setTimeout(() => process.exit(1), 5000);
|
|
95
|
+
};
|
|
96
|
+
process.on('SIGINT', shutdown);
|
|
97
|
+
process.on('SIGTERM', shutdown);
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentsam tunnel — expose local PTY (:3099) to the IAM platform.
|
|
3
|
+
*
|
|
4
|
+
* Default (--quick): cloudflared quick tunnel → register ws_url via SDK API.
|
|
5
|
+
* Named (--named): platform provisions CF named tunnel + DNS; run with --token.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
8
|
+
import { authenticateViaBrowser } from '../lib/auth.js';
|
|
9
|
+
import { postJson } from '../lib/core-client.js';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_PORT = 3099;
|
|
12
|
+
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const opts = {
|
|
15
|
+
mode: 'quick',
|
|
16
|
+
port: DEFAULT_PORT,
|
|
17
|
+
tunnelName: '',
|
|
18
|
+
hostname: '',
|
|
19
|
+
zoneId: '',
|
|
20
|
+
platform: process.platform === 'win32' ? 'windows' : process.platform === 'darwin' ? 'macos' : 'linux',
|
|
21
|
+
shell: process.platform === 'win32' ? 'powershell' : process.env.SHELL || '/bin/zsh',
|
|
22
|
+
skipAuth: false,
|
|
23
|
+
};
|
|
24
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
25
|
+
const a = argv[i];
|
|
26
|
+
if (a === '--quick') opts.mode = 'quick';
|
|
27
|
+
else if (a === '--named') opts.mode = 'named';
|
|
28
|
+
else if (a === '--port') opts.port = Number(argv[++i]) || DEFAULT_PORT;
|
|
29
|
+
else if (a === '--tunnel-name') opts.tunnelName = argv[++i] || '';
|
|
30
|
+
else if (a === '--hostname') opts.hostname = argv[++i] || '';
|
|
31
|
+
else if (a === '--zone-id') opts.zoneId = argv[++i] || '';
|
|
32
|
+
else if (a === '--platform') opts.platform = argv[++i] || opts.platform;
|
|
33
|
+
else if (a === '--shell') opts.shell = argv[++i] || opts.shell;
|
|
34
|
+
else if (a === '--token' && argv[i + 1]) {
|
|
35
|
+
process.env.AGENTSAM_SDK_TOKEN = argv[++i];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return opts;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ensureCloudflared() {
|
|
42
|
+
const which = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['cloudflared'], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
});
|
|
45
|
+
if (which.status !== 0) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
'cloudflared not found. Install: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function resolveToken() {
|
|
53
|
+
const existing = String(process.env.AGENTSAM_SDK_TOKEN || '').trim();
|
|
54
|
+
if (existing.startsWith('sdk_')) return existing;
|
|
55
|
+
const session = await authenticateViaBrowser();
|
|
56
|
+
const tok = String(session?.access_token || '').trim();
|
|
57
|
+
if (!tok.startsWith('sdk_')) throw new Error('IAM auth did not return an sdk_ bearer token');
|
|
58
|
+
process.env.AGENTSAM_SDK_TOKEN = tok;
|
|
59
|
+
return tok;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function assertLocalPty(port) {
|
|
63
|
+
const url = `http://127.0.0.1:${port}/health`;
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(2500) });
|
|
66
|
+
if (!res.ok) throw new Error(`health ${res.status}`);
|
|
67
|
+
return true;
|
|
68
|
+
} catch {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Local PTY not reachable at ${url}. In another terminal run: npx agentsam start-local`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function httpsToWss(url) {
|
|
76
|
+
const u = String(url || '').trim();
|
|
77
|
+
if (!u) return '';
|
|
78
|
+
if (u.startsWith('wss://') || u.startsWith('ws://')) return u.replace(/\/$/, '');
|
|
79
|
+
if (u.startsWith('https://')) return `wss://${u.slice(8)}`.replace(/\/$/, '');
|
|
80
|
+
if (u.startsWith('http://')) return `ws://${u.slice(7)}`.replace(/\/$/, '');
|
|
81
|
+
return `wss://${u.replace(/^\/+/, '')}`.replace(/\/$/, '');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Parse trycloudflare.com URL from cloudflared stderr/stdout.
|
|
86
|
+
* @param {import('node:child_process').ChildProcessWithoutNullStreams} child
|
|
87
|
+
* @returns {Promise<string>}
|
|
88
|
+
*/
|
|
89
|
+
function waitForQuickTunnelUrl(child) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
let buf = '';
|
|
92
|
+
const timer = setTimeout(() => {
|
|
93
|
+
reject(new Error('Timed out waiting for cloudflared quick tunnel URL (60s)'));
|
|
94
|
+
}, 60_000);
|
|
95
|
+
|
|
96
|
+
const onData = (chunk) => {
|
|
97
|
+
const text = chunk.toString();
|
|
98
|
+
buf += text;
|
|
99
|
+
process.stderr.write(text);
|
|
100
|
+
const m =
|
|
101
|
+
buf.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i) ||
|
|
102
|
+
buf.match(/https:\/\/[a-z0-9.-]+\.cfargotunnel\.com/i);
|
|
103
|
+
if (m) {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
child.stdout?.off('data', onData);
|
|
106
|
+
child.stderr?.off('data', onData);
|
|
107
|
+
resolve(m[0]);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
child.stdout?.on('data', onData);
|
|
112
|
+
child.stderr?.on('data', onData);
|
|
113
|
+
child.on('error', (e) => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
reject(e);
|
|
116
|
+
});
|
|
117
|
+
child.on('exit', (code) => {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
reject(new Error(`cloudflared exited early (code ${code})`));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function runQuick(opts, token) {
|
|
125
|
+
await assertLocalPty(opts.port);
|
|
126
|
+
ensureCloudflared();
|
|
127
|
+
|
|
128
|
+
console.log(`
|
|
129
|
+
Agent Sam — tunnel (quick)
|
|
130
|
+
Local PTY http://127.0.0.1:${opts.port}
|
|
131
|
+
Mode cloudflared quick tunnel → IAM register-local
|
|
132
|
+
`);
|
|
133
|
+
|
|
134
|
+
const child = spawn(
|
|
135
|
+
'cloudflared',
|
|
136
|
+
['tunnel', '--url', `http://127.0.0.1:${opts.port}`, '--no-autoupdate'],
|
|
137
|
+
{ stdio: ['ignore', 'pipe', 'pipe'] },
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const publicUrl = await waitForQuickTunnelUrl(child);
|
|
141
|
+
const wsUrl = httpsToWss(publicUrl);
|
|
142
|
+
console.log(`\n ✓ Public URL ${publicUrl}`);
|
|
143
|
+
console.log(` ✓ Registering ${wsUrl}\n`);
|
|
144
|
+
|
|
145
|
+
const registered = await postJson(
|
|
146
|
+
'/api/sdk/terminal/register-local',
|
|
147
|
+
{
|
|
148
|
+
ws_url: wsUrl,
|
|
149
|
+
platform: opts.platform,
|
|
150
|
+
shell: opts.shell,
|
|
151
|
+
},
|
|
152
|
+
token,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
console.log(` ✓ IAM local lane active`);
|
|
156
|
+
if (registered?.connection?.id) {
|
|
157
|
+
console.log(` ✓ connection ${registered.connection.id}`);
|
|
158
|
+
}
|
|
159
|
+
console.log(`
|
|
160
|
+
Keep this process running. In the dashboard: Terminal → Local.
|
|
161
|
+
|
|
162
|
+
Ctrl+C stops the tunnel.
|
|
163
|
+
`);
|
|
164
|
+
|
|
165
|
+
await new Promise((resolve) => {
|
|
166
|
+
child.on('exit', resolve);
|
|
167
|
+
process.on('SIGINT', () => {
|
|
168
|
+
child.kill('SIGINT');
|
|
169
|
+
});
|
|
170
|
+
process.on('SIGTERM', () => {
|
|
171
|
+
child.kill('SIGTERM');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function runNamed(opts, token) {
|
|
177
|
+
if (!opts.tunnelName || !opts.hostname || !opts.zoneId) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
'Named mode requires --tunnel-name, --hostname, and --zone-id (from your CF zone).',
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
await assertLocalPty(opts.port);
|
|
183
|
+
ensureCloudflared();
|
|
184
|
+
|
|
185
|
+
console.log(`
|
|
186
|
+
Agent Sam — tunnel (named)
|
|
187
|
+
Provisioning Cloudflare tunnel ${opts.tunnelName} → ${opts.hostname}
|
|
188
|
+
`);
|
|
189
|
+
|
|
190
|
+
const provisioned = await postJson(
|
|
191
|
+
'/api/sdk/terminal/tunnel/provision',
|
|
192
|
+
{
|
|
193
|
+
tunnel_name: opts.tunnelName,
|
|
194
|
+
hostname: opts.hostname,
|
|
195
|
+
zone_id: opts.zoneId,
|
|
196
|
+
port: opts.port,
|
|
197
|
+
platform: opts.platform,
|
|
198
|
+
shell: opts.shell,
|
|
199
|
+
},
|
|
200
|
+
token,
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const runToken = String(provisioned?.run_token || '').trim();
|
|
204
|
+
if (!runToken) throw new Error('Platform did not return a cloudflared run_token');
|
|
205
|
+
|
|
206
|
+
console.log(` ✓ ws_url ${provisioned.ws_url || `wss://${opts.hostname}`}`);
|
|
207
|
+
console.log(` ✓ Starting cloudflared tunnel run --token …\n`);
|
|
208
|
+
|
|
209
|
+
const child = spawn('cloudflared', ['tunnel', 'run', '--token', runToken, '--no-autoupdate'], {
|
|
210
|
+
stdio: 'inherit',
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await new Promise((resolve, reject) => {
|
|
214
|
+
child.on('error', reject);
|
|
215
|
+
child.on('exit', resolve);
|
|
216
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {string[]} [argv]
|
|
222
|
+
*/
|
|
223
|
+
export async function runTunnel(argv = []) {
|
|
224
|
+
const opts = parseArgs(argv);
|
|
225
|
+
const token = await resolveToken();
|
|
226
|
+
|
|
227
|
+
if (opts.mode === 'named') {
|
|
228
|
+
await runNamed(opts, token);
|
|
229
|
+
} else {
|
|
230
|
+
await runQuick(opts, token);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @inneranimalmedia/agentsam-sdk — scaffold system
|
|
3
|
+
* Entry point for all guided scaffold wizards.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx @inneranimalmedia/agentsam-sdk scaffold
|
|
7
|
+
* npx @inneranimalmedia/agentsam-sdk scaffold cms
|
|
8
|
+
* npx @inneranimalmedia/agentsam-sdk scaffold worker-api
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { intro, outro, select, cancel, isCancel, note } from '@clack/prompts';
|
|
12
|
+
import { runCmsWizard } from './wizards/cms.js';
|
|
13
|
+
import { runWorkerApiWizard } from './wizards/worker-api.js';
|
|
14
|
+
import pc from 'picocolors';
|
|
15
|
+
|
|
16
|
+
const SCAFFOLDS = {
|
|
17
|
+
cms: {
|
|
18
|
+
label: 'CMS Site',
|
|
19
|
+
description: 'Cloudflare Worker + D1 + R2 with nav, pages, and reusable templates',
|
|
20
|
+
run: runCmsWizard,
|
|
21
|
+
},
|
|
22
|
+
'worker-api': {
|
|
23
|
+
label: 'Worker API',
|
|
24
|
+
description: 'Bare Cloudflare Worker with typed route handlers and D1 binding',
|
|
25
|
+
run: runWorkerApiWizard,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export async function runScaffold(type) {
|
|
30
|
+
intro(pc.bgCyan(pc.black(' Agent Sam Scaffold ')));
|
|
31
|
+
|
|
32
|
+
// If a type was passed directly (e.g. `scaffold cms`), run it
|
|
33
|
+
if (type && SCAFFOLDS[type]) {
|
|
34
|
+
await SCAFFOLDS[type].run();
|
|
35
|
+
outro(pc.green('Done. Files written — check the output above.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (type && !SCAFFOLDS[type]) {
|
|
40
|
+
note(`Unknown scaffold type: "${type}"\nAvailable: ${Object.keys(SCAFFOLDS).join(', ')}`, 'Error');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// No type passed — show picker
|
|
45
|
+
const choice = await select({
|
|
46
|
+
message: 'What do you want to scaffold?',
|
|
47
|
+
options: Object.entries(SCAFFOLDS).map(([value, { label, description }]) => ({
|
|
48
|
+
value,
|
|
49
|
+
label,
|
|
50
|
+
hint: description,
|
|
51
|
+
})),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (isCancel(choice)) {
|
|
55
|
+
cancel('Cancelled.');
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await SCAFFOLDS[choice].run();
|
|
60
|
+
outro(pc.green('Done. Files written — check the output above.'));
|
|
61
|
+
}
|