@decoopus/chatgpt-setup 0.0.0-stage → 1.0.1

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -2
  3. package/bin/cli.js +631 -0
  4. package/package.json +32 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 decoopus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # chatgpt-desktop-setup
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Automated one-command setup for **ChatGPT Desktop** & **Codex CLI** with custom 3P inference gateway, model selection (`gpt-6-astra`), and OpenAI emerald mascot theme.
4
+
5
+ Easily configure ChatGPT Desktop and Codex CLI to route requests through custom third-party inference gateways (such as `https://api.vokes.in/v1`, LiteLLM, or custom proxies) without manual TOML editing.
6
+
7
+ ---
8
+
9
+ ## ⚡ Quick Start
10
+
11
+ Users can simply open **PowerShell** (Windows) or **Terminal** (macOS/Linux) and run:
12
+
13
+ ```bash
14
+ npx chatgpt-desktop-setup
15
+ ```
16
+
17
+ *(or `npx codex-setup`)*
18
+
19
+ It will display the OpenAI pixel mascot and prompt for your API key:
20
+ ```text
21
+ ▟███▙ ▟█▙ ChatGPT & Codex Setup v1.0.0
22
+ ▐█▛ ▜█▛ ▀█▌ ChatGPT Pro 5x · API Usage Billing
23
+ ▐█▄ ▟█▛ ▄█▌ https://api.vokes.in/v1
24
+ ▜█▛ ▜███▛
25
+ ──────────────────────────────────────────────────────────
26
+
27
+ ✳ Enter your API Key:
28
+ API Key (e.g. sk-...): sk-ant-api03-...
29
+ ```
30
+
31
+ Or pass your API key directly on the command line:
32
+
33
+ ```bash
34
+ npx chatgpt-desktop-setup sk-your-api-key
35
+ ```
36
+
37
+ To launch the **Codex CLI** after setup instead of Desktop:
38
+
39
+ ```bash
40
+ npx codex-setup sk-your-api-key --cli
41
+ ```
42
+
43
+ ---
44
+
45
+ ## 🚀 Features
46
+
47
+ - **OpenAI Pixel Rosette Mascot**: Features the iconic knot rosette in emerald green (`#10a37f`).
48
+ - **Terminal Connection Animation**: Live terminal bridge animating the connection from your API Key to the OpenAI Gateway.
49
+ - **Auto-Configures Both Desktop & CLI**: Writes the exact provider, auth command, and desktop tuning to `~/.codex/config.toml`.
50
+ - **Pre-Configured Models & Settings**:
51
+ - **Provider**: `chatgpt_pro_5x` (*ChatGPT Pro 5x*)
52
+ - **Model**: `gpt-6-astra`
53
+ - **Reasoning Effort**: `ultra`
54
+ - **Wire API**: `responses`
55
+ - **Desktop Tuning**: Pre-configures smooth font sizes, prose conversation mode, and steer follow-ups.
56
+ - **Cross-Platform Dynamic Auth**:
57
+ - Windows: Auto-configures `powershell` with `-NoProfile -Command Write-Output '<KEY>'`
58
+ - macOS / Linux: Auto-configures standard POSIX `echo`
59
+ - **Config Preserving**: Updates `~/.codex/config.toml` while keeping all your existing plugins, marketplaces, and MCP servers intact.
60
+ - **Process Management**: Gracefully terminates running `ChatGPT.exe` or `codex.exe` so the new configuration applies cleanly without locking issues.
61
+ - **Auto Launcher**: Automatically detects and restarts ChatGPT Desktop (via Windows MSIX / macOS Application bundles) or launches Codex CLI.
62
+ - **Zero Dependencies**: Pure Node.js standard library with no external dependencies. Runs in milliseconds.
63
+
64
+ ---
65
+
66
+ ## 📖 CLI Usage
67
+
68
+ ```text
69
+ Usage:
70
+ npx chatgpt-desktop-setup [apiKey] [options]
71
+ npx codex-setup [apiKey] [options]
72
+
73
+ Arguments:
74
+ apiKey Your gateway API key (prompted interactively if omitted)
75
+
76
+ Options:
77
+ -k, --key <key> Specify API key
78
+ -u, --url <url> Gateway base URL (default: https://api.vokes.in/v1)
79
+ -m, --model <model> Active model ID (default: gpt-6-astra)
80
+ -p, --provider <id> Model provider ID (default: chatgpt_pro_5x)
81
+ --provider-name <name> Model provider display name (default: ChatGPT Pro 5x)
82
+ --reasoning <effort> Model reasoning effort: low, medium, high, ultra (default: ultra)
83
+ --cli Launch Codex CLI instead of ChatGPT Desktop app after setup
84
+ --no-launch Configure settings only; do not auto-launch ChatGPT or Codex
85
+ -s, --status Display current ~/.codex/config.toml configuration
86
+ -r, --revert Remove custom gateway configuration and restore OpenAI defaults
87
+ -h, --help Show this help message
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 📄 License
93
+
94
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,631 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * ChatGPT Desktop & Codex CLI Auto-Configurator
6
+ * Automates configuring ChatGPT Desktop and Codex CLI with custom third-party inference gateway.
7
+ *
8
+ * Base URL: https://api.vokes.in/v1
9
+ * Provider: chatgpt_pro_5x ("ChatGPT Pro 5x")
10
+ * Model: gpt-6-astra (Reasoning: ultra)
11
+ *
12
+ * Cross-platform: Windows, macOS, Linux
13
+ * Zero dependencies (Pure Node.js standard library)
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const os = require('os');
18
+ const path = require('path');
19
+ const readline = require('readline');
20
+ const { spawn, spawnSync, execSync } = require('child_process');
21
+
22
+ const pkg = require('../package.json');
23
+
24
+ // Default Configurations
25
+ const DEFAULT_GATEWAY_URL = 'https://api.vokes.in/v1';
26
+ const DEFAULT_PROVIDER = 'chatgpt_pro_5x';
27
+ const DEFAULT_PROVIDER_NAME = 'ChatGPT Pro 5x';
28
+ const DEFAULT_MODEL = 'gpt-6-astra';
29
+ const DEFAULT_REASONING = 'ultra';
30
+ const DEFAULT_WIRE_API = 'responses';
31
+
32
+ const isWin = process.platform === 'win32';
33
+ const isMac = process.platform === 'darwin';
34
+
35
+ // OpenAI / ChatGPT Aesthetic Color Palette
36
+ const c = {
37
+ reset: '\x1b[0m',
38
+ bold: '\x1b[1m',
39
+ dim: '\x1b[38;2;142;142;160m',
40
+ green: '\x1b[38;2;16;163;127m',
41
+ greenBold: '\x1b[1m\x1b[38;2;16;163;127m',
42
+ white: '\x1b[38;2;236;236;241m',
43
+ whiteBold: '\x1b[1m\x1b[38;2;236;236;241m',
44
+ gray: '\x1b[38;2;180;180;190m',
45
+ amber: '\x1b[38;2;245;158;11m',
46
+ amberBold: '\x1b[1m\x1b[38;2;245;158;11m',
47
+ red: '\x1b[38;2;239;68;68m',
48
+ cyan: '\x1b[38;2;6;182;212m'
49
+ };
50
+
51
+ function sleep(ms) {
52
+ return new Promise((resolve) => setTimeout(resolve, ms));
53
+ }
54
+
55
+ function printBanner() {
56
+ const g = c.greenBold;
57
+ const r = c.reset;
58
+ const w = c.whiteBold;
59
+ const d = c.dim;
60
+ const lines = [
61
+ '',
62
+ ` ${g} ▟███▙ ▟█▙${r} ${w}ChatGPT & Codex Setup${r} ${d}v${pkg.version}${r}`,
63
+ ` ${g}▐█▛ ▜█▛ ▀█▌${r} ${w}${DEFAULT_PROVIDER_NAME}${r} ${d}· API Usage Billing${r}`,
64
+ ` ${g}▐█▄ ▟█▛ ▄█▌${r} ${d}${DEFAULT_GATEWAY_URL}${r}`,
65
+ ` ${g} ▜█▛ ▜███▛${r}`,
66
+ ` ${d}${'─'.repeat(58)}${r}`,
67
+ ''
68
+ ];
69
+ console.log(lines.join('\n'));
70
+ }
71
+
72
+ function printHelp() {
73
+ printBanner();
74
+ console.log(`${c.whiteBold}Usage:${c.reset}
75
+ npx chatgpt-desktop-setup [apiKey] [options]
76
+ npx codex-setup [apiKey] [options]
77
+
78
+ ${c.whiteBold}Arguments:${c.reset}
79
+ apiKey Your gateway API key (prompted interactively if omitted)
80
+
81
+ ${c.whiteBold}Options:${c.reset}
82
+ -k, --key <key> Specify API key
83
+ -u, --url <url> Gateway base URL (default: ${DEFAULT_GATEWAY_URL})
84
+ -m, --model <model> Active model ID (default: ${DEFAULT_MODEL})
85
+ -p, --provider <id> Model provider ID (default: ${DEFAULT_PROVIDER})
86
+ --provider-name <name> Model provider display name (default: ${DEFAULT_PROVIDER_NAME})
87
+ --reasoning <effort> Model reasoning effort: low, medium, high, ultra (default: ${DEFAULT_REASONING})
88
+ --cli Launch Codex CLI instead of ChatGPT Desktop app after setup
89
+ --no-launch Configure settings only; do not auto-launch ChatGPT or Codex
90
+ -s, --status Display current ~/.codex/config.toml configuration
91
+ -r, --revert Remove custom gateway configuration and restore OpenAI defaults
92
+ -h, --help Show this help message
93
+
94
+ ${c.whiteBold}Examples:${c.reset}
95
+ npx chatgpt-desktop-setup
96
+ npx chatgpt-desktop-setup sk-your-api-key
97
+ npx codex-setup sk-your-api-key --cli
98
+ npx chatgpt-desktop-setup --status
99
+ npx chatgpt-desktop-setup --revert
100
+ `);
101
+ }
102
+
103
+ function askQuestion(promptText) {
104
+ return new Promise((resolve) => {
105
+ const rl = readline.createInterface({
106
+ input: process.stdin,
107
+ output: process.stdout
108
+ });
109
+ rl.question(promptText, (answer) => {
110
+ rl.close();
111
+ resolve(answer.trim());
112
+ });
113
+ });
114
+ }
115
+
116
+ // -------------------------------------------------------------
117
+ // Terminal Connection Animation
118
+ // -------------------------------------------------------------
119
+
120
+ async function runConnectionAnimation(key, gatewayUrl, width = 28) {
121
+ const isTTY = Boolean(process.stdout.isTTY);
122
+ const masked = key && key.length > 12 ? `${key.slice(0, 7)}...${key.slice(-4)}` : 'API Key';
123
+ const src = `${c.whiteBold}${masked}${c.reset}`;
124
+ const tgt = `${c.greenBold}ChatGPT & Codex${c.reset}`;
125
+
126
+ console.log(`\n ${c.greenBold}✳${c.reset} ${c.whiteBold}Connecting API Key to OpenAI Gateway...${c.reset}\n`);
127
+
128
+ if (isTTY) {
129
+ const frames = 16;
130
+ for (let i = 0; i <= frames; i++) {
131
+ const pos = Math.floor((i / frames) * width);
132
+ let beam = '';
133
+ for (let j = 0; j < width; j++) {
134
+ if (j === pos) {
135
+ beam += `${c.greenBold}●${c.reset}`;
136
+ } else if (j < pos) {
137
+ beam += `${c.green}━${c.reset}`;
138
+ } else {
139
+ beam += `${c.dim}─${c.reset}`;
140
+ }
141
+ }
142
+
143
+ process.stdout.write(`\r\x1b[K ${src} ${beam}► ${tgt}`);
144
+ await sleep(40);
145
+ }
146
+
147
+ const fullBeam = `${c.greenBold}${'═'.repeat(width)}►${c.reset}`;
148
+ process.stdout.write(`\r\x1b[K ${src} ${fullBeam} ${tgt} ${c.greenBold}✔ CONNECTED${c.reset}\n`);
149
+ } else {
150
+ console.log(` ${src} ════════════════════════════► ${tgt} ${c.greenBold}✔ CONNECTED${c.reset}`);
151
+ }
152
+
153
+ console.log(` ${c.dim}↳ Gateway connected to ${gatewayUrl} (${DEFAULT_PROVIDER_NAME} · ${DEFAULT_MODEL}).${c.reset}\n`);
154
+ }
155
+
156
+ // -------------------------------------------------------------
157
+ // Process Management (Close running ChatGPT Desktop / Codex CLI)
158
+ // -------------------------------------------------------------
159
+
160
+ function killRunningProcesses() {
161
+ try {
162
+ if (isWin) {
163
+ execSync('taskkill /F /IM ChatGPT.exe /IM codex.exe /T 2>nul', { stdio: 'ignore' });
164
+ } else if (isMac) {
165
+ execSync('killall ChatGPT Codex 2>/dev/null', { stdio: 'ignore' });
166
+ } else {
167
+ execSync("pkill -f 'ChatGPT|codex' 2>/dev/null", { stdio: 'ignore' });
168
+ }
169
+ } catch (_) {
170
+ // Process may not be running, ignore
171
+ }
172
+ }
173
+
174
+ // -------------------------------------------------------------
175
+ // TOML Parser & Updater (Zero external dependencies)
176
+ // -------------------------------------------------------------
177
+
178
+ function getCodexConfigPath() {
179
+ const customHome = process.env.CODEX_HOME;
180
+ if (customHome) {
181
+ return path.join(customHome, 'config.toml');
182
+ }
183
+ return path.join(os.homedir(), '.codex', 'config.toml');
184
+ }
185
+
186
+ function splitTomlSections(content) {
187
+ const lines = (content || '').split(/\r?\n/);
188
+ const sections = [];
189
+ let currentHeader = '';
190
+ let currentLines = [];
191
+
192
+ for (let i = 0; i < lines.length; i++) {
193
+ const line = lines[i];
194
+ const headerMatch = line.match(/^\[([^\]]+)\]/);
195
+ if (headerMatch) {
196
+ sections.push({ header: currentHeader, lines: currentLines });
197
+ currentHeader = headerMatch[1].trim();
198
+ currentLines = [line];
199
+ } else {
200
+ currentLines.push(line);
201
+ }
202
+ }
203
+ sections.push({ header: currentHeader, lines: currentLines });
204
+ return sections;
205
+ }
206
+
207
+ function updateCodexConfig(existingToml, opts) {
208
+ const model = opts.model || DEFAULT_MODEL;
209
+ const provider = opts.provider || DEFAULT_PROVIDER;
210
+ const providerName = opts.providerName || DEFAULT_PROVIDER_NAME;
211
+ const reasoning = opts.reasoning || DEFAULT_REASONING;
212
+ const baseUrl = opts.baseUrl || DEFAULT_GATEWAY_URL;
213
+ const wireApi = opts.wireApi || DEFAULT_WIRE_API;
214
+ const apiKey = opts.apiKey || '';
215
+
216
+ const sections = splitTomlSections(existingToml);
217
+
218
+ // 1. Process or create Root Section
219
+ let root = sections.find((s) => s.header === '');
220
+ if (!root) {
221
+ root = { header: '', lines: [] };
222
+ sections.unshift(root);
223
+ }
224
+
225
+ function setRootKey(key, value) {
226
+ const reg = new RegExp('^\\s*' + key + '\\s*=');
227
+ const idx = root.lines.findIndex((l) => reg.test(l));
228
+ const lineVal = `${key} = ${JSON.stringify(value)}`;
229
+ if (idx >= 0) {
230
+ root.lines[idx] = lineVal;
231
+ } else {
232
+ // Find where to place: put near top
233
+ root.lines.unshift(lineVal);
234
+ }
235
+ }
236
+
237
+ setRootKey('model_reasoning_effort', reasoning);
238
+ setRootKey('model_provider', provider);
239
+ setRootKey('model', model);
240
+
241
+ // 2. Ensure [desktop] Section exists and has optimal settings
242
+ let desktop = sections.find((s) => s.header === 'desktop');
243
+ if (!desktop) {
244
+ desktop = {
245
+ header: 'desktop',
246
+ lines: [
247
+ '[desktop]',
248
+ 'followUpQueueMode = "steer"',
249
+ 'conversationDetailMode = "STEPS_PROSE"',
250
+ 'sansFontSize = 14',
251
+ 'codeFontSize = 13',
252
+ 'ambient-suggestions-enabled = true'
253
+ ]
254
+ };
255
+ // Insert immediately after root
256
+ const rootIdx = sections.indexOf(root);
257
+ sections.splice(rootIdx + 1, 0, desktop);
258
+ } else {
259
+ // Ensure standard keys exist
260
+ const ensureDesktopKey = (key, rawVal) => {
261
+ const reg = new RegExp('^\\s*' + key + '\\s*=');
262
+ if (!desktop.lines.some((l) => reg.test(l))) {
263
+ desktop.lines.push(`${key} = ${rawVal}`);
264
+ }
265
+ };
266
+ ensureDesktopKey('followUpQueueMode', '"steer"');
267
+ ensureDesktopKey('conversationDetailMode', '"STEPS_PROSE"');
268
+ ensureDesktopKey('sansFontSize', '14');
269
+ ensureDesktopKey('codeFontSize', '13');
270
+ ensureDesktopKey('ambient-suggestions-enabled', 'true');
271
+ }
272
+
273
+ // 3. Remove existing provider sections for this provider ID
274
+ const filtered = sections.filter((s) => {
275
+ return s.header !== `model_providers.${provider}` &&
276
+ !s.header.startsWith(`model_providers.${provider}.`);
277
+ });
278
+
279
+ // 4. Construct Provider & Auth Configuration
280
+ const winPlatform = opts.isWin !== undefined ? opts.isWin : isWin;
281
+ const authCommand = winPlatform ? 'powershell' : 'echo';
282
+ let authArgs;
283
+ if (winPlatform) {
284
+ const escapedKey = apiKey.replace(/'/g, "''");
285
+ authArgs = ['-NoProfile', '-Command', `Write-Output '${escapedKey}'`];
286
+ } else {
287
+ authArgs = [apiKey];
288
+ }
289
+
290
+ const providerSection = {
291
+ header: `model_providers.${provider}`,
292
+ lines: [
293
+ `[model_providers.${provider}]`,
294
+ `name = ${JSON.stringify(providerName)}`,
295
+ `base_url = ${JSON.stringify(baseUrl)}`,
296
+ `wire_api = ${JSON.stringify(wireApi)}`
297
+ ]
298
+ };
299
+
300
+ const authArgsFormatted = JSON.stringify(authArgs).replace(/,/g, ', ');
301
+ const authSection = {
302
+ header: `model_providers.${provider}.auth`,
303
+ lines: [
304
+ `[model_providers.${provider}.auth]`,
305
+ `command = ${JSON.stringify(authCommand)}`,
306
+ `args = ${authArgsFormatted}`
307
+ ]
308
+ };
309
+
310
+ // Locate insertion point: right before [windows] if present, else at end
311
+ const winIdx = filtered.findIndex((s) => s.header === 'windows');
312
+ if (winIdx >= 0) {
313
+ filtered.splice(winIdx, 0, providerSection, authSection);
314
+ } else {
315
+ filtered.push(providerSection);
316
+ filtered.push(authSection);
317
+ }
318
+
319
+ // 5. Serialize back to TOML
320
+ const outputChunks = [];
321
+ for (const s of filtered) {
322
+ const cleanLines = s.lines.filter((l) => l.trim().length > 0);
323
+ if (cleanLines.length > 0) {
324
+ outputChunks.push(cleanLines.join('\n'));
325
+ }
326
+ }
327
+
328
+ return outputChunks.join('\n\n') + '\n';
329
+ }
330
+
331
+ function revertCodexConfig(existingToml, provider = DEFAULT_PROVIDER) {
332
+ const sections = splitTomlSections(existingToml);
333
+
334
+ // 1. Reset root keys
335
+ const root = sections.find((s) => s.header === '');
336
+ if (root) {
337
+ root.lines = root.lines.filter((line) => {
338
+ return !line.match(/^\s*model_provider\s*=/) &&
339
+ !line.match(/^\s*model_reasoning_effort\s*=/);
340
+ });
341
+ // Set default model
342
+ const mIdx = root.lines.findIndex((l) => /^\s*model\s*=/.test(l));
343
+ if (mIdx >= 0) {
344
+ root.lines[mIdx] = 'model = "gpt-5"';
345
+ }
346
+ }
347
+
348
+ // 2. Remove provider sections
349
+ const filtered = sections.filter((s) => {
350
+ return s.header !== `model_providers.${provider}` &&
351
+ !s.header.startsWith(`model_providers.${provider}.`);
352
+ });
353
+
354
+ const outputChunks = [];
355
+ for (const s of filtered) {
356
+ const cleanLines = s.lines.filter((l) => l.trim().length > 0);
357
+ if (cleanLines.length > 0) {
358
+ outputChunks.push(cleanLines.join('\n'));
359
+ }
360
+ }
361
+ return outputChunks.join('\n\n') + '\n';
362
+ }
363
+
364
+ // -------------------------------------------------------------
365
+ // Launch Helpers
366
+ // -------------------------------------------------------------
367
+
368
+ function findCodexCli() {
369
+ // 1. Check PATH
370
+ const finder = isWin ? 'where.exe' : 'which';
371
+ try {
372
+ const res = spawnSync(finder, ['codex'], { encoding: 'utf8', shell: false });
373
+ if (res.status === 0 && res.stdout) {
374
+ const first = res.stdout.split(/\r?\n/)[0]?.trim();
375
+ if (first && fs.existsSync(first)) return first;
376
+ }
377
+ } catch (_) {}
378
+
379
+ // 2. Windows specific locations
380
+ if (isWin) {
381
+ const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
382
+ const binDir = path.join(localAppData, 'OpenAI', 'Codex', 'bin');
383
+ if (fs.existsSync(binDir)) {
384
+ try {
385
+ const subdirs = fs.readdirSync(binDir);
386
+ for (const sub of subdirs) {
387
+ const exePath = path.join(binDir, sub, 'codex.exe');
388
+ if (fs.existsSync(exePath)) return exePath;
389
+ }
390
+ } catch (_) {}
391
+ }
392
+ }
393
+
394
+ // 3. Check ~/.codex/config.toml CODEX_CLI_PATH
395
+ try {
396
+ const confPath = getCodexConfigPath();
397
+ if (fs.existsSync(confPath)) {
398
+ const content = fs.readFileSync(confPath, 'utf8');
399
+ const match = content.match(/CODEX_CLI_PATH\s*=\s*['"]([^'"]+)['"]/);
400
+ if (match && fs.existsSync(match[1])) return match[1];
401
+ }
402
+ } catch (_) {}
403
+
404
+ return null;
405
+ }
406
+
407
+ function launchChatGPTDesktop() {
408
+ if (isWin) {
409
+ try {
410
+ // 1. Try launching via Windows Apps AUMID
411
+ const psScript = `
412
+ $pkg = Get-AppxPackage -Name "*Codex*" -ErrorAction SilentlyContinue
413
+ if (-not $pkg) { $pkg = Get-AppxPackage -Name "*ChatGPT*" -ErrorAction SilentlyContinue }
414
+ if ($pkg) {
415
+ Start-Process "shell:AppsFolder\\$($pkg.PackageFamilyName)!App"
416
+ } else {
417
+ Start-Process "shell:AppsFolder\\OpenAI.Codex_2p2nqsd0c76g0!App"
418
+ }
419
+ `;
420
+ spawnSync('powershell', ['-NoProfile', '-Command', psScript], { stdio: 'ignore' });
421
+ return true;
422
+ } catch (_) {
423
+ try {
424
+ execSync('start explorer.exe shell:AppsFolder\\OpenAI.Codex_2p2nqsd0c76g0!App', { stdio: 'ignore' });
425
+ return true;
426
+ } catch (_) {}
427
+ }
428
+ } else if (isMac) {
429
+ try {
430
+ execSync('open -a "ChatGPT" || open -a "Codex"', { stdio: 'ignore' });
431
+ return true;
432
+ } catch (_) {}
433
+ } else {
434
+ try {
435
+ const child = spawn('chatgpt', [], { detached: true, stdio: 'ignore' });
436
+ child.unref();
437
+ return true;
438
+ } catch (_) {}
439
+ }
440
+ return false;
441
+ }
442
+
443
+ function launchCodexCli() {
444
+ const exe = findCodexCli();
445
+ if (exe) {
446
+ console.log(`\n ${c.greenBold}Starting Codex CLI...${c.reset}\n`);
447
+ spawn(exe, [], { stdio: 'inherit' });
448
+ return true;
449
+ }
450
+ return false;
451
+ }
452
+
453
+ // -------------------------------------------------------------
454
+ // Status Display
455
+ // -------------------------------------------------------------
456
+
457
+ function showStatus() {
458
+ printBanner();
459
+ const confPath = getCodexConfigPath();
460
+ if (!fs.existsSync(confPath)) {
461
+ console.log(` ${c.amber}⚠${c.reset} No config found at: ${confPath}\n`);
462
+ return;
463
+ }
464
+
465
+ const content = fs.readFileSync(confPath, 'utf8');
466
+ const modelMatch = content.match(/model\s*=\s*["']([^"']+)["']/);
467
+ const providerMatch = content.match(/model_provider\s*=\s*["']([^"']+)["']/);
468
+ const reasoningMatch = content.match(/model_reasoning_effort\s*=\s*["']([^"']+)["']/);
469
+ const urlMatch = content.match(/base_url\s*=\s*["']([^"']+)["']/);
470
+
471
+ console.log(` ${c.whiteBold}Current Configuration:${c.reset} ${c.dim}(${confPath})${c.reset}`);
472
+ console.log(` ${c.gray}Model${c.reset} : ${c.whiteBold}${modelMatch ? modelMatch[1] : 'default'}${c.reset}`);
473
+ console.log(` ${c.gray}Provider${c.reset} : ${c.whiteBold}${providerMatch ? providerMatch[1] : 'default'}${c.reset}`);
474
+ console.log(` ${c.gray}Reasoning Effort${c.reset} : ${c.whiteBold}${reasoningMatch ? reasoningMatch[1] : 'standard'}${c.reset}`);
475
+ console.log(` ${c.gray}Gateway Base URL${c.reset} : ${c.whiteBold}${urlMatch ? urlMatch[1] : 'https://api.openai.com/v1'}${c.reset}`);
476
+
477
+ const cliPath = findCodexCli();
478
+ console.log(` ${c.gray}Codex CLI Binary${c.reset} : ${cliPath ? `${c.greenBold}✔ Found${c.reset} ${c.dim}(${cliPath})${c.reset}` : `${c.amber}Not found on PATH${c.reset}`}`);
479
+ console.log('');
480
+ }
481
+
482
+ // -------------------------------------------------------------
483
+ // Main CLI Execution
484
+ // -------------------------------------------------------------
485
+
486
+ async function main() {
487
+ const args = process.argv.slice(2);
488
+
489
+ if (args.includes('-h') || args.includes('--help')) {
490
+ printHelp();
491
+ process.exit(0);
492
+ }
493
+
494
+ if (args.includes('-s') || args.includes('--status')) {
495
+ showStatus();
496
+ process.exit(0);
497
+ }
498
+
499
+ const confPath = getCodexConfigPath();
500
+
501
+ if (args.includes('-r') || args.includes('--revert')) {
502
+ printBanner();
503
+ if (!fs.existsSync(confPath)) {
504
+ console.log(` ${c.amber}⚠${c.reset} No configuration file found to revert.\n`);
505
+ process.exit(0);
506
+ }
507
+ const current = fs.readFileSync(confPath, 'utf8');
508
+ const reverted = revertCodexConfig(current);
509
+ fs.writeFileSync(confPath, reverted, 'utf8');
510
+ console.log(` ${c.greenBold}✔ Configuration reverted to standard OpenAI defaults in:${c.reset}`);
511
+ console.log(` ${c.dim}${confPath}${c.reset}\n`);
512
+ process.exit(0);
513
+ }
514
+
515
+ let apiKey = '';
516
+ let gatewayUrl = DEFAULT_GATEWAY_URL;
517
+ let model = DEFAULT_MODEL;
518
+ let provider = DEFAULT_PROVIDER;
519
+ let providerName = DEFAULT_PROVIDER_NAME;
520
+ let reasoning = DEFAULT_REASONING;
521
+ let launchMode = 'desktop';
522
+ let shouldLaunch = true;
523
+
524
+ for (let i = 0; i < args.length; i++) {
525
+ const arg = args[i];
526
+ if (arg === '-k' || arg === '--key') {
527
+ apiKey = args[++i];
528
+ } else if (arg === '-u' || arg === '--url') {
529
+ gatewayUrl = args[++i];
530
+ } else if (arg === '-m' || arg === '--model') {
531
+ model = args[++i];
532
+ } else if (arg === '-p' || arg === '--provider') {
533
+ provider = args[++i];
534
+ } else if (arg === '--provider-name') {
535
+ providerName = args[++i];
536
+ } else if (arg === '--reasoning') {
537
+ reasoning = args[++i];
538
+ } else if (arg === '--cli') {
539
+ launchMode = 'cli';
540
+ } else if (arg === '--no-launch') {
541
+ shouldLaunch = false;
542
+ } else if (!arg.startsWith('-') && !apiKey) {
543
+ apiKey = arg;
544
+ }
545
+ }
546
+
547
+ printBanner();
548
+
549
+ // If no API key provided, prompt interactively
550
+ if (!apiKey) {
551
+ console.log(` ${c.greenBold}✳ Enter your API Key:${c.reset}`);
552
+ const entered = await askQuestion(` ${c.gray}API Key (e.g. sk-...):${c.reset} `);
553
+ if (!entered) {
554
+ console.log(`\n ${c.red}✖ No API key provided. Aborting.${c.reset}\n`);
555
+ process.exit(1);
556
+ }
557
+ apiKey = entered;
558
+ }
559
+
560
+ // Gracefully terminate existing instances before writing config
561
+ killRunningProcesses();
562
+
563
+ // Run visual connection animation
564
+ await runConnectionAnimation(apiKey, gatewayUrl);
565
+
566
+ // Ensure ~/.codex directory exists
567
+ const codexDir = path.dirname(confPath);
568
+ if (!fs.existsSync(codexDir)) {
569
+ fs.mkdirSync(codexDir, { recursive: true });
570
+ }
571
+
572
+ // Read existing TOML or initialize empty
573
+ let existingToml = '';
574
+ if (fs.existsSync(confPath)) {
575
+ existingToml = fs.readFileSync(confPath, 'utf8');
576
+ }
577
+
578
+ // Generate updated TOML
579
+ const updatedToml = updateCodexConfig(existingToml, {
580
+ apiKey,
581
+ baseUrl: gatewayUrl,
582
+ model,
583
+ provider,
584
+ providerName,
585
+ reasoning,
586
+ wireApi: DEFAULT_WIRE_API
587
+ });
588
+
589
+ fs.writeFileSync(confPath, updatedToml, 'utf8');
590
+
591
+ console.log(` ${c.greenBold}✔ Configured settings & provider in ~/.codex/config.toml${c.reset}`);
592
+ console.log(` ${c.greenBold}✔ ChatGPT Desktop & Codex CLI successfully configured!${c.reset}\n`);
593
+
594
+ if (shouldLaunch) {
595
+ if (launchMode === 'cli') {
596
+ const launched = launchCodexCli();
597
+ if (!launched) {
598
+ console.log(` ${c.amber}Codex CLI binary not found on PATH. Open terminal and run 'codex' to start.${c.reset}\n`);
599
+ }
600
+ } else {
601
+ console.log(` ${c.whiteBold}Starting ChatGPT Desktop...${c.reset}\n`);
602
+ const launched = launchChatGPTDesktop();
603
+ if (!launched) {
604
+ // Fallback to Codex CLI if desktop failed
605
+ const cliLaunched = launchCodexCli();
606
+ if (!cliLaunched) {
607
+ console.log(` ${c.greenBold}Configuration complete! Open ChatGPT Desktop or run 'codex' to start.${c.reset}\n`);
608
+ }
609
+ }
610
+ }
611
+ } else {
612
+ console.log(` ${c.dim}Auto-launch skipped (--no-launch specified).${c.reset}\n`);
613
+ }
614
+ }
615
+
616
+ if (require.main === module) {
617
+ main().catch((err) => {
618
+ console.error(`\n ${c.red}✖ Setup error: ${err.message}${c.reset}\n`);
619
+ process.exit(1);
620
+ });
621
+ }
622
+
623
+ module.exports = {
624
+ updateCodexConfig,
625
+ revertCodexConfig,
626
+ getCodexConfigPath,
627
+ splitTomlSections,
628
+ findCodexCli,
629
+ launchChatGPTDesktop,
630
+ launchCodexCli
631
+ };
package/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "@decoopus/chatgpt-setup",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.1",
4
+ "description": "One-command setup for ChatGPT Desktop & Codex CLI with custom 3P inference gateway",
5
+ "main": "bin/cli.js",
6
+ "bin": {
7
+ "chatgpt-setup": "bin/cli.js",
8
+ "codex-setup": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "start": "node ./bin/cli.js"
17
+ },
18
+ "keywords": [
19
+ "chatgpt",
20
+ "chatgpt-desktop",
21
+ "codex",
22
+ "codex-cli",
23
+ "openai",
24
+ "setup",
25
+ "gateway",
26
+ "gpt-6-astra",
27
+ "3p-inference"
28
+ ],
29
+ "author": "decoopus",
30
+ "license": "MIT",
31
+ "publishConfig": {
32
+ "access": "public"
33
+ }
34
+ }