@inneranimalmedia/agentsam-sdk 1.2.0 → 1.5.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 +23 -5
- package/package.json +5 -1
- package/src/cli.js +96 -177
- package/src/commands/deploy.js +146 -0
- package/src/commands/start-local.js +61 -0
- package/src/lib/auth.js +76 -0
- package/src/lib/core-client.js +88 -0
- package/src/lib/detect-context.js +345 -0
- package/src/lib/gcp-setup.js +54 -0
- package/src/lib/local-scaffold.js +373 -0
- package/src/lib/prompt-byok.js +57 -0
- package/src/lib/save-sdk-token.js +19 -0
- package/src/lib/scaffold.js +7 -259
- package/src/lib/slash-commands.js +1 -1
- package/src/lib/write-files.js +21 -0
- package/src/local-pty/server.js +133 -0
- package/test/smoke.mjs +24 -5
package/README.md
CHANGED
|
@@ -22,24 +22,42 @@ It is not a wrapper around a chat API. It is a command fabric with a conversatio
|
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
## Quickstart
|
|
25
|
+
## Quickstart (local-first — Node only)
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
28
|
npx @inneranimalmedia/agentsam-sdk init
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Default path: **localhost**. No IAM login, no Cloudflare OAuth, no accounts. Under 2 minutes with Node 20+ installed.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cd my-project
|
|
35
|
+
npm install
|
|
36
|
+
npm run smoke
|
|
37
|
+
npx agentsam start-local # local PTY on ws://127.0.0.1:3099
|
|
38
|
+
npm run dev # http://127.0.0.1:8787
|
|
39
|
+
npm run db:migrate # local D1 schema
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
When you're ready to ship to **your** Cloudflare account:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx agentsam deploy
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Cloudflare OAuth is prompted **only at deploy** — not at init.
|
|
49
|
+
|
|
50
|
+
Non-interactive init:
|
|
32
51
|
|
|
33
52
|
```bash
|
|
34
53
|
npx @inneranimalmedia/agentsam-sdk init \
|
|
35
54
|
--name my-agent \
|
|
36
55
|
--lane fullstack \
|
|
37
|
-
--
|
|
38
|
-
--agent orchestrator \
|
|
56
|
+
--run-target local \
|
|
39
57
|
--yes
|
|
40
58
|
```
|
|
41
59
|
|
|
42
|
-
|
|
60
|
+
Run targets at init: `local` (default) · `cloudflare` · `gcp` — all scaffold locally first; cloud credentials come at deploy.
|
|
43
61
|
|
|
44
62
|
See [DEVELOPMENT.md](./DEVELOPMENT.md) for linking the SDK into Inner Animal Media locally.
|
|
45
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inneranimalmedia/agentsam-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.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",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=20"
|
|
30
30
|
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"node-pty": "^1.0.0",
|
|
33
|
+
"ws": "^8.18.0"
|
|
34
|
+
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public"
|
|
33
37
|
},
|
package/src/cli.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import readline from 'readline';
|
|
4
3
|
import pkg from '../package.json' with { type: 'json' };
|
|
5
|
-
import
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
import { buildLocalScaffoldMeta, LANE_KEYS, RUN_TARGETS } from './lib/local-scaffold.js';
|
|
6
|
+
import { writeScaffoldFiles } from './lib/write-files.js';
|
|
7
|
+
import { printContextSummary } from './lib/detect-context.js';
|
|
8
|
+
import { promptOptionalByokKeys } from './lib/prompt-byok.js';
|
|
9
|
+
import { runStartLocal } from './commands/start-local.js';
|
|
10
|
+
import { runDeploy } from './commands/deploy.js';
|
|
6
11
|
import { SLASH_COMMANDS, SHELL_PHASES } from './lib/slash-commands.js';
|
|
7
12
|
|
|
8
13
|
const VERSION = pkg.version;
|
|
@@ -15,150 +20,95 @@ function createPrompt() {
|
|
|
15
20
|
};
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
const LANES = {
|
|
19
|
-
'1': 'Full Stack',
|
|
20
|
-
'2': 'CMS',
|
|
21
|
-
'3': 'Data Solutions',
|
|
22
|
-
'4': 'Customer Management',
|
|
23
|
-
'5': 'Creative & Design',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const LANE_BY_SLUG = {
|
|
27
|
-
fullstack: 'Full Stack',
|
|
28
|
-
cms: 'CMS',
|
|
29
|
-
data: 'Data Solutions',
|
|
30
|
-
crm: 'Customer Management',
|
|
31
|
-
creative: 'Creative & Design',
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const PROVIDERS = {
|
|
35
|
-
'1': 'Cloudflare Workers',
|
|
36
|
-
'2': 'GitHub + Cloudflare',
|
|
37
|
-
'3': 'Local / Self-hosted',
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const PROVIDER_BY_SLUG = {
|
|
41
|
-
cloudflare: 'Cloudflare Workers',
|
|
42
|
-
github: 'GitHub + Cloudflare',
|
|
43
|
-
local: 'Local / Self-hosted',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const AGENTS = {
|
|
47
|
-
'1': 'orchestrator',
|
|
48
|
-
'2': 'cms',
|
|
49
|
-
'3': 'data',
|
|
50
|
-
'4': 'crm',
|
|
51
|
-
'5': 'creative',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
function laneKeyFromLabel(label) {
|
|
55
|
-
const entry = Object.entries(LANES).find(([, v]) => v === label);
|
|
56
|
-
return entry?.[0] ?? '1';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
23
|
function printHelp() {
|
|
60
24
|
console.log(`
|
|
61
25
|
Agent Sam SDK — CLI v${VERSION}
|
|
62
26
|
|
|
63
27
|
Usage:
|
|
64
|
-
agentsam init
|
|
65
|
-
agentsam
|
|
66
|
-
agentsam
|
|
67
|
-
agentsam
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
28
|
+
agentsam init Local-first project scaffold (default: localhost, no accounts)
|
|
29
|
+
agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
|
|
30
|
+
agentsam deploy Graduate to Cloudflare / GCP when ready
|
|
31
|
+
agentsam shell Slash commands + shell UX info
|
|
32
|
+
agentsam --version
|
|
33
|
+
agentsam --help
|
|
34
|
+
|
|
35
|
+
Init is completable with Node only — no IAM login, no OAuth, no Cloudflare.
|
|
36
|
+
Prove locally first; deploy prompts for accounts only when you choose to ship.
|
|
37
|
+
|
|
38
|
+
Init options:
|
|
39
|
+
--name <name> Project directory name
|
|
71
40
|
--lane <fullstack|cms|data|crm|creative>
|
|
72
|
-
--
|
|
73
|
-
--
|
|
74
|
-
--cf-account <id> Cloudflare account ID
|
|
75
|
-
--yes Skip confirmation prompt
|
|
41
|
+
--run-target <local|cloudflare|gcp> Default: local
|
|
42
|
+
--yes Skip confirmation
|
|
76
43
|
`);
|
|
77
44
|
}
|
|
78
45
|
|
|
79
46
|
function parseInitArgs(argv) {
|
|
80
47
|
const opts = {
|
|
81
48
|
projectName: '',
|
|
82
|
-
lane: '',
|
|
83
|
-
|
|
84
|
-
agent: '',
|
|
85
|
-
cfAccountId: '',
|
|
49
|
+
lane: 'fullstack',
|
|
50
|
+
runTarget: 'local',
|
|
86
51
|
yes: false,
|
|
87
52
|
};
|
|
88
|
-
|
|
89
53
|
for (let i = 0; i < argv.length; i += 1) {
|
|
90
54
|
const arg = argv[i];
|
|
91
55
|
if (arg === '--yes' || arg === '-y') opts.yes = true;
|
|
92
56
|
else if (arg === '--name') opts.projectName = argv[++i] || '';
|
|
93
|
-
else if (arg === '--lane')
|
|
94
|
-
|
|
95
|
-
opts.lane = LANE_BY_SLUG[slug] || slug;
|
|
96
|
-
} else if (arg === '--provider') {
|
|
97
|
-
const slug = argv[++i] || '';
|
|
98
|
-
opts.provider = PROVIDER_BY_SLUG[slug] || slug;
|
|
99
|
-
} else if (arg === '--agent') opts.agent = argv[++i] || '';
|
|
100
|
-
else if (arg === '--cf-account') opts.cfAccountId = argv[++i] || '';
|
|
57
|
+
else if (arg === '--lane') opts.lane = argv[++i] || 'fullstack';
|
|
58
|
+
else if (arg === '--run-target' || arg === '--target') opts.runTarget = argv[++i] || 'local';
|
|
101
59
|
}
|
|
60
|
+
return opts;
|
|
61
|
+
}
|
|
102
62
|
|
|
63
|
+
function parseDeployArgs(argv) {
|
|
64
|
+
const opts = { target: '', accountId: '' };
|
|
65
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
66
|
+
const arg = argv[i];
|
|
67
|
+
if (arg === '--target') opts.target = argv[++i] || '';
|
|
68
|
+
else if (arg === '--account-id') opts.accountId = argv[++i] || '';
|
|
69
|
+
}
|
|
103
70
|
return opts;
|
|
104
71
|
}
|
|
105
72
|
|
|
106
|
-
async function
|
|
107
|
-
const {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
yes = false,
|
|
114
|
-
prompt = null,
|
|
115
|
-
} = config;
|
|
73
|
+
async function runLocalInit(config) {
|
|
74
|
+
const { projectName, lane, runTarget, prompt } = config;
|
|
75
|
+
|
|
76
|
+
const meta = buildLocalScaffoldMeta(
|
|
77
|
+
{ projectName, lane, runTarget },
|
|
78
|
+
VERSION,
|
|
79
|
+
);
|
|
116
80
|
|
|
117
|
-
const safeDir = projectName.trim();
|
|
118
81
|
console.log(`
|
|
119
82
|
┌─────────────────────────────────────┐
|
|
120
|
-
│ Agent Sam
|
|
83
|
+
│ Agent Sam — local-first scaffold │
|
|
121
84
|
├─────────────────────────────────────┤
|
|
122
|
-
│
|
|
123
|
-
│ Lane:
|
|
124
|
-
│
|
|
125
|
-
│ Agent: ${agent.padEnd(25)}│
|
|
126
|
-
│ CF Acct: ${(cfAccountId || 'not set').padEnd(25)}│
|
|
85
|
+
│ Name: ${meta.projectName.padEnd(25)}│
|
|
86
|
+
│ Lane: ${meta.laneKey.padEnd(25)}│
|
|
87
|
+
│ Run: ${meta.runTarget.padEnd(25)}│
|
|
127
88
|
└─────────────────────────────────────┘
|
|
128
89
|
`);
|
|
129
90
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
91
|
+
const dir = writeScaffoldFiles(meta.projectName, meta.files);
|
|
92
|
+
|
|
93
|
+
console.log(`
|
|
94
|
+
✓ Project ready: ${dir}
|
|
95
|
+
|
|
96
|
+
Next steps:`);
|
|
97
|
+
for (const step of meta.next_steps) {
|
|
98
|
+
console.log(` ${step}`);
|
|
133
99
|
}
|
|
134
100
|
|
|
135
|
-
if (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
lane,
|
|
140
|
-
provider,
|
|
141
|
-
agent,
|
|
142
|
-
cfAccountId,
|
|
143
|
-
sdkVersion: VERSION,
|
|
144
|
-
});
|
|
145
|
-
console.log(`
|
|
146
|
-
✓ Project created at ${dir}
|
|
101
|
+
if (prompt && process.env.AGENTSAM_SDK_TOKEN) {
|
|
102
|
+
console.log('\n Optional — BYOK keys for IAM dashboard Agent Sam (skip with Enter):\n');
|
|
103
|
+
await promptOptionalByokKeys(process.env.AGENTSAM_SDK_TOKEN, prompt);
|
|
104
|
+
}
|
|
147
105
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
npm run smoke
|
|
106
|
+
console.log(`
|
|
107
|
+
Local in ~60 seconds:
|
|
108
|
+
cd ${meta.projectName} && npm install && npm run smoke
|
|
109
|
+
npx agentsam start-local
|
|
153
110
|
npm run dev
|
|
154
|
-
|
|
155
|
-
} catch (error) {
|
|
156
|
-
console.error(`\n ✗ ${error.message}\n`);
|
|
157
|
-
process.exitCode = 1;
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
console.log('\n Cancelled.\n');
|
|
161
|
-
}
|
|
111
|
+
`);
|
|
162
112
|
}
|
|
163
113
|
|
|
164
114
|
async function initInteractive(partial = {}) {
|
|
@@ -166,66 +116,41 @@ async function initInteractive(partial = {}) {
|
|
|
166
116
|
|
|
167
117
|
console.log(`
|
|
168
118
|
╔═══════════════════════════════════╗
|
|
169
|
-
║
|
|
119
|
+
║ Agent Sam SDK — Init ║
|
|
120
|
+
║ Local-first · Node only ║
|
|
170
121
|
╚═══════════════════════════════════╝
|
|
171
122
|
`);
|
|
172
123
|
|
|
173
|
-
|
|
124
|
+
printContextSummary(await import('./lib/detect-context.js').then((m) => m.detectContext()));
|
|
125
|
+
|
|
126
|
+
const projectName =
|
|
127
|
+
partial.projectName ||
|
|
128
|
+
(await prompt.ask(' 1) Project name: '));
|
|
174
129
|
|
|
175
130
|
if (!partial.lane) {
|
|
176
131
|
console.log(`
|
|
177
|
-
|
|
178
|
-
1) Full Stack
|
|
179
|
-
2) CMS
|
|
180
|
-
3) Data Solutions
|
|
181
|
-
4) Customer Management
|
|
182
|
-
5) Creative & Design
|
|
132
|
+
2) Lane:
|
|
133
|
+
1) Full Stack 2) CMS 3) Data 4) CRM 5) Creative
|
|
183
134
|
`);
|
|
184
135
|
}
|
|
185
|
-
const laneKey = partial.lane
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
console.log(`
|
|
189
|
-
Select a provider:
|
|
190
|
-
1) Cloudflare Workers
|
|
191
|
-
2) GitHub + Cloudflare
|
|
192
|
-
3) Local / Self-hosted
|
|
193
|
-
`);
|
|
194
|
-
const providerKey = partial.provider
|
|
195
|
-
? Object.entries(PROVIDERS).find(([, v]) => v === partial.provider)?.[0] || '1'
|
|
196
|
-
: await prompt.ask(' Provider [1-3]: ');
|
|
197
|
-
const provider = partial.provider || PROVIDERS[providerKey] || 'Cloudflare Workers';
|
|
136
|
+
const laneKey = partial.lane
|
|
137
|
+
? partial.lane
|
|
138
|
+
: LANE_KEYS[await prompt.ask(' Pick lane [1-5]: ')]?.key || 'fullstack';
|
|
198
139
|
|
|
199
|
-
if (!partial.
|
|
140
|
+
if (!partial.runTarget) {
|
|
200
141
|
console.log(`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
5) Creative Agent — design, 3D, media, content
|
|
142
|
+
3) Where do you want to run your project?
|
|
143
|
+
|
|
144
|
+
1) Local (localhost — start here, no accounts needed)
|
|
145
|
+
2) Cloudflare (Workers, D1, R2 — deploy when ready)
|
|
146
|
+
3) GCP (your own Google Cloud project)
|
|
207
147
|
`);
|
|
208
148
|
}
|
|
209
|
-
const
|
|
210
|
-
?
|
|
211
|
-
: await prompt.ask('
|
|
212
|
-
const agent = partial.agent || AGENTS[agentKey] || 'orchestrator';
|
|
213
|
-
|
|
214
|
-
const cfAccountId =
|
|
215
|
-
partial.cfAccountId !== undefined && partial.cfAccountId !== ''
|
|
216
|
-
? partial.cfAccountId
|
|
217
|
-
: await prompt.ask(' Cloudflare Account ID (enter to skip): ');
|
|
218
|
-
|
|
219
|
-
await runInit({
|
|
220
|
-
projectName,
|
|
221
|
-
lane,
|
|
222
|
-
provider,
|
|
223
|
-
agent,
|
|
224
|
-
cfAccountId,
|
|
225
|
-
yes: partial.yes,
|
|
226
|
-
prompt,
|
|
227
|
-
});
|
|
149
|
+
const runTarget = partial.runTarget
|
|
150
|
+
? partial.runTarget
|
|
151
|
+
: RUN_TARGETS[await prompt.ask(' Select [1]: ')] || 'local';
|
|
228
152
|
|
|
153
|
+
await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
|
|
229
154
|
prompt.close();
|
|
230
155
|
}
|
|
231
156
|
|
|
@@ -235,15 +160,7 @@ async function initFromArgs(argv) {
|
|
|
235
160
|
console.error('\n ✗ --name is required for non-interactive init.\n');
|
|
236
161
|
process.exit(1);
|
|
237
162
|
}
|
|
238
|
-
await
|
|
239
|
-
projectName: opts.projectName,
|
|
240
|
-
lane: opts.lane || 'Full Stack',
|
|
241
|
-
provider: opts.provider || 'Cloudflare Workers',
|
|
242
|
-
agent: opts.agent || 'orchestrator',
|
|
243
|
-
cfAccountId: opts.cfAccountId,
|
|
244
|
-
yes: true,
|
|
245
|
-
prompt: null,
|
|
246
|
-
});
|
|
163
|
+
await runLocalInit({ ...opts, prompt: null });
|
|
247
164
|
}
|
|
248
165
|
|
|
249
166
|
async function runShellInfo() {
|
|
@@ -253,21 +170,14 @@ async function runShellInfo() {
|
|
|
253
170
|
║ Agent Sam Shell (Gorilla) ║
|
|
254
171
|
╚═══════════════════════════════════╝
|
|
255
172
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
Phase 0 prototype: examples/gorilla-shell/
|
|
259
|
-
Docs: docs/CLI_SHELL.md
|
|
260
|
-
Next milestone: ${next?.label ?? 'PTY connection'}
|
|
173
|
+
Local PTY: agentsam start-local (ws://127.0.0.1:3099)
|
|
174
|
+
Next milestone: ${next?.label ?? 'dashboard bridge after deploy'}
|
|
261
175
|
|
|
262
176
|
Slash commands (${SLASH_COMMANDS.length} registered):
|
|
263
177
|
`);
|
|
264
178
|
for (const row of SLASH_COMMANDS) {
|
|
265
179
|
console.log(` ${row.cmd.padEnd(14)} ${row.description}`);
|
|
266
180
|
}
|
|
267
|
-
console.log(`
|
|
268
|
-
Run the visual prototype:
|
|
269
|
-
cd examples/gorilla-shell && npm install && npm run dev
|
|
270
|
-
`);
|
|
271
181
|
}
|
|
272
182
|
|
|
273
183
|
const command = process.argv[2];
|
|
@@ -279,6 +189,15 @@ if (command === '--version' || command === '-v') {
|
|
|
279
189
|
printHelp();
|
|
280
190
|
} else if (command === 'shell') {
|
|
281
191
|
await runShellInfo();
|
|
192
|
+
} else if (command === 'start-local') {
|
|
193
|
+
await runStartLocal({});
|
|
194
|
+
} else if (command === 'deploy') {
|
|
195
|
+
try {
|
|
196
|
+
await runDeploy(parseDeployArgs(rest));
|
|
197
|
+
} catch (e) {
|
|
198
|
+
console.error(`\n ✗ ${e?.message || e}\n`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
282
201
|
} else if (command === 'init') {
|
|
283
202
|
const hasFlags = rest.some((a) => a.startsWith('--'));
|
|
284
203
|
if (hasFlags) {
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graduate local project to Cloudflare (or GCP hints) — OAuth only here, not at init.
|
|
3
|
+
*/
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { authenticateViaBrowser } from '../lib/auth.js';
|
|
8
|
+
import { getJson, streamScaffold } from '../lib/core-client.js';
|
|
9
|
+
|
|
10
|
+
function readConfig(cwd) {
|
|
11
|
+
const configPath = path.join(cwd, '.agentsam', 'config.json');
|
|
12
|
+
if (!fs.existsSync(configPath)) {
|
|
13
|
+
throw new Error('Not an Agent Sam project — run agentsam init first.');
|
|
14
|
+
}
|
|
15
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function writeConfig(cwd, config) {
|
|
19
|
+
const configPath = path.join(cwd, '.agentsam', 'config.json');
|
|
20
|
+
fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function patchWranglerToml(cwd, cf) {
|
|
24
|
+
const tomlPath = path.join(cwd, 'wrangler.toml');
|
|
25
|
+
if (!fs.existsSync(tomlPath)) return;
|
|
26
|
+
let text = fs.readFileSync(tomlPath, 'utf8');
|
|
27
|
+
if (cf.account_id) {
|
|
28
|
+
if (/^account_id\s*=/m.test(text)) {
|
|
29
|
+
text = text.replace(/^account_id\s*=.*$/m, `account_id = "${cf.account_id}"`);
|
|
30
|
+
} else {
|
|
31
|
+
text = text.replace(/(compatibility_date\s*=.*\n)/, `$1account_id = "${cf.account_id}"\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (cf.d1_database_id) {
|
|
35
|
+
text = text.replace(/^database_id\s*=.*$/m, `database_id = "${cf.d1_database_id}"`);
|
|
36
|
+
}
|
|
37
|
+
if (cf.kv_namespace_id) {
|
|
38
|
+
text = text.replace(/^id\s*=.*$/m, `id = "${cf.kv_namespace_id}"`);
|
|
39
|
+
}
|
|
40
|
+
fs.writeFileSync(tomlPath, text, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ask(question) {
|
|
44
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
rl.question(question, (answer) => {
|
|
47
|
+
rl.close();
|
|
48
|
+
resolve(answer);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runCloudflareDeploy(cwd, config, accountId) {
|
|
54
|
+
console.log('\n Cloudflare deploy — browser sign-in + resource provisioning…\n');
|
|
55
|
+
|
|
56
|
+
let token = process.env.AGENTSAM_SDK_TOKEN || '';
|
|
57
|
+
if (!token) {
|
|
58
|
+
const session = await authenticateViaBrowser();
|
|
59
|
+
token = session.access_token;
|
|
60
|
+
console.log(`\n ✓ Signed in (${session.user_id})\n`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const ctx = await getJson('/api/sdk/context', token);
|
|
64
|
+
if (!ctx?.cloudflare?.ok) {
|
|
65
|
+
throw new Error('Connect Cloudflare in IAM Integrations during browser sign-in, then retry.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let complete = null;
|
|
69
|
+
await streamScaffold(
|
|
70
|
+
{
|
|
71
|
+
project_name: config.project,
|
|
72
|
+
lane: config.lane,
|
|
73
|
+
hosting: 'cloudflare',
|
|
74
|
+
provision_only: true,
|
|
75
|
+
account_id: accountId || undefined,
|
|
76
|
+
workspace_id: ctx.workspace_id,
|
|
77
|
+
},
|
|
78
|
+
token,
|
|
79
|
+
async (evt) => {
|
|
80
|
+
if (evt.type === 'log') console.log(` · ${evt.message}`);
|
|
81
|
+
else if (evt.type === 'warn') console.log(` ⚠ ${evt.message}`);
|
|
82
|
+
else if (evt.type === 'account_selection_required') {
|
|
83
|
+
console.log('\n Multiple Cloudflare accounts — re-run with: agentsam deploy --account-id <id>\n');
|
|
84
|
+
for (const a of evt.accounts || []) console.log(` ${a.id} ${a.name || ''}`);
|
|
85
|
+
throw new Error('cloudflare_account_selection_required');
|
|
86
|
+
} else if (evt.type === 'error') throw new Error(evt.error || 'deploy failed');
|
|
87
|
+
else if (evt.type === 'complete') complete = evt;
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
if (!complete?.cloudflare) throw new Error('deploy incomplete — no cloudflare ids returned');
|
|
92
|
+
|
|
93
|
+
patchWranglerToml(cwd, complete.cloudflare);
|
|
94
|
+
writeConfig(cwd, {
|
|
95
|
+
...config,
|
|
96
|
+
deploy_target: 'cloudflare',
|
|
97
|
+
cloudflare: complete.cloudflare,
|
|
98
|
+
deployed_at: new Date().toISOString(),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
console.log(`
|
|
102
|
+
✓ Cloudflare resources provisioned in YOUR account
|
|
103
|
+
✓ wrangler.toml updated
|
|
104
|
+
|
|
105
|
+
Next:
|
|
106
|
+
npx wrangler deploy
|
|
107
|
+
npm run db:migrate -- --remote # when ready for remote D1
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {{ cwd?: string, target?: string, accountId?: string }} [opts]
|
|
113
|
+
*/
|
|
114
|
+
export async function runDeploy(opts = {}) {
|
|
115
|
+
const cwd = path.resolve(opts.cwd || process.cwd());
|
|
116
|
+
const config = readConfig(cwd);
|
|
117
|
+
|
|
118
|
+
let target = opts.target || config.deploy_target || 'cloudflare';
|
|
119
|
+
if (!opts.target && !config.deploy_target) {
|
|
120
|
+
console.log(`
|
|
121
|
+
Where do you want to deploy?
|
|
122
|
+
|
|
123
|
+
1) Cloudflare (Workers, D1, R2)
|
|
124
|
+
2) GCP (your Google Cloud project — manual wrangler/container step after)
|
|
125
|
+
`);
|
|
126
|
+
const pick = (await ask(' Select [1]: ')).trim() || '1';
|
|
127
|
+
target = pick === '2' ? 'gcp' : 'cloudflare';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (target === 'gcp') {
|
|
131
|
+
console.log(`
|
|
132
|
+
GCP deploy path:
|
|
133
|
+
|
|
134
|
+
gcloud auth login --no-browser
|
|
135
|
+
gcloud config set project YOUR_PROJECT_ID
|
|
136
|
+
export USER_GCP_PROJECT=YOUR_PROJECT_ID
|
|
137
|
+
|
|
138
|
+
Your local project keeps running with npm run dev.
|
|
139
|
+
Container/Worker deploy scripts are project-specific — add when ready.
|
|
140
|
+
`);
|
|
141
|
+
writeConfig(cwd, { ...config, deploy_target: 'gcp' });
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await runCloudflareDeploy(cwd, config, opts.accountId || '');
|
|
146
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { startLocalPtyServer } from '../local-pty/server.js';
|
|
4
|
+
|
|
5
|
+
function readConfig(cwd) {
|
|
6
|
+
const configPath = path.join(cwd, '.agentsam', 'config.json');
|
|
7
|
+
if (!fs.existsSync(configPath)) return null;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function findProjectRoot(startDir) {
|
|
16
|
+
let dir = path.resolve(startDir);
|
|
17
|
+
for (let i = 0; i < 12; i += 1) {
|
|
18
|
+
if (fs.existsSync(path.join(dir, '.agentsam', 'config.json'))) return dir;
|
|
19
|
+
const parent = path.dirname(dir);
|
|
20
|
+
if (parent === dir) break;
|
|
21
|
+
dir = parent;
|
|
22
|
+
}
|
|
23
|
+
return path.resolve(startDir);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {{ cwd?: string, port?: number }} [opts]
|
|
28
|
+
*/
|
|
29
|
+
export async function runStartLocal(opts = {}) {
|
|
30
|
+
const root = findProjectRoot(opts.cwd || process.cwd());
|
|
31
|
+
const config = readConfig(root);
|
|
32
|
+
const port = opts.port ?? config?.pty_port ?? 3099;
|
|
33
|
+
|
|
34
|
+
console.log(`
|
|
35
|
+
Agent Sam — local PTY
|
|
36
|
+
No Cloudflare · no tunnel · no IAM login
|
|
37
|
+
`);
|
|
38
|
+
|
|
39
|
+
const server = await startLocalPtyServer({ cwd: root, port });
|
|
40
|
+
|
|
41
|
+
console.log(` ✓ PTY listening ${server.url}`);
|
|
42
|
+
console.log(` ✓ Health ${server.healthUrl}`);
|
|
43
|
+
console.log(` ✓ Project root ${server.cwd}`);
|
|
44
|
+
console.log(` ✓ Shell ${server.shell}`);
|
|
45
|
+
console.log(`
|
|
46
|
+
Next:
|
|
47
|
+
npm run dev → http://127.0.0.1:${config?.dev_port ?? 8787}
|
|
48
|
+
npm run db:migrate → local D1 schema (first run)
|
|
49
|
+
|
|
50
|
+
Press Ctrl+C to stop.
|
|
51
|
+
`);
|
|
52
|
+
|
|
53
|
+
const shutdown = async () => {
|
|
54
|
+
await server.close().catch(() => {});
|
|
55
|
+
process.exit(0);
|
|
56
|
+
};
|
|
57
|
+
process.on('SIGINT', shutdown);
|
|
58
|
+
process.on('SIGTERM', shutdown);
|
|
59
|
+
|
|
60
|
+
await new Promise(() => {});
|
|
61
|
+
}
|