@ceo-ai/cli 1.0.2

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 ADDED
@@ -0,0 +1,301 @@
1
+ ## CLI README
2
+
3
+ ### `ceo-cli/README.md`
4
+
5
+ ```
6
+ `markdown
7
+ # @ceo-ai/cli
8
+
9
+ Command-line interface for the CEO.AI API. Send prompts to your AI agents, poll for results, and manage your configuration — all from the terminal.
10
+
11
+ ## Installation
12
+
13
+
14
+ ```bash
15
+ npm install -g @ceo-ai/cli
16
+ ```
17
+
18
+
19
+ ## Quick Start
20
+
21
+
22
+ ```bash
23
+ # 1. Configure your API key
24
+ ceo configure --key sk_live_your_api_key_here
25
+
26
+ # 2. Send a prompt and wait for the result
27
+ ceo prompt "What was our Q4 revenue?" --poll
28
+
29
+ # 3. That's it!
30
+ ```
31
+
32
+
33
+ ## Configuration
34
+
35
+ ### Interactive Setup
36
+
37
+
38
+ ```bash
39
+ ceo configure
40
+ ```
41
+
42
+
43
+ You'll be prompted for:
44
+ - **API Key** — your `sk_live_...` key from the CEO.AI dashboard
45
+ - **Endpoint** — the API URL (defaults to `https://ingestion.api.ceo.ai`)
46
+
47
+ ### Inline Setup
48
+
49
+
50
+ ```bash
51
+ ceo configure --key sk_live_abc123 --endpoint https://ingestion.api.ceo.ai
52
+ ```
53
+
54
+
55
+ ### View Current Config
56
+
57
+
58
+ ```bash
59
+ ceo config:show
60
+ ```
61
+
62
+
63
+
64
+ ```
65
+ Current Configuration:
66
+
67
+ Endpoint: https://ingestion.api.ceo.ai
68
+ API Key: sk_live_abc1••••••••def4
69
+ ```
70
+
71
+
72
+ Your configuration is stored in `~/.ceo-ai/config.json` with restricted file permissions (`0600`).
73
+
74
+ ## Commands
75
+
76
+ ### `ceo prompt <text>`
77
+
78
+ Send a prompt to your AI agent.
79
+
80
+
81
+ ```bash
82
+ # Basic — returns the presigned URL immediately
83
+ ceo prompt "What was our Q4 revenue?"
84
+
85
+ # Wait for the result
86
+ ceo prompt "What was our Q4 revenue?" --poll
87
+
88
+ # Save result to a file
89
+ ceo prompt "What was our Q4 revenue?" --poll -o result.json
90
+
91
+ # Disable RAG mode
92
+ ceo prompt "What is 2+2?" --no-rag --poll
93
+
94
+ # Raw JSON output (useful for piping)
95
+ ceo prompt "What was our Q4 revenue?" --poll --json
96
+
97
+ # Override API key for a single request
98
+ ceo prompt "Hello" --key sk_live_different_key --poll
99
+
100
+ # Custom polling settings
101
+ ceo prompt "Complex query..." --poll --poll-interval 5000 --poll-timeout 300000
102
+ ```
103
+
104
+
105
+ **Options:**
106
+
107
+ | Flag | Description | Default |
108
+ |------|-------------|---------|
109
+ | `--poll` | Wait for results instead of returning the presigned URL | `false` |
110
+ | `--poll-interval <ms>` | How often to check for results | `2000` |
111
+ | `--poll-timeout <ms>` | Maximum time to wait for results | `120000` |
112
+ | `--no-rag` | Disable RAG mode (RAG is enabled by default) | — |
113
+ | `-o, --output <file>` | Write results to a file (implies `--poll`) | — |
114
+ | `-k, --key <apiKey>` | Override the configured API key | — |
115
+ | `-e, --endpoint <url>` | Override the configured endpoint | — |
116
+ | `--json` | Output raw JSON | `false` |
117
+
118
+ ### `ceo poll <url>`
119
+
120
+ Poll an existing presigned URL for results. Useful if you submitted a prompt earlier and want to check for results later.
121
+
122
+
123
+ ```bash
124
+ # Poll a presigned URL
125
+ ceo poll "https://ceo-ai-api-output-results.s3.amazonaws.com/api-data/..."
126
+
127
+ # Save to file
128
+ ceo poll "https://..." -o result.json
129
+
130
+ # Custom interval and timeout
131
+ ceo poll "https://..." --interval 5000 --timeout 300000
132
+
133
+ # Raw JSON output
134
+ ceo poll "https://..." --json
135
+ ```
136
+
137
+
138
+ **Options:**
139
+
140
+ | Flag | Description | Default |
141
+ |------|-------------|---------|
142
+ | `--interval <ms>` | Polling interval | `2000` |
143
+ | `--timeout <ms>` | Maximum wait time | `120000` |
144
+ | `-o, --output <file>` | Write results to a file | — |
145
+ | `--json` | Output raw JSON | `false` |
146
+
147
+ ### `ceo configure`
148
+
149
+ Set your API key and endpoint.
150
+
151
+
152
+ ```bash
153
+ # Interactive
154
+ ceo configure
155
+
156
+ # Inline
157
+ ceo configure --key sk_live_abc123
158
+ ceo configure --endpoint https://custom.endpoint.com
159
+ ceo configure --key sk_live_abc123 --endpoint https://custom.endpoint.com
160
+ ```
161
+
162
+
163
+ ### `ceo config:show`
164
+
165
+ Display the current configuration (API key is masked).
166
+
167
+ ## Examples
168
+
169
+ ### Basic Prompt and Poll
170
+
171
+
172
+ ```bash
173
+ $ ceo prompt "Summarize our sales performance this quarter" --poll
174
+
175
+ ✓ Request submitted
176
+ Agent: Sales Analyst
177
+ Model: gpt-4
178
+ Credits: 1500
179
+ ⠋ Waiting for results... (attempt 12)
180
+ ✓ Results received
181
+
182
+ --- Response ---
183
+
184
+ Based on the available data, here are the key highlights...
185
+ ```
186
+
187
+
188
+ ### Save Results to File
189
+
190
+
191
+ ```bash
192
+ $ ceo prompt "Generate a detailed financial report" --poll -o report.json
193
+ ✓ Request submitted
194
+ ✓ Results received
195
+
196
+ ✓ Results written to /Users/you/report.json
197
+ ```
198
+
199
+
200
+ ### Pipe JSON Output
201
+
202
+
203
+ ```bash
204
+ # Pipe to jq for processing
205
+ ceo prompt "List all customers" --poll --json | jq '.response.customers'
206
+
207
+ # Pipe to another command
208
+ ceo prompt "Get metrics" --poll --json > metrics.json
209
+ ```
210
+
211
+
212
+ ### Two-Step Workflow
213
+
214
+
215
+ ```bash
216
+ # Step 1: Submit prompt, get presigned URL
217
+ $ ceo prompt "Long running analysis..."
218
+
219
+ ✓ Request submitted
220
+ Agent: Data Analyst
221
+ Model: claude-sonnet-4-5
222
+ Credits: 3000
223
+
224
+ Presigned URL:
225
+ https://ceo-ai-api-output-results.s3.amazonaws.com/api-data/abc/def/1234.json?X-Amz-...
226
+
227
+ Use --poll to wait for results, or fetch the URL when ready.
228
+
229
+ # Step 2: Later, poll for results
230
+ $ ceo poll "https://ceo-ai-api-output-results.s3.amazonaws.com/api-data/abc/def/1234.json?X-Amz-..."
231
+
232
+ ✓ Results received
233
+
234
+ --- Response ---
235
+
236
+ Analysis complete...
237
+ ```
238
+
239
+
240
+ ### Environment Variable Override
241
+
242
+ You can also provide your API key via an environment variable:
243
+
244
+
245
+ ```bash
246
+ CEO_API_KEY=sk_live_abc123 ceo prompt "Hello" --poll
247
+ ```
248
+
249
+
250
+ ## Error Handling
251
+
252
+ The CLI provides clear error messages:
253
+
254
+
255
+ ```bash
256
+ # No API key configured
257
+ $ ceo prompt "Hello"
258
+ Error: No API key configured. Run: ceo configure
259
+
260
+ # Invalid API key
261
+ $ ceo prompt "Hello" --key invalid_key
262
+ Error: Invalid API key format
263
+
264
+ # Insufficient credits
265
+ $ ceo prompt "Hello" --poll
266
+ ✗ Request failed
267
+ Error: You need 1500 credits but only have 200 available.
268
+ Details: {"requiredCredits":1500,"availableCredits":200,"deficit":1300}
269
+
270
+ # Polling timeout
271
+ $ ceo prompt "Hello" --poll --poll-timeout 5000
272
+ ✗ Polling failed
273
+ Error: Polling timed out after 5000ms (3 attempts)
274
+ ```
275
+
276
+
277
+ ## Getting Your API Key
278
+
279
+ 1. Log in to [CEO.AI Dashboard](https://app.ceo.ai)
280
+ 2. Navigate to **API Keys**
281
+ 3. Click **Create API Key** (NB: you must have a paid subscription to create api keys)
282
+ 4. Select the agent you want to use
283
+ 5. Copy the generated key (it will only be shown once)
284
+
285
+ ## Uninstall
286
+
287
+
288
+ ```bash
289
+ npm uninstall -g @ceo-ai/cli
290
+
291
+ # Optionally remove config
292
+ rm -rf ~/.ceo-ai
293
+ ```
294
+
295
+
296
+ ## Requirements
297
+
298
+ - Node.js 16 or later
299
+ - A CEO.AI account with an active API key and sufficient credits
300
+
301
+
package/bin/ceo.js ADDED
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const chalk = require('chalk');
5
+ const { CeoClient } = require('../lib/client');
6
+ const { ConfigManager } = require('../lib/config');
7
+ const ora = require('ora');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const pkg = require('../package.json');
11
+
12
+ const program = new Command();
13
+ const config = new ConfigManager();
14
+
15
+ program
16
+ .name('ceo')
17
+ .description('CLI for CEO.AI API')
18
+ .version(pkg.version);
19
+
20
+ // ---- Configure API key ----
21
+
22
+ program
23
+ .command('configure')
24
+ .description('Set your API key and endpoint')
25
+ .option('-k, --key <apiKey>', 'API key (sk_live_...)')
26
+ .option('-e, --endpoint <url>', 'API endpoint URL')
27
+ .action(async (opts) => {
28
+ if (opts.key) {
29
+ config.set('apiKey', opts.key);
30
+ console.log(chalk.green('✓ API key saved'));
31
+ }
32
+
33
+ if (opts.endpoint) {
34
+ config.set('endpoint', opts.endpoint);
35
+ console.log(chalk.green('✓ Endpoint saved'));
36
+ }
37
+
38
+ // Only go interactive if neither flag was provided
39
+ if (!opts.key && !opts.endpoint) {
40
+ const readline = require('readline');
41
+ const rl = readline.createInterface({
42
+ input: process.stdin,
43
+ output: process.stdout
44
+ });
45
+
46
+ const ask = (question) => new Promise((resolve) => {
47
+ rl.question(question, resolve);
48
+ });
49
+
50
+ const apiKey = await ask('Enter your API key (sk_live_...): ');
51
+ if (apiKey.trim()) {
52
+ config.set('apiKey', apiKey.trim());
53
+ console.log(chalk.green('✓ API key saved'));
54
+ }
55
+
56
+ const endpoint = await ask(`API endpoint [${config.get('endpoint') || 'https://injestion.api.ceo.ai'}]: `);
57
+ if (endpoint.trim()) {
58
+ config.set('endpoint', endpoint.trim());
59
+ console.log(chalk.green('✓ Endpoint saved'));
60
+ }
61
+
62
+ rl.close();
63
+ }
64
+ });
65
+
66
+ // ---- Show current config ----
67
+
68
+ program
69
+ .command('config:show')
70
+ .description('Show current configuration')
71
+ .action(() => {
72
+ const apiKey = config.get('apiKey');
73
+ const endpoint = config.get('endpoint') || 'https://injestion.api.ceo.ai';
74
+
75
+ console.log(chalk.bold('\nCurrent Configuration:\n'));
76
+ console.log(` Endpoint: ${chalk.cyan(endpoint)}`);
77
+
78
+ if (apiKey) {
79
+ const masked = apiKey.substring(0, 12) + '••••••••' + apiKey.substring(apiKey.length - 4);
80
+ console.log(` API Key: ${chalk.cyan(masked)}`);
81
+ } else {
82
+ console.log(` API Key: ${chalk.yellow('Not set')}`);
83
+ }
84
+ console.log();
85
+ });
86
+
87
+ // ---- Send a prompt ----
88
+
89
+ program
90
+ .command('prompt')
91
+ .description('Send a prompt to your agent')
92
+ .argument('<text>', 'The prompt text to send')
93
+ .option('--no-rag', 'Disable RAG mode')
94
+ .option('--poll', 'Poll for results and display them')
95
+ .option('--poll-interval <ms>', 'Polling interval in milliseconds', '2000')
96
+ .option('--poll-timeout <ms>', 'Max time to wait for results', '120000')
97
+ .option('-o, --output <file>', 'Write result to file (implies --poll)')
98
+ .option('-k, --key <apiKey>', 'Override configured API key')
99
+ .option('-e, --endpoint <url>', 'Override configured endpoint')
100
+ .option('--json', 'Output raw JSON response')
101
+ .action(async (text, opts) => {
102
+ const apiKey = opts.key || config.get('apiKey');
103
+ const endpoint = opts.endpoint || config.get('endpoint') || 'https://injestion.api.ceo.ai';
104
+
105
+ if (!apiKey) {
106
+ console.error(chalk.red('Error: No API key configured. Run: ceo configure'));
107
+ process.exit(1);
108
+ }
109
+
110
+ const client = new CeoClient({ apiKey, endpoint });
111
+ const spinner = ora('Sending prompt...').start();
112
+
113
+ try {
114
+ const result = await client.prompt(text, {
115
+ ragMode: opts.rag !== false
116
+ });
117
+
118
+ spinner.succeed('Request submitted');
119
+
120
+ if (!opts.json) {
121
+ console.log(chalk.dim(` Agent: ${result.agentName || result.agentId}`));
122
+ console.log(chalk.dim(` Model: ${result.model || 'N/A'}`));
123
+ console.log(chalk.dim(` Credits: ${result.estimatedCredits || 'N/A'}`));
124
+ }
125
+
126
+ // If --poll or --output, wait for results
127
+ if (opts.poll || opts.output) {
128
+ spinner.start('Waiting for results...');
129
+
130
+ const pollInterval = parseInt(opts.pollInterval);
131
+ const pollTimeout = parseInt(opts.pollTimeout);
132
+
133
+ const response = await client.pollForResult(result.presignedUrl, {
134
+ interval: pollInterval,
135
+ timeout: pollTimeout,
136
+ onPoll: (attempt) => {
137
+ spinner.text = `Waiting for results... (attempt ${attempt})`;
138
+ }
139
+ });
140
+
141
+ spinner.succeed('Results received');
142
+
143
+ if (opts.output) {
144
+ const outputPath = path.resolve(opts.output);
145
+ const content = typeof response === 'string' ? response : JSON.stringify(response, null, 2);
146
+ fs.writeFileSync(outputPath, content);
147
+ console.log(chalk.green(`\n✓ Results written to ${outputPath}`));
148
+ } else if (opts.json) {
149
+ console.log(JSON.stringify(response, null, 2));
150
+ } else {
151
+ console.log(chalk.bold('\n--- Response ---\n'));
152
+ const content = typeof response === 'string' ? response : JSON.stringify(response, null, 2);
153
+ console.log(content);
154
+ console.log();
155
+ }
156
+ } else {
157
+ // Just show the presigned URL
158
+ if (opts.json) {
159
+ console.log(JSON.stringify(result, null, 2));
160
+ } else {
161
+ console.log(chalk.bold('\n Presigned URL:'));
162
+ console.log(chalk.cyan(` ${result.presignedUrl}`));
163
+ console.log(chalk.dim('\n Use --poll to wait for results, or fetch the URL when ready.\n'));
164
+ }
165
+ }
166
+
167
+ } catch (error) {
168
+ spinner.fail('Request failed');
169
+ console.error(chalk.red(`\n Error: ${error.message}`));
170
+
171
+ if (error.details) {
172
+ console.error(chalk.yellow(` Details: ${JSON.stringify(error.details)}`));
173
+ }
174
+
175
+ process.exit(1);
176
+ }
177
+ });
178
+
179
+ // ---- Poll an existing presigned URL ----
180
+
181
+ program
182
+ .command('poll')
183
+ .description('Poll an existing presigned URL for results')
184
+ .argument('<url>', 'Presigned URL to poll')
185
+ .option('--interval <ms>', 'Polling interval in milliseconds', '2000')
186
+ .option('--timeout <ms>', 'Max time to wait', '120000')
187
+ .option('-o, --output <file>', 'Write result to file')
188
+ .option('--json', 'Output raw JSON')
189
+ .action(async (url, opts) => {
190
+ const client = new CeoClient({ apiKey: 'unused', endpoint: 'unused' });
191
+ const spinner = ora('Polling for results...').start();
192
+
193
+ try {
194
+ const response = await client.pollForResult(url, {
195
+ interval: parseInt(opts.interval),
196
+ timeout: parseInt(opts.timeout),
197
+ onPoll: (attempt) => {
198
+ spinner.text = `Polling for results... (attempt ${attempt})`;
199
+ }
200
+ });
201
+
202
+ spinner.succeed('Results received');
203
+
204
+ if (opts.output) {
205
+ const outputPath = path.resolve(opts.output);
206
+ const content = typeof response === 'string' ? response : JSON.stringify(response, null, 2);
207
+ fs.writeFileSync(outputPath, content);
208
+ console.log(chalk.green(`\n✓ Results written to ${outputPath}`));
209
+ } else if (opts.json) {
210
+ console.log(JSON.stringify(response, null, 2));
211
+ } else {
212
+ console.log(chalk.bold('\n--- Response ---\n'));
213
+ const content = typeof response === 'string' ? response : JSON.stringify(response, null, 2);
214
+ console.log(content);
215
+ console.log();
216
+ }
217
+ } catch (error) {
218
+ spinner.fail('Polling failed');
219
+ console.error(chalk.red(`\n Error: ${error.message}`));
220
+ process.exit(1);
221
+ }
222
+ });
223
+
224
+ program.parse();
package/lib/client.js ADDED
@@ -0,0 +1,170 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+
4
+ class CeoClient {
5
+ constructor({ apiKey, endpoint }) {
6
+ this.apiKey = apiKey;
7
+ this.endpoint = endpoint;
8
+ }
9
+
10
+ /**
11
+ * Send a prompt
12
+ */
13
+ async prompt(text, options = {}) {
14
+ const body = {
15
+ prompt: text,
16
+ ...(options.ragMode !== undefined && { ragMode: options.ragMode }),
17
+ ...(options.conversationHistory && { conversationHistory: options.conversationHistory })
18
+ };
19
+
20
+ const response = await this._request('POST', this.endpoint, body);
21
+
22
+ if (response.error) {
23
+ const error = new Error(response.error || response.message);
24
+ error.details = response.details;
25
+ error.statusCode = response.statusCode;
26
+ throw error;
27
+ }
28
+
29
+ return response.data || response;
30
+ }
31
+
32
+ /**
33
+ * Poll a presigned URL until content is available
34
+ */
35
+ async pollForResult(presignedUrl, options = {}) {
36
+ const {
37
+ interval = 2000,
38
+ timeout = 120000,
39
+ onPoll = null
40
+ } = options;
41
+
42
+ const startTime = Date.now();
43
+ let attempt = 0;
44
+
45
+ while (Date.now() - startTime < timeout) {
46
+ attempt++;
47
+
48
+ if (onPoll) {
49
+ onPoll(attempt);
50
+ }
51
+
52
+ try {
53
+ const result = await this._fetch(presignedUrl);
54
+ // S3 returns the content when ready
55
+ if (result !== null) {
56
+ // Try to parse as JSON
57
+ try {
58
+ return JSON.parse(result);
59
+ } catch {
60
+ return result;
61
+ }
62
+ }
63
+ } catch (error) {
64
+ // 403/404 means the object doesn't exist yet - keep polling
65
+ if (error.statusCode === 403 || error.statusCode === 404) {
66
+ // Expected - not ready yet
67
+ } else {
68
+ throw error;
69
+ }
70
+ }
71
+
72
+ await this._sleep(interval);
73
+ }
74
+
75
+ throw new Error(`Polling timed out after ${timeout}ms (${attempt} attempts)`);
76
+ }
77
+
78
+ /**
79
+ * Make an HTTP request to the API
80
+ */
81
+ _request(method, url, body = null) {
82
+ return new Promise((resolve, reject) => {
83
+ const parsedUrl = new URL(url);
84
+ const isHttps = parsedUrl.protocol === 'https:';
85
+ const lib = isHttps ? https : http;
86
+
87
+ const payload = body ? JSON.stringify(body) : null;
88
+
89
+ const options = {
90
+ hostname: parsedUrl.hostname,
91
+ port: parsedUrl.port || (isHttps ? 443 : 80),
92
+ path: parsedUrl.pathname + parsedUrl.search,
93
+ method,
94
+ headers: {
95
+ 'Authorization': `Bearer ${this.apiKey}`,
96
+ 'Content-Type': 'application/json',
97
+ ...(payload && { 'Content-Length': Buffer.byteLength(payload) })
98
+ }
99
+ };
100
+
101
+ const req = lib.request(options, (res) => {
102
+ let data = '';
103
+ res.on('data', (chunk) => { data += chunk; });
104
+ res.on('end', () => {
105
+ try {
106
+ const parsed = JSON.parse(data);
107
+ if (res.statusCode >= 400) {
108
+ parsed.statusCode = res.statusCode;
109
+ }
110
+ resolve(parsed);
111
+ } catch {
112
+ resolve({ data: data, statusCode: res.statusCode });
113
+ }
114
+ });
115
+ });
116
+
117
+ req.on('error', reject);
118
+ req.setTimeout(30000, () => {
119
+ req.destroy();
120
+ reject(new Error('Request timed out'));
121
+ });
122
+
123
+ if (payload) req.write(payload);
124
+ req.end();
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Fetch a URL (for presigned URL polling)
130
+ */
131
+ _fetch(url) {
132
+ return new Promise((resolve, reject) => {
133
+ const parsedUrl = new URL(url);
134
+ const isHttps = parsedUrl.protocol === 'https:';
135
+ const lib = isHttps ? https : http;
136
+
137
+ const req = lib.get(url, (res) => {
138
+ if (res.statusCode === 403 || res.statusCode === 404) {
139
+ const error = new Error('Not ready');
140
+ error.statusCode = res.statusCode;
141
+ res.resume(); // Drain response
142
+ reject(error);
143
+ return;
144
+ }
145
+
146
+ let data = '';
147
+ res.on('data', (chunk) => { data += chunk; });
148
+ res.on('end', () => {
149
+ if (res.statusCode >= 200 && res.statusCode < 300 && data.length > 0) {
150
+ resolve(data);
151
+ } else {
152
+ resolve(null);
153
+ }
154
+ });
155
+ });
156
+
157
+ req.on('error', reject);
158
+ req.setTimeout(10000, () => {
159
+ req.destroy();
160
+ reject(new Error('Fetch timed out'));
161
+ });
162
+ });
163
+ }
164
+
165
+ _sleep(ms) {
166
+ return new Promise((resolve) => setTimeout(resolve, ms));
167
+ }
168
+ }
169
+
170
+ module.exports = { CeoClient };
package/lib/config.js ADDED
@@ -0,0 +1,58 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ class ConfigManager {
6
+ constructor() {
7
+ this.configDir = path.join(os.homedir(), '.ceo-ai');
8
+ this.configFile = path.join(this.configDir, 'config.json');
9
+ this._ensureConfigDir();
10
+ }
11
+
12
+ _ensureConfigDir() {
13
+ if (!fs.existsSync(this.configDir)) {
14
+ fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
15
+ }
16
+ }
17
+
18
+ _read() {
19
+ try {
20
+ if (fs.existsSync(this.configFile)) {
21
+ const content = fs.readFileSync(this.configFile, 'utf8');
22
+ return JSON.parse(content);
23
+ }
24
+ } catch {
25
+ // Corrupted config, start fresh
26
+ }
27
+ return {};
28
+ }
29
+
30
+ _write(data) {
31
+ fs.writeFileSync(
32
+ this.configFile,
33
+ JSON.stringify(data, null, 2),
34
+ { mode: 0o600 } // Owner read/write only
35
+ );
36
+ }
37
+
38
+ get(key) {
39
+ const data = this._read();
40
+ return data[key];
41
+ }
42
+
43
+ set(key, value) {
44
+ const data = this._read();
45
+ data[key] = value;
46
+ this._write(data);
47
+ }
48
+
49
+ getAll() {
50
+ return this._read();
51
+ }
52
+
53
+ clear() {
54
+ this._write({});
55
+ }
56
+ }
57
+
58
+ module.exports = { ConfigManager };
package/package.json ADDED
@@ -0,0 +1 @@
1
+ {"name":"@ceo-ai/cli","version":"1.0.2","description":"CLI tool for CEO.AI API","bin":{"ceo":"./bin/ceo.js"},"files":["bin/","lib/"],"scripts":{"test":"node test/index.test.js"},"keywords":["ceo-ai","cli","ai","api"],"license":"MIT","dependencies":{"commander":"^12.1.0","chalk":"^4.1.2","conf":"^10.2.0","ora":"^5.4.1"}}