@castari/cli 0.0.6 → 0.0.8
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/dist/commands/client-id.d.ts +1 -0
- package/dist/commands/client-id.js +25 -0
- package/dist/commands/deploy.js +8 -1
- package/dist/commands/generate-client-id.d.ts +1 -0
- package/dist/commands/generate-client-id.js +57 -0
- package/dist/commands/init.js +311 -115
- package/dist/commands/start.js +13 -4
- package/dist/index.js +5 -1
- package/dist/utils/client-auth.d.ts +11 -0
- package/dist/utils/client-auth.js +21 -0
- package/dist/utils/client-id.d.ts +10 -0
- package/dist/utils/client-id.js +36 -0
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveClientId(): Promise<string>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
const CONFIG_PATH = join(homedir(), '.castari', 'client.json');
|
|
7
|
+
export async function resolveClientId() {
|
|
8
|
+
const envId = process.env.CASTARI_CLIENT_ID;
|
|
9
|
+
if (envId)
|
|
10
|
+
return envId.trim();
|
|
11
|
+
if (existsSync(CONFIG_PATH)) {
|
|
12
|
+
try {
|
|
13
|
+
const raw = await readFile(CONFIG_PATH, 'utf-8');
|
|
14
|
+
const data = JSON.parse(raw);
|
|
15
|
+
if (data.clientId)
|
|
16
|
+
return data.clientId.trim();
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// ignore and fall through
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
console.error(chalk.red('❌ CASTARI_CLIENT_ID is required.'));
|
|
23
|
+
console.error(chalk.white('Run `castari generate-client-id` to create one (stored in ~/.castari/client.json), or set CASTARI_CLIENT_ID in your environment.'));
|
|
24
|
+
throw new Error('Missing CASTARI_CLIENT_ID');
|
|
25
|
+
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { readFile } from 'fs/promises';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import AdmZip from 'adm-zip';
|
|
4
|
+
import { getClientAuthOrExit } from '../utils/client-auth';
|
|
4
5
|
export async function deploy(options) {
|
|
6
|
+
const { clientId, apiKey } = await getClientAuthOrExit();
|
|
5
7
|
// Read package.json to get default snapshot name
|
|
6
8
|
let snapshotName = options.snapshot;
|
|
7
9
|
if (!snapshotName) {
|
|
@@ -43,17 +45,22 @@ export async function deploy(options) {
|
|
|
43
45
|
const formData = new FormData();
|
|
44
46
|
formData.append('file', new Blob([zipBuffer]), 'source.zip');
|
|
45
47
|
formData.append('snapshot', snapshotName);
|
|
48
|
+
formData.append('clientId', clientId);
|
|
46
49
|
try {
|
|
50
|
+
const headers = {};
|
|
51
|
+
if (apiKey)
|
|
52
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
47
53
|
const response = await fetch(`${platformUrl}/deploy`, {
|
|
48
54
|
method: 'POST',
|
|
49
55
|
body: formData,
|
|
56
|
+
headers,
|
|
50
57
|
});
|
|
51
58
|
if (!response.ok) {
|
|
52
59
|
const errorText = await response.text();
|
|
53
60
|
throw new Error(`Platform error (${response.status}): ${errorText}`);
|
|
54
61
|
}
|
|
55
62
|
const result = await response.json();
|
|
56
|
-
console.log(chalk.green(`✅ Snapshot "${result.snapshot}" created successfully
|
|
63
|
+
console.log(chalk.green(`✅ Snapshot "${result.snapshot}" created successfully for client ${clientId}`));
|
|
57
64
|
}
|
|
58
65
|
catch (err) {
|
|
59
66
|
console.error(chalk.red('Deploy failed:'), err.message || err);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateClientId(): Promise<void>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { saveClientAuth } from '../utils/client-auth';
|
|
5
|
+
export async function generateClientId() {
|
|
6
|
+
const platformUrl = process.env.CASTARI_PLATFORM_URL || 'http://localhost:3000';
|
|
7
|
+
const mintOnline = async () => {
|
|
8
|
+
try {
|
|
9
|
+
const res = await fetch(`${platformUrl}/client-ids`, { method: 'POST' });
|
|
10
|
+
if (!res.ok) {
|
|
11
|
+
const text = await res.text();
|
|
12
|
+
throw new Error(text || `status ${res.status}`);
|
|
13
|
+
}
|
|
14
|
+
const data = (await res.json());
|
|
15
|
+
if (!data.clientId)
|
|
16
|
+
throw new Error('No clientId returned');
|
|
17
|
+
await saveClientAuth({
|
|
18
|
+
clientId: data.clientId,
|
|
19
|
+
apiKey: data.apiKey,
|
|
20
|
+
source: 'online',
|
|
21
|
+
platformUrl,
|
|
22
|
+
});
|
|
23
|
+
console.log(chalk.green(`✅ Client ID minted: ${data.clientId}`));
|
|
24
|
+
if (data.apiKey) {
|
|
25
|
+
console.log(chalk.green(`✅ API Key issued`));
|
|
26
|
+
}
|
|
27
|
+
console.log(chalk.white(`Saved to ~/.castari/client.json`));
|
|
28
|
+
return data.clientId;
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
console.error(chalk.yellow(`⚠️ Failed to mint via platform: ${err?.message || err}`));
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const onlineId = await mintOnline();
|
|
36
|
+
if (onlineId)
|
|
37
|
+
return;
|
|
38
|
+
const { confirmOffline } = await inquirer.prompt([
|
|
39
|
+
{
|
|
40
|
+
type: 'confirm',
|
|
41
|
+
name: 'confirmOffline',
|
|
42
|
+
message: 'Generate an offline client ID instead?',
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
if (!confirmOffline) {
|
|
47
|
+
console.error(chalk.red('Aborted. No client ID generated.'));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const offlineId = randomUUID();
|
|
51
|
+
await saveClientAuth({
|
|
52
|
+
clientId: offlineId,
|
|
53
|
+
source: 'offline',
|
|
54
|
+
});
|
|
55
|
+
console.log(chalk.green(`✅ Offline client ID generated: ${offlineId}`));
|
|
56
|
+
console.log(chalk.white(`Saved to ~/.castari/client.json`));
|
|
57
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -25,8 +25,8 @@ export async function init(options = {}) {
|
|
|
25
25
|
start: 'castari start',
|
|
26
26
|
},
|
|
27
27
|
dependencies: {
|
|
28
|
-
'@castari/sdk': '^0.0.
|
|
29
|
-
'@anthropic-ai/claude-agent-sdk': '^0.1.
|
|
28
|
+
'@castari/sdk': '^0.0.6',
|
|
29
|
+
'@anthropic-ai/claude-agent-sdk': '^0.1.50',
|
|
30
30
|
},
|
|
31
31
|
castari: {
|
|
32
32
|
volume: 'default-data',
|
|
@@ -41,8 +41,7 @@ export async function init(options = {}) {
|
|
|
41
41
|
skipLibCheck: true,
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
|
-
const agentTs = `import { serve } from '@castari/sdk
|
|
45
|
-
import { tool } from '@castari/sdk'
|
|
44
|
+
const agentTs = `import { serve, tool } from '@castari/sdk'
|
|
46
45
|
|
|
47
46
|
// Define your agent tools and logic here
|
|
48
47
|
const myTool = tool({
|
|
@@ -65,7 +64,7 @@ serve({
|
|
|
65
64
|
systemPrompt: 'You are a helpful Castari agent.',
|
|
66
65
|
})
|
|
67
66
|
`;
|
|
68
|
-
const envExample = `ANTHROPIC_API_KEY=sk-ant-...\n# CASTARI_PLATFORM_URL=https://api.castari.com\n`;
|
|
67
|
+
const envExample = `ANTHROPIC_API_KEY=sk-ant-...\nCASTARI_CLIENT_ID=your-client-id\nCASTARI_API_KEY=castari_your-api-key\n# CASTARI_PLATFORM_URL=https://api.castari.com\n`;
|
|
69
68
|
await writeFile('package.json', JSON.stringify(packageJson, null, 2));
|
|
70
69
|
await writeFile('tsconfig.json', JSON.stringify(tsConfig, null, 2));
|
|
71
70
|
await writeFile('.env.example', envExample);
|
|
@@ -93,7 +92,7 @@ async function initDemo() {
|
|
|
93
92
|
start: 'bun run src/agent.ts',
|
|
94
93
|
},
|
|
95
94
|
dependencies: {
|
|
96
|
-
'@castari/sdk': '^0.0.
|
|
95
|
+
'@castari/sdk': '^0.0.6',
|
|
97
96
|
'@anthropic-ai/claude-agent-sdk': '^0.1.50',
|
|
98
97
|
},
|
|
99
98
|
castari: {
|
|
@@ -111,7 +110,7 @@ async function initDemo() {
|
|
|
111
110
|
},
|
|
112
111
|
include: ['src'],
|
|
113
112
|
});
|
|
114
|
-
await writeFile(join(agentDir, '.env.example'), 'ANTHROPIC_API_KEY=sk-ant-...\n# CASTARI_PLATFORM_URL=http://localhost:3000\n');
|
|
113
|
+
await writeFile(join(agentDir, '.env.example'), 'ANTHROPIC_API_KEY=sk-ant-...\nCASTARI_CLIENT_ID=your-client-id\nCASTARI_API_KEY=castari_your-api-key\n# CASTARI_PLATFORM_URL=http://localhost:3000\n');
|
|
115
114
|
await writeFile(join(agentDir, 'src', 'agent.ts'), `import { serve } from '@castari/sdk'
|
|
116
115
|
|
|
117
116
|
serve({
|
|
@@ -126,6 +125,7 @@ serve({
|
|
|
126
125
|
// Web scaffold
|
|
127
126
|
await mkdir(join(webDir, 'app', 'api', 'chat'), { recursive: true });
|
|
128
127
|
await mkdir(join(webDir, 'lib'), { recursive: true });
|
|
128
|
+
await mkdir(join(webDir, 'public'), { recursive: true });
|
|
129
129
|
await writeJson(join(webDir, 'package.json'), {
|
|
130
130
|
name: 'castari-demo-web',
|
|
131
131
|
version: '0.1.0',
|
|
@@ -136,7 +136,7 @@ serve({
|
|
|
136
136
|
start: 'next start',
|
|
137
137
|
},
|
|
138
138
|
dependencies: {
|
|
139
|
-
'@castari/sdk': '^0.0.
|
|
139
|
+
'@castari/sdk': '^0.0.6',
|
|
140
140
|
next: '14.2.3',
|
|
141
141
|
react: '18.3.1',
|
|
142
142
|
'react-dom': '18.3.1',
|
|
@@ -187,11 +187,16 @@ const nextConfig = {
|
|
|
187
187
|
|
|
188
188
|
export default nextConfig
|
|
189
189
|
`);
|
|
190
|
-
await writeFile(join(webDir, '.env.example'), 'ANTHROPIC_API_KEY=sk-ant-...\n# CASTARI_PLATFORM_URL=http://localhost:3000\n# CASTARI_DEBUG=false\n');
|
|
190
|
+
await writeFile(join(webDir, '.env.example'), 'ANTHROPIC_API_KEY=sk-ant-...\nCASTARI_CLIENT_ID=your-client-id\nCASTARI_API_KEY=castari_your-api-key\n# CASTARI_PLATFORM_URL=http://localhost:3000\n# CASTARI_DEBUG=false\n');
|
|
191
191
|
await writeFile(join(webDir, 'app', 'globals.css'), `:root {
|
|
192
|
-
color-scheme:
|
|
193
|
-
|
|
194
|
-
|
|
192
|
+
color-scheme: dark;
|
|
193
|
+
--bg: #131418;
|
|
194
|
+
--panel: #1c1d24;
|
|
195
|
+
--border: #2a2b36;
|
|
196
|
+
--muted: #888888;
|
|
197
|
+
--text: #ffffff;
|
|
198
|
+
--accent: #ffffff;
|
|
199
|
+
font-family: var(--font-inter), system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
195
200
|
}
|
|
196
201
|
|
|
197
202
|
* {
|
|
@@ -200,9 +205,10 @@ export default nextConfig
|
|
|
200
205
|
|
|
201
206
|
body {
|
|
202
207
|
margin: 0;
|
|
203
|
-
background:
|
|
204
|
-
color:
|
|
208
|
+
background-color: var(--bg);
|
|
209
|
+
color: var(--text);
|
|
205
210
|
min-height: 100vh;
|
|
211
|
+
font-weight: 300;
|
|
206
212
|
}
|
|
207
213
|
|
|
208
214
|
a {
|
|
@@ -210,139 +216,261 @@ a {
|
|
|
210
216
|
text-decoration: none;
|
|
211
217
|
}
|
|
212
218
|
|
|
213
|
-
.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
.page-shell {
|
|
220
|
+
position: relative;
|
|
221
|
+
min-height: 100vh;
|
|
222
|
+
overflow: hidden;
|
|
223
|
+
display: flex;
|
|
224
|
+
justify-content: center;
|
|
225
|
+
padding: 36px 20px 72px;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.content {
|
|
229
|
+
position: relative;
|
|
230
|
+
width: min(1180px, 100%);
|
|
217
231
|
display: flex;
|
|
218
232
|
flex-direction: column;
|
|
219
|
-
gap:
|
|
233
|
+
gap: 28px;
|
|
234
|
+
z-index: 1;
|
|
220
235
|
}
|
|
221
236
|
|
|
222
|
-
.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
padding:
|
|
237
|
+
.topbar {
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
justify-content: space-between;
|
|
241
|
+
gap: 12px;
|
|
242
|
+
flex-wrap: wrap;
|
|
243
|
+
padding-bottom: 12px;
|
|
244
|
+
border-bottom: 1px solid var(--border);
|
|
229
245
|
}
|
|
230
246
|
|
|
231
|
-
.
|
|
247
|
+
.brand {
|
|
232
248
|
display: flex;
|
|
233
249
|
align-items: center;
|
|
250
|
+
gap: 14px;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.brand-logo {
|
|
254
|
+
width: 32px;
|
|
255
|
+
height: auto;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.brand-name {
|
|
259
|
+
font-family: var(--font-orbitron), 'Orbitron', sans-serif;
|
|
260
|
+
letter-spacing: 0.06em;
|
|
261
|
+
text-transform: uppercase;
|
|
262
|
+
font-size: 18px;
|
|
263
|
+
color: var(--text);
|
|
264
|
+
font-weight: 600;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.top-pills {
|
|
268
|
+
display: flex;
|
|
269
|
+
gap: 8px;
|
|
270
|
+
flex-wrap: wrap;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.hero {
|
|
274
|
+
display: flex;
|
|
275
|
+
flex-direction: column;
|
|
234
276
|
gap: 12px;
|
|
235
|
-
|
|
236
|
-
|
|
277
|
+
padding: 40px 0 20px;
|
|
278
|
+
align-items: center;
|
|
279
|
+
text-align: center;
|
|
237
280
|
}
|
|
238
281
|
|
|
239
|
-
.
|
|
240
|
-
font-
|
|
282
|
+
.hero-title {
|
|
283
|
+
font-family: var(--font-orbitron), 'Orbitron', sans-serif;
|
|
284
|
+
font-size: clamp(30px, 4vw, 44px);
|
|
285
|
+
margin: 0;
|
|
286
|
+
letter-spacing: 0.02em;
|
|
241
287
|
font-weight: 600;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.hero-copy {
|
|
291
|
+
color: var(--muted);
|
|
292
|
+
font-size: 18px;
|
|
293
|
+
line-height: 1.6;
|
|
294
|
+
max-width: 600px;
|
|
295
|
+
margin: 0;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.card {
|
|
299
|
+
position: relative;
|
|
300
|
+
background: var(--panel);
|
|
301
|
+
border: 1px solid var(--border);
|
|
302
|
+
border-radius: 20px;
|
|
303
|
+
padding: 22px;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.panel {
|
|
307
|
+
overflow: hidden;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.panel-header {
|
|
311
|
+
display: flex;
|
|
312
|
+
align-items: center;
|
|
313
|
+
justify-content: space-between;
|
|
314
|
+
gap: 14px;
|
|
315
|
+
flex-wrap: wrap;
|
|
316
|
+
margin-bottom: 20px;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.panel-title {
|
|
320
|
+
font-size: 18px;
|
|
321
|
+
font-weight: 500;
|
|
322
|
+
margin: 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.panel-copy {
|
|
326
|
+
color: var(--muted);
|
|
327
|
+
font-size: 14px;
|
|
328
|
+
margin: 4px 0 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.pill-row {
|
|
332
|
+
display: flex;
|
|
333
|
+
gap: 8px;
|
|
334
|
+
flex-wrap: wrap;
|
|
335
|
+
margin-bottom: 12px;
|
|
247
336
|
}
|
|
248
337
|
|
|
249
338
|
.messages {
|
|
250
339
|
display: flex;
|
|
251
340
|
flex-direction: column;
|
|
252
341
|
gap: 12px;
|
|
342
|
+
background: #16171d;
|
|
343
|
+
border: 1px solid var(--border);
|
|
344
|
+
border-radius: 16px;
|
|
345
|
+
padding: 14px;
|
|
346
|
+
min-height: 300px;
|
|
253
347
|
}
|
|
254
348
|
|
|
255
349
|
.bubble {
|
|
256
350
|
padding: 14px 16px;
|
|
257
351
|
border-radius: 14px;
|
|
258
|
-
max-width: 90%;
|
|
259
352
|
line-height: 1.5;
|
|
260
353
|
font-size: 15px;
|
|
354
|
+
max-width: 85%;
|
|
261
355
|
}
|
|
262
356
|
|
|
263
357
|
.bubble.assistant {
|
|
264
|
-
background: #
|
|
265
|
-
color:
|
|
358
|
+
background: #1c1d24;
|
|
359
|
+
color: var(--text);
|
|
266
360
|
align-self: flex-start;
|
|
267
|
-
border
|
|
361
|
+
border: 1px solid var(--border);
|
|
268
362
|
}
|
|
269
363
|
|
|
270
364
|
.bubble.user {
|
|
271
|
-
background:
|
|
272
|
-
color: #
|
|
273
|
-
border: 1px solid rgba(15, 23, 42, 0.06);
|
|
365
|
+
background: #ffffff;
|
|
366
|
+
color: #000000;
|
|
274
367
|
align-self: flex-end;
|
|
275
|
-
border-bottom-right-radius: 4px;
|
|
276
368
|
}
|
|
277
369
|
|
|
278
370
|
.form {
|
|
279
371
|
display: flex;
|
|
280
372
|
gap: 12px;
|
|
281
|
-
|
|
373
|
+
align-items: center;
|
|
374
|
+
margin-top: 16px;
|
|
375
|
+
background: #16171d;
|
|
376
|
+
border: 1px solid var(--border);
|
|
377
|
+
border-radius: 14px;
|
|
378
|
+
padding: 10px;
|
|
282
379
|
}
|
|
283
380
|
|
|
284
381
|
.input {
|
|
285
382
|
flex: 1;
|
|
286
|
-
padding: 14px
|
|
383
|
+
padding: 12px 14px;
|
|
287
384
|
border-radius: 12px;
|
|
288
|
-
border: 1px solid
|
|
385
|
+
border: 1px solid transparent;
|
|
289
386
|
font-size: 15px;
|
|
290
387
|
outline: none;
|
|
291
|
-
|
|
388
|
+
background: transparent;
|
|
389
|
+
color: var(--text);
|
|
292
390
|
}
|
|
293
391
|
|
|
294
|
-
.input
|
|
295
|
-
|
|
296
|
-
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.12);
|
|
392
|
+
.input::placeholder {
|
|
393
|
+
color: #666666;
|
|
297
394
|
}
|
|
298
395
|
|
|
299
396
|
.button {
|
|
300
|
-
background:
|
|
301
|
-
color:
|
|
397
|
+
background: #ffffff;
|
|
398
|
+
color: #000000;
|
|
302
399
|
border: none;
|
|
303
400
|
border-radius: 12px;
|
|
304
|
-
padding: 12px
|
|
305
|
-
font-weight:
|
|
401
|
+
padding: 12px 24px;
|
|
402
|
+
font-weight: 600;
|
|
306
403
|
cursor: pointer;
|
|
307
|
-
|
|
308
|
-
min-width: 110px;
|
|
309
|
-
transition: transform 0.12s ease, box-shadow 0.12s ease, opacity 0.12s ease;
|
|
404
|
+
transition: opacity 0.2s;
|
|
310
405
|
}
|
|
311
406
|
|
|
312
407
|
.button:disabled {
|
|
313
|
-
opacity: 0.
|
|
408
|
+
opacity: 0.5;
|
|
314
409
|
cursor: not-allowed;
|
|
315
|
-
box-shadow: none;
|
|
316
410
|
}
|
|
317
411
|
|
|
318
|
-
.button:not(:disabled)
|
|
319
|
-
|
|
320
|
-
box-shadow: 0 16px 30px rgba(99, 102, 241, 0.45);
|
|
412
|
+
.button:hover:not(:disabled) {
|
|
413
|
+
opacity: 0.9;
|
|
321
414
|
}
|
|
322
415
|
|
|
323
416
|
.status {
|
|
324
417
|
font-size: 13px;
|
|
325
|
-
color:
|
|
326
|
-
margin-top:
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
.pill-row {
|
|
330
|
-
display: flex;
|
|
331
|
-
gap: 8px;
|
|
332
|
-
flex-wrap: wrap;
|
|
418
|
+
color: var(--muted);
|
|
419
|
+
margin-top: 10px;
|
|
420
|
+
text-align: center;
|
|
333
421
|
}
|
|
334
422
|
|
|
335
423
|
.pill {
|
|
336
|
-
|
|
424
|
+
display: inline-flex;
|
|
425
|
+
align-items: center;
|
|
426
|
+
gap: 8px;
|
|
427
|
+
padding: 6px 12px;
|
|
337
428
|
border-radius: 999px;
|
|
338
|
-
|
|
429
|
+
border: 1px solid var(--border);
|
|
430
|
+
background: transparent;
|
|
431
|
+
color: var(--muted);
|
|
339
432
|
font-size: 12px;
|
|
340
|
-
|
|
341
|
-
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.pill-accent {
|
|
436
|
+
color: var(--text);
|
|
437
|
+
border-color: var(--text);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.pill-ghost {
|
|
441
|
+
border-color: var(--border);
|
|
442
|
+
color: var(--muted);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
@media (max-width: 720px) {
|
|
446
|
+
.page-shell {
|
|
447
|
+
padding: 28px 16px 56px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.form {
|
|
451
|
+
flex-direction: column;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.button {
|
|
455
|
+
width: 100%;
|
|
456
|
+
}
|
|
342
457
|
}
|
|
343
458
|
`);
|
|
344
459
|
await writeFile(join(webDir, 'app', 'layout.tsx'), `import './globals.css'
|
|
345
460
|
import { ReactNode } from 'react'
|
|
461
|
+
import { Inter, Orbitron } from 'next/font/google'
|
|
462
|
+
|
|
463
|
+
const inter = Inter({
|
|
464
|
+
subsets: ['latin'],
|
|
465
|
+
weight: ['300', '500', '700'],
|
|
466
|
+
variable: '--font-inter',
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
const orbitron = Orbitron({
|
|
470
|
+
subsets: ['latin'],
|
|
471
|
+
weight: ['600'],
|
|
472
|
+
variable: '--font-orbitron',
|
|
473
|
+
})
|
|
346
474
|
|
|
347
475
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
348
476
|
return (
|
|
@@ -350,7 +478,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
350
478
|
<head>
|
|
351
479
|
<title>Castari Demo Chat</title>
|
|
352
480
|
</head>
|
|
353
|
-
<body>{children}</body>
|
|
481
|
+
<body className={\`\${inter.variable} \${orbitron.variable}\`}>{children}</body>
|
|
354
482
|
</html>
|
|
355
483
|
)
|
|
356
484
|
}
|
|
@@ -358,6 +486,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
|
|
358
486
|
await writeFile(join(webDir, 'app', 'page.tsx'), `"use client"
|
|
359
487
|
|
|
360
488
|
import { FormEvent, useState } from 'react'
|
|
489
|
+
import Image from 'next/image'
|
|
361
490
|
|
|
362
491
|
type ChatMessage = {
|
|
363
492
|
role: 'assistant' | 'user'
|
|
@@ -437,43 +566,71 @@ export default function Home() {
|
|
|
437
566
|
}
|
|
438
567
|
|
|
439
568
|
return (
|
|
440
|
-
<main className="
|
|
441
|
-
<div className="
|
|
442
|
-
<
|
|
443
|
-
<
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
<span className="pill">Volume: castari-demo-workspace</span>
|
|
454
|
-
<span className="pill">Labels: app=castari-demo, env=local</span>
|
|
455
|
-
</div>
|
|
456
|
-
|
|
457
|
-
<div className="messages">
|
|
458
|
-
{messages.map((m, idx) => (
|
|
459
|
-
<div key={idx} className={\`bubble \${m.role}\`}>
|
|
460
|
-
{m.content}
|
|
569
|
+
<main className="page-shell">
|
|
570
|
+
<div className="content">
|
|
571
|
+
<header className="topbar">
|
|
572
|
+
<div className="brand">
|
|
573
|
+
<Image
|
|
574
|
+
src="/logo.svg"
|
|
575
|
+
alt="Castari Logo"
|
|
576
|
+
width={32}
|
|
577
|
+
height={32}
|
|
578
|
+
className="brand-logo"
|
|
579
|
+
/>
|
|
580
|
+
<div>
|
|
581
|
+
<div className="brand-name">Castari</div>
|
|
461
582
|
</div>
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
<div className="top-pills">
|
|
586
|
+
<span className="pill pill-ghost">Demo sandbox</span>
|
|
587
|
+
</div>
|
|
588
|
+
</header>
|
|
589
|
+
|
|
590
|
+
<section className="hero">
|
|
591
|
+
<h1 className="hero-title">Production-Ready Agents</h1>
|
|
592
|
+
<p className="hero-copy">
|
|
593
|
+
Secure, sandboxed AI agents for your infrastructure.
|
|
594
|
+
</p>
|
|
595
|
+
</section>
|
|
596
|
+
|
|
597
|
+
<section className="card panel">
|
|
598
|
+
<div className="panel-header">
|
|
599
|
+
<div>
|
|
600
|
+
<div className="panel-title">Chat with Castari</div>
|
|
601
|
+
<p className="panel-copy">
|
|
602
|
+
This agent runs in a live Daytona sandbox.
|
|
603
|
+
</p>
|
|
604
|
+
</div>
|
|
605
|
+
<span className="pill pill-accent">Live</span>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<div className="pill-row">
|
|
609
|
+
<span className="pill">Snapshot: castari-demo-agent</span>
|
|
610
|
+
<span className="pill">Volume: castari-demo-workspace</span>
|
|
611
|
+
</div>
|
|
612
|
+
|
|
613
|
+
<div className="messages">
|
|
614
|
+
{messages.map((m, idx) => (
|
|
615
|
+
<div key={idx} className={\`bubble \${m.role}\`}>
|
|
616
|
+
{m.content}
|
|
617
|
+
</div>
|
|
618
|
+
))}
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
<form className="form" onSubmit={sendMessage}>
|
|
622
|
+
<input
|
|
623
|
+
className="input"
|
|
624
|
+
placeholder="Ask the Castari agent anything..."
|
|
625
|
+
value={input}
|
|
626
|
+
onChange={e => setInput(e.target.value)}
|
|
627
|
+
/>
|
|
628
|
+
<button className="button" type="submit" disabled={sending}>
|
|
629
|
+
{sending ? 'Talking…' : 'Send'}
|
|
630
|
+
</button>
|
|
631
|
+
</form>
|
|
632
|
+
{status && <div className="status">{status}</div>}
|
|
633
|
+
</section>
|
|
477
634
|
</div>
|
|
478
635
|
</main>
|
|
479
636
|
)
|
|
@@ -495,9 +652,11 @@ async function createClient() {
|
|
|
495
652
|
|
|
496
653
|
const client = new CastariClient({
|
|
497
654
|
snapshot: SNAPSHOT,
|
|
498
|
-
volume: VOLUME,
|
|
655
|
+
// volume: VOLUME, // opt-in; omit to run ephemeral unless provided
|
|
499
656
|
labels: LABELS,
|
|
500
657
|
platformUrl: process.env.CASTARI_PLATFORM_URL,
|
|
658
|
+
clientId: process.env.CASTARI_CLIENT_ID,
|
|
659
|
+
platformApiKey: process.env.CASTARI_API_KEY,
|
|
501
660
|
anthropicApiKey,
|
|
502
661
|
debug: process.env.CASTARI_DEBUG === 'true',
|
|
503
662
|
})
|
|
@@ -651,6 +810,35 @@ export async function POST(request: Request) {
|
|
|
651
810
|
},
|
|
652
811
|
})
|
|
653
812
|
}
|
|
813
|
+
`);
|
|
814
|
+
// Logo SVG
|
|
815
|
+
await writeFile(join(webDir, 'public', 'logo.svg'), `<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
816
|
+
<path d="M0 32L31.619 0L63.2381 32L31.619 64L0 32Z" fill="#A44A0C"/>
|
|
817
|
+
<path d="M31.527 31.8743H6.87042L24.3595 24.3417L31.527 7.24737V31.8743Z" fill="white"/>
|
|
818
|
+
<path d="M31.527 31.8743H6.87042L24.3595 24.3417L31.527 31.8743Z" fill="url(#paint0_linear_173_2089)"/>
|
|
819
|
+
<path d="M31.5268 31.872L31.5192 6.91829L24.0817 24.6204L31.5268 31.872Z" fill="url(#paint1_linear_173_2089)"/>
|
|
820
|
+
<path d="M31.7116 32.128L31.7192 57.0817L38.9973 39.3796L31.7116 32.128Z" fill="url(#paint2_linear_173_2089)"/>
|
|
821
|
+
<path d="M31.7117 32.1258H56.3683L38.8792 39.6584L31.7117 56.7527V32.1258Z" fill="white"/>
|
|
822
|
+
<path d="M31.7117 32.1258H56.3683L38.8792 39.6584L31.7117 32.1258Z" fill="url(#paint3_linear_173_2089)"/>
|
|
823
|
+
<defs>
|
|
824
|
+
<linearGradient id="paint0_linear_173_2089" x1="53.6475" y1="32.6825" x2="36.7362" y2="37.9872" gradientUnits="userSpaceOnUse">
|
|
825
|
+
<stop stop-color="#F8F8F8"/>
|
|
826
|
+
<stop offset="1" stop-color="#EACAB4"/>
|
|
827
|
+
</linearGradient>
|
|
828
|
+
<linearGradient id="paint1_linear_173_2089" x1="6.97479" y1="59.2675" x2="54.1923" y2="54.7965" gradientUnits="userSpaceOnUse">
|
|
829
|
+
<stop stop-color="white"/>
|
|
830
|
+
<stop offset="1" stop-color="#EDEDED"/>
|
|
831
|
+
</linearGradient>
|
|
832
|
+
<linearGradient id="paint2_linear_173_2089" x1="6.97479" y1="59.2675" x2="54.1923" y2="54.7965" gradientUnits="userSpaceOnUse">
|
|
833
|
+
<stop stop-color="white"/>
|
|
834
|
+
<stop offset="1" stop-color="#EDEDED"/>
|
|
835
|
+
</linearGradient>
|
|
836
|
+
<linearGradient id="paint3_linear_173_2089" x1="53.6475" y1="32.6825" x2="36.7362" y2="37.9872" gradientUnits="userSpaceOnUse">
|
|
837
|
+
<stop stop-color="#F8F8F8"/>
|
|
838
|
+
<stop offset="1" stop-color="#EACAB4"/>
|
|
839
|
+
</linearGradient>
|
|
840
|
+
</defs>
|
|
841
|
+
</svg>
|
|
654
842
|
`);
|
|
655
843
|
// Demo README
|
|
656
844
|
await writeFile(join(demoRoot, 'README.md'), `# Castari Demo (Web + Agent)
|
|
@@ -665,12 +853,20 @@ Structure:
|
|
|
665
853
|
- Bun (for the Castari CLI and scripts)
|
|
666
854
|
- Node 18+ (for the Next.js app)
|
|
667
855
|
- \`ANTHROPIC_API_KEY\`
|
|
856
|
+
- Castari credentials (\`CASTARI_CLIENT_ID\` and \`CASTARI_API_KEY\`)
|
|
668
857
|
- Castari Platform running locally or reachable via \`CASTARI_PLATFORM_URL\`
|
|
669
858
|
|
|
859
|
+
## Getting Castari Credentials
|
|
860
|
+
Generate your clientId and apiKey by running:
|
|
861
|
+
\`\`\`bash
|
|
862
|
+
castari generate-client-id
|
|
863
|
+
\`\`\`
|
|
864
|
+
Or via the Castari Platform Console if available.
|
|
865
|
+
|
|
670
866
|
## 1) Prepare and deploy the agent
|
|
671
867
|
\`\`\`bash
|
|
672
868
|
cd castari_demo/agent
|
|
673
|
-
cp .env.example .env # add ANTHROPIC_API_KEY
|
|
869
|
+
cp .env.example .env # add ANTHROPIC_API_KEY, CASTARI_CLIENT_ID, CASTARI_API_KEY
|
|
674
870
|
bun install # pulls @castari/sdk from npm
|
|
675
871
|
castari deploy # builds snapshot castari-demo-agent (CLI must be installed)
|
|
676
872
|
# Optional: bun run src/agent.ts # run locally without the CLI
|
|
@@ -679,7 +875,7 @@ castari deploy # builds snapshot castari-demo-agent (CLI must be installe
|
|
|
679
875
|
## 2) Run the web app
|
|
680
876
|
\`\`\`bash
|
|
681
877
|
cd ../web
|
|
682
|
-
cp .env.example .env # add ANTHROPIC_API_KEY
|
|
878
|
+
cp .env.example .env # add ANTHROPIC_API_KEY, CASTARI_CLIENT_ID, CASTARI_API_KEY
|
|
683
879
|
npm install # or bun install
|
|
684
880
|
npm run dev # opens http://localhost:3000
|
|
685
881
|
\`\`\`
|
package/dist/commands/start.js
CHANGED
|
@@ -2,6 +2,7 @@ import { CastariClient } from '@castari/sdk/client';
|
|
|
2
2
|
import { readFile } from 'fs/promises';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import dotenv from 'dotenv';
|
|
5
|
+
import { getClientAuthOrExit } from '../utils/client-auth';
|
|
5
6
|
export async function start(options) {
|
|
6
7
|
// Load .env from current directory
|
|
7
8
|
dotenv.config();
|
|
@@ -18,6 +19,7 @@ export async function start(options) {
|
|
|
18
19
|
catch (e) {
|
|
19
20
|
// Ignore if package.json missing
|
|
20
21
|
}
|
|
22
|
+
const { clientId, apiKey } = await getClientAuthOrExit();
|
|
21
23
|
if (!snapshotName) {
|
|
22
24
|
console.error(chalk.red('Error: Snapshot name is required.'));
|
|
23
25
|
process.exit(1);
|
|
@@ -26,14 +28,21 @@ export async function start(options) {
|
|
|
26
28
|
if (volumeName) {
|
|
27
29
|
console.log(chalk.blue(`📦 Using volume: ${volumeName}`));
|
|
28
30
|
}
|
|
31
|
+
else {
|
|
32
|
+
console.log(chalk.blue(`📦 No volume specified (sandbox will be ephemeral)`));
|
|
33
|
+
}
|
|
29
34
|
const platformUrl = process.env.CASTARI_PLATFORM_URL;
|
|
30
|
-
const
|
|
35
|
+
const clientOptions = {
|
|
31
36
|
snapshot: snapshotName,
|
|
32
|
-
volume: volumeName,
|
|
37
|
+
...(volumeName ? { volume: volumeName } : {}),
|
|
33
38
|
platformUrl,
|
|
34
39
|
debug: true, // Enable debug logs for CLI
|
|
35
|
-
anthropicApiKey: process.env.ANTHROPIC_API_KEY
|
|
36
|
-
|
|
40
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY,
|
|
41
|
+
clientId,
|
|
42
|
+
};
|
|
43
|
+
if (apiKey)
|
|
44
|
+
clientOptions.platformApiKey = apiKey;
|
|
45
|
+
const client = new CastariClient(clientOptions);
|
|
37
46
|
try {
|
|
38
47
|
await client.start();
|
|
39
48
|
console.log(chalk.green('✅ Agent started!'));
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { init } from './commands/init';
|
|
|
4
4
|
import { deploy } from './commands/deploy';
|
|
5
5
|
import { start } from './commands/start';
|
|
6
6
|
import { dev } from './commands/dev';
|
|
7
|
+
import { generateClientId } from './commands/generate-client-id';
|
|
7
8
|
const cli = cac('castari');
|
|
8
9
|
cli
|
|
9
10
|
.command('init', 'Initialize a new Castari agent project')
|
|
@@ -22,6 +23,9 @@ cli
|
|
|
22
23
|
cli
|
|
23
24
|
.command('dev', 'Run the agent locally for development')
|
|
24
25
|
.action(dev);
|
|
26
|
+
cli
|
|
27
|
+
.command('generate-client-id', 'Generate a new Castari Client ID and save it locally')
|
|
28
|
+
.action(generateClientId);
|
|
25
29
|
cli.help();
|
|
26
|
-
cli.version('0.0.
|
|
30
|
+
cli.version('0.0.7');
|
|
27
31
|
cli.parse();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ClientAuth = {
|
|
2
|
+
clientId: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
};
|
|
5
|
+
export declare function getClientAuthOrExit(): Promise<ClientAuth>;
|
|
6
|
+
export declare function saveClientAuth(data: {
|
|
7
|
+
clientId: string;
|
|
8
|
+
apiKey?: string;
|
|
9
|
+
source?: 'online' | 'offline';
|
|
10
|
+
platformUrl?: string;
|
|
11
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getClientIdOrExit, loadClientId, saveClientId } from './client-id';
|
|
2
|
+
export async function getClientAuthOrExit() {
|
|
3
|
+
const clientId = await getClientIdOrExit();
|
|
4
|
+
const stored = await loadClientId();
|
|
5
|
+
const apiKey = process.env.CASTARI_API_KEY ||
|
|
6
|
+
stored?.apiKey;
|
|
7
|
+
if (!apiKey) {
|
|
8
|
+
// We allow missing apiKey for now (platform may allow unauthenticated in dev)
|
|
9
|
+
return { clientId };
|
|
10
|
+
}
|
|
11
|
+
return { clientId, apiKey };
|
|
12
|
+
}
|
|
13
|
+
export async function saveClientAuth(data) {
|
|
14
|
+
await saveClientId({
|
|
15
|
+
clientId: data.clientId,
|
|
16
|
+
apiKey: data.apiKey,
|
|
17
|
+
source: data.source,
|
|
18
|
+
createdAt: new Date().toISOString(),
|
|
19
|
+
platformUrl: data.platformUrl,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type StoredClientId = {
|
|
2
|
+
clientId: string;
|
|
3
|
+
source?: 'online' | 'offline';
|
|
4
|
+
createdAt?: string;
|
|
5
|
+
platformUrl?: string;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function loadClientId(): Promise<StoredClientId | null>;
|
|
9
|
+
export declare function saveClientId(data: StoredClientId): Promise<void>;
|
|
10
|
+
export declare function getClientIdOrExit(explicit?: string): Promise<string>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { mkdir, readFile, writeFile } from 'fs/promises';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.castari');
|
|
6
|
+
const CONFIG_PATH = join(CONFIG_DIR, 'client.json');
|
|
7
|
+
export async function loadClientId() {
|
|
8
|
+
try {
|
|
9
|
+
const raw = await readFile(CONFIG_PATH, 'utf-8');
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
if (parsed.clientId && typeof parsed.clientId === 'string') {
|
|
12
|
+
return parsed;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function saveClientId(data) {
|
|
21
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
22
|
+
await writeFile(CONFIG_PATH, JSON.stringify(data, null, 2));
|
|
23
|
+
}
|
|
24
|
+
export async function getClientIdOrExit(explicit) {
|
|
25
|
+
const envId = process.env.CASTARI_CLIENT_ID;
|
|
26
|
+
if (explicit)
|
|
27
|
+
return explicit;
|
|
28
|
+
if (envId)
|
|
29
|
+
return envId;
|
|
30
|
+
const stored = await loadClientId();
|
|
31
|
+
if (stored?.clientId)
|
|
32
|
+
return stored.clientId;
|
|
33
|
+
console.error(chalk.red('CASTARI_CLIENT_ID is required.'));
|
|
34
|
+
console.error(chalk.white('Run `castari generate-client-id` to create one, or set CASTARI_CLIENT_ID in your environment.'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@castari/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"castari": "./dist/index.js"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@anthropic-ai/claude-agent-sdk": "^0.1.44",
|
|
21
|
-
"@castari/sdk": "^0.0.
|
|
21
|
+
"@castari/sdk": "^0.0.6",
|
|
22
22
|
"adm-zip": "^0.5.16",
|
|
23
23
|
"cac": "^6.7.14",
|
|
24
24
|
"chalk": "^5.3.0",
|
|
@@ -33,4 +33,4 @@
|
|
|
33
33
|
"@types/node": "^20.10.0",
|
|
34
34
|
"typescript": "^5.6.3"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|