@tokenaut/opentoken 1.4.5 → 1.4.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenaut/opentoken",
3
- "version": "1.4.5",
3
+ "version": "1.4.6",
4
4
  "description": "OpenToken 一键接入 AI 模型 · https://docs.opentoken.io/",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const crypto = require('crypto');
7
+
8
+ function getConfigLibraryPath() {
9
+ if (process.platform === 'win32') {
10
+ return path.join(process.env.APPDATA || '', 'Claude', 'configLibrary');
11
+ }
12
+ return path.join(os.homedir(), 'Library', 'Application Support', 'Claude-3p', 'configLibrary');
13
+ }
14
+
15
+ function getMetaPath() {
16
+ return path.join(getConfigLibraryPath(), '_meta.json');
17
+ }
18
+
19
+ function getConfigDir() {
20
+ const dir = getConfigLibraryPath();
21
+ if (!fs.existsSync(dir)) {
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ }
24
+ return dir;
25
+ }
26
+
27
+ function readMeta() {
28
+ try {
29
+ const metaPath = getMetaPath();
30
+ if (!fs.existsSync(metaPath)) {
31
+ return { appliedId: null, entries: [] };
32
+ }
33
+ const raw = fs.readFileSync(metaPath, 'utf-8');
34
+ return JSON.parse(raw);
35
+ } catch (e) {
36
+ return { appliedId: null, entries: [] };
37
+ }
38
+ }
39
+
40
+ function writeMeta(meta) {
41
+ const dir = getConfigDir();
42
+ const metaPath = getMetaPath();
43
+ fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf-8');
44
+ }
45
+
46
+ function findOrCreateConfigEntry() {
47
+ const meta = readMeta();
48
+
49
+ if (meta.appliedId && meta.entries && meta.entries.length > 0) {
50
+ const existingEntry = meta.entries.find(e => e.id === meta.appliedId);
51
+ if (existingEntry) {
52
+ return { configPath: path.join(getConfigDir(), meta.appliedId + '.json'), entryId: meta.appliedId, isNew: false };
53
+ }
54
+ }
55
+
56
+ const newId = crypto.randomUUID();
57
+ const newEntry = { id: newId, name: 'OpenToken' };
58
+
59
+ if (!meta.entries) {
60
+ meta.entries = [];
61
+ }
62
+ meta.entries.push(newEntry);
63
+ meta.appliedId = newId;
64
+
65
+ writeMeta(meta);
66
+
67
+ return { configPath: path.join(getConfigDir(), newId + '.json'), entryId: newId, isNew: true };
68
+ }
69
+
70
+ function readDesktopConfig() {
71
+ const { configPath } = findOrCreateConfigEntry();
72
+
73
+ try {
74
+ if (!fs.existsSync(configPath)) {
75
+ return getDefaultConfig();
76
+ }
77
+ const raw = fs.readFileSync(configPath, 'utf-8');
78
+ const parsed = JSON.parse(raw);
79
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
80
+ return getDefaultConfig();
81
+ }
82
+ return parsed;
83
+ } catch (e) {
84
+ return getDefaultConfig();
85
+ }
86
+ }
87
+
88
+ function writeDesktopConfig(config) {
89
+ const { configPath, isNew } = findOrCreateConfigEntry();
90
+ const dir = path.dirname(configPath);
91
+ if (!fs.existsSync(dir)) {
92
+ fs.mkdirSync(dir, { recursive: true });
93
+ }
94
+
95
+ const mergedConfig = { ...getDefaultConfig(), ...config };
96
+ fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2), 'utf-8');
97
+
98
+ if (isNew) {
99
+ console.log(' (已创建新的桌面端配置)');
100
+ }
101
+ }
102
+
103
+ function getDefaultConfig() {
104
+ return {
105
+ coworkEgressAllowedHosts: ['*'],
106
+ disableDeploymentModeChooser: true,
107
+ inferenceProvider: 'gateway',
108
+ inferenceGatewayAuthScheme: 'bearer',
109
+ inferenceGatewayBaseUrl: '',
110
+ inferenceGatewayApiKey: '',
111
+ };
112
+ }
113
+
114
+ function getCurrentConfig() {
115
+ const config = readDesktopConfig();
116
+ return {
117
+ apiKey: config.inferenceGatewayApiKey || '',
118
+ baseUrl: config.inferenceGatewayBaseUrl || '',
119
+ };
120
+ }
121
+
122
+ function applyConfig({ apiKey, baseUrl }) {
123
+ const config = readDesktopConfig();
124
+
125
+ if (apiKey !== undefined) {
126
+ config.inferenceGatewayApiKey = apiKey;
127
+ }
128
+
129
+ if (baseUrl !== undefined) {
130
+ config.inferenceGatewayBaseUrl = baseUrl;
131
+ }
132
+
133
+ config.inferenceProvider = 'gateway';
134
+ config.inferenceGatewayAuthScheme = 'bearer';
135
+
136
+ writeDesktopConfig(config);
137
+ }
138
+
139
+ module.exports = {
140
+ getCurrentConfig,
141
+ applyConfig,
142
+ };
package/src/cli.js CHANGED
@@ -5,6 +5,7 @@ const inquirer = require('inquirer');
5
5
  const { printLogo } = require('./logo');
6
6
  const { clearScreen } = require('./screen');
7
7
  const claude = require('./tools/claude');
8
+ const claudeDesktop = require('./tools/claude_desktop');
8
9
  const atomcode = require('./tools/atomcode');
9
10
  const hermes = require('./tools/hermes');
10
11
  const openclaw = require('./tools/openclaw');
@@ -36,6 +37,10 @@ function isCodexCommand(tool) {
36
37
  return tool === 'codex' || tool === 'cx';
37
38
  }
38
39
 
40
+ function isClaudeDesktopCommand(tool) {
41
+ return tool === 'claude_desktop' || tool === 'cd';
42
+ }
43
+
39
44
  function printVersion() {
40
45
  const pkg = require('../package.json');
41
46
  console.log(`${pkg.name} ${pkg.version}`);
@@ -86,20 +91,28 @@ function printHelp() {
86
91
  console.log(' opentoken cx --model <model> 设置默认模型');
87
92
  console.log(' opentoken cx -k <k> -u <u> -m <m> 一键设置 URL 与模型');
88
93
  console.log('');
94
+ console.log(' opentoken claude_desktop 进入 Claude Desktop 配置(configLibrary/*.json)');
95
+ console.log(' opentoken cd 同上(缩写)');
96
+ console.log(' opentoken cd --key <apikey> 设置 API Key(inferenceGatewayApiKey)');
97
+ console.log(' opentoken cd --url <baseurl> 设置 Base URL(inferenceGatewayBaseUrl)');
98
+ console.log(' opentoken cd -k <k> -u <u> 一键设置 Key 与 URL');
99
+ console.log('');
89
100
  console.log(chalk.bold('快捷别名(--key / -k,--url / -u,--model / -m)'));
90
101
  console.log('');
91
102
  console.log(chalk.bold('示例:'));
92
103
  console.log(' opentoken cc -k sk-xxx');
93
104
  console.log(` opentoken cc -k sk-xxx -u ${DEFAULT_BASE_URL} -m claude-opus-4-5`);
105
+ console.log(` opentoken cd -k sk-xxx -u ${DEFAULT_BASE_URL}`);
94
106
  console.log('');
95
107
  console.log(chalk.bold('主菜单:'));
96
108
  console.log(' 1. 配置 Claude Code');
97
- console.log(' 2. 配置 Atomcode');
98
- console.log(' 3. 配置 OpenClaw');
99
- console.log(' 4. 配置 Hermes');
100
- console.log(' 5. 配置 OpenCode');
101
- console.log(' 6. 配置 Codex');
102
- console.log(' 7. 退出');
109
+ console.log(' 2. 配置 Claude Desktop');
110
+ console.log(' 3. 配置 Atomcode');
111
+ console.log(' 4. 配置 OpenClaw');
112
+ console.log(' 5. 配置 Hermes');
113
+ console.log(' 6. 配置 OpenCode');
114
+ console.log(' 7. 配置 Codex');
115
+ console.log(' 8. 退出');
103
116
  console.log('');
104
117
  }
105
118
 
@@ -154,14 +167,15 @@ async function mainMenu() {
154
167
  message: '请选择:',
155
168
  choices: [
156
169
  { name: '1. 配置 Claude Code', value: 'claude' },
157
- { name: '2. 配置 Atomcode', value: 'atomcode' },
158
- { name: '3. 配置 OpenClaw', value: 'openclaw' },
159
- { name: '4. 配置 Hermes', value: 'hermes' },
160
- { name: '5. 配置 OpenCode', value: 'opencode' },
161
- { name: '6. 配置 Codex', value: 'codex' },
162
- { name: '7. 退出', value: 'exit' },
170
+ { name: '2. 配置 Claude Desktop', value: 'claudeDesktop' },
171
+ { name: '3. 配置 Atomcode', value: 'atomcode' },
172
+ { name: '4. 配置 OpenClaw', value: 'openclaw' },
173
+ { name: '5. 配置 Hermes', value: 'hermes' },
174
+ { name: '6. 配置 OpenCode', value: 'opencode' },
175
+ { name: '7. 配置 Codex', value: 'codex' },
176
+ { name: '8. 退出', value: 'exit' },
163
177
  ],
164
- pageSize: 8,
178
+ pageSize: 9,
165
179
  },
166
180
  ]);
167
181
 
@@ -175,6 +189,10 @@ async function mainMenu() {
175
189
  await claude.run();
176
190
  }
177
191
 
192
+ if (action === 'claudeDesktop') {
193
+ await claudeDesktop.run();
194
+ }
195
+
178
196
  if (action === 'atomcode') {
179
197
  await atomcode.run();
180
198
  }
@@ -252,6 +270,13 @@ async function run() {
252
270
  return;
253
271
  }
254
272
 
273
+ if (isClaudeDesktopCommand(args.tool) && (args.key || args.url)) {
274
+ clearScreen();
275
+ printLogo();
276
+ claudeDesktop.quickSet({ key: args.key, url: args.url });
277
+ return;
278
+ }
279
+
255
280
  if (isCcCommand(args.tool)) {
256
281
  await claude.run();
257
282
  return;
@@ -282,6 +307,11 @@ async function run() {
282
307
  return;
283
308
  }
284
309
 
310
+ if (isClaudeDesktopCommand(args.tool)) {
311
+ await claudeDesktop.run();
312
+ return;
313
+ }
314
+
285
315
  await mainMenu();
286
316
  }
287
317
 
package/src/logo.js CHANGED
@@ -4,12 +4,12 @@ const chalk = require('chalk');
4
4
  const stringWidth = require('string-width');
5
5
 
6
6
  const LOGO_LINES = [
7
- ' ████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗ ',
8
- ' ██║ ██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║ ',
9
- ' ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗██║ ',
10
- ' ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚████║ ',
11
- ' ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚███║ ',
12
- ' ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚══╝ ',
7
+ ' ██████╗ ██████╗ ███████╗███╗ ██╗████████╗ ██████╗ ██╗ ██╗███████╗███╗ ██╗',
8
+ '██╔═══██╗██╔══██╗██╔════╝████╗ ██║╚══██╔══╝██╔═══██╗██║ ██╔╝██╔════╝████╗ ██║',
9
+ '██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║ ██║█████╔╝ █████╗ ██╔██╗ ██║',
10
+ '██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║ ██║██╔═██╗ ██╔══╝ ██║╚██╗██║',
11
+ '╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ╚██████╔╝██║ ██╗███████╗██║ ╚████║',
12
+ ' ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝',
13
13
  ];
14
14
 
15
15
  function padToDisplayWidth(str, targetCols) {
@@ -20,21 +20,6 @@ function padToDisplayWidth(str, targetCols) {
20
20
  return s;
21
21
  }
22
22
 
23
- function splitAtDisplayHalf(str, totalDisplayWidth) {
24
- const half = totalDisplayWidth / 2;
25
- let acc = 0;
26
- let i = 0;
27
- while (i < str.length) {
28
- const cp = str.codePointAt(i);
29
- const ch = String.fromCodePoint(cp);
30
- const w = stringWidth(ch);
31
- if (acc + w > half) break;
32
- acc += w;
33
- i += ch.length;
34
- }
35
- return { left: str.slice(0, i), right: str.slice(i) };
36
- }
37
-
38
23
  function printLogo() {
39
24
  const logoDisplayWidths = LOGO_LINES.map((l) => stringWidth(l));
40
25
  const innerWidth = Math.max(...logoDisplayWidths);
@@ -46,11 +31,9 @@ function printLogo() {
46
31
 
47
32
  for (const line of LOGO_LINES) {
48
33
  const padded = padToDisplayWidth(line, innerWidth);
49
- const { left, right } = splitAtDisplayHalf(padded, innerWidth);
50
34
  console.log(
51
35
  chalk.cyan('║') +
52
- chalk.bold(chalk.hex('#c084fc')(left)) +
53
- chalk.bold(chalk.white(right)) +
36
+ chalk.bold(chalk.white(padded)) +
54
37
  chalk.cyan('║')
55
38
  );
56
39
  }
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+
3
+ const chalk = require('chalk');
4
+ const inquirer = require('inquirer');
5
+ const { API_NODES, DEFAULT_BASE_URL } = require('../models');
6
+ const { getCurrentConfig, applyConfig } = require('../claude-desktop-config');
7
+ const { printLogo } = require('../logo');
8
+ const { clearScreen } = require('../screen');
9
+
10
+ let pendingConfig = { apiKey: null, baseUrl: null };
11
+
12
+ function resetPending() {
13
+ const disk = getCurrentConfig();
14
+ pendingConfig = {
15
+ apiKey: null,
16
+ baseUrl: disk.baseUrl ? null : DEFAULT_BASE_URL,
17
+ };
18
+ }
19
+
20
+ function maskApiKey(key) {
21
+ if (!key || key.length < 8) return key || '';
22
+ return key.slice(0, 6) + '****' + key.slice(-4);
23
+ }
24
+
25
+ async function handleConfigApiKey() {
26
+ const { apiKey } = await inquirer.prompt([
27
+ {
28
+ type: 'password',
29
+ name: 'apiKey',
30
+ prefix: '',
31
+ message: '请输入 API Key:',
32
+ mask: '*',
33
+ },
34
+ ]);
35
+
36
+ if (apiKey && apiKey.trim()) {
37
+ pendingConfig.apiKey = apiKey.trim();
38
+ console.log(chalk.green(' ✓ API Key 已更新'));
39
+ } else {
40
+ console.log(chalk.gray(' (留空,保持不变)'));
41
+ }
42
+ console.log('');
43
+ }
44
+
45
+ async function handleConfigBaseUrl() {
46
+ const { baseUrl: existing } = getCurrentConfig();
47
+ const current = pendingConfig.baseUrl !== null ? pendingConfig.baseUrl : existing;
48
+
49
+ const nodeChoices = API_NODES.map((n) => ({
50
+ ...n,
51
+ name: current === n.value ? chalk.green('► ' + n.name) : ' ' + n.name,
52
+ }));
53
+
54
+ const { selected } = await inquirer.prompt([
55
+ {
56
+ type: 'list',
57
+ name: 'selected',
58
+ prefix: '',
59
+ message: '请选择 Base URL:',
60
+ choices: nodeChoices,
61
+ default: current || API_NODES[0].value,
62
+ },
63
+ ]);
64
+
65
+ if (selected === '__custom__') {
66
+ const { customUrl } = await inquirer.prompt([
67
+ {
68
+ type: 'input',
69
+ name: 'customUrl',
70
+ prefix: '',
71
+ message: '请输入自定义 Base URL:',
72
+ default: current && current !== '__custom__' ? current : DEFAULT_BASE_URL,
73
+ validate: (v) => v.trim().startsWith('http') || '请输入合法的 URL(以 http 开头)',
74
+ },
75
+ ]);
76
+ pendingConfig.baseUrl = customUrl.trim();
77
+ console.log(chalk.green(' ✓ Base URL 已更新:' + pendingConfig.baseUrl));
78
+ } else {
79
+ pendingConfig.baseUrl = selected;
80
+ console.log(chalk.green(' ✓ Base URL 已更新:' + selected));
81
+ }
82
+ console.log('');
83
+ }
84
+
85
+ function getEffectiveConfig() {
86
+ const disk = getCurrentConfig();
87
+ return {
88
+ apiKey: pendingConfig.apiKey !== null ? pendingConfig.apiKey : disk.apiKey,
89
+ baseUrl: pendingConfig.baseUrl !== null ? pendingConfig.baseUrl : disk.baseUrl,
90
+ };
91
+ }
92
+
93
+ function grayParen(text) {
94
+ return chalk.gray('(' + text + ')');
95
+ }
96
+
97
+ function buildMenuChoices() {
98
+ const ev = getEffectiveConfig();
99
+ const keyShow = ev.apiKey ? maskApiKey(ev.apiKey) : '未配置';
100
+ const urlShow = ev.baseUrl ? ev.baseUrl : '未配置';
101
+
102
+ return [
103
+ { name: '1. 配置 Base URL' + grayParen(urlShow), value: 'apiNode' },
104
+ { name: '2. 配置 API Key' + grayParen(keyShow), value: 'apiKey' },
105
+ new inquirer.Separator(),
106
+ { name: '3. 应用配置并保存', value: 'apply' },
107
+ new inquirer.Separator(),
108
+ { name: '← 返回上级菜单', value: 'back' },
109
+ ];
110
+ }
111
+
112
+ function doApplyConfig() {
113
+ const toWrite = {};
114
+ if (pendingConfig.apiKey !== null) toWrite.apiKey = pendingConfig.apiKey;
115
+ if (pendingConfig.baseUrl !== null) toWrite.baseUrl = pendingConfig.baseUrl;
116
+
117
+ console.log('');
118
+ if (Object.keys(toWrite).length > 0) {
119
+ applyConfig(toWrite);
120
+ console.log(chalk.green(' ✓ 配置已成功写入'));
121
+ } else {
122
+ console.log(chalk.gray(' (无 Key / URL 变更)'));
123
+ }
124
+ console.log('');
125
+ }
126
+
127
+ function paintScreen() {
128
+ clearScreen();
129
+ printLogo();
130
+ }
131
+
132
+ async function run() {
133
+ resetPending();
134
+
135
+ while (true) {
136
+ paintScreen();
137
+
138
+ const { action } = await inquirer.prompt([
139
+ {
140
+ type: 'list',
141
+ name: 'action',
142
+ prefix: '',
143
+ message: '请选择操作:',
144
+ choices: buildMenuChoices(),
145
+ pageSize: 10,
146
+ },
147
+ ]);
148
+
149
+ switch (action) {
150
+ case 'apiNode':
151
+ paintScreen();
152
+ await handleConfigBaseUrl();
153
+ break;
154
+ case 'apiKey':
155
+ paintScreen();
156
+ await handleConfigApiKey();
157
+ break;
158
+ case 'apply':
159
+ doApplyConfig();
160
+ return;
161
+ case 'back':
162
+ return;
163
+ }
164
+ }
165
+ }
166
+
167
+ function quickSet({ key, url }) {
168
+ const toWrite = {};
169
+ if (key) toWrite.apiKey = key;
170
+ if (url) toWrite.baseUrl = url;
171
+
172
+ if (Object.keys(toWrite).length === 0) return false;
173
+
174
+ applyConfig(toWrite);
175
+
176
+ console.log('');
177
+ console.log(chalk.bold(chalk.cyan('Claude Desktop 配置已更新:')));
178
+ if (key) console.log(chalk.green(' ✓ API Key : ') + maskApiKey(key));
179
+ if (url) console.log(chalk.green(' ✓ Base URL : ') + url);
180
+ console.log('');
181
+
182
+ return true;
183
+ }
184
+
185
+ module.exports = { run, quickSet };