@kikkimo/claude-launcher 3.0.0 → 3.1.0
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/CHANGELOG.md +28 -0
- package/README.md +7 -4
- package/claude-launcher +634 -38
- package/docs/README-zh.md +7 -4
- package/lib/api-manager.js +501 -67
- package/lib/i18n/locales/de.js +144 -6
- package/lib/i18n/locales/en.js +150 -6
- package/lib/i18n/locales/es.js +144 -6
- package/lib/i18n/locales/fr.js +144 -6
- package/lib/i18n/locales/it.js +144 -6
- package/lib/i18n/locales/ja.js +144 -6
- package/lib/i18n/locales/ko.js +144 -6
- package/lib/i18n/locales/pt.js +144 -6
- package/lib/i18n/locales/ru.js +144 -6
- package/lib/i18n/locales/zh-TW.js +144 -6
- package/lib/i18n/locales/zh.js +150 -6
- package/lib/launcher.js +46 -17
- package/lib/presets/providers.js +143 -39
- package/lib/ui/api-editor.js +668 -210
- package/lib/ui/i18n-labels.js +16 -0
- package/lib/ui/menu.js +19 -13
- package/lib/ui/screen.js +125 -125
- package/lib/utils/version-checker.js +6 -5
- package/lib/validators.js +102 -1
- package/package.json +2 -2
package/lib/ui/api-editor.js
CHANGED
|
@@ -1,210 +1,668 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Editor Module — Edit API field-by-field with validation
|
|
3
|
-
* Flow: Select API → Field Menu → Edit Single Field → Save
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const colors = require('./colors');
|
|
7
|
-
const Menu = require('./menu');
|
|
8
|
-
const screen = require('./screen');
|
|
9
|
-
const { showApiSelectionTable } = require('./interactive-table');
|
|
10
|
-
const { simpleInput, waitForKey, selectProvider } = require('./prompts');
|
|
11
|
-
const { getProvider, detectProvider, getSuggestedModels } = require('../presets/providers');
|
|
12
|
-
const { truncateStringToWidth, getStringWidth, padStringToWidth } = require('../utils/string-width');
|
|
13
|
-
const i18n = require('../i18n');
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*
|
|
21
|
-
* @
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
1
|
+
/**
|
|
2
|
+
* API Editor Module — Edit API field-by-field with validation
|
|
3
|
+
* Flow: Select API → Field Menu → Edit Single Field → Save
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const colors = require('./colors');
|
|
7
|
+
const Menu = require('./menu');
|
|
8
|
+
const screen = require('./screen');
|
|
9
|
+
const { showApiSelectionTable } = require('./interactive-table');
|
|
10
|
+
const { simpleInput, waitForKey, selectProvider } = require('./prompts');
|
|
11
|
+
const { getProvider, detectProvider, getSuggestedModels } = require('../presets/providers');
|
|
12
|
+
const { truncateStringToWidth, getStringWidth, padStringToWidth } = require('../utils/string-width');
|
|
13
|
+
const i18n = require('../i18n');
|
|
14
|
+
const { i18nLabel } = require('./i18n-labels');
|
|
15
|
+
|
|
16
|
+
const FIELD_VALUE_MAX_WIDTH = 30;
|
|
17
|
+
const HINT_PROVIDER_NAME_MAX_WIDTH = 20;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve provider id to display name
|
|
21
|
+
* @param {string} providerId
|
|
22
|
+
* @returns {string} Display name or raw id as fallback
|
|
23
|
+
*/
|
|
24
|
+
function resolveProviderName(providerId) {
|
|
25
|
+
const provider = getProvider(providerId);
|
|
26
|
+
return provider ? provider.name : providerId;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Build field menu options with current values
|
|
31
|
+
*/
|
|
32
|
+
function buildFieldMenuOptions(api) {
|
|
33
|
+
const { PREDEFINED_MODEL_ENV_KEYS, PREDEFINED_RUNTIME_KEYS } = require('../validators');
|
|
34
|
+
const modelCustomCount = PREDEFINED_MODEL_ENV_KEYS.filter((key) => {
|
|
35
|
+
const currentVal = (api.modelEnvVars || {})[key] || '';
|
|
36
|
+
const autoVal = api._autoModelEnvVars ? api._autoModelEnvVars[key] : undefined;
|
|
37
|
+
return autoVal !== undefined ? currentVal !== autoVal : currentVal !== '';
|
|
38
|
+
}).length;
|
|
39
|
+
const runtimeManualCount = PREDEFINED_RUNTIME_KEYS.filter((key) => {
|
|
40
|
+
return ((api._runtimeEnvSources || {})[key] || 'auto') === 'manual';
|
|
41
|
+
}).length;
|
|
42
|
+
const customCount = Object.keys(api.customEnvVars || {}).length;
|
|
43
|
+
const envSummary = i18n.tSync('status.overridden') + ' ' + modelCustomCount + '/' + PREDEFINED_MODEL_ENV_KEYS.length
|
|
44
|
+
+ ' | ' + i18n.tSync('status.overridden') + ' ' + runtimeManualCount + '/' + PREDEFINED_RUNTIME_KEYS.length
|
|
45
|
+
+ ' | ' + i18n.tSync('summary.x_items', String(customCount));
|
|
46
|
+
const fields = [
|
|
47
|
+
{ key: 'name', label: i18n.tSync('api.edit.field_name'), value: api.name || '' },
|
|
48
|
+
{ key: 'provider', label: i18n.tSync('api.edit.field_provider'), value: resolveProviderName(api.provider) },
|
|
49
|
+
{ key: 'baseUrl', label: i18n.tSync('api.edit.field_base_url'), value: api.baseUrl || '' },
|
|
50
|
+
{ key: 'model', label: i18n.tSync('api.edit.field_model'), value: api.model || '' },
|
|
51
|
+
{ key: 'model_runtime', label: i18n.tSync('page.model_runtime_config'), value: envSummary },
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const maxLabelWidth = Math.max(...fields.map(f => getStringWidth(f.label)));
|
|
55
|
+
const options = fields.map(f => {
|
|
56
|
+
const paddedLabel = padStringToWidth(f.label, maxLabelWidth);
|
|
57
|
+
const truncatedValue = truncateStringToWidth(f.value, FIELD_VALUE_MAX_WIDTH);
|
|
58
|
+
return `${paddedLabel} ${truncatedValue}`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getApiById(apiManager, apiId) {
|
|
66
|
+
return apiManager.config.apis.find((api) => api.id === apiId) || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function editApiEnvVarsById(apiManager, { apiId, initialSection } = {}) {
|
|
70
|
+
let api = getApiById(apiManager, apiId);
|
|
71
|
+
if (!api) {
|
|
72
|
+
screen.write(colors.red + i18n.tSync('errors.api.not_found', apiId) + colors.reset + '\n');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const menu = new Menu();
|
|
77
|
+
const { PREDEFINED_MODEL_ENV_KEYS, PREDEFINED_RUNTIME_KEYS } = require('../validators');
|
|
78
|
+
|
|
79
|
+
while (true) {
|
|
80
|
+
if (initialSection === 'modelEnvVars') { await editModelEnvVarsMenu(apiManager, api); initialSection = null; api = getApiById(apiManager, apiId); continue; }
|
|
81
|
+
if (initialSection === 'runtimeEnvVars') { await editRuntimeEnvVarsMenu(apiManager, api); initialSection = null; api = getApiById(apiManager, apiId); continue; }
|
|
82
|
+
if (initialSection === 'customEnvVars') { await editCustomEnvVarsMenu(apiManager, api); initialSection = null; api = getApiById(apiManager, apiId); continue; }
|
|
83
|
+
|
|
84
|
+
const providerConfig = getProvider(api.provider);
|
|
85
|
+
const providerDefaults = providerConfig ? providerConfig.envVars || {} : {};
|
|
86
|
+
|
|
87
|
+
const modelOverridden = PREDEFINED_MODEL_ENV_KEYS.filter(k => {
|
|
88
|
+
const v = (api.modelEnvVars || {})[k] || '';
|
|
89
|
+
const a = api._autoModelEnvVars ? api._autoModelEnvVars[k] : undefined;
|
|
90
|
+
return a !== undefined && v !== a;
|
|
91
|
+
}).length;
|
|
92
|
+
const runtimeOverridden = PREDEFINED_RUNTIME_KEYS.filter(k => {
|
|
93
|
+
const val = (api.runtimeEnvVars || {})[k] || '';
|
|
94
|
+
const providerVal = (providerDefaults || {})[k];
|
|
95
|
+
if (val === '') return false;
|
|
96
|
+
const effective = val;
|
|
97
|
+
const recommended = providerVal !== undefined ? providerVal : '';
|
|
98
|
+
return effective !== recommended;
|
|
99
|
+
}).length;
|
|
100
|
+
const customCount = Object.keys(api.customEnvVars || {}).length;
|
|
101
|
+
|
|
102
|
+
const opts = [
|
|
103
|
+
i18n.tSync('page.model_config') + ' ' + modelOverridden + '/' + PREDEFINED_MODEL_ENV_KEYS.length + ' ' + i18n.tSync('status.overridden'),
|
|
104
|
+
i18n.tSync('page.runtime_config') + ' ' + runtimeOverridden + '/' + PREDEFINED_RUNTIME_KEYS.length + ' ' + i18n.tSync('status.overridden'),
|
|
105
|
+
i18n.tSync('page.custom_vars') + ' ' + i18n.tSync('summary.x_items', String(customCount)),
|
|
106
|
+
i18n.tSync('api.edit.back'),
|
|
107
|
+
];
|
|
108
|
+
menu.setOptions(opts);
|
|
109
|
+
const hintFn = (idx) => {
|
|
110
|
+
if (idx === 0) return i18n.tSync('page.model_config') + ': ' + i18n.tSync('hints.model.desc');
|
|
111
|
+
if (idx === 1) return i18n.tSync('page.runtime_config') + ': ' + i18n.tSync('hints.runtime.desc');
|
|
112
|
+
if (idx === 2) return i18n.tSync('page.custom_vars') + ': ' + i18n.tSync('hints.custom.desc');
|
|
113
|
+
return null;
|
|
114
|
+
};
|
|
115
|
+
const choice = await menu.navigate(null, hintFn, 'navigation.enter_to_select');
|
|
116
|
+
|
|
117
|
+
if (choice === -1 || choice === 3) return;
|
|
118
|
+
if (choice === 0) await editModelEnvVarsMenu(apiManager, api);
|
|
119
|
+
else if (choice === 1) await editRuntimeEnvVarsMenu(apiManager, api);
|
|
120
|
+
else if (choice === 2) await editCustomEnvVarsMenu(apiManager, api);
|
|
121
|
+
api = getApiById(apiManager, apiId);
|
|
122
|
+
if (!api) return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Build hintCallback for field menu — shows mismatch warning or blank
|
|
128
|
+
*/
|
|
129
|
+
function buildFieldMenuHintCallback(api) {
|
|
130
|
+
return function (_selectedIndex) {
|
|
131
|
+
const detected = detectProvider(api.baseUrl);
|
|
132
|
+
if (detected !== api.provider) {
|
|
133
|
+
const currentName = truncateStringToWidth(resolveProviderName(api.provider), HINT_PROVIDER_NAME_MAX_WIDTH);
|
|
134
|
+
const detectedName = truncateStringToWidth(resolveProviderName(detected), HINT_PROVIDER_NAME_MAX_WIDTH);
|
|
135
|
+
const line1 = i18n.tSync('api.edit.provider_url_mismatch');
|
|
136
|
+
const line2 = i18n.tSync('api.edit.provider_url_mismatch_detail', currentName, detectedName);
|
|
137
|
+
return `${line1}\n${line2}\n\n`;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Edit a single text field (name, baseUrl, model)
|
|
145
|
+
*/
|
|
146
|
+
async function editTextField(apiManager, api, fieldKey, fieldLabel) {
|
|
147
|
+
const currentValue = api[fieldKey] || '';
|
|
148
|
+
|
|
149
|
+
const headerLines = [
|
|
150
|
+
'',
|
|
151
|
+
colors.cyan + i18n.tSync('api.edit.current_value', currentValue) + colors.reset,
|
|
152
|
+
'',
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// Show suggested models for model field
|
|
156
|
+
if (fieldKey === 'model') {
|
|
157
|
+
const suggested = getSuggestedModels(api.provider);
|
|
158
|
+
if (suggested.length > 0) {
|
|
159
|
+
headerLines.push(colors.gray + ' Suggested models: ' + suggested.join(', ') + colors.reset);
|
|
160
|
+
headerLines.push('');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
screen.render(headerLines);
|
|
165
|
+
|
|
166
|
+
const input = await simpleInput(colors.green + i18n.tSync('api.edit.new_value') + colors.reset);
|
|
167
|
+
|
|
168
|
+
// Cancel check
|
|
169
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
170
|
+
screen.write(colors.yellow + i18n.tSync('api.edit.cancelled') + colors.reset + '\n');
|
|
171
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Attempt save
|
|
176
|
+
try {
|
|
177
|
+
const updated = apiManager.updateApiField(api.id, fieldKey, input);
|
|
178
|
+
|
|
179
|
+
// Success message
|
|
180
|
+
screen.write(colors.green + i18n.tSync('api.edit.success', fieldLabel) + colors.reset + '\n');
|
|
181
|
+
|
|
182
|
+
// baseUrl mismatch warning on same screen
|
|
183
|
+
if (fieldKey === 'baseUrl') {
|
|
184
|
+
const detected = detectProvider(input);
|
|
185
|
+
if (detected !== updated.provider) {
|
|
186
|
+
const detectedName = resolveProviderName(detected);
|
|
187
|
+
const currentName = resolveProviderName(updated.provider);
|
|
188
|
+
screen.write(colors.yellow + i18n.tSync('api.edit.url_provider_hint', detectedName, currentName) + colors.reset + '\n');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
193
|
+
return updated;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
screen.write(colors.red + '❌ ' + error.message + colors.reset + '\n');
|
|
196
|
+
// Re-prompt — stay in field edit
|
|
197
|
+
return await editTextField(apiManager, api, fieldKey, fieldLabel);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Edit provider field via selectProvider() helper
|
|
203
|
+
*/
|
|
204
|
+
async function editProviderField(apiManager, api) {
|
|
205
|
+
screen.render([
|
|
206
|
+
'',
|
|
207
|
+
colors.cyan + i18n.tSync('api.edit.current_value', resolveProviderName(api.provider)) + colors.reset,
|
|
208
|
+
'',
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
const result = await selectProvider({ title: null, showNote: false });
|
|
212
|
+
|
|
213
|
+
if (!result) {
|
|
214
|
+
screen.write(colors.yellow + i18n.tSync('api.edit.cancelled') + colors.reset + '\n');
|
|
215
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
try {
|
|
220
|
+
const { api: updated, warnings } = apiManager.updateApiProvider(api.id, result.id);
|
|
221
|
+
screen.write(colors.green + i18n.tSync('api.edit.success', i18n.tSync('api.edit.field_provider')) + colors.reset + '\n');
|
|
222
|
+
for (const w of warnings) {
|
|
223
|
+
if (w.code === 'MODEL_NOT_IN_PROVIDER') {
|
|
224
|
+
screen.write(colors.yellow + i18n.tSync('api.edit.warn_model_not_in_provider', w.messageArgs.model, w.messageArgs.providerName) + colors.reset + '\n');
|
|
225
|
+
} else if (w.code === 'BASE_URL_NOT_UPDATED') {
|
|
226
|
+
screen.write(colors.yellow + i18n.tSync('api.edit.warn_base_url_not_updated', w.messageArgs.baseUrl) + colors.reset + '\n');
|
|
227
|
+
} else if (w.code === 'MIXED_PROVIDER_CONFIG') {
|
|
228
|
+
screen.write(colors.yellow + i18n.tSync('api.edit.warn_mixed_provider') + colors.reset + '\n');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
232
|
+
return updated;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
screen.write(colors.red + '❌ ' + error.message + colors.reset + '\n');
|
|
235
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Shared field-menu loop for editing an API.
|
|
242
|
+
* Handles choices 0-5 and refreshes currentApi after sub-page edits.
|
|
243
|
+
*/
|
|
244
|
+
async function _runFieldEditLoop(apiManager, currentApi) {
|
|
245
|
+
const fieldMenu = new Menu();
|
|
246
|
+
|
|
247
|
+
while (true) {
|
|
248
|
+
fieldMenu.setOptions(buildFieldMenuOptions(currentApi));
|
|
249
|
+
const hintCallback = buildFieldMenuHintCallback(currentApi);
|
|
250
|
+
const choice = await fieldMenu.navigate(null, hintCallback);
|
|
251
|
+
|
|
252
|
+
if (choice === -1 || choice === 5) return;
|
|
253
|
+
|
|
254
|
+
const fieldKeys = ['name', 'provider', 'baseUrl', 'model'];
|
|
255
|
+
const fieldLabels = [
|
|
256
|
+
i18n.tSync('api.edit.field_name'), i18n.tSync('api.edit.field_provider'),
|
|
257
|
+
i18n.tSync('api.edit.field_base_url'), i18n.tSync('api.edit.field_model'),
|
|
258
|
+
];
|
|
259
|
+
|
|
260
|
+
let updated = null;
|
|
261
|
+
if (choice === 1) {
|
|
262
|
+
updated = await editProviderField(apiManager, currentApi);
|
|
263
|
+
} else if (choice >= 0 && choice < 4) {
|
|
264
|
+
updated = await editTextField(apiManager, currentApi, fieldKeys[choice], fieldLabels[choice]);
|
|
265
|
+
} else if (choice === 4) {
|
|
266
|
+
await editApiEnvVarsById(apiManager, { apiId: currentApi.id });
|
|
267
|
+
currentApi = getApiById(apiManager, currentApi.id);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (updated) currentApi = updated;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Programmatic entry: edit a specific API by ID with optional sub-menu jump.
|
|
276
|
+
*/
|
|
277
|
+
async function editApiById(apiManager, { apiId, initialSection }) {
|
|
278
|
+
const apis = apiManager.getApis();
|
|
279
|
+
const index = apis.findIndex(a => a.id === apiId);
|
|
280
|
+
if (index === -1) {
|
|
281
|
+
screen.write(colors.red + i18n.tSync('errors.api.not_found', apiId) + colors.reset + '\n');
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
let currentApi = apis[index];
|
|
285
|
+
|
|
286
|
+
while (initialSection) {
|
|
287
|
+
if (initialSection === 'runtimeEnvVars') {
|
|
288
|
+
await editRuntimeEnvVarsMenu(apiManager, currentApi);
|
|
289
|
+
initialSection = null;
|
|
290
|
+
currentApi = apiManager.config.apis.find(a => a.id === apiId);
|
|
291
|
+
continue;
|
|
292
|
+
} else if (initialSection === 'modelEnvVars') {
|
|
293
|
+
await editModelEnvVarsMenu(apiManager, currentApi);
|
|
294
|
+
initialSection = null;
|
|
295
|
+
currentApi = getApiById(apiManager, apiId);
|
|
296
|
+
continue;
|
|
297
|
+
} else if (initialSection === 'customEnvVars') {
|
|
298
|
+
await editCustomEnvVarsMenu(apiManager, currentApi);
|
|
299
|
+
initialSection = null;
|
|
300
|
+
currentApi = getApiById(apiManager, apiId);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return _runFieldEditLoop(apiManager, currentApi);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Main edit API flow
|
|
311
|
+
* @param {Object} apiManager - ApiManager instance
|
|
312
|
+
*/
|
|
313
|
+
async function editApi(apiManager) {
|
|
314
|
+
const apis = apiManager.getApis();
|
|
315
|
+
const selectedApi = await showApiSelectionTable(
|
|
316
|
+
apis,
|
|
317
|
+
i18n.tSync('api.edit.select_api'),
|
|
318
|
+
'edit'
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
if (!selectedApi) return;
|
|
322
|
+
|
|
323
|
+
return _runFieldEditLoop(apiManager, selectedApi);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// === Sub-menus for env config editing ===
|
|
327
|
+
|
|
328
|
+
async function editModelEnvVarsMenu(apiManager, api) {
|
|
329
|
+
const menu = new Menu();
|
|
330
|
+
const { PREDEFINED_MODEL_ENV_KEYS } = require('../validators');
|
|
331
|
+
const { MODEL_CONFIG_LABELS } = require('../api-manager');
|
|
332
|
+
const getLabel = (key) => i18nLabel("model", key, MODEL_CONFIG_LABELS);
|
|
333
|
+
|
|
334
|
+
while (true) {
|
|
335
|
+
const rows = PREDEFINED_MODEL_ENV_KEYS.map((key) => {
|
|
336
|
+
const currentVal = (api.modelEnvVars || {})[key] || '';
|
|
337
|
+
const autoVal = api._autoModelEnvVars ? (api._autoModelEnvVars[key] || '') : '';
|
|
338
|
+
const effectiveVal = currentVal || autoVal;
|
|
339
|
+
const displayVal = effectiveVal || i18n.tSync('status.not_set');
|
|
340
|
+
const isOverridden = autoVal !== '' && currentVal !== autoVal;
|
|
341
|
+
const mark = isOverridden ? ' ' + colors.cyan + i18n.tSync('status.overridden') + colors.reset : '';
|
|
342
|
+
return { label: ' ' + getLabel(key), displayVal, mark };
|
|
343
|
+
});
|
|
344
|
+
const maxLabelW = Math.max(...rows.map(r => getStringWidth(r.label)));
|
|
345
|
+
const maxValueW = Math.max(...rows.map(r => getStringWidth(r.displayVal)));
|
|
346
|
+
const options = rows.map(r => {
|
|
347
|
+
return colors.reset + padStringToWidth(r.label, maxLabelW + 2) + ' ' + padStringToWidth(r.displayVal, maxValueW) + r.mark;
|
|
348
|
+
});
|
|
349
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
350
|
+
menu.setOptions(options);
|
|
351
|
+
const hintFn = (idx) => {
|
|
352
|
+
if (idx >= PREDEFINED_MODEL_ENV_KEYS.length) return null;
|
|
353
|
+
const key = PREDEFINED_MODEL_ENV_KEYS[idx];
|
|
354
|
+
const detailKeys = {
|
|
355
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'hints.model.sonnet_detail',
|
|
356
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: 'hints.model.opus_detail',
|
|
357
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'hints.model.haiku_detail',
|
|
358
|
+
CLAUDE_CODE_SUBAGENT_MODEL: 'hints.model.subagent_detail',
|
|
359
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: 'hints.model.custom_option_detail',
|
|
360
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: 'hints.model.custom_name_detail',
|
|
361
|
+
};
|
|
362
|
+
return detailKeys[key] ? i18n.tSync(detailKeys[key]) : null;
|
|
363
|
+
};
|
|
364
|
+
const choice = await menu.navigate(null, hintFn, 'navigation.enter_to_edit');
|
|
365
|
+
|
|
366
|
+
if (choice === -1 || choice === PREDEFINED_MODEL_ENV_KEYS.length) return;
|
|
367
|
+
|
|
368
|
+
const key = PREDEFINED_MODEL_ENV_KEYS[choice];
|
|
369
|
+
await editModelEnvSingleField(apiManager, api, key);
|
|
370
|
+
api = getApiById(apiManager, api.id);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function editModelEnvSingleField(apiManager, api, key) {
|
|
375
|
+
const { MODEL_CONFIG_LABELS } = require('../api-manager');
|
|
376
|
+
const label = i18nLabel("model", key, MODEL_CONFIG_LABELS);
|
|
377
|
+
const currentVal = api.modelEnvVars[key] || '';
|
|
378
|
+
const autoVal = (api._autoModelEnvVars || {})[key] || '';
|
|
379
|
+
const menu = new Menu();
|
|
380
|
+
const shortKeyMap = {
|
|
381
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'sonnet', ANTHROPIC_DEFAULT_OPUS_MODEL: 'opus',
|
|
382
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'haiku', CLAUDE_CODE_SUBAGENT_MODEL: 'subagent',
|
|
383
|
+
ANTHROPIC_CUSTOM_MODEL_OPTION: 'custom_option', ANTHROPIC_CUSTOM_MODEL_OPTION_NAME: 'custom_name',
|
|
384
|
+
};
|
|
385
|
+
const descKey = 'hints.model.' + (shortKeyMap[key] || key) + '_detail';
|
|
386
|
+
|
|
387
|
+
while (true) {
|
|
388
|
+
screen.render([
|
|
389
|
+
'', colors.cyan + label + colors.reset, '',
|
|
390
|
+
i18n.tSync('status.current_value') + ': ' + (currentVal || i18n.tSync('status.not_set')),
|
|
391
|
+
i18n.tSync('status.recommended_value') + ': ' + (autoVal || i18n.tSync('status.not_set')),
|
|
392
|
+
'', colors.gray + i18n.tSync(descKey) + colors.reset,
|
|
393
|
+
'', i18n.tSync('action.please_choose'), '',
|
|
394
|
+
]);
|
|
395
|
+
menu.setOptions([
|
|
396
|
+
i18n.tSync('action.follow_recommended'),
|
|
397
|
+
i18n.tSync('action.custom_input'),
|
|
398
|
+
i18n.tSync('api.edit.back'),
|
|
399
|
+
]);
|
|
400
|
+
const choice = await menu.navigate(null, null);
|
|
401
|
+
if (choice === -1 || choice === 2) return;
|
|
402
|
+
|
|
403
|
+
if (choice === 0) {
|
|
404
|
+
try {
|
|
405
|
+
api = apiManager.updateModelEnvVar(api.id, key, '');
|
|
406
|
+
screen.write(colors.green + i18n.tSync('api.edit.success', label) + colors.reset + '\n');
|
|
407
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
408
|
+
return;
|
|
409
|
+
} catch (e) {
|
|
410
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
411
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
412
|
+
}
|
|
413
|
+
} else if (choice === 1) {
|
|
414
|
+
screen.render(['', colors.cyan + i18n.tSync('api.edit.current_value', currentVal) + colors.reset,
|
|
415
|
+
colors.gray + i18n.tSync('prompt.exit_to_cancel') + colors.reset, '']);
|
|
416
|
+
const input = await simpleInput(colors.green + i18n.tSync('api.edit.new_value') + colors.reset);
|
|
417
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') continue;
|
|
418
|
+
try {
|
|
419
|
+
api = apiManager.updateModelEnvVar(api.id, key, input);
|
|
420
|
+
screen.write(colors.green + i18n.tSync('api.edit.success', label) + colors.reset + '\n');
|
|
421
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
422
|
+
return;
|
|
423
|
+
} catch (e) {
|
|
424
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
425
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function editRuntimeEnvVarsMenu(apiManager, api) {
|
|
432
|
+
const menu = new Menu();
|
|
433
|
+
const { PREDEFINED_RUNTIME_KEYS, TYPE_A_FIELDS, TYPE_B_FIELDS } = require('../validators');
|
|
434
|
+
const { RUNTIME_CONFIG_LABELS } = require('../api-manager');
|
|
435
|
+
const providerConfig = getProvider(api.provider);
|
|
436
|
+
const providerDefaults = providerConfig ? providerConfig.envVars || {} : {};
|
|
437
|
+
const getLabel = (key) => i18nLabel("runtime", key, RUNTIME_CONFIG_LABELS);
|
|
438
|
+
|
|
439
|
+
while (true) {
|
|
440
|
+
const runtimeKeys = [...PREDEFINED_RUNTIME_KEYS];
|
|
441
|
+
const rows = runtimeKeys.map(key => {
|
|
442
|
+
const val = api.runtimeEnvVars[key] || '';
|
|
443
|
+
const providerVal = providerDefaults[key];
|
|
444
|
+
let display;
|
|
445
|
+
if (val === '') {
|
|
446
|
+
if (providerVal !== undefined) {
|
|
447
|
+
if (TYPE_A_FIELDS.includes(key) && providerVal === '1') {
|
|
448
|
+
display = i18n.tSync('status.enabled');
|
|
449
|
+
} else {
|
|
450
|
+
display = providerVal;
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
display = i18n.tSync('status.auto');
|
|
454
|
+
}
|
|
455
|
+
} else if (TYPE_A_FIELDS.includes(key) && val === 'off') {
|
|
456
|
+
display = i18n.tSync('status.disabled');
|
|
457
|
+
} else if (TYPE_A_FIELDS.includes(key) && val === '1') {
|
|
458
|
+
display = i18n.tSync('status.enabled');
|
|
459
|
+
} else {
|
|
460
|
+
display = val;
|
|
461
|
+
}
|
|
462
|
+
// result-based override check
|
|
463
|
+
let effectiveForCompare = val;
|
|
464
|
+
if (val === '') { effectiveForCompare = providerVal !== undefined ? providerVal : ''; }
|
|
465
|
+
const recommendedVal = providerVal !== undefined ? providerVal : '';
|
|
466
|
+
const isOverridden = val !== '' && effectiveForCompare !== recommendedVal;
|
|
467
|
+
const mark = isOverridden ? ' ' + colors.cyan + i18n.tSync('status.overridden') + colors.reset : '';
|
|
468
|
+
return { label: ' ' + getLabel(key), display, mark };
|
|
469
|
+
});
|
|
470
|
+
const maxLabelW = Math.max(...rows.map(r => getStringWidth(r.label)));
|
|
471
|
+
const maxValueW = Math.max(...rows.map(r => getStringWidth(r.display)));
|
|
472
|
+
const options = rows.map(r => {
|
|
473
|
+
return colors.reset + padStringToWidth(r.label, maxLabelW + 2) + ' ' + padStringToWidth(r.display, maxValueW) + r.mark;
|
|
474
|
+
});
|
|
475
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
476
|
+
menu.setOptions(options);
|
|
477
|
+
const hintFn = (idx) => {
|
|
478
|
+
if (idx >= runtimeKeys.length) return null;
|
|
479
|
+
const key = runtimeKeys[idx];
|
|
480
|
+
const detailKeys = {
|
|
481
|
+
API_TIMEOUT_MS: 'hints.runtime.timeout_detail',
|
|
482
|
+
CLAUDE_CODE_ATTRIBUTION_HEADER: 'hints.runtime.attribution_detail',
|
|
483
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 'hints.runtime.nonessential_detail',
|
|
484
|
+
CLAUDE_CODE_EFFORT_LEVEL: 'hints.runtime.effort_detail',
|
|
485
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: 'hints.runtime.experimental_detail',
|
|
486
|
+
CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK: 'hints.runtime.nonstreaming_detail',
|
|
487
|
+
};
|
|
488
|
+
if (!detailKeys[key]) return null;
|
|
489
|
+
// Dynamic source indicator
|
|
490
|
+
const val = api.runtimeEnvVars[key] || '';
|
|
491
|
+
const isManual = (api._runtimeEnvSources || {})[key] === 'manual';
|
|
492
|
+
let sourceText;
|
|
493
|
+
if (isManual && val !== '') {
|
|
494
|
+
sourceText = i18n.tSync('hints.runtime.source_manual');
|
|
495
|
+
} else if (providerDefaults[key] !== undefined) {
|
|
496
|
+
sourceText = i18n.tSync('hints.runtime.source_provider');
|
|
497
|
+
} else {
|
|
498
|
+
sourceText = i18n.tSync('hints.runtime.source_default');
|
|
499
|
+
}
|
|
500
|
+
return i18n.tSync(detailKeys[key]) + '\n' + sourceText;
|
|
501
|
+
};
|
|
502
|
+
const choice = await menu.navigate(null, hintFn, 'navigation.enter_to_edit');
|
|
503
|
+
|
|
504
|
+
if (choice === -1 || choice === runtimeKeys.length) return;
|
|
505
|
+
|
|
506
|
+
const key = runtimeKeys[choice];
|
|
507
|
+
if (TYPE_A_FIELDS.includes(key) || TYPE_B_FIELDS.includes(key)) {
|
|
508
|
+
await editRuntimeSwitchField(apiManager, api, key);
|
|
509
|
+
} else {
|
|
510
|
+
await editRuntimeTextField(apiManager, api, key);
|
|
511
|
+
}
|
|
512
|
+
api = getApiById(apiManager, api.id);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
async function editRuntimeSwitchField(apiManager, api, key) {
|
|
517
|
+
const { RUNTIME_CONFIG_LABELS } = require('../api-manager');
|
|
518
|
+
const { TYPE_A_FIELDS, TYPE_B_FIELDS } = require('../validators');
|
|
519
|
+
const label = i18nLabel("runtime", key, RUNTIME_CONFIG_LABELS);
|
|
520
|
+
const currentVal = api.runtimeEnvVars[key] || '';
|
|
521
|
+
const isManual = (api._runtimeEnvSources || {})[key] === 'manual';
|
|
522
|
+
const providerConfig = getProvider(api.provider);
|
|
523
|
+
const providerVal = (providerConfig && providerConfig.envVars) ? providerConfig.envVars[key] : undefined;
|
|
524
|
+
const effectiveVal = isManual ? currentVal : (providerVal !== undefined ? providerVal : '');
|
|
525
|
+
let currentDisplay;
|
|
526
|
+
if (effectiveVal === '') {
|
|
527
|
+
currentDisplay = i18n.tSync('status.auto');
|
|
528
|
+
} else if (TYPE_A_FIELDS.includes(key) && effectiveVal === '1') {
|
|
529
|
+
currentDisplay = i18n.tSync('status.enabled');
|
|
530
|
+
} else if (TYPE_A_FIELDS.includes(key) && effectiveVal === 'off') {
|
|
531
|
+
currentDisplay = i18n.tSync('status.disabled');
|
|
532
|
+
} else {
|
|
533
|
+
currentDisplay = effectiveVal;
|
|
534
|
+
}
|
|
535
|
+
const runtimeShortKeys = {
|
|
536
|
+
API_TIMEOUT_MS: 'timeout', CLAUDE_CODE_ATTRIBUTION_HEADER: 'attribution',
|
|
537
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 'nonessential', CLAUDE_CODE_EFFORT_LEVEL: 'effort',
|
|
538
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: 'experimental', CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK: 'nonstreaming',
|
|
539
|
+
};
|
|
540
|
+
const descKey = 'hints.runtime.' + (runtimeShortKeys[key] || key) + '_detail';
|
|
541
|
+
const menu = new Menu();
|
|
542
|
+
while (true) {
|
|
543
|
+
screen.render([
|
|
544
|
+
'', colors.cyan + label + colors.reset, '',
|
|
545
|
+
i18n.tSync('status.current_value') + ': ' + currentDisplay,
|
|
546
|
+
'', colors.gray + i18n.tSync(descKey) + colors.reset,
|
|
547
|
+
'', i18n.tSync('action.please_choose'), '',
|
|
548
|
+
]);
|
|
549
|
+
menu.setOptions([
|
|
550
|
+
i18n.tSync('action.follow_recommended'),
|
|
551
|
+
i18n.tSync('action.force_enable'),
|
|
552
|
+
i18n.tSync('action.force_disable'),
|
|
553
|
+
i18n.tSync('api.edit.back'),
|
|
554
|
+
]);
|
|
555
|
+
const choice = await menu.navigate(null, null);
|
|
556
|
+
if (choice === -1 || choice === 3) return;
|
|
557
|
+
const values = TYPE_A_FIELDS.includes(key) ? ['', '1', 'off'] : ['', '1', '0'];
|
|
558
|
+
try {
|
|
559
|
+
api = apiManager.updateRuntimeEnvVar(api.id, key, values[choice]);
|
|
560
|
+
return;
|
|
561
|
+
} catch (e) {
|
|
562
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
563
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
async function editRuntimeTextField(apiManager, api, key) {
|
|
569
|
+
const { RUNTIME_CONFIG_LABELS } = require('../api-manager');
|
|
570
|
+
const label = i18nLabel("runtime", key, RUNTIME_CONFIG_LABELS);
|
|
571
|
+
const currentVal = api.runtimeEnvVars[key] || '';
|
|
572
|
+
const providerConfig = getProvider(api.provider);
|
|
573
|
+
const providerVal = (providerConfig && providerConfig.envVars) ? providerConfig.envVars[key] : undefined;
|
|
574
|
+
const displayVal = currentVal || providerVal || i18n.tSync('status.auto');
|
|
575
|
+
const menu = new Menu();
|
|
576
|
+
const runtimeShortKeys = {
|
|
577
|
+
API_TIMEOUT_MS: 'timeout', CLAUDE_CODE_ATTRIBUTION_HEADER: 'attribution',
|
|
578
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 'nonessential', CLAUDE_CODE_EFFORT_LEVEL: 'effort',
|
|
579
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: 'experimental', CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK: 'nonstreaming',
|
|
580
|
+
};
|
|
581
|
+
const descKey = 'hints.runtime.' + (runtimeShortKeys[key] || key) + '_detail';
|
|
582
|
+
const isEffortLevel = (key === 'CLAUDE_CODE_EFFORT_LEVEL');
|
|
583
|
+
while (true) {
|
|
584
|
+
const renderLines = [
|
|
585
|
+
'', colors.cyan + label + colors.reset, '',
|
|
586
|
+
i18n.tSync('status.current_value') + ': ' + displayVal,
|
|
587
|
+
'', colors.gray + i18n.tSync(descKey) + colors.reset,
|
|
588
|
+
];
|
|
589
|
+
if (isEffortLevel) {
|
|
590
|
+
renderLines.push(colors.gray + i18n.tSync('hints.runtime.effort_values') + colors.reset);
|
|
591
|
+
}
|
|
592
|
+
renderLines.push('', i18n.tSync('action.please_choose'), '');
|
|
593
|
+
screen.render(renderLines);
|
|
594
|
+
menu.setOptions([
|
|
595
|
+
i18n.tSync('action.follow_recommended'),
|
|
596
|
+
i18n.tSync('action.custom_input'),
|
|
597
|
+
i18n.tSync('api.edit.back'),
|
|
598
|
+
]);
|
|
599
|
+
const choice = await menu.navigate(null, null);
|
|
600
|
+
if (choice === -1 || choice === 2) return;
|
|
601
|
+
if (choice === 0) {
|
|
602
|
+
try {
|
|
603
|
+
api = apiManager.updateRuntimeEnvVar(api.id, key, '');
|
|
604
|
+
return;
|
|
605
|
+
} catch (e) {
|
|
606
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
607
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
608
|
+
}
|
|
609
|
+
} else if (choice === 1) {
|
|
610
|
+
screen.render(['', colors.cyan + i18n.tSync('api.edit.current_value', currentVal) + colors.reset,
|
|
611
|
+
colors.gray + i18n.tSync('prompt.exit_to_cancel') + colors.reset, '']);
|
|
612
|
+
const input = await simpleInput(colors.green + i18n.tSync('api.edit.new_value') + colors.reset);
|
|
613
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') continue;
|
|
614
|
+
try {
|
|
615
|
+
api = apiManager.updateRuntimeEnvVar(api.id, key, input);
|
|
616
|
+
return;
|
|
617
|
+
} catch (e) {
|
|
618
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
619
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function editCustomEnvVarsMenu(apiManager, api) {
|
|
626
|
+
const menu = new Menu();
|
|
627
|
+
while (true) {
|
|
628
|
+
const keys = Object.keys(api.customEnvVars || {});
|
|
629
|
+
const options = keys.length > 0
|
|
630
|
+
? keys.map(k => ' ' + k + ' = ' + api.customEnvVars[k])
|
|
631
|
+
: [colors.gray + i18n.tSync('api.edit.no_custom_vars') + colors.reset];
|
|
632
|
+
options.push(i18n.tSync('action.add_variable'));
|
|
633
|
+
options.push(i18n.tSync('api.edit.back'));
|
|
634
|
+
menu.setOptions(options);
|
|
635
|
+
const choice = await menu.navigate(null, null, 'navigation.enter_to_edit');
|
|
636
|
+
|
|
637
|
+
if (choice === -1 || choice === options.length - 1) return;
|
|
638
|
+
if (choice === options.length - 2) {
|
|
639
|
+
screen.render(['', colors.green + i18n.tSync('api.edit.enter_custom_key') + colors.reset]);
|
|
640
|
+
const key = await simpleInput('> ');
|
|
641
|
+
if (!key || key.toLowerCase() === 'exit') continue;
|
|
642
|
+
screen.render(['', colors.green + i18n.tSync('api.edit.enter_custom_value') + colors.reset]);
|
|
643
|
+
const val = await simpleInput('> ');
|
|
644
|
+
try {
|
|
645
|
+
api = apiManager.setCustomEnvVar(api.id, key, val || '');
|
|
646
|
+
screen.write(colors.green + i18n.tSync('api.edit.success', key) + colors.reset + '\n');
|
|
647
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
648
|
+
} catch (e) {
|
|
649
|
+
screen.write(colors.red + e.message + colors.reset + '\n');
|
|
650
|
+
await waitForKey(i18n.tSync('ui.general.press_any_key_continue'));
|
|
651
|
+
}
|
|
652
|
+
} else if (choice < keys.length) {
|
|
653
|
+
const key = keys[choice];
|
|
654
|
+
screen.render(['', colors.yellow + i18n.tSync('confirm.delete_variable') + colors.reset]);
|
|
655
|
+
const confirm = await simpleInput('> ');
|
|
656
|
+
if (confirm.toLowerCase() === 'y') {
|
|
657
|
+
api = apiManager.deleteCustomEnvVar(api.id, key);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
module.exports = {
|
|
664
|
+
editApi,
|
|
665
|
+
editApiById,
|
|
666
|
+
editApiEnvVarsById,
|
|
667
|
+
resolveProviderName,
|
|
668
|
+
};
|