set-agent-provider 0.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/LICENSE +21 -0
- package/README.en.md +164 -0
- package/README.md +157 -0
- package/bin/set-agent-provider.js +7 -0
- package/package.json +30 -0
- package/src/adapters/claude.js +64 -0
- package/src/adapters/codex.js +171 -0
- package/src/adapters/dsh.js +158 -0
- package/src/adapters/index.js +15 -0
- package/src/adapters/opencode.js +78 -0
- package/src/adapters/pi.js +73 -0
- package/src/apply.js +68 -0
- package/src/backup.js +28 -0
- package/src/cli/args.js +43 -0
- package/src/cli/help.js +33 -0
- package/src/cli/prompt.js +69 -0
- package/src/config/baseurl.js +49 -0
- package/src/config/load.js +50 -0
- package/src/config/name.js +18 -0
- package/src/config/resolve.js +58 -0
- package/src/config/schema.js +47 -0
- package/src/discover.js +34 -0
- package/src/domain/resolve.js +99 -0
- package/src/index.js +48 -0
- package/src/models/fetch.js +43 -0
- package/src/models/select.js +120 -0
- package/src/output.js +47 -0
- package/src/status.js +51 -0
- package/src/util/fs.js +40 -0
- package/src/util/http.js +62 -0
- package/src/util/paths.js +18 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { resolveBaseUrl } = require('./baseurl');
|
|
4
|
+
const { resolveName } = require('./name');
|
|
5
|
+
const { parseModels } = require('./schema');
|
|
6
|
+
const { askApiKey } = require('../cli/prompt');
|
|
7
|
+
const { fetchModels } = require('../models/fetch');
|
|
8
|
+
const { multiSelect } = require('../models/select');
|
|
9
|
+
|
|
10
|
+
async function resolveConfig(args) {
|
|
11
|
+
const resolved = await resolveBaseUrl(args);
|
|
12
|
+
const config = resolved.config;
|
|
13
|
+
const baseUrl = resolved.baseUrl;
|
|
14
|
+
|
|
15
|
+
const apiKey = await resolveApiKey(args, config);
|
|
16
|
+
const models = await resolveModels(args, config, baseUrl, apiKey);
|
|
17
|
+
const name = resolveName(args, config, baseUrl);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
name: name,
|
|
21
|
+
baseUrl: baseUrl,
|
|
22
|
+
apiKey: apiKey,
|
|
23
|
+
models: models
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function resolveApiKey(args, config) {
|
|
28
|
+
if (args.apiKey != null) return args.apiKey;
|
|
29
|
+
if (config && config.apiKey) return config.apiKey;
|
|
30
|
+
return askApiKey();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function resolveModels(args, config, baseUrl, apiKey) {
|
|
34
|
+
if (args.models != null) return parseModelsArg(args.models);
|
|
35
|
+
if (config && config.models) return config.models;
|
|
36
|
+
|
|
37
|
+
const fetched = await fetchModels(baseUrl, apiKey);
|
|
38
|
+
if (!fetched.length) {
|
|
39
|
+
throw new Error('No models returned from ' + baseUrl + ' (use -M/--models to provide them).');
|
|
40
|
+
}
|
|
41
|
+
const picked = await multiSelect(fetched);
|
|
42
|
+
if (!picked.length) {
|
|
43
|
+
throw new Error('No models selected.');
|
|
44
|
+
}
|
|
45
|
+
return parseModels(picked);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseModelsArg(str) {
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = JSON.parse(str);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
throw new Error('Invalid JSON for --models: ' + e.message);
|
|
54
|
+
}
|
|
55
|
+
return parseModels(raw);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { resolveConfig: resolveConfig };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function normalizeConfig(cfg) {
|
|
4
|
+
if (!cfg || typeof cfg !== 'object') {
|
|
5
|
+
throw new Error('Config must be an object');
|
|
6
|
+
}
|
|
7
|
+
const out = {
|
|
8
|
+
name: typeof cfg.name === 'string' && cfg.name ? cfg.name.trim() : null,
|
|
9
|
+
baseUrl: typeof cfg.baseUrl === 'string' && cfg.baseUrl ? cfg.baseUrl.trim() : null,
|
|
10
|
+
apiKey: typeof cfg.apiKey === 'string' && cfg.apiKey ? cfg.apiKey : undefined,
|
|
11
|
+
models: cfg.models != null ? parseModels(cfg.models) : undefined
|
|
12
|
+
};
|
|
13
|
+
return out;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function parseModels(models) {
|
|
17
|
+
let arr;
|
|
18
|
+
if (Array.isArray(models)) {
|
|
19
|
+
arr = models;
|
|
20
|
+
} else if (typeof models === 'object') {
|
|
21
|
+
arr = Object.keys(models).map(function (id) {
|
|
22
|
+
const v = models[id];
|
|
23
|
+
if (v && typeof v === 'object') return Object.assign({ id: id }, v);
|
|
24
|
+
return { id: id, name: v };
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error('models must be an array or object');
|
|
28
|
+
}
|
|
29
|
+
return arr.map(normalizeModel);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeModel(m, i) {
|
|
33
|
+
if (typeof m === 'string') return { id: m, name: m, reasoning: false, limit: undefined };
|
|
34
|
+
if (!m || typeof m !== 'object') throw new Error('Invalid model at index ' + i);
|
|
35
|
+
const id = m.id != null ? m.id : m.name;
|
|
36
|
+
if (id == null) throw new Error('Model missing id/name at index ' + i);
|
|
37
|
+
return {
|
|
38
|
+
id: String(id),
|
|
39
|
+
name: m.name != null ? String(m.name) : String(id),
|
|
40
|
+
reasoning: m.reasoning === true,
|
|
41
|
+
limit: m.limit && typeof m.limit === 'object'
|
|
42
|
+
? { context: m.limit.context, output: m.limit.output }
|
|
43
|
+
: undefined
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { normalizeConfig, parseModels };
|
package/src/discover.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { adapters } = require('./adapters');
|
|
4
|
+
const { expandHome } = require('./util/paths');
|
|
5
|
+
const { exists } = require('./util/fs');
|
|
6
|
+
|
|
7
|
+
function detectInstalled() {
|
|
8
|
+
const out = [];
|
|
9
|
+
for (const a of adapters) {
|
|
10
|
+
const installed = (a.configPaths || []).some(function (p) {
|
|
11
|
+
return exists(expandHome(p));
|
|
12
|
+
});
|
|
13
|
+
if (installed) out.push(a);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function resolveTargets(targetArg) {
|
|
19
|
+
const installed = detectInstalled();
|
|
20
|
+
const byId = new Map(installed.map(function (a) { return [a.id, a]; }));
|
|
21
|
+
if (targetArg) {
|
|
22
|
+
const wanted = String(targetArg).split(',').map(function (s) { return s.trim().toLowerCase(); }).filter(Boolean);
|
|
23
|
+
const selected = [];
|
|
24
|
+
const missing = [];
|
|
25
|
+
for (const w of wanted) {
|
|
26
|
+
if (byId.has(w)) selected.push(byId.get(w));
|
|
27
|
+
else missing.push(w);
|
|
28
|
+
}
|
|
29
|
+
return { selected: selected, missing: missing, all: installed };
|
|
30
|
+
}
|
|
31
|
+
return { selected: installed, missing: [], all: installed };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { detectInstalled, resolveTargets };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { get, post } = require('../util/http');
|
|
4
|
+
const { normalizeConfig } = require('../config/schema');
|
|
5
|
+
|
|
6
|
+
const CONFIG_PATH = '/set-agent-provider-config.json';
|
|
7
|
+
const SCHEMES = ['https', 'http'];
|
|
8
|
+
const NON_EXISTENT = [404, 405, 501];
|
|
9
|
+
|
|
10
|
+
function normalizeHost(domain) {
|
|
11
|
+
let s = String(domain == null ? '' : domain).trim();
|
|
12
|
+
s = s.replace(/^https?:\/\//i, '');
|
|
13
|
+
s = s.replace(/[/?#].*$/, '');
|
|
14
|
+
s = s.replace(/\/+$/, '');
|
|
15
|
+
return s;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function getSafe(url, apiKey) {
|
|
19
|
+
const headers = { Accept: 'application/json' };
|
|
20
|
+
if (apiKey) headers.Authorization = 'Bearer ' + apiKey;
|
|
21
|
+
try {
|
|
22
|
+
return await get(url, { headers: headers, timeout: 10000 });
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function postSafe(url, apiKey) {
|
|
29
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
|
|
30
|
+
if (apiKey) headers.Authorization = 'Bearer ' + apiKey;
|
|
31
|
+
try {
|
|
32
|
+
return await post(url, { headers: headers, body: {}, timeout: 10000 });
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function probeConfig(host) {
|
|
39
|
+
for (const scheme of SCHEMES) {
|
|
40
|
+
const url = scheme + '://' + host + CONFIG_PATH;
|
|
41
|
+
const res = await getSafe(url);
|
|
42
|
+
if (!res || res.status < 200 || res.status >= 300) continue;
|
|
43
|
+
let raw;
|
|
44
|
+
try {
|
|
45
|
+
raw = JSON.parse(res.body);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) continue;
|
|
50
|
+
try {
|
|
51
|
+
return normalizeConfig(raw);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function probeChat(host, apiKey) {
|
|
60
|
+
for (const scheme of SCHEMES) {
|
|
61
|
+
const origin = scheme + '://' + host;
|
|
62
|
+
const res = await postSafe(origin + '/v1/chat/completions', apiKey);
|
|
63
|
+
if (!res) continue;
|
|
64
|
+
if (NON_EXISTENT.indexOf(res.status) !== -1) continue;
|
|
65
|
+
try {
|
|
66
|
+
JSON.parse(res.body);
|
|
67
|
+
} catch (e) {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
return origin + '/v1';
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function resolveDomain(domain, opts) {
|
|
76
|
+
opts = opts || {};
|
|
77
|
+
const host = normalizeHost(domain);
|
|
78
|
+
if (!host) {
|
|
79
|
+
throw new Error('Invalid domain: ' + domain);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const config = await probeConfig(host);
|
|
83
|
+
if (config) {
|
|
84
|
+
return { config: config, baseUrl: config.baseUrl || null };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (opts.probe === false) {
|
|
88
|
+
return { config: null, baseUrl: null };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const baseUrl = await probeChat(host, opts.apiKey);
|
|
92
|
+
if (!baseUrl) {
|
|
93
|
+
throw new Error('Could not detect an OpenAI-compatible API at ' + host +
|
|
94
|
+
' (no /set-agent-provider-config.json or POST /v1/chat/completions).');
|
|
95
|
+
}
|
|
96
|
+
return { config: null, baseUrl: baseUrl };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { resolveDomain: resolveDomain, normalizeHost: normalizeHost };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseArgs } = require('./cli/args');
|
|
4
|
+
const { printHelp } = require('./cli/help');
|
|
5
|
+
const { resolveConfig } = require('./config/resolve');
|
|
6
|
+
const { promptDefault, isInteractive } = require('./models/select');
|
|
7
|
+
const { confirm } = require('./cli/prompt');
|
|
8
|
+
const { runStatus } = require('./status');
|
|
9
|
+
const { applyProvider } = require('./apply');
|
|
10
|
+
const { printSummary } = require('./output');
|
|
11
|
+
|
|
12
|
+
async function run(argv) {
|
|
13
|
+
const args = parseArgs(argv);
|
|
14
|
+
|
|
15
|
+
if (args.help) {
|
|
16
|
+
printHelp();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (args.version) {
|
|
20
|
+
process.stdout.write(require('../package.json').version + '\n');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (args.status) {
|
|
24
|
+
runStatus(args);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cfg = await resolveConfig(args);
|
|
29
|
+
|
|
30
|
+
if (isInteractive()) {
|
|
31
|
+
const ok = await confirm(cfg);
|
|
32
|
+
if (!ok) {
|
|
33
|
+
process.stdout.write('Aborted.\n');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const defaultModel = (await promptDefault(cfg.models)) || cfg.models[0].id;
|
|
39
|
+
|
|
40
|
+
const applied = await applyProvider(cfg, {
|
|
41
|
+
targetArg: args.target,
|
|
42
|
+
defaultModel: defaultModel,
|
|
43
|
+
interactive: isInteractive()
|
|
44
|
+
});
|
|
45
|
+
printSummary(cfg, applied.results);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { run: run };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { get } = require('../util/http');
|
|
4
|
+
|
|
5
|
+
function modelsUrl(baseUrl) {
|
|
6
|
+
const b = String(baseUrl).replace(/\/+$/, '');
|
|
7
|
+
if (/\/v1$/i.test(b)) return b + '/models';
|
|
8
|
+
return b + '/v1/models';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function fetchModels(baseUrl, apiKey) {
|
|
12
|
+
const url = modelsUrl(baseUrl);
|
|
13
|
+
const headers = { Accept: 'application/json' };
|
|
14
|
+
if (apiKey) headers.Authorization = 'Bearer ' + apiKey;
|
|
15
|
+
|
|
16
|
+
let res;
|
|
17
|
+
try {
|
|
18
|
+
res = await get(url, { headers: headers, timeout: 15000 });
|
|
19
|
+
} catch (e) {
|
|
20
|
+
throw new Error('Failed to fetch models from ' + url + ': ' + e.message);
|
|
21
|
+
}
|
|
22
|
+
if (res.status < 200 || res.status >= 300) {
|
|
23
|
+
throw new Error('Model list request returned HTTP ' + res.status + ' for ' + url);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let data;
|
|
27
|
+
try {
|
|
28
|
+
data = JSON.parse(res.body);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
throw new Error('Invalid JSON from ' + url);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const list = Array.isArray(data) ? data : (data.data || data.models || []);
|
|
34
|
+
return list
|
|
35
|
+
.map(function (m) {
|
|
36
|
+
if (typeof m === 'string') return { id: m, name: m };
|
|
37
|
+
const id = m && (m.id || m.name);
|
|
38
|
+
return id ? { id: String(id), name: String(m.name || id) } : null;
|
|
39
|
+
})
|
|
40
|
+
.filter(Boolean);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { fetchModels: fetchModels, modelsUrl: modelsUrl };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const readline = require('readline');
|
|
4
|
+
const { isInteractive } = require('../cli/prompt');
|
|
5
|
+
|
|
6
|
+
async function multiSelect(items) {
|
|
7
|
+
if (!items.length) return [];
|
|
8
|
+
if (!isInteractive()) return items.slice();
|
|
9
|
+
return tuiSelect(items);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function tuiSelect(items) {
|
|
13
|
+
return new Promise(function (resolve, reject) {
|
|
14
|
+
let cursor = 0;
|
|
15
|
+
const selected = new Set();
|
|
16
|
+
let rendered = 0;
|
|
17
|
+
|
|
18
|
+
const stdin = process.stdin;
|
|
19
|
+
const stdout = process.stdout;
|
|
20
|
+
readline.emitKeypressEvents(stdin);
|
|
21
|
+
stdin.setRawMode(true);
|
|
22
|
+
stdin.resume();
|
|
23
|
+
|
|
24
|
+
function lineFor(i) {
|
|
25
|
+
const it = items[i];
|
|
26
|
+
const box = selected.has(i) ? '[x]' : '[ ]';
|
|
27
|
+
const pointer = i === cursor ? '>' : ' ';
|
|
28
|
+
const label = it.name && it.name !== it.id ? ' ' + it.name : '';
|
|
29
|
+
return ' ' + pointer + ' ' + box + ' ' + it.id + label;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function render() {
|
|
33
|
+
if (rendered > 0) stdout.write('\x1b[' + rendered + 'A');
|
|
34
|
+
const lines = ['Select models (Space toggle, a=all, Enter confirm, Ctrl-C cancel):'];
|
|
35
|
+
for (let i = 0; i < items.length; i++) lines.push(lineFor(i));
|
|
36
|
+
let out = '';
|
|
37
|
+
for (const l of lines) out += '\x1b[2K' + l + '\n';
|
|
38
|
+
stdout.write(out);
|
|
39
|
+
rendered = lines.length;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function cleanup() {
|
|
43
|
+
stdin.setRawMode(false);
|
|
44
|
+
stdin.pause();
|
|
45
|
+
stdin.removeListener('keypress', onKey);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function onKey(str, key) {
|
|
49
|
+
if (!key) return;
|
|
50
|
+
if (key.ctrl && key.name === 'c') {
|
|
51
|
+
cleanup();
|
|
52
|
+
stdout.write('\n');
|
|
53
|
+
return reject(new Error('Cancelled by user'));
|
|
54
|
+
}
|
|
55
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
56
|
+
cursor = (cursor - 1 + items.length) % items.length; render();
|
|
57
|
+
} else if (key.name === 'down' || key.name === 'j') {
|
|
58
|
+
cursor = (cursor + 1) % items.length; render();
|
|
59
|
+
} else if (key.name === 'space') {
|
|
60
|
+
if (selected.has(cursor)) selected.delete(cursor); else selected.add(cursor);
|
|
61
|
+
render();
|
|
62
|
+
} else if (key.name === 'a') {
|
|
63
|
+
if (selected.size === items.length) selected.clear();
|
|
64
|
+
else items.forEach(function (_v, i) { selected.add(i); });
|
|
65
|
+
render();
|
|
66
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
67
|
+
if (selected.size === 0) selected.add(cursor);
|
|
68
|
+
cleanup();
|
|
69
|
+
stdout.write('\n');
|
|
70
|
+
resolve(items.filter(function (_v, i) { return selected.has(i); }));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
render();
|
|
75
|
+
stdin.on('keypress', onKey);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function promptDefault(models) {
|
|
80
|
+
if (!models || !models.length) return Promise.resolve(null);
|
|
81
|
+
if (!isInteractive()) return Promise.resolve(models[0].id);
|
|
82
|
+
|
|
83
|
+
return new Promise(function (resolve) {
|
|
84
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
85
|
+
const lines = models.map(function (m, i) {
|
|
86
|
+
const label = m.name && m.name !== m.id ? ' (' + m.name + ')' : '';
|
|
87
|
+
return ' ' + (i + 1) + ') ' + m.id + label;
|
|
88
|
+
});
|
|
89
|
+
process.stdout.write('Choose the default model:\n' + lines.join('\n') + '\n');
|
|
90
|
+
rl.question('Default model [1]: ', function (ans) {
|
|
91
|
+
rl.close();
|
|
92
|
+
const n = parseInt(String(ans).trim(), 10);
|
|
93
|
+
if (!isNaN(n) && n >= 1 && n <= models.length) resolve(models[n - 1].id);
|
|
94
|
+
else resolve(models[0].id);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function promptChoice(question, choices) {
|
|
100
|
+
if (!choices || !choices.length) return Promise.resolve(-1);
|
|
101
|
+
if (!isInteractive()) return Promise.resolve(0);
|
|
102
|
+
return new Promise(function (resolve) {
|
|
103
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
104
|
+
const lines = choices.map(function (c, i) { return ' ' + (i + 1) + ') ' + c; });
|
|
105
|
+
process.stdout.write(question + '\n' + lines.join('\n') + '\n');
|
|
106
|
+
rl.question('Choice [1]: ', function (ans) {
|
|
107
|
+
rl.close();
|
|
108
|
+
const n = parseInt(String(ans).trim(), 10);
|
|
109
|
+
if (!isNaN(n) && n >= 1 && n <= choices.length) resolve(n - 1);
|
|
110
|
+
else resolve(0);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
multiSelect: multiSelect,
|
|
117
|
+
promptDefault: promptDefault,
|
|
118
|
+
promptChoice: promptChoice,
|
|
119
|
+
isInteractive: isInteractive
|
|
120
|
+
};
|
package/src/output.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function maskKey(apiKey) {
|
|
4
|
+
if (!apiKey) return null;
|
|
5
|
+
const s = String(apiKey);
|
|
6
|
+
if (s.length <= 6) return '****';
|
|
7
|
+
return s.slice(0, 3) + '...' + s.slice(-3);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function printSummary(cfg, results) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
lines.push('');
|
|
13
|
+
lines.push('Provider : ' + cfg.name);
|
|
14
|
+
lines.push('Base URL : ' + cfg.baseUrl);
|
|
15
|
+
lines.push('API key : ' + (cfg.apiKey ? maskKey(cfg.apiKey) : '(none)'));
|
|
16
|
+
lines.push('Models : ' + (cfg.models ? cfg.models.length : 0));
|
|
17
|
+
lines.push('');
|
|
18
|
+
|
|
19
|
+
for (const r of results) {
|
|
20
|
+
if (r.ok) {
|
|
21
|
+
const b = r.backedUp && r.backedUp.length ? ' [backed up ' + r.backedUp.length + ' file(s)]' : '';
|
|
22
|
+
const d = r.wireApi ? ' [wire_api=' + r.wireApi + ']' : '';
|
|
23
|
+
const u = r.changed === false ? ' [unchanged]' : '';
|
|
24
|
+
lines.push(' OK ' + r.name + d + u + b);
|
|
25
|
+
} else if (r.skipped) {
|
|
26
|
+
lines.push(' SKIP ' + r.name + ' — ' + r.error);
|
|
27
|
+
} else {
|
|
28
|
+
lines.push(' FAIL ' + r.name + ' — ' + r.error);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const okCount = results.filter(function (r) { return r.ok; }).length;
|
|
33
|
+
const failCount = results.filter(function (r) { return !r.ok && !r.skipped; }).length;
|
|
34
|
+
lines.push('');
|
|
35
|
+
lines.push('Done: ' + okCount + ' configured, ' + failCount + ' failed.');
|
|
36
|
+
|
|
37
|
+
const failed = results.filter(function (r) { return !r.ok && !r.skipped; });
|
|
38
|
+
if (failed.length) {
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push('Failed targets:');
|
|
41
|
+
for (const f of failed) lines.push(' - ' + f.name + ': ' + f.error);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { maskKey: maskKey, printSummary: printSummary };
|
package/src/status.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { adapters } = require('./adapters');
|
|
4
|
+
const { resolveTargets } = require('./discover');
|
|
5
|
+
|
|
6
|
+
function pad(s, n) {
|
|
7
|
+
s = String(s);
|
|
8
|
+
while (s.length < n) s += ' ';
|
|
9
|
+
return s;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readOne(adapter) {
|
|
13
|
+
try {
|
|
14
|
+
return adapter.readStatus();
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function runStatus(args) {
|
|
21
|
+
const resolved = resolveTargets(args.target);
|
|
22
|
+
const showAll = !args.target;
|
|
23
|
+
const lines = [];
|
|
24
|
+
lines.push('');
|
|
25
|
+
lines.push('Provider status:');
|
|
26
|
+
|
|
27
|
+
const list = showAll ? adapters : resolved.selected;
|
|
28
|
+
for (const a of list) {
|
|
29
|
+
const s = readOne(a);
|
|
30
|
+
if (s && s.configured) {
|
|
31
|
+
const parts = ['name=' + (s.name || '?')];
|
|
32
|
+
if (s.baseUrl) parts.push('baseUrl=' + s.baseUrl);
|
|
33
|
+
if (s.defaultModel) parts.push('default=' + s.defaultModel);
|
|
34
|
+
if (s.modelCount != null) parts.push('models=' + s.modelCount);
|
|
35
|
+
lines.push(' ' + pad(a.name, 14) + ': ' + parts.join(' '));
|
|
36
|
+
} else {
|
|
37
|
+
lines.push(' ' + pad(a.name, 14) + ': (not configured)');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!showAll) {
|
|
42
|
+
for (const m of resolved.missing) {
|
|
43
|
+
lines.push(' ' + pad(m, 14) + ': (unknown target)');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
lines.push('');
|
|
48
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { runStatus: runStatus };
|
package/src/util/fs.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
let backupFileHook = null;
|
|
7
|
+
|
|
8
|
+
function setBackupHook(fn) {
|
|
9
|
+
backupFileHook = typeof fn === 'function' ? fn : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function exists(file) {
|
|
13
|
+
return fs.existsSync(file);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readText(file) {
|
|
17
|
+
return fs.readFileSync(file, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeText(file, text) {
|
|
21
|
+
if (exists(file)) {
|
|
22
|
+
let current = null;
|
|
23
|
+
try { current = readText(file); } catch (e) { current = null; }
|
|
24
|
+
if (current === text) return false;
|
|
25
|
+
if (backupFileHook) backupFileHook(file);
|
|
26
|
+
}
|
|
27
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
28
|
+
fs.writeFileSync(file, text, 'utf8');
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readJson(file) {
|
|
33
|
+
return JSON.parse(readText(file));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function writeJson(file, obj) {
|
|
37
|
+
return writeText(file, JSON.stringify(obj, null, 2) + '\n');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { exists, readText, writeText, readJson, writeJson, setBackupHook };
|
package/src/util/http.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const { URL } = require('url');
|
|
6
|
+
|
|
7
|
+
function request(method, url, opts) {
|
|
8
|
+
opts = opts || {};
|
|
9
|
+
const headers = opts.headers || {};
|
|
10
|
+
const body = opts.body == null ? null : (typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body));
|
|
11
|
+
const timeout = opts.timeout || 15000;
|
|
12
|
+
const maxRedirects = opts.maxRedirects == null ? 5 : opts.maxRedirects;
|
|
13
|
+
|
|
14
|
+
return new Promise(function (resolve, reject) {
|
|
15
|
+
let u;
|
|
16
|
+
try {
|
|
17
|
+
u = new URL(url);
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return reject(new Error('Invalid URL: ' + url));
|
|
20
|
+
}
|
|
21
|
+
const lib = u.protocol === 'https:' ? https : http;
|
|
22
|
+
const reqHeaders = Object.assign({}, headers);
|
|
23
|
+
if (body != null) {
|
|
24
|
+
if (reqHeaders['Content-Length'] == null) reqHeaders['Content-Length'] = Buffer.byteLength(body);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const req = lib.request({
|
|
28
|
+
hostname: u.hostname,
|
|
29
|
+
port: u.port || (u.protocol === 'https:' ? 443 : 80),
|
|
30
|
+
path: u.pathname + u.search,
|
|
31
|
+
method: method,
|
|
32
|
+
headers: reqHeaders
|
|
33
|
+
}, function (res) {
|
|
34
|
+
const code = res.statusCode;
|
|
35
|
+
if ([301, 302, 303, 307, 308].indexOf(code) !== -1 && res.headers.location && maxRedirects > 0) {
|
|
36
|
+
res.resume();
|
|
37
|
+
const next = new URL(res.headers.location, url).toString();
|
|
38
|
+
return resolve(request(method, next, {
|
|
39
|
+
headers: headers,
|
|
40
|
+
body: opts.body,
|
|
41
|
+
timeout: timeout,
|
|
42
|
+
maxRedirects: maxRedirects - 1
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
const chunks = [];
|
|
46
|
+
res.on('data', function (c) { chunks.push(c); });
|
|
47
|
+
res.on('end', function () {
|
|
48
|
+
resolve({ status: code, body: Buffer.concat(chunks).toString('utf8'), headers: res.headers });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
req.on('error', reject);
|
|
53
|
+
req.setTimeout(timeout, function () { req.destroy(new Error('Request timeout')); });
|
|
54
|
+
if (body != null) req.write(body);
|
|
55
|
+
req.end();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function get(url, opts) { return request('GET', url, opts); }
|
|
60
|
+
function post(url, opts) { return request('POST', url, opts); }
|
|
61
|
+
|
|
62
|
+
module.exports = { request, get, post };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function home() {
|
|
7
|
+
return os.homedir();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function expandHome(p) {
|
|
11
|
+
if (p === '~') return home();
|
|
12
|
+
if (typeof p === 'string' && (p.startsWith('~/') || p.startsWith('~\\'))) {
|
|
13
|
+
return path.join(home(), p.slice(2));
|
|
14
|
+
}
|
|
15
|
+
return p;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = { home, expandHome };
|