@inneranimalmedia/agentsam-sdk 1.1.1 → 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/DEVELOPMENT.md +4 -1
- package/README.md +79 -43
- package/docs/CLI_SHELL.md +90 -0
- package/examples/gorilla-shell/App.tsx +540 -0
- package/examples/gorilla-shell/README.md +22 -0
- package/examples/gorilla-shell/index.html +15 -0
- package/examples/gorilla-shell/main.jsx +9 -0
- package/examples/gorilla-shell/package.json +19 -0
- package/examples/gorilla-shell/vite.config.js +10 -0
- package/package.json +8 -2
- package/src/cli.js +114 -167
- package/src/commands/deploy.js +146 -0
- package/src/commands/start-local.js +61 -0
- package/src/index.js +6 -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 +40 -0
- package/src/lib/write-files.js +21 -0
- package/src/local-pty/server.js +133 -0
- package/test/smoke.mjs +28 -5
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 { 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';
|
|
11
|
+
import { SLASH_COMMANDS, SHELL_PHASES } from './lib/slash-commands.js';
|
|
6
12
|
|
|
7
13
|
const VERSION = pkg.version;
|
|
8
14
|
|
|
@@ -14,149 +20,95 @@ function createPrompt() {
|
|
|
14
20
|
};
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
const LANES = {
|
|
18
|
-
'1': 'Full Stack',
|
|
19
|
-
'2': 'CMS',
|
|
20
|
-
'3': 'Data Solutions',
|
|
21
|
-
'4': 'Customer Management',
|
|
22
|
-
'5': 'Creative & Design',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const LANE_BY_SLUG = {
|
|
26
|
-
fullstack: 'Full Stack',
|
|
27
|
-
cms: 'CMS',
|
|
28
|
-
data: 'Data Solutions',
|
|
29
|
-
crm: 'Customer Management',
|
|
30
|
-
creative: 'Creative & Design',
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const PROVIDERS = {
|
|
34
|
-
'1': 'Cloudflare Workers',
|
|
35
|
-
'2': 'GitHub + Cloudflare',
|
|
36
|
-
'3': 'Local / Self-hosted',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const PROVIDER_BY_SLUG = {
|
|
40
|
-
cloudflare: 'Cloudflare Workers',
|
|
41
|
-
github: 'GitHub + Cloudflare',
|
|
42
|
-
local: 'Local / Self-hosted',
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const AGENTS = {
|
|
46
|
-
'1': 'orchestrator',
|
|
47
|
-
'2': 'cms',
|
|
48
|
-
'3': 'data',
|
|
49
|
-
'4': 'crm',
|
|
50
|
-
'5': 'creative',
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
function laneKeyFromLabel(label) {
|
|
54
|
-
const entry = Object.entries(LANES).find(([, v]) => v === label);
|
|
55
|
-
return entry?.[0] ?? '1';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
23
|
function printHelp() {
|
|
59
24
|
console.log(`
|
|
60
25
|
Agent Sam SDK — CLI v${VERSION}
|
|
61
26
|
|
|
62
27
|
Usage:
|
|
63
|
-
agentsam init
|
|
64
|
-
agentsam
|
|
65
|
-
agentsam
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
--
|
|
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
|
|
69
40
|
--lane <fullstack|cms|data|crm|creative>
|
|
70
|
-
--
|
|
71
|
-
--
|
|
72
|
-
--cf-account <id> Cloudflare account ID
|
|
73
|
-
--yes Skip confirmation prompt
|
|
41
|
+
--run-target <local|cloudflare|gcp> Default: local
|
|
42
|
+
--yes Skip confirmation
|
|
74
43
|
`);
|
|
75
44
|
}
|
|
76
45
|
|
|
77
46
|
function parseInitArgs(argv) {
|
|
78
47
|
const opts = {
|
|
79
48
|
projectName: '',
|
|
80
|
-
lane: '',
|
|
81
|
-
|
|
82
|
-
agent: '',
|
|
83
|
-
cfAccountId: '',
|
|
49
|
+
lane: 'fullstack',
|
|
50
|
+
runTarget: 'local',
|
|
84
51
|
yes: false,
|
|
85
52
|
};
|
|
86
|
-
|
|
87
53
|
for (let i = 0; i < argv.length; i += 1) {
|
|
88
54
|
const arg = argv[i];
|
|
89
55
|
if (arg === '--yes' || arg === '-y') opts.yes = true;
|
|
90
56
|
else if (arg === '--name') opts.projectName = argv[++i] || '';
|
|
91
|
-
else if (arg === '--lane')
|
|
92
|
-
|
|
93
|
-
opts.lane = LANE_BY_SLUG[slug] || slug;
|
|
94
|
-
} else if (arg === '--provider') {
|
|
95
|
-
const slug = argv[++i] || '';
|
|
96
|
-
opts.provider = PROVIDER_BY_SLUG[slug] || slug;
|
|
97
|
-
} else if (arg === '--agent') opts.agent = argv[++i] || '';
|
|
98
|
-
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';
|
|
99
59
|
}
|
|
60
|
+
return opts;
|
|
61
|
+
}
|
|
100
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
|
+
}
|
|
101
70
|
return opts;
|
|
102
71
|
}
|
|
103
72
|
|
|
104
|
-
async function
|
|
105
|
-
const {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
yes = false,
|
|
112
|
-
prompt = null,
|
|
113
|
-
} = 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
|
+
);
|
|
114
80
|
|
|
115
|
-
const safeDir = projectName.trim();
|
|
116
81
|
console.log(`
|
|
117
82
|
┌─────────────────────────────────────┐
|
|
118
|
-
│ Agent Sam
|
|
83
|
+
│ Agent Sam — local-first scaffold │
|
|
119
84
|
├─────────────────────────────────────┤
|
|
120
|
-
│
|
|
121
|
-
│ Lane:
|
|
122
|
-
│
|
|
123
|
-
│ Agent: ${agent.padEnd(25)}│
|
|
124
|
-
│ 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)}│
|
|
125
88
|
└─────────────────────────────────────┘
|
|
126
89
|
`);
|
|
127
90
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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}`);
|
|
131
99
|
}
|
|
132
100
|
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
lane,
|
|
138
|
-
provider,
|
|
139
|
-
agent,
|
|
140
|
-
cfAccountId,
|
|
141
|
-
sdkVersion: VERSION,
|
|
142
|
-
});
|
|
143
|
-
console.log(`
|
|
144
|
-
✓ 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
|
+
}
|
|
145
105
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
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
|
|
151
110
|
npm run dev
|
|
152
|
-
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error(`\n ✗ ${error.message}\n`);
|
|
155
|
-
process.exitCode = 1;
|
|
156
|
-
}
|
|
157
|
-
} else {
|
|
158
|
-
console.log('\n Cancelled.\n');
|
|
159
|
-
}
|
|
111
|
+
`);
|
|
160
112
|
}
|
|
161
113
|
|
|
162
114
|
async function initInteractive(partial = {}) {
|
|
@@ -164,66 +116,41 @@ async function initInteractive(partial = {}) {
|
|
|
164
116
|
|
|
165
117
|
console.log(`
|
|
166
118
|
╔═══════════════════════════════════╗
|
|
167
|
-
║
|
|
119
|
+
║ Agent Sam SDK — Init ║
|
|
120
|
+
║ Local-first · Node only ║
|
|
168
121
|
╚═══════════════════════════════════╝
|
|
169
122
|
`);
|
|
170
123
|
|
|
171
|
-
|
|
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: '));
|
|
172
129
|
|
|
173
130
|
if (!partial.lane) {
|
|
174
131
|
console.log(`
|
|
175
|
-
|
|
176
|
-
1) Full Stack
|
|
177
|
-
2) CMS
|
|
178
|
-
3) Data Solutions
|
|
179
|
-
4) Customer Management
|
|
180
|
-
5) Creative & Design
|
|
132
|
+
2) Lane:
|
|
133
|
+
1) Full Stack 2) CMS 3) Data 4) CRM 5) Creative
|
|
181
134
|
`);
|
|
182
135
|
}
|
|
183
|
-
const laneKey = partial.lane
|
|
184
|
-
|
|
136
|
+
const laneKey = partial.lane
|
|
137
|
+
? partial.lane
|
|
138
|
+
: LANE_KEYS[await prompt.ask(' Pick lane [1-5]: ')]?.key || 'fullstack';
|
|
185
139
|
|
|
186
|
-
|
|
187
|
-
Select a provider:
|
|
188
|
-
1) Cloudflare Workers
|
|
189
|
-
2) GitHub + Cloudflare
|
|
190
|
-
3) Local / Self-hosted
|
|
191
|
-
`);
|
|
192
|
-
const providerKey = partial.provider
|
|
193
|
-
? Object.entries(PROVIDERS).find(([, v]) => v === partial.provider)?.[0] || '1'
|
|
194
|
-
: await prompt.ask(' Provider [1-3]: ');
|
|
195
|
-
const provider = partial.provider || PROVIDERS[providerKey] || 'Cloudflare Workers';
|
|
196
|
-
|
|
197
|
-
if (!partial.agent) {
|
|
140
|
+
if (!partial.runTarget) {
|
|
198
141
|
console.log(`
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
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)
|
|
205
147
|
`);
|
|
206
148
|
}
|
|
207
|
-
const
|
|
208
|
-
?
|
|
209
|
-
: await prompt.ask('
|
|
210
|
-
const agent = partial.agent || AGENTS[agentKey] || 'orchestrator';
|
|
211
|
-
|
|
212
|
-
const cfAccountId =
|
|
213
|
-
partial.cfAccountId !== undefined && partial.cfAccountId !== ''
|
|
214
|
-
? partial.cfAccountId
|
|
215
|
-
: await prompt.ask(' Cloudflare Account ID (enter to skip): ');
|
|
216
|
-
|
|
217
|
-
await runInit({
|
|
218
|
-
projectName,
|
|
219
|
-
lane,
|
|
220
|
-
provider,
|
|
221
|
-
agent,
|
|
222
|
-
cfAccountId,
|
|
223
|
-
yes: partial.yes,
|
|
224
|
-
prompt,
|
|
225
|
-
});
|
|
149
|
+
const runTarget = partial.runTarget
|
|
150
|
+
? partial.runTarget
|
|
151
|
+
: RUN_TARGETS[await prompt.ask(' Select [1]: ')] || 'local';
|
|
226
152
|
|
|
153
|
+
await runLocalInit({ projectName, lane: laneKey, runTarget, prompt });
|
|
227
154
|
prompt.close();
|
|
228
155
|
}
|
|
229
156
|
|
|
@@ -233,15 +160,24 @@ async function initFromArgs(argv) {
|
|
|
233
160
|
console.error('\n ✗ --name is required for non-interactive init.\n');
|
|
234
161
|
process.exit(1);
|
|
235
162
|
}
|
|
236
|
-
await
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
163
|
+
await runLocalInit({ ...opts, prompt: null });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runShellInfo() {
|
|
167
|
+
const next = SHELL_PHASES.find((p) => p.status === 'next');
|
|
168
|
+
console.log(`
|
|
169
|
+
╔═══════════════════════════════════╗
|
|
170
|
+
║ Agent Sam Shell (Gorilla) ║
|
|
171
|
+
╚═══════════════════════════════════╝
|
|
172
|
+
|
|
173
|
+
Local PTY: agentsam start-local (ws://127.0.0.1:3099)
|
|
174
|
+
Next milestone: ${next?.label ?? 'dashboard bridge after deploy'}
|
|
175
|
+
|
|
176
|
+
Slash commands (${SLASH_COMMANDS.length} registered):
|
|
177
|
+
`);
|
|
178
|
+
for (const row of SLASH_COMMANDS) {
|
|
179
|
+
console.log(` ${row.cmd.padEnd(14)} ${row.description}`);
|
|
180
|
+
}
|
|
245
181
|
}
|
|
246
182
|
|
|
247
183
|
const command = process.argv[2];
|
|
@@ -251,6 +187,17 @@ if (command === '--version' || command === '-v') {
|
|
|
251
187
|
console.log(VERSION);
|
|
252
188
|
} else if (command === '--help' || command === '-h' || !command) {
|
|
253
189
|
printHelp();
|
|
190
|
+
} else if (command === 'shell') {
|
|
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
|
+
}
|
|
254
201
|
} else if (command === 'init') {
|
|
255
202
|
const hasFlags = rest.some((a) => a.startsWith('--'));
|
|
256
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
|
+
}
|
package/src/index.js
CHANGED
|
@@ -6,6 +6,12 @@ export { AgentSam } from './AgentSam.js';
|
|
|
6
6
|
export { routeIntent } from './lib/router.js';
|
|
7
7
|
export { getToolCatalog } from './lib/tools.js';
|
|
8
8
|
export { scaffoldProject } from './lib/scaffold.js';
|
|
9
|
+
export {
|
|
10
|
+
SLASH_COMMANDS,
|
|
11
|
+
SHELL_THEMES,
|
|
12
|
+
SHELL_PHASES,
|
|
13
|
+
listSlashCommands,
|
|
14
|
+
} from './lib/slash-commands.js';
|
|
9
15
|
|
|
10
16
|
export const version = pkg.version;
|
|
11
17
|
export const name = pkg.name;
|
package/src/lib/auth.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser OAuth for SDK init — one click IAM login + Cloudflare connect.
|
|
3
|
+
*/
|
|
4
|
+
import http from 'http';
|
|
5
|
+
import { randomBytes } from 'crypto';
|
|
6
|
+
import { postJson } from './core-client.js';
|
|
7
|
+
|
|
8
|
+
function randomState() {
|
|
9
|
+
return randomBytes(16).toString('hex');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function openBrowser(url) {
|
|
13
|
+
const start =
|
|
14
|
+
process.platform === 'darwin'
|
|
15
|
+
? ['open', url]
|
|
16
|
+
: process.platform === 'win32'
|
|
17
|
+
? ['cmd', '/c', 'start', '', url]
|
|
18
|
+
: ['xdg-open', url];
|
|
19
|
+
import('child_process').then(({ spawn }) => {
|
|
20
|
+
spawn(start[0], start.slice(1), { stdio: 'ignore', detached: true }).unref();
|
|
21
|
+
}).catch(() => {
|
|
22
|
+
console.log(`\n Open in browser:\n ${url}\n`);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @returns {Promise<{ access_token: string, user_id: string, workspace_id: string, tenant_id: string }>}
|
|
28
|
+
*/
|
|
29
|
+
export async function authenticateViaBrowser() {
|
|
30
|
+
const state = randomState();
|
|
31
|
+
const port = 8791 + (randomBytes(1)[0] % 20);
|
|
32
|
+
const redirectUri = `http://127.0.0.1:${port}/callback`;
|
|
33
|
+
|
|
34
|
+
const { auth_url: authUrl } = await postJson('/api/sdk/auth/start', {
|
|
35
|
+
redirect_uri: redirectUri,
|
|
36
|
+
state,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
40
|
+
const server = http.createServer((req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
const u = new URL(req.url || '/', `http://127.0.0.1:${port}`);
|
|
43
|
+
if (u.pathname !== '/callback') {
|
|
44
|
+
res.writeHead(404);
|
|
45
|
+
res.end('Not found');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const code = u.searchParams.get('code');
|
|
49
|
+
const gotState = u.searchParams.get('state');
|
|
50
|
+
if (!code || gotState !== state) {
|
|
51
|
+
res.writeHead(400);
|
|
52
|
+
res.end('Invalid callback');
|
|
53
|
+
reject(new Error('auth callback invalid'));
|
|
54
|
+
server.close();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
58
|
+
res.end('<html><body style="font-family:system-ui"><h1>Agent Sam</h1><p>You can close this tab.</p></body></html>');
|
|
59
|
+
resolve(code);
|
|
60
|
+
server.close();
|
|
61
|
+
} catch (e) {
|
|
62
|
+
reject(e);
|
|
63
|
+
server.close();
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
server.on('error', reject);
|
|
67
|
+
server.listen(port, '127.0.0.1');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log('\n Opening browser for IAM sign-in + Cloudflare connect…\n');
|
|
71
|
+
openBrowser(authUrl);
|
|
72
|
+
|
|
73
|
+
const code = await codePromise;
|
|
74
|
+
const session = await postJson('/api/sdk/auth/exchange', { code, state });
|
|
75
|
+
return session;
|
|
76
|
+
}
|