@castari/cli 0.0.6 → 0.0.7

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.
@@ -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
+ }
@@ -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
+ }
@@ -25,7 +25,7 @@ export async function init(options = {}) {
25
25
  start: 'castari start',
26
26
  },
27
27
  dependencies: {
28
- '@castari/sdk': '^0.0.4',
28
+ '@castari/sdk': '^0.0.6',
29
29
  '@anthropic-ai/claude-agent-sdk': '^0.1.44',
30
30
  },
31
31
  castari: {
@@ -65,7 +65,7 @@ serve({
65
65
  systemPrompt: 'You are a helpful Castari agent.',
66
66
  })
67
67
  `;
68
- const envExample = `ANTHROPIC_API_KEY=sk-ant-...\n# CASTARI_PLATFORM_URL=https://api.castari.com\n`;
68
+ const envExample = `ANTHROPIC_API_KEY=sk-ant-...\nCASTARI_CLIENT_ID=your-client-id\n# CASTARI_PLATFORM_URL=https://api.castari.com\n`;
69
69
  await writeFile('package.json', JSON.stringify(packageJson, null, 2));
70
70
  await writeFile('tsconfig.json', JSON.stringify(tsConfig, null, 2));
71
71
  await writeFile('.env.example', envExample);
@@ -93,7 +93,7 @@ async function initDemo() {
93
93
  start: 'bun run src/agent.ts',
94
94
  },
95
95
  dependencies: {
96
- '@castari/sdk': '^0.0.4',
96
+ '@castari/sdk': '^0.0.6',
97
97
  '@anthropic-ai/claude-agent-sdk': '^0.1.50',
98
98
  },
99
99
  castari: {
@@ -111,7 +111,7 @@ async function initDemo() {
111
111
  },
112
112
  include: ['src'],
113
113
  });
114
- await writeFile(join(agentDir, '.env.example'), 'ANTHROPIC_API_KEY=sk-ant-...\n# CASTARI_PLATFORM_URL=http://localhost:3000\n');
114
+ await writeFile(join(agentDir, '.env.example'), 'ANTHROPIC_API_KEY=sk-ant-...\nCASTARI_CLIENT_ID=your-client-id\n# CASTARI_PLATFORM_URL=http://localhost:3000\n');
115
115
  await writeFile(join(agentDir, 'src', 'agent.ts'), `import { serve } from '@castari/sdk'
116
116
 
117
117
  serve({
@@ -136,7 +136,7 @@ serve({
136
136
  start: 'next start',
137
137
  },
138
138
  dependencies: {
139
- '@castari/sdk': '^0.0.4',
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,7 +187,7 @@ 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\n# CASTARI_PLATFORM_URL=http://localhost:3000\n# CASTARI_DEBUG=false\n');
191
191
  await writeFile(join(webDir, 'app', 'globals.css'), `:root {
192
192
  color-scheme: light;
193
193
  background: #f5f5f5;
@@ -498,6 +498,7 @@ async function createClient() {
498
498
  volume: VOLUME,
499
499
  labels: LABELS,
500
500
  platformUrl: process.env.CASTARI_PLATFORM_URL,
501
+ clientId: process.env.CASTARI_CLIENT_ID,
501
502
  anthropicApiKey,
502
503
  debug: process.env.CASTARI_DEBUG === 'true',
503
504
  })
@@ -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);
@@ -27,13 +29,17 @@ export async function start(options) {
27
29
  console.log(chalk.blue(`📦 Using volume: ${volumeName}`));
28
30
  }
29
31
  const platformUrl = process.env.CASTARI_PLATFORM_URL;
30
- const client = new CastariClient({
32
+ const clientOptions = {
31
33
  snapshot: snapshotName,
32
34
  volume: volumeName,
33
35
  platformUrl,
34
36
  debug: true, // Enable debug logs for CLI
35
- anthropicApiKey: process.env.ANTHROPIC_API_KEY
36
- });
37
+ anthropicApiKey: process.env.ANTHROPIC_API_KEY,
38
+ clientId,
39
+ };
40
+ if (apiKey)
41
+ clientOptions.platformApiKey = apiKey;
42
+ const client = new CastariClient(clientOptions);
37
43
  try {
38
44
  await client.start();
39
45
  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.4');
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.6",
3
+ "version": "0.0.7",
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.4",
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
+ }