codexdb-sdk 0.1.16 → 0.1.18

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,183 @@
1
+ # CodexDB SDK - Default Data Directory
2
+
3
+ ## Novo Comportamento
4
+
5
+ A partir desta versão, o SDK CodexDB Node.js possui um comportamento padrão para armazenamento de dados:
6
+
7
+ ### ✨ Comportamento Padrão (Sem opção `file`)
8
+
9
+ Quando você criar um cliente sem especificar o arquivo:
10
+
11
+ ```javascript
12
+ const { CodexClient } = require('codexdb-sdk');
13
+ const client = new CodexClient();
14
+ ```
15
+
16
+ O SDK automaticamente:
17
+ - Usa o diretório `.codex-data` na pasta corrente
18
+ - Usa `default.db` como nome do arquivo padrão
19
+ - Cria o diretório `.codex-data` se não existir
20
+ - Inicializa o diretório na primeira operação
21
+
22
+ **Resultado**: Arquivo em `.codex-data/default.db` no diretório corrente
23
+
24
+ ### 🎯 Comportamento com Opção `file`
25
+
26
+ Você ainda pode especificar um arquivo customizado:
27
+
28
+ ```javascript
29
+ // Opção 1: Caminho completo
30
+ const client1 = new CodexClient({
31
+ file: '/path/to/my-database.db'
32
+ });
33
+
34
+ // Opção 2: Caminho relativo
35
+ const client2 = new CodexClient({
36
+ file: 'my-data/custom.db'
37
+ });
38
+
39
+ // Opção 3: Apenas o nome do arquivo
40
+ const client3 = new CodexClient({
41
+ file: 'data.db'
42
+ });
43
+ ```
44
+
45
+ Em todos os casos:
46
+ - O caminho é resolvido em relação ao diretório corrente (`process.cwd()`)
47
+ - O diretório é criado automaticamente se não existir
48
+ - O arquivo é criado na primeira operação
49
+
50
+ ## Exemplos
51
+
52
+ ### Exemplo 1: Uso Padrão
53
+
54
+ ```javascript
55
+ const { CodexClient } = require('codexdb-sdk');
56
+
57
+ (async () => {
58
+ // Sem opções - usa .codex-data/default.db
59
+ const client = new CodexClient();
60
+
61
+ // O diretório é criado automaticamente na primeira operação
62
+ await client.set('chave', { valor: 'dados' });
63
+
64
+ const dados = await client.get('chave');
65
+ console.log(dados); // { valor: 'dados' }
66
+ })();
67
+ ```
68
+
69
+ ### Exemplo 2: Uso com Diretório Customizado
70
+
71
+ ```javascript
72
+ const { CodexClient } = require('codexdb-sdk');
73
+
74
+ (async () => {
75
+ // Com arquivo customizado
76
+ const client = new CodexClient({
77
+ file: 'dados/meu-banco.db'
78
+ });
79
+
80
+ await client.set('usuario', { nome: 'João' });
81
+ const usuario = await client.get('usuario');
82
+ console.log(usuario); // { nome: 'João' }
83
+ })();
84
+ ```
85
+
86
+ ### Exemplo 3: Com Criptografia e Ledger
87
+
88
+ ```javascript
89
+ const { CodexClient } = require('codexdb-sdk');
90
+
91
+ (async () => {
92
+ const client = new CodexClient({
93
+ // Se não especificar 'file', usa .codex-data/default.db
94
+ encryptionKey: 'minha-chave-secreta',
95
+ ledger: true
96
+ });
97
+
98
+ await client.set('dados-sensíveis', { token: '...' });
99
+ })();
100
+ ```
101
+
102
+ ## Método `initialize()`
103
+
104
+ Você pode chamar explicitamente `initialize()` para garantir que o diretório foi criado:
105
+
106
+ ```javascript
107
+ const { CodexClient } = require('codexdb-sdk');
108
+
109
+ const client = new CodexClient();
110
+
111
+ // Cria o diretório .codex-data explicitamente
112
+ await client.initialize();
113
+
114
+ // Agora é seguro fazer operações
115
+ await client.set('chave', { valor: 'dado' });
116
+ ```
117
+
118
+ **Nota**: O método `initialize()` é chamado automaticamente na primeira operação (set, get, delete, keys, has, clear), então geralmente você não precisa chamar explicitamente.
119
+
120
+ ## Estrutura de Diretórios
121
+
122
+ ### Padrão (sem opção `file`)
123
+ ```
124
+ projeto/
125
+ ├── .codex-data/ ← Criado automaticamente
126
+ │ └── default.db ← Arquivo de banco de dados
127
+ ├── node_modules/
128
+ ├── package.json
129
+ └── app.js
130
+ ```
131
+
132
+ ### Custom (com opção `file`)
133
+ ```
134
+ projeto/
135
+ ├── dados/ ← Criado automaticamente se necessário
136
+ │ └── meu-banco.db ← Arquivo customizado
137
+ ├── node_modules/
138
+ ├── package.json
139
+ └── app.js
140
+ ```
141
+
142
+ ## Testes Unitários
143
+
144
+ Para validar o comportamento padrão, execute:
145
+
146
+ ```bash
147
+ npm run test:unit
148
+ ```
149
+
150
+ Os testes cobrem:
151
+ - ✓ Uso do diretório padrão `.codex-data`
152
+ - ✓ Uso de arquivo customizado
153
+ - ✓ Criação automática de diretórios
154
+ - ✓ Suporte a múltiplos níveis de diretórios
155
+ - ✓ Propriedades do cliente (encriptação, ledger)
156
+ - ✓ Flag `_initialized`
157
+
158
+ ## Compatibilidade
159
+
160
+ - **Versão anterior**: `options.file` era obrigatório
161
+ - **Versão nova**: `options.file` é opcional (usa `.codex-data/default.db` por padrão)
162
+
163
+ Se seu código já usa a opção `file`, ele continuará funcionando normalmente! ✓
164
+
165
+ ## Migração
166
+
167
+ Se você tinha código assim:
168
+
169
+ ```javascript
170
+ // ❌ Antigo (file era obrigatório)
171
+ const client = new CodexClient({
172
+ file: './.codex-data/default.db'
173
+ });
174
+ ```
175
+
176
+ Você pode simplificar para:
177
+
178
+ ```javascript
179
+ // ✓ Novo (file é opcional)
180
+ const client = new CodexClient();
181
+ ```
182
+
183
+ Ambas as formas funcionam, mas a segunda é mais simples! 🎉
@@ -0,0 +1,66 @@
1
+ const { CodexClient } = require('./index');
2
+
3
+ // Example 1: Using default data directory (.codex-data/default.db)
4
+ (async () => {
5
+ console.log('Example 1: Using default data directory\n');
6
+
7
+ // No file option needed - uses .codex-data/default.db automatically
8
+ const client = new CodexClient();
9
+
10
+ console.log('Database file:', client.file);
11
+ console.log('Data directory:', client.dataDir);
12
+ console.log('Initialized:', client._initialized);
13
+
14
+ // Initialize creates the directory if it doesn't exist
15
+ await client.initialize();
16
+ console.log('After initialize - Initialized:', client._initialized);
17
+
18
+ try {
19
+ // Set a value
20
+ await client.set('message', { text: 'Hello from CodexDB!' });
21
+ console.log('✓ Set message');
22
+
23
+ // Get the value
24
+ const value = await client.get('message');
25
+ console.log('✓ Got message:', value.text);
26
+
27
+ // List all keys
28
+ const keys = await client.keys();
29
+ console.log('✓ All keys:', keys);
30
+
31
+ // Check if key exists
32
+ const exists = await client.has('message');
33
+ console.log('✓ Key exists:', exists);
34
+
35
+ // Delete the key
36
+ await client.delete('message');
37
+ console.log('✓ Deleted message');
38
+ } catch (error) {
39
+ console.error('Error:', error.message);
40
+ }
41
+ })();
42
+
43
+ // Example 2: Using custom data directory
44
+ console.log('\n---\n');
45
+ console.log('Example 2: Using custom data directory\n');
46
+
47
+ (async () => {
48
+ const path = require('path');
49
+
50
+ // Custom file path - resolved relative to current working directory
51
+ const client = new CodexClient({ file: 'custom-data.db' });
52
+
53
+ console.log('Database file:', client.file);
54
+ console.log('Data directory:', client.dataDir);
55
+
56
+ try {
57
+ // Operations automatically initialize the directory
58
+ await client.set('config', { theme: 'dark', language: 'pt-BR' });
59
+ console.log('✓ Set config');
60
+
61
+ const config = await client.get('config');
62
+ console.log('✓ Got config:', config);
63
+ } catch (error) {
64
+ console.error('Error:', error.message);
65
+ }
66
+ })();
package/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  const { execFile } = require('child_process');
2
+ const path = require('path');
3
+ const fs = require('fs/promises');
2
4
 
3
5
  function execCodexCli(cliPath, args, env = {}) {
4
6
  return new Promise((resolve, reject) => {
@@ -12,14 +14,32 @@ function execCodexCli(cliPath, args, env = {}) {
12
14
  });
13
15
  }
14
16
 
17
+ async function ensureDataDirectory(dataPath) {
18
+ try {
19
+ await fs.mkdir(dataPath, { recursive: true });
20
+ } catch (error) {
21
+ if (error.code !== 'EEXIST') {
22
+ throw error;
23
+ }
24
+ }
25
+ }
26
+
15
27
  class CodexClient {
16
28
  constructor(options = {}) {
17
- if (!options.file) {
18
- throw new Error('options.file is required (path to database file)');
29
+ // If no file is provided, use default: .codex-data/default.db
30
+ let file = options.file;
31
+ if (!file) {
32
+ const dataDir = path.resolve(process.cwd(), '.codex-data');
33
+ file = path.join(dataDir, 'default.db');
34
+ } else {
35
+ // If file is provided but it's just a filename (no path separators),
36
+ // resolve it relative to current working directory
37
+ file = path.resolve(process.cwd(), file);
19
38
  }
20
39
 
21
40
  this.cliPath = options.cliPath || 'codex-cli';
22
- this.file = options.file;
41
+ this.file = file;
42
+ this.dataDir = path.dirname(this.file);
23
43
  this.ledger = options.ledger ? '--ledger' : null;
24
44
  this.env = {};
25
45
 
@@ -28,6 +48,18 @@ class CodexClient {
28
48
  }
29
49
  }
30
50
 
51
+ async initialize() {
52
+ // Create data directory if it doesn't exist
53
+ await ensureDataDirectory(this.dataDir);
54
+ this._initialized = true;
55
+ }
56
+
57
+ async _ensureInitialized() {
58
+ if (!this._initialized) {
59
+ await this.initialize();
60
+ }
61
+ }
62
+
31
63
  _baseArgs() {
32
64
  const args = ['--file', this.file];
33
65
  if (this.ledger) args.push(this.ledger);
@@ -35,6 +67,7 @@ class CodexClient {
35
67
  }
36
68
 
37
69
  async set(key, value) {
70
+ await this._ensureInitialized();
38
71
  const payload = JSON.stringify(value);
39
72
  const args = [...this._baseArgs(), 'set', key, payload];
40
73
  await execCodexCli(this.cliPath, args, this.env);
@@ -42,6 +75,7 @@ class CodexClient {
42
75
  }
43
76
 
44
77
  async get(key) {
78
+ await this._ensureInitialized();
45
79
  const args = [...this._baseArgs(), 'get', key];
46
80
  const raw = await execCodexCli(this.cliPath, args, this.env);
47
81
  if (!raw) return null;
@@ -49,12 +83,14 @@ class CodexClient {
49
83
  }
50
84
 
51
85
  async delete(key) {
86
+ await this._ensureInitialized();
52
87
  const args = [...this._baseArgs(), 'delete', key];
53
88
  await execCodexCli(this.cliPath, args, this.env);
54
89
  return true;
55
90
  }
56
91
 
57
92
  async keys() {
93
+ await this._ensureInitialized();
58
94
  const args = [...this._baseArgs(), 'keys'];
59
95
  const raw = await execCodexCli(this.cliPath, args, this.env);
60
96
  if (!raw) return [];
@@ -62,12 +98,14 @@ class CodexClient {
62
98
  }
63
99
 
64
100
  async has(key) {
101
+ await this._ensureInitialized();
65
102
  const args = [...this._baseArgs(), 'has', key];
66
103
  const raw = await execCodexCli(this.cliPath, args, this.env);
67
104
  return raw.trim() === 'true';
68
105
  }
69
106
 
70
107
  async clear() {
108
+ await this._ensureInitialized();
71
109
  const args = [...this._baseArgs(), 'clear'];
72
110
  await execCodexCli(this.cliPath, args, this.env);
73
111
  return true;
package/index.test.js ADDED
@@ -0,0 +1,339 @@
1
+ const path = require('path');
2
+ const fs = require('fs/promises');
3
+ const fsSync = require('fs');
4
+ const { CodexClient } = require('./index');
5
+
6
+ // Test utilities
7
+ const TEST_DIR = path.join(__dirname, '.test-temp');
8
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
9
+
10
+ async function setupTestDir() {
11
+ try {
12
+ await fs.rm(TEST_DIR, { recursive: true, force: true });
13
+ } catch (e) {
14
+ // ignore
15
+ }
16
+ await fs.mkdir(TEST_DIR, { recursive: true });
17
+ }
18
+
19
+ async function cleanupTestDir() {
20
+ try {
21
+ await fs.rm(TEST_DIR, { recursive: true, force: true });
22
+ } catch (e) {
23
+ // ignore
24
+ }
25
+ }
26
+
27
+ async function dirExists(dirPath) {
28
+ try {
29
+ const stats = await fs.stat(dirPath);
30
+ return stats.isDirectory();
31
+ } catch (e) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ // Color console output
37
+ const colors = {
38
+ reset: '\x1b[0m',
39
+ green: '\x1b[32m',
40
+ red: '\x1b[31m',
41
+ yellow: '\x1b[33m',
42
+ blue: '\x1b[34m',
43
+ };
44
+
45
+ function log(message, color = 'reset') {
46
+ console.log(`${colors[color]}${message}${colors.reset}`);
47
+ }
48
+
49
+ function logTest(name) {
50
+ log(`\n ✓ ${name}`, 'blue');
51
+ }
52
+
53
+ function logPass(message) {
54
+ log(` ✓ ${message}`, 'green');
55
+ }
56
+
57
+ function logFail(message) {
58
+ log(` ✗ ${message}`, 'red');
59
+ process.exitCode = 1;
60
+ }
61
+
62
+ // ============================================
63
+ // TESTS
64
+ // ============================================
65
+
66
+ async function testDefaultDataDirectory() {
67
+ logTest('Default data directory behavior');
68
+
69
+ await cleanupTestDir();
70
+ await setupTestDir();
71
+
72
+ const originalCwd = process.cwd();
73
+
74
+ try {
75
+ process.chdir(TEST_DIR);
76
+
77
+ // Create client without file option
78
+ const client = new CodexClient();
79
+
80
+ // Check that file path includes .codex-data
81
+ const expectedPath = path.join(TEST_DIR, '.codex-data', 'default.db');
82
+ if (client.file === expectedPath) {
83
+ logPass(`File path defaults to ${expectedPath}`);
84
+ } else {
85
+ logFail(
86
+ `File path should be ${expectedPath}, got ${client.file}`
87
+ );
88
+ }
89
+
90
+ // Check dataDir
91
+ const expectedDataDir = path.join(TEST_DIR, '.codex-data');
92
+ if (client.dataDir === expectedDataDir) {
93
+ logPass(`Data directory defaults to ${expectedDataDir}`);
94
+ } else {
95
+ logFail(
96
+ `Data directory should be ${expectedDataDir}, got ${client.dataDir}`
97
+ );
98
+ }
99
+
100
+ // Check that directory is created on initialize
101
+ const existsBefore = await dirExists(expectedDataDir);
102
+ if (!existsBefore) {
103
+ logPass('Directory does not exist before initialize()');
104
+ } else {
105
+ logFail('Directory should not exist before initialize()');
106
+ }
107
+
108
+ await client.initialize();
109
+
110
+ const existsAfter = await dirExists(expectedDataDir);
111
+ if (existsAfter) {
112
+ logPass('Directory created after initialize()');
113
+ } else {
114
+ logFail('Directory should be created after initialize()');
115
+ }
116
+ } finally {
117
+ process.chdir(originalCwd);
118
+ }
119
+ }
120
+
121
+ async function testCustomFileOption() {
122
+ logTest('Custom file option handling');
123
+
124
+ await cleanupTestDir();
125
+ await setupTestDir();
126
+
127
+ const originalCwd = process.cwd();
128
+
129
+ try {
130
+ process.chdir(TEST_DIR);
131
+
132
+ // Test with full path
133
+ const customPath = path.join(TEST_DIR, 'custom', 'my-db.db');
134
+ const client = new CodexClient({ file: customPath });
135
+
136
+ if (client.file === customPath) {
137
+ logPass(`Full path option preserved: ${customPath}`);
138
+ } else {
139
+ logFail(
140
+ `File path should be ${customPath}, got ${client.file}`
141
+ );
142
+ }
143
+
144
+ // Test with relative filename
145
+ const client2 = new CodexClient({ file: 'my-db.db' });
146
+ const expectedPath = path.resolve(process.cwd(), 'my-db.db');
147
+
148
+ if (client2.file === expectedPath) {
149
+ logPass(`Relative filename resolved to: ${expectedPath}`);
150
+ } else {
151
+ logFail(
152
+ `File path should be ${expectedPath}, got ${client2.file}`
153
+ );
154
+ }
155
+ } finally {
156
+ process.chdir(originalCwd);
157
+ }
158
+ }
159
+
160
+ async function testDirectoryCreationOnDemand() {
161
+ logTest('Directory creation on first operation');
162
+
163
+ await cleanupTestDir();
164
+ await setupTestDir();
165
+
166
+ const originalCwd = process.cwd();
167
+
168
+ try {
169
+ process.chdir(TEST_DIR);
170
+
171
+ const client = new CodexClient();
172
+ const dataDir = client.dataDir;
173
+
174
+ // Check directory doesn't exist yet
175
+ const existsBefore = await dirExists(dataDir);
176
+ if (!existsBefore) {
177
+ logPass('Directory does not exist before first operation');
178
+ } else {
179
+ logFail('Directory should not exist before first operation');
180
+ }
181
+
182
+ // Call initialize (which would be called automatically in real operations)
183
+ await client.initialize();
184
+
185
+ // Check directory now exists
186
+ const existsAfter = await dirExists(dataDir);
187
+ if (existsAfter) {
188
+ logPass('Directory created on initialize()');
189
+ } else {
190
+ logFail('Directory should be created on initialize()');
191
+ }
192
+ } finally {
193
+ process.chdir(originalCwd);
194
+ }
195
+ }
196
+
197
+ async function testMultipleLevelsPath() {
198
+ logTest('Multiple levels of directory creation');
199
+
200
+ await cleanupTestDir();
201
+ await setupTestDir();
202
+
203
+ const originalCwd = process.cwd();
204
+
205
+ try {
206
+ process.chdir(TEST_DIR);
207
+
208
+ const customPath = path.join(TEST_DIR, 'a', 'b', 'c', 'db.db');
209
+ const client = new CodexClient({ file: customPath });
210
+
211
+ const dataDir = client.dataDir;
212
+ const existsBefore = await dirExists(dataDir);
213
+
214
+ if (!existsBefore) {
215
+ logPass('Nested directories do not exist before initialize()');
216
+ } else {
217
+ logFail('Nested directories should not exist before initialize()');
218
+ }
219
+
220
+ await client.initialize();
221
+
222
+ const existsAfter = await dirExists(dataDir);
223
+ if (existsAfter) {
224
+ logPass('Nested directories created: ' + dataDir);
225
+ } else {
226
+ logFail('Nested directories should be created');
227
+ }
228
+ } finally {
229
+ process.chdir(originalCwd);
230
+ }
231
+ }
232
+
233
+ async function testClientProperties() {
234
+ logTest('Client properties and configuration');
235
+
236
+ await cleanupTestDir();
237
+ await setupTestDir();
238
+
239
+ const originalCwd = process.cwd();
240
+
241
+ try {
242
+ process.chdir(TEST_DIR);
243
+
244
+ const client = new CodexClient({
245
+ encryptionKey: 'secret-key',
246
+ ledger: true,
247
+ });
248
+
249
+ if (client.env.CODEX_KEY === 'secret-key') {
250
+ logPass('Encryption key stored in environment');
251
+ } else {
252
+ logFail('Encryption key not stored correctly');
253
+ }
254
+
255
+ if (client.ledger === '--ledger') {
256
+ logPass('Ledger flag set correctly');
257
+ } else {
258
+ logFail('Ledger flag not set correctly');
259
+ }
260
+
261
+ const baseArgs = client._baseArgs();
262
+ if (baseArgs.includes('--file') && baseArgs.includes('--ledger')) {
263
+ logPass('Base args include both --file and --ledger flags');
264
+ } else {
265
+ logFail('Base args should include both flags');
266
+ }
267
+ } finally {
268
+ process.chdir(originalCwd);
269
+ }
270
+ }
271
+
272
+ async function testInitializedFlag() {
273
+ logTest('Initialized flag behavior');
274
+
275
+ const originalCwd = process.cwd();
276
+
277
+ try {
278
+ process.chdir(TEST_DIR);
279
+
280
+ const client = new CodexClient();
281
+
282
+ if (!client._initialized) {
283
+ logPass('_initialized flag is false before initialize()');
284
+ } else {
285
+ logFail('_initialized flag should be false initially');
286
+ }
287
+
288
+ await client.initialize();
289
+
290
+ if (client._initialized) {
291
+ logPass('_initialized flag is true after initialize()');
292
+ } else {
293
+ logFail('_initialized flag should be true after initialize()');
294
+ }
295
+
296
+ // Calling initialize again should not cause issues
297
+ await client.initialize();
298
+ logPass('Multiple initialize() calls are safe');
299
+ } finally {
300
+ process.chdir(originalCwd);
301
+ }
302
+ }
303
+
304
+ // ============================================
305
+ // RUN ALL TESTS
306
+ // ============================================
307
+
308
+ async function runAllTests() {
309
+ log('\n═══════════════════════════════════════════════════════', 'yellow');
310
+ log(' CodexDB SDK - Unit Tests', 'yellow');
311
+ log('═══════════════════════════════════════════════════════\n', 'yellow');
312
+
313
+ await setupTestDir();
314
+
315
+ try {
316
+ await testDefaultDataDirectory();
317
+ await testCustomFileOption();
318
+ await testDirectoryCreationOnDemand();
319
+ await testMultipleLevelsPath();
320
+ await testClientProperties();
321
+ await testInitializedFlag();
322
+
323
+ log('\n═══════════════════════════════════════════════════════', 'yellow');
324
+ if (process.exitCode) {
325
+ log(' Some tests failed!', 'red');
326
+ } else {
327
+ log(' All tests passed! ✓', 'green');
328
+ }
329
+ log('═══════════════════════════════════════════════════════\n', 'yellow');
330
+ } finally {
331
+ await cleanupTestDir();
332
+ }
333
+ }
334
+
335
+ // Run tests
336
+ runAllTests().catch((error) => {
337
+ log(`Fatal error: ${error.message}`, 'red');
338
+ process.exit(1);
339
+ });
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "codexdb-sdk",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Node.js SDK for CodexDB (wrapper around codex-cli)",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "node test.js",
8
+ "test:unit": "node index.test.js",
8
9
  "postinstall": "node scripts/postinstall.js",
9
10
  "publish-check": "node scripts/publish-check.js",
10
11
  "release": "bash scripts/publish.sh"
@@ -0,0 +1,141 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { https } = require('follow-redirects');
5
+ const { execSync } = require('child_process');
6
+
7
+ // Get package.json path relative to this script
8
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
9
+ const packageJson = require(packageJsonPath);
10
+ const version = packageJson.version;
11
+
12
+ // Map platform to OS name in release
13
+ const OS_MAP = {
14
+ darwin: 'darwin',
15
+ linux: 'linux',
16
+ win32: 'windows'
17
+ };
18
+
19
+ // Get system info
20
+ const platform = os.platform();
21
+ const osName = OS_MAP[platform];
22
+ const arch = os.arch() === 'arm64' ? 'arm64' : 'amd64';
23
+
24
+ if (!osName) {
25
+ console.log(`⚠️ No binary available for platform: ${platform}`);
26
+ process.exit(0); // Don't fail, just skip
27
+ }
28
+
29
+ const dest = path.join(__dirname, '..', 'codex-cli' + (platform === 'win32' ? '.exe' : ''));
30
+ const tempDir = path.join(__dirname, '..', '.temp-extract');
31
+
32
+ console.log(`Fetching release info for v${version}...`);
33
+
34
+ // Fetch release information from GitHub API
35
+ const apiUrl = `https://api.github.com/repos/evertonmj/codex/releases/tags/v${version}`;
36
+
37
+ https.get(apiUrl, {
38
+ headers: { 'User-Agent': 'codex-postinstall' }
39
+ }, (res) => {
40
+ let data = '';
41
+ res.on('data', chunk => { data += chunk; });
42
+ res.on('end', () => {
43
+ try {
44
+ const release = JSON.parse(data);
45
+
46
+ // Check if the API returned an error
47
+ if (release.message) {
48
+ console.log(`⚠️ Release v${version} not found on GitHub`);
49
+ console.log(` This is normal during development before publishing`);
50
+ console.log(` The codex-cli binary will be available after creating a GitHub release`);
51
+ process.exit(0); // Don't fail, just skip
52
+ }
53
+
54
+ if (!release.assets || !Array.isArray(release.assets)) {
55
+ console.log(`⚠️ Invalid release data: no assets found`);
56
+ process.exit(0); // Don't fail, just skip
57
+ }
58
+
59
+ // Find the correct binary for this platform/arch
60
+ const pattern = `codexdb_${version}_${osName}_${arch}`;
61
+ const asset = release.assets.find(a => a.name.includes(pattern));
62
+
63
+ if (!asset) {
64
+ console.log(`⚠️ No binary found for ${osName}/${arch}`);
65
+ console.log(` Expected pattern: ${pattern}`);
66
+ console.log(` Available assets: ${release.assets.map(a => a.name).join(', ')}`);
67
+ process.exit(0); // Don't fail, just skip
68
+ }
69
+
70
+ console.log(`Downloading ${asset.name}...`);
71
+
72
+ // Create temp directory
73
+ if (!fs.existsSync(tempDir)) {
74
+ fs.mkdirSync(tempDir, { recursive: true });
75
+ }
76
+
77
+ const tempFile = path.join(tempDir, asset.name);
78
+
79
+ https.get(asset.browser_download_url, (res) => {
80
+ if (res.statusCode !== 200) {
81
+ console.log(`⚠️ Failed to download binary: ${res.statusCode}`);
82
+ process.exit(0); // Don't fail, just skip
83
+ }
84
+ const file = fs.createWriteStream(tempFile);
85
+ res.pipe(file);
86
+ file.on('finish', () => {
87
+ file.close(() => {
88
+ extractAndInstall(tempFile, asset.name, dest, tempDir);
89
+ });
90
+ });
91
+ }).on('error', (err) => {
92
+ console.log(`⚠️ Download error: ${err.message}`);
93
+ process.exit(0); // Don't fail, just skip
94
+ });
95
+ } catch (err) {
96
+ console.log(`⚠️ Failed to parse release info: ${err.message}`);
97
+ process.exit(0); // Don't fail, just skip
98
+ }
99
+ });
100
+ }).on('error', (err) => {
101
+ console.log(`⚠️ API error: ${err.message}`);
102
+ console.log(` The codex-cli binary must be downloaded manually or will be installed on first use`);
103
+ process.exit(0); // Don't fail, just skip
104
+ });
105
+
106
+ function extractAndInstall(archivePath, archiveName, destPath, tempDir) {
107
+ try {
108
+ console.log(`Extracting ${archiveName}...`);
109
+
110
+ if (archiveName.endsWith('.tar.gz')) {
111
+ execSync(`tar -xzf ${archivePath} -C ${tempDir}`);
112
+ } else if (archiveName.endsWith('.zip')) {
113
+ execSync(`unzip -q ${archivePath} -d ${tempDir}`);
114
+ } else if (archiveName.endsWith('.deb')) {
115
+ // For .deb files, extract the data
116
+ execSync(`ar x ${archivePath} data.tar.xz -o ${tempDir}/data.tar.xz`);
117
+ execSync(`tar -xf ${tempDir}/data.tar.xz -C ${tempDir}`);
118
+ }
119
+
120
+ // Find the binary in the extracted files
121
+ const files = execSync(`find ${tempDir} -type f -name 'codex-cli*' -o -name 'codexdb' 2>/dev/null`, { encoding: 'utf-8' }).trim().split('\n').filter(f => f);
122
+
123
+ if (files.length === 0) {
124
+ console.log('⚠️ Binary not found in archive');
125
+ process.exit(0); // Don't fail, just skip
126
+ }
127
+
128
+ const binaryPath = files[0];
129
+ fs.copyFileSync(binaryPath, destPath);
130
+ fs.chmodSync(destPath, 0o755);
131
+
132
+ // Cleanup
133
+ execSync(`rm -rf ${tempDir}`);
134
+
135
+ console.log('✓ codex-cli installed to', destPath);
136
+ process.exit(0);
137
+ } catch (err) {
138
+ console.log(`⚠️ Extraction error: ${err.message}`);
139
+ process.exit(0); // Don't fail, just skip
140
+ }
141
+ }