@inneranimalmedia/agentsam-sdk 1.2.0 → 1.5.1
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 +6 -1
- package/src/cli.js +99 -178
- 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/gorilla-template.js +54 -0
- package/src/lib/local-scaffold.js +397 -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/templates/gorilla-shell/App.tsx +639 -0
- package/templates/gorilla-shell/README.md +22 -0
- package/templates/gorilla-shell/index.html +15 -0
- package/templates/gorilla-shell/main.jsx +9 -0
- package/templates/gorilla-shell/package.json +19 -0
- package/templates/gorilla-shell/vite.config.js +21 -0
- package/test/smoke.mjs +33 -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.1",
|
|
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",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"files": [
|
|
15
15
|
"src",
|
|
16
|
+
"templates",
|
|
16
17
|
"docs",
|
|
17
18
|
"examples",
|
|
18
19
|
"test",
|
|
@@ -28,6 +29,10 @@
|
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=20"
|
|
30
31
|
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"node-pty": "^1.0.0",
|
|
34
|
+
"ws": "^8.18.0"
|
|
35
|
+
},
|
|
31
36
|
"publishConfig": {
|
|
32
37
|
"access": "public"
|
|
33
38
|
},
|
package/src/cli.js
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
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 { copyGorillaTemplate } from './lib/gorilla-template.js';
|
|
8
|
+
import { printContextSummary } from './lib/detect-context.js';
|
|
9
|
+
import { promptOptionalByokKeys } from './lib/prompt-byok.js';
|
|
10
|
+
import { runStartLocal } from './commands/start-local.js';
|
|
11
|
+
import { runDeploy } from './commands/deploy.js';
|
|
6
12
|
import { SLASH_COMMANDS, SHELL_PHASES } from './lib/slash-commands.js';
|
|
7
13
|
|
|
8
14
|
const VERSION = pkg.version;
|
|
@@ -15,150 +21,96 @@ function createPrompt() {
|
|
|
15
21
|
};
|
|
16
22
|
}
|
|
17
23
|
|
|
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
24
|
function printHelp() {
|
|
60
25
|
console.log(`
|
|
61
26
|
Agent Sam SDK — CLI v${VERSION}
|
|
62
27
|
|
|
63
28
|
Usage:
|
|
64
|
-
agentsam init
|
|
65
|
-
agentsam
|
|
66
|
-
agentsam
|
|
67
|
-
agentsam
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
29
|
+
agentsam init Local-first project scaffold (default: localhost, no accounts)
|
|
30
|
+
agentsam start-local Local PTY on ws://127.0.0.1:3099 (no tunnel, no Cloudflare)
|
|
31
|
+
agentsam deploy Graduate to Cloudflare / GCP when ready
|
|
32
|
+
agentsam shell Slash commands + shell UX info
|
|
33
|
+
agentsam --version
|
|
34
|
+
agentsam --help
|
|
35
|
+
|
|
36
|
+
Init is completable with Node only — no IAM login, no OAuth, no Cloudflare.
|
|
37
|
+
Prove locally first; deploy prompts for accounts only when you choose to ship.
|
|
38
|
+
|
|
39
|
+
Init options:
|
|
40
|
+
--name <name> Project directory name
|
|
71
41
|
--lane <fullstack|cms|data|crm|creative>
|
|
72
|
-
--
|
|
73
|
-
--
|
|
74
|
-
--cf-account <id> Cloudflare account ID
|
|
75
|
-
--yes Skip confirmation prompt
|
|
42
|
+
--run-target <local|cloudflare|gcp> Default: local
|
|
43
|
+
--yes Skip confirmation
|
|
76
44
|
`);
|
|
77
45
|
}
|
|
78
46
|
|
|
79
47
|
function parseInitArgs(argv) {
|
|
80
48
|
const opts = {
|
|
81
49
|
projectName: '',
|
|
82
|
-
lane: '',
|
|
83
|
-
|
|
84
|
-
agent: '',
|
|
85
|
-
cfAccountId: '',
|
|
50
|
+
lane: 'fullstack',
|
|
51
|
+
runTarget: 'local',
|
|
86
52
|
yes: false,
|
|
87
53
|
};
|
|
88
|
-
|
|
89
54
|
for (let i = 0; i < argv.length; i += 1) {
|
|
90
55
|
const arg = argv[i];
|
|
91
56
|
if (arg === '--yes' || arg === '-y') opts.yes = true;
|
|
92
57
|
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] || '';
|
|
58
|
+
else if (arg === '--lane') opts.lane = argv[++i] || 'fullstack';
|
|
59
|
+
else if (arg === '--run-target' || arg === '--target') opts.runTarget = argv[++i] || 'local';
|
|
101
60
|
}
|
|
61
|
+
return opts;
|
|
62
|
+
}
|
|
102
63
|
|
|
64
|
+
function parseDeployArgs(argv) {
|
|
65
|
+
const opts = { target: '', accountId: '' };
|
|
66
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
67
|
+
const arg = argv[i];
|
|
68
|
+
if (arg === '--target') opts.target = argv[++i] || '';
|
|
69
|
+
else if (arg === '--account-id') opts.accountId = argv[++i] || '';
|
|
70
|
+
}
|
|
103
71
|
return opts;
|
|
104
72
|
}
|
|
105
73
|
|
|
106
|
-
async function
|
|
107
|
-
const {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
yes = false,
|
|
114
|
-
prompt = null,
|
|
115
|
-
} = config;
|
|
74
|
+
async function runLocalInit(config) {
|
|
75
|
+
const { projectName, lane, runTarget, prompt } = config;
|
|
76
|
+
|
|
77
|
+
const meta = buildLocalScaffoldMeta(
|
|
78
|
+
{ projectName, lane, runTarget },
|
|
79
|
+
VERSION,
|
|
80
|
+
);
|
|
116
81
|
|
|
117
|
-
const safeDir = projectName.trim();
|
|
118
82
|
console.log(`
|
|
119
83
|
┌─────────────────────────────────────┐
|
|
120
|
-
│ Agent Sam
|
|
84
|
+
│ Agent Sam — local-first scaffold │
|
|
121
85
|
├─────────────────────────────────────┤
|
|
122
|
-
│
|
|
123
|
-
│ Lane:
|
|
124
|
-
│
|
|
125
|
-
│ Agent: ${agent.padEnd(25)}│
|
|
126
|
-
│ CF Acct: ${(cfAccountId || 'not set').padEnd(25)}│
|
|
86
|
+
│ Name: ${meta.projectName.padEnd(25)}│
|
|
87
|
+
│ Lane: ${meta.laneKey.padEnd(25)}│
|
|
88
|
+
│ Run: ${meta.runTarget.padEnd(25)}│
|
|
127
89
|
└─────────────────────────────────────┘
|
|
128
90
|
`);
|
|
129
91
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
confirm = await prompt.ask(' Scaffold project? (y/n): ');
|
|
133
|
-
}
|
|
92
|
+
const dir = writeScaffoldFiles(meta.projectName, meta.files);
|
|
93
|
+
copyGorillaTemplate(dir, meta);
|
|
134
94
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
projectName: safeDir,
|
|
139
|
-
lane,
|
|
140
|
-
provider,
|
|
141
|
-
agent,
|
|
142
|
-
cfAccountId,
|
|
143
|
-
sdkVersion: VERSION,
|
|
144
|
-
});
|
|
145
|
-
console.log(`
|
|
146
|
-
✓ Project created at ${dir}
|
|
95
|
+
console.log(`
|
|
96
|
+
✓ Project ready: ${dir}
|
|
97
|
+
✓ Gorilla Mode UI → gorilla/ (http://localhost:5173 after npm run dev)
|
|
147
98
|
|
|
148
|
-
Next steps
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
console.error(`\n ✗ ${error.message}\n`);
|
|
157
|
-
process.exitCode = 1;
|
|
158
|
-
}
|
|
159
|
-
} else {
|
|
160
|
-
console.log('\n Cancelled.\n');
|
|
99
|
+
Next steps:`);
|
|
100
|
+
for (const step of meta.next_steps) {
|
|
101
|
+
console.log(` ${step}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (prompt && process.env.AGENTSAM_SDK_TOKEN) {
|
|
105
|
+
console.log('\n Optional — BYOK keys for IAM dashboard Agent Sam (skip with Enter):\n');
|
|
106
|
+
await promptOptionalByokKeys(process.env.AGENTSAM_SDK_TOKEN, prompt);
|
|
161
107
|
}
|
|
108
|
+
|
|
109
|
+
console.log(`
|
|
110
|
+
Local in ~60 seconds:
|
|
111
|
+
cd ${meta.projectName} && npm install && npm run smoke && npm run dev
|
|
112
|
+
open http://localhost:5173
|
|
113
|
+
`);
|
|
162
114
|
}
|
|
163
115
|
|
|
164
116
|
async function initInteractive(partial = {}) {
|
|
@@ -166,66 +118,41 @@ async function initInteractive(partial = {}) {
|
|
|
166
118
|
|
|
167
119
|
console.log(`
|
|
168
120
|
╔═══════════════════════════════════╗
|
|
169
|
-
║
|
|
121
|
+
║ Agent Sam SDK — Init ║
|
|
122
|
+
║ Local-first · Node only ║
|
|
170
123
|
╚═══════════════════════════════════╝
|
|
171
124
|
`);
|
|
172
125
|
|
|
173
|
-
|
|
126
|
+
printContextSummary(await import('./lib/detect-context.js').then((m) => m.detectContext()));
|
|
127
|
+
|
|
128
|
+
const projectName =
|
|
129
|
+
partial.projectName ||
|
|
130
|
+
(await prompt.ask(' 1) Project name: '));
|
|
174
131
|
|
|
175
132
|
if (!partial.lane) {
|
|
176
133
|
console.log(`
|
|
177
|
-
|
|
178
|
-
1) Full Stack
|
|
179
|
-
2) CMS
|
|
180
|
-
3) Data Solutions
|
|
181
|
-
4) Customer Management
|
|
182
|
-
5) Creative & Design
|
|
134
|
+
2) Lane:
|
|
135
|
+
1) Full Stack 2) CMS 3) Data 4) CRM 5) Creative
|
|
183
136
|
`);
|
|
184
137
|
}
|
|
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';
|
|
138
|
+
const laneKey = partial.lane
|
|
139
|
+
? partial.lane
|
|
140
|
+
: LANE_KEYS[await prompt.ask(' Pick lane [1-5]: ')]?.key || 'fullstack';
|
|
198
141
|
|
|
199
|
-
if (!partial.
|
|
142
|
+
if (!partial.runTarget) {
|
|
200
143
|
console.log(`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
5) Creative Agent — design, 3D, media, content
|
|
144
|
+
3) Where do you want to run your project?
|
|
145
|
+
|
|
146
|
+
1) Local (localhost — start here, no accounts needed)
|
|
147
|
+
2) Cloudflare (Workers, D1, R2 — deploy when ready)
|
|
148
|
+
3) GCP (your own Google Cloud project)
|
|
207
149
|
`);
|
|
208
150
|
}
|
|
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
|
-
});
|
|
151
|
+
const runTarget = partial.runTarget
|
|
152
|
+
? partial.runTarget
|
|
153
|
+
: RUN_TARGETS[await prompt.ask(' Select [1]: ')] || 'local';
|
|
228
154
|
|
|
155
|
+
await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
|
|
229
156
|
prompt.close();
|
|
230
157
|
}
|
|
231
158
|
|
|
@@ -235,15 +162,7 @@ async function initFromArgs(argv) {
|
|
|
235
162
|
console.error('\n ✗ --name is required for non-interactive init.\n');
|
|
236
163
|
process.exit(1);
|
|
237
164
|
}
|
|
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
|
-
});
|
|
165
|
+
await runLocalInit({ ...opts, prompt: null });
|
|
247
166
|
}
|
|
248
167
|
|
|
249
168
|
async function runShellInfo() {
|
|
@@ -253,21 +172,14 @@ async function runShellInfo() {
|
|
|
253
172
|
║ Agent Sam Shell (Gorilla) ║
|
|
254
173
|
╚═══════════════════════════════════╝
|
|
255
174
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
Phase 0 prototype: examples/gorilla-shell/
|
|
259
|
-
Docs: docs/CLI_SHELL.md
|
|
260
|
-
Next milestone: ${next?.label ?? 'PTY connection'}
|
|
175
|
+
Local PTY: agentsam start-local (ws://127.0.0.1:3099)
|
|
176
|
+
Next milestone: ${next?.label ?? 'dashboard bridge after deploy'}
|
|
261
177
|
|
|
262
178
|
Slash commands (${SLASH_COMMANDS.length} registered):
|
|
263
179
|
`);
|
|
264
180
|
for (const row of SLASH_COMMANDS) {
|
|
265
181
|
console.log(` ${row.cmd.padEnd(14)} ${row.description}`);
|
|
266
182
|
}
|
|
267
|
-
console.log(`
|
|
268
|
-
Run the visual prototype:
|
|
269
|
-
cd examples/gorilla-shell && npm install && npm run dev
|
|
270
|
-
`);
|
|
271
183
|
}
|
|
272
184
|
|
|
273
185
|
const command = process.argv[2];
|
|
@@ -279,6 +191,15 @@ if (command === '--version' || command === '-v') {
|
|
|
279
191
|
printHelp();
|
|
280
192
|
} else if (command === 'shell') {
|
|
281
193
|
await runShellInfo();
|
|
194
|
+
} else if (command === 'start-local') {
|
|
195
|
+
await runStartLocal({});
|
|
196
|
+
} else if (command === 'deploy') {
|
|
197
|
+
try {
|
|
198
|
+
await runDeploy(parseDeployArgs(rest));
|
|
199
|
+
} catch (e) {
|
|
200
|
+
console.error(`\n ✗ ${e?.message || e}\n`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
282
203
|
} else if (command === 'init') {
|
|
283
204
|
const hasFlags = rest.some((a) => a.startsWith('--'));
|
|
284
205
|
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
|
+
}
|