@castari/cli 0.0.5 → 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({
@@ -125,6 +125,7 @@ serve({
125
125
  `);
126
126
  // Web scaffold
127
127
  await mkdir(join(webDir, 'app', 'api', 'chat'), { recursive: true });
128
+ await mkdir(join(webDir, 'lib'), { recursive: true });
128
129
  await writeJson(join(webDir, 'package.json'), {
129
130
  name: 'castari-demo-web',
130
131
  version: '0.1.0',
@@ -135,7 +136,7 @@ serve({
135
136
  start: 'next start',
136
137
  },
137
138
  dependencies: {
138
- '@castari/sdk': '^0.0.4',
139
+ '@castari/sdk': '^0.0.6',
139
140
  next: '14.2.3',
140
141
  react: '18.3.1',
141
142
  'react-dom': '18.3.1',
@@ -186,7 +187,7 @@ const nextConfig = {
186
187
 
187
188
  export default nextConfig
188
189
  `);
189
- 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');
190
191
  await writeFile(join(webDir, 'app', 'globals.css'), `:root {
191
192
  color-scheme: light;
192
193
  background: #f5f5f5;
@@ -497,6 +498,7 @@ async function createClient() {
497
498
  volume: VOLUME,
498
499
  labels: LABELS,
499
500
  platformUrl: process.env.CASTARI_PLATFORM_URL,
501
+ clientId: process.env.CASTARI_CLIENT_ID,
500
502
  anthropicApiKey,
501
503
  debug: process.env.CASTARI_DEBUG === 'true',
502
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.5",
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",