smewai 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/cli.js ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+
3
+ const prompts = require('prompts');
4
+ const { version: packageVersion } = require('./package.json');
5
+ const { logInfo, logWarn, logError, hasCommand } = require('./lib/utils');
6
+ const { configureCodex } = require('./lib/config');
7
+ const { checkForUpdates } = require('./lib/updater');
8
+
9
+ const cliVersion = packageVersion;
10
+
11
+ function extractApiKeyArg() {
12
+ for (const arg of process.argv.slice(2)) {
13
+ if (arg.startsWith('sk-') && arg.length >= 20) return arg;
14
+ }
15
+ return null;
16
+ }
17
+
18
+ async function getApiKeyInput() {
19
+ console.log('');
20
+
21
+ let response;
22
+ try {
23
+ response = await prompts({
24
+ type: 'password',
25
+ name: 'input',
26
+ message: 'Smew API Key',
27
+ validate: (input) => {
28
+ const trimmed = input.trim();
29
+ if (!trimmed) return '请输入 API Key';
30
+ return true;
31
+ },
32
+ });
33
+ } catch {
34
+ logInfo('已取消');
35
+ process.exit(0);
36
+ }
37
+
38
+ if (!response.input) {
39
+ logInfo('已取消');
40
+ process.exit(0);
41
+ }
42
+ return response.input.trim();
43
+ }
44
+
45
+ function checkCodexInstalled() {
46
+ if (!hasCommand('codex')) {
47
+ logWarn('Codex CLI 未安装,请先运行以下命令安装:');
48
+ console.log('');
49
+ console.log(' npm install -g @openai/codex@latest');
50
+ console.log('');
51
+ }
52
+ }
53
+
54
+ async function run(apiKey) {
55
+ console.log('');
56
+ await configureCodex(apiKey);
57
+
58
+ console.log('');
59
+ logInfo('配置完成!');
60
+ logInfo('model 字段可替换为 Smew 支持的任意模型名称');
61
+ console.log('');
62
+
63
+ checkCodexInstalled();
64
+
65
+ logWarn('如果 Codex 正在运行,请先退出再重新启动以加载新配置');
66
+ }
67
+
68
+ async function main() {
69
+ try {
70
+ if (process.argv.includes('--version') || process.argv.includes('-v')) {
71
+ console.log(cliVersion);
72
+ return;
73
+ }
74
+
75
+ await checkForUpdates(cliVersion);
76
+
77
+ logInfo(`Smew Codex 一键配置 v${cliVersion}`);
78
+
79
+ const quickApiKey = extractApiKeyArg();
80
+ if (quickApiKey) {
81
+ await run(quickApiKey);
82
+ return;
83
+ }
84
+
85
+ const apiKey = await getApiKeyInput();
86
+ await run(apiKey);
87
+ } catch (error) {
88
+ logError(`配置失败: ${error.message}`);
89
+ process.exit(1);
90
+ }
91
+ }
92
+
93
+ main();
package/lib/config.js ADDED
@@ -0,0 +1,117 @@
1
+ const fs = require('fs').promises;
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const TOML = require('@iarna/toml');
5
+ const { logInfo, logWarn } = require('./utils');
6
+ const {
7
+ SMEW_RELAY_BASE_URL,
8
+ CODEX_DEFAULT_MODEL,
9
+ CODEX_DEFAULT_REASONING_EFFORT,
10
+ CODEX_MODEL_PROVIDER,
11
+ } = require('./constants');
12
+
13
+ const CODEX_DIR = path.join(os.homedir(), '.codex');
14
+ const CONFIG_PATH = path.join(CODEX_DIR, 'config.toml');
15
+ const AUTH_PATH = path.join(CODEX_DIR, 'auth.json');
16
+
17
+ const CONFIG_HEADER = `# Managed by smewai CLI — core fields are force-updated on each run.
18
+ # Other user customizations (projects, approval_mode, etc.) are preserved.
19
+ `;
20
+
21
+ async function ensureDir(dirPath) {
22
+ await fs.mkdir(dirPath, { recursive: true, mode: 0o700 });
23
+ }
24
+
25
+ async function safeChmod(filePath, mode) {
26
+ try {
27
+ await fs.chmod(filePath, mode);
28
+ } catch {
29
+ // Ignore on unsupported platforms.
30
+ }
31
+ }
32
+
33
+ async function readExistingToml(filePath) {
34
+ try {
35
+ const raw = await fs.readFile(filePath, 'utf8');
36
+ return TOML.parse(raw);
37
+ } catch (error) {
38
+ if (error.code === 'ENOENT') return {};
39
+ logWarn(`无法解析 ${path.basename(filePath)},将重新生成`);
40
+ return {};
41
+ }
42
+ }
43
+
44
+ async function readExistingJson(filePath) {
45
+ try {
46
+ const raw = await fs.readFile(filePath, 'utf8');
47
+ return JSON.parse(raw);
48
+ } catch (error) {
49
+ if (error.code === 'ENOENT') return {};
50
+ if (error instanceof SyntaxError) {
51
+ logWarn(`${path.basename(filePath)} 格式损坏,将重新生成`);
52
+ return {};
53
+ }
54
+ throw error;
55
+ }
56
+ }
57
+
58
+ async function writeSecure(filePath, content) {
59
+ await safeChmod(filePath, 0o600);
60
+ await fs.writeFile(filePath, content, { encoding: 'utf8', mode: 0o600 });
61
+ await safeChmod(filePath, 0o600);
62
+ }
63
+
64
+ async function configureCodex(apiKey) {
65
+ if (!apiKey || !apiKey.trim()) {
66
+ throw new Error('API Key 不能为空');
67
+ }
68
+
69
+ await ensureDir(CODEX_DIR);
70
+
71
+ const existing = await readExistingToml(CONFIG_PATH);
72
+ const merged = {
73
+ ...existing,
74
+ model_provider: CODEX_MODEL_PROVIDER,
75
+ model: CODEX_DEFAULT_MODEL,
76
+ model_reasoning_effort: CODEX_DEFAULT_REASONING_EFFORT,
77
+ disable_response_storage: true,
78
+ model_providers: {
79
+ ...(existing.model_providers || {}),
80
+ [CODEX_MODEL_PROVIDER]: {
81
+ name: CODEX_MODEL_PROVIDER,
82
+ base_url: SMEW_RELAY_BASE_URL,
83
+ wire_api: 'responses',
84
+ requires_openai_auth: true,
85
+ },
86
+ },
87
+ };
88
+
89
+ logInfo(`写入 ${CONFIG_PATH}`);
90
+ await writeSecure(CONFIG_PATH, CONFIG_HEADER + TOML.stringify(merged));
91
+
92
+ const existingAuth = await readExistingJson(AUTH_PATH);
93
+ const authJson = { ...existingAuth, OPENAI_API_KEY: apiKey };
94
+
95
+ logInfo(`写入 ${AUTH_PATH}`);
96
+ await writeSecure(AUTH_PATH, `${JSON.stringify(authJson, null, 2)}\n`);
97
+
98
+ await safeChmod(CODEX_DIR, 0o700);
99
+ await verifyConfig();
100
+ }
101
+
102
+ async function verifyConfig() {
103
+ const content = await fs.readFile(CONFIG_PATH, 'utf8');
104
+ const parsed = TOML.parse(content);
105
+
106
+ const provider = parsed.model_providers && parsed.model_providers[CODEX_MODEL_PROVIDER];
107
+ if (!provider) {
108
+ throw new Error(`配置验证失败: 未找到 model_providers.${CODEX_MODEL_PROVIDER}`);
109
+ }
110
+ if (provider.base_url !== SMEW_RELAY_BASE_URL) {
111
+ throw new Error(`base_url 不匹配: ${provider.base_url}`);
112
+ }
113
+
114
+ logInfo('配置验证通过 ✓');
115
+ }
116
+
117
+ module.exports = { configureCodex };
@@ -0,0 +1,11 @@
1
+ const SMEW_RELAY_BASE_URL = 'https://smew.ai/v1';
2
+ const CODEX_MODEL_PROVIDER = 'Smew';
3
+ const CODEX_DEFAULT_MODEL = 'gpt-5.3-codex';
4
+ const CODEX_DEFAULT_REASONING_EFFORT = 'medium';
5
+
6
+ module.exports = {
7
+ SMEW_RELAY_BASE_URL,
8
+ CODEX_MODEL_PROVIDER,
9
+ CODEX_DEFAULT_MODEL,
10
+ CODEX_DEFAULT_REASONING_EFFORT,
11
+ };
package/lib/updater.js ADDED
@@ -0,0 +1,70 @@
1
+ const { execFile } = require('child_process');
2
+ const chalk = require('chalk');
3
+
4
+ const PACKAGE_NAME = 'smewai';
5
+
6
+ function parseSemver(version) {
7
+ const match = String(version || '').match(/(\d+)\.(\d+)\.(\d+)(?:-(.+))?/);
8
+ if (!match) return null;
9
+ return {
10
+ major: Number(match[1]),
11
+ minor: Number(match[2]),
12
+ patch: Number(match[3]),
13
+ prerelease: match[4] || null,
14
+ };
15
+ }
16
+
17
+ function isNewer(latest, current) {
18
+ const a = parseSemver(latest);
19
+ const b = parseSemver(current);
20
+ if (!a || !b) return false;
21
+ if (a.major !== b.major) return a.major > b.major;
22
+ if (a.minor !== b.minor) return a.minor > b.minor;
23
+ if (a.patch !== b.patch) return a.patch > b.patch;
24
+ if (b.prerelease && !a.prerelease) return true;
25
+ return false;
26
+ }
27
+
28
+ function npmView() {
29
+ const isWin = process.platform === 'win32';
30
+ const cmd = isWin ? 'npm.cmd' : 'npm';
31
+
32
+ return new Promise((resolve, reject) => {
33
+ const timer = setTimeout(() => reject(new Error('timeout')), 5_000);
34
+ execFile(cmd, ['view', PACKAGE_NAME, 'version'], (err, stdout) => {
35
+ clearTimeout(timer);
36
+ if (err) return reject(err);
37
+ resolve(stdout.trim());
38
+ });
39
+ });
40
+ }
41
+
42
+ async function checkForUpdates(currentVersion) {
43
+ try {
44
+ const latest = await npmView();
45
+ if (latest && isNewer(latest, currentVersion)) {
46
+ console.log('');
47
+ console.log(
48
+ chalk.yellow('╭─────────────────────────────────────────────╮')
49
+ );
50
+ console.log(
51
+ chalk.yellow('│'),
52
+ ` 新版本可用: ${chalk.gray(currentVersion)} → ${chalk.green(latest)}`,
53
+ chalk.yellow(' │')
54
+ );
55
+ console.log(
56
+ chalk.yellow('│'),
57
+ ` 运行 ${chalk.cyan('npx smewai@latest')} 获取最新版本`,
58
+ chalk.yellow(' │')
59
+ );
60
+ console.log(
61
+ chalk.yellow('╰─────────────────────────────────────────────╯')
62
+ );
63
+ console.log('');
64
+ }
65
+ } catch {
66
+ // Network error or timeout — skip silently.
67
+ }
68
+ }
69
+
70
+ module.exports = { checkForUpdates };
package/lib/utils.js ADDED
@@ -0,0 +1,31 @@
1
+ const chalk = require('chalk');
2
+ const { execFileSync } = require('child_process');
3
+
4
+ function logInfo(message) {
5
+ console.log(chalk.green('[INFO]'), message);
6
+ }
7
+
8
+ function logWarn(message) {
9
+ console.log(chalk.yellow('[WARN]'), message);
10
+ }
11
+
12
+ function logError(message) {
13
+ console.error(chalk.red('[ERROR]'), message);
14
+ }
15
+
16
+ function hasCommand(command) {
17
+ try {
18
+ const cmd = process.platform === 'win32' ? 'where' : 'which';
19
+ execFileSync(cmd, [command], { stdio: 'pipe' });
20
+ return true;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+
26
+ module.exports = {
27
+ logInfo,
28
+ logWarn,
29
+ logError,
30
+ hasCommand,
31
+ };
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "smewai",
3
+ "version": "0.1.0",
4
+ "description": "Smew Codex CLI 一键配置工具",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "smewai": "cli.js"
8
+ },
9
+ "files": [
10
+ "cli.js",
11
+ "lib/"
12
+ ],
13
+ "keywords": [
14
+ "smew",
15
+ "codex",
16
+ "cli",
17
+ "openai"
18
+ ],
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=18.0.0"
22
+ },
23
+ "dependencies": {
24
+ "@iarna/toml": "^2.2.5",
25
+ "chalk": "^4.1.2",
26
+ "prompts": "^2.4.2"
27
+ }
28
+ }