@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.
- package/LICENSE +21 -0
- package/README.md +93 -2
- package/bin/cli.js +631 -0
- 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
|
-
#
|
|
1
|
+
# chatgpt-desktop-setup
|
|
2
2
|
|
|
3
|
-
|
|
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": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
}
|