@dahawa/hawa-cli-analysis 1.0.4

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.
@@ -0,0 +1,42 @@
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
3
+ import LogManager from '../logger-manager.js';
4
+
5
+ const logger = LogManager.getSystemLogger();
6
+
7
+ async function testSupabaseMCP() {
8
+ try {
9
+ logger.debug('Testing Supabase MCP connection...');
10
+
11
+ const client = new Client({ name: 'supabase-test', version: '1.0.0' });
12
+ const transport = new StreamableHTTPClientTransport(new URL('https://mcp.supabase.com/mcp'), {
13
+ requestInit: {
14
+ headers: {
15
+ Authorization: `Bearer sbp_75d326e31e6cc3d152fc1b4132755cf79e21f434`
16
+ }
17
+ }
18
+ });
19
+
20
+ logger.debug('Connecting to Supabase MCP...');
21
+ await client.connect(transport);
22
+ logger.debug('✅ Connected successfully');
23
+
24
+ logger.debug('Listing tools...');
25
+ const tools = await client.listTools();
26
+ logger.debug('Raw tools response:', JSON.stringify(tools, null, 2));
27
+
28
+ if (tools && tools.tools) {
29
+ logger.debug(`Found ${tools.tools.length} tools:`);
30
+ tools.tools.forEach((tool, index) => {
31
+ logger.debug(`${index + 1}. ${tool.name}: ${tool.description || 'No description'}`);
32
+ });
33
+ } else {
34
+ logger.debug('No tools found or unexpected response format');
35
+ }
36
+
37
+ } catch (error) {
38
+ logger.error('Error testing Supabase MCP:', error);
39
+ }
40
+ }
41
+
42
+ testSupabaseMCP();
package/uclaude.js ADDED
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env node
2
+
3
+ global.CLI_TYPE = "claude"
4
+ import {initConfig,loadConfig} from "./config.js"
5
+ import readline from 'readline';
6
+ import { spawn } from 'child_process';
7
+ import {getClaudePath} from './untils.js';
8
+ import inquirer from 'inquirer';
9
+ import path from "path";
10
+ import { fileURLToPath, pathToFileURL } from "url";
11
+ import LogManager from './logger-manager.js';
12
+ const logger = LogManager.getSystemLogger();
13
+ import portManager from './port-manager.js';
14
+ import { join } from 'path';
15
+
16
+
17
+ const startServer = async (openai, base_url, port = null) => {
18
+ let dir = path.dirname(fileURLToPath(import.meta.url));
19
+ const child = spawn('node ' + path.join(dir, "claude" , openai?"claude-openai-proxy.js":'claude-proxy.js'), [],{
20
+ stdio: ['ignore', 'pipe', 'pipe'],
21
+ shell: true,
22
+ env:{
23
+ ...process.env,
24
+ BASE_URL: base_url,
25
+ PROXY_PORT: port
26
+ }
27
+ });
28
+
29
+ // 监听标准输出
30
+ child.stdout.on('data', (data) => {
31
+ //console.log(`子进程 stdout: ${data}`);
32
+ });
33
+
34
+ // 监听错误输出
35
+ child.stderr.on('data', (data) => {
36
+ logger.error(`子进程 stderr: ${data}`);
37
+ });
38
+
39
+ child.on('close', (code) => {
40
+ logger.debug(`codex 退出,退出码: ${code}`);
41
+ });
42
+ };
43
+
44
+
45
+ /**
46
+ * 启动 calude code
47
+ */
48
+ function start(){
49
+ initConfig();
50
+ let allConfig = loadConfig();
51
+ let choices = [];
52
+ Object.entries(allConfig).forEach(([key, value], index) => {
53
+ if (value.enable === true) {
54
+ choices.push({ name: `${index}. ${key}`, value: key });
55
+ }
56
+ });
57
+
58
+ // 检查是否有启用的模型
59
+ if (choices.length === 0) {
60
+ console.error("错误:没有启用的模型配置!");
61
+ console.log("请检查配置文件,确保至少有一个模型的 enable 设置为 true。");
62
+ logger.error("没有启用的模型配置,程序退出");
63
+ process.exit(1);
64
+ }
65
+
66
+ (async () => {
67
+ const answers = await inquirer.prompt([
68
+ {
69
+ type: "list", // 单选模式
70
+ name: "choice", // 返回结果的 key
71
+ message: "请选择一个模型:",
72
+ choices: choices
73
+ }
74
+ ]);
75
+
76
+ var config = allConfig[answers.choice];
77
+ let env = config.env;
78
+ // 添加 ANTHROPIC_ 前缀到环境变量
79
+ let anthropicEnv = {};
80
+ Object.keys(env).forEach(key => {
81
+ if (['BASE_URL', 'AUTH_TOKEN', 'MODEL', 'SMALL_FAST_MODEL'].includes(key)) {
82
+ anthropicEnv[`ANTHROPIC_${key}`] = env[key];
83
+ } else {
84
+ anthropicEnv[key] = env[key];
85
+ }
86
+ });
87
+
88
+ anthropicEnv[`ANTHROPIC_API_KEY`] = env["ANTHROPIC_AUTH_TOKEN"];
89
+ let BASE_URL = anthropicEnv[`ANTHROPIC_BASE_URL`];
90
+
91
+ // claudecode 环境变量是可以通过 env 传递到 mcpserver
92
+ let claudePath = config?.CLAUDE_PATH || process.env.CLAUDE_PATH || getClaudePath();
93
+ let dir = path.dirname(fileURLToPath(import.meta.url));
94
+
95
+ // 动态分配端口
96
+ let proxyPort;
97
+ if(answers.choice=="openrouter"){
98
+ //启动 claude-openai-proxy.js 代理
99
+ proxyPort = await portManager.getAvailablePort();
100
+ if (!proxyPort) {
101
+ console.error("错误:无法获取可用端口!");
102
+ logger.error("无法获取可用端口,程序退出");
103
+ process.exit(1);
104
+ }
105
+ startServer(true, BASE_URL, proxyPort);
106
+ }else{
107
+ //启动 claude-proxy.js 代理
108
+ proxyPort = await portManager.getAvailablePort();
109
+ if (!proxyPort) {
110
+ console.error("错误:无法获取可用端口!");
111
+ logger.error("无法获取可用端口,程序退出");
112
+ process.exit(1);
113
+ }
114
+ startServer(false, BASE_URL, proxyPort);
115
+ }
116
+
117
+ // 设置代理地址
118
+ anthropicEnv[`ANTHROPIC_BASE_URL`] = `http://127.0.0.1:${proxyPort}`;
119
+
120
+ claudePath = "node " + claudePath;
121
+
122
+ logger.debug(`启动 Claude 进程: ${claudePath}`);
123
+
124
+ const child = spawn(claudePath,[],{
125
+ env:{
126
+ ...anthropicEnv,
127
+ PIPE_PATH_PRE: process.pid
128
+ },
129
+ stdio: 'inherit', // 继承父进程 stdio,方便交互,
130
+ shell: true
131
+ }
132
+ );
133
+
134
+ child.on("error", (error) => {
135
+ console.error("Failed to start claude command:", error.message);
136
+ logger.debug(
137
+ "Make sure Claude Code is installed: npm install -g @anthropic-ai/claude-code"
138
+ );
139
+ process.exit(1);
140
+ });
141
+
142
+ child.on("close", (code) => {
143
+ process.exit(code || 0);
144
+ });
145
+
146
+
147
+ })();
148
+
149
+
150
+
151
+ }
152
+ // 这里需要判断是否启动完成,不然后面 mcp 会连接失败
153
+ /**
154
+ function startMCPServerProxy(){
155
+ let dir = path.dirname(fileURLToPath(import.meta.url));
156
+ // 启动 MCP 代理服务
157
+ const child = spawn("node " + (path.join(dir, "mcp" ,'claude-mcpproxy-launcher.js')), [], {
158
+ stdio: "pipe" ,
159
+ shell: true,
160
+ env: {
161
+ PIPE_PATH_PRE: process.pid
162
+ }
163
+ });
164
+
165
+ child.stdout.on("data", (data) => {
166
+ console.log("子进程输出:", data.toString());
167
+ });
168
+
169
+ child.on("error", (error) => {
170
+ console.error("Failed to start MCP server proxy:", error.message);
171
+ process.exit(1);
172
+ });
173
+
174
+ child.on("close", (code) => {
175
+ process.exit(code || 0);
176
+ });
177
+ }
178
+ */
179
+
180
+ async function startMCPServerProxy() {
181
+ return new Promise((resolve, reject) => {
182
+ const dir = path.dirname(fileURLToPath(import.meta.url));
183
+ const child = spawn("node " + path.join(dir, "mcp", "claude-mcpproxy-launcher.js"), [], {
184
+ stdio: "pipe",
185
+ shell: true,
186
+ env: {
187
+ PIPE_PATH_PRE: process.pid
188
+ }
189
+ });
190
+
191
+ child.stdout.on("data", (data) => {
192
+ const msg = data.toString().trim();
193
+ //console.log("子进程输出:", msg);
194
+ if (msg.includes("ok_ok")) {
195
+ resolve("MCP server proxy started successfully");
196
+ }
197
+ });
198
+
199
+ child.stderr.on("data", (data) => {
200
+ console.error("子进程错误输出:", data.toString());
201
+ });
202
+
203
+ child.on("error", (error) => {
204
+ reject(new Error("Failed to start MCP server proxy: " + error.message));
205
+ });
206
+
207
+ child.on("close", (code) => {
208
+ if (code !== 0) {
209
+ reject(new Error("MCP server proxy exited with code " + code));
210
+ }
211
+ });
212
+ });
213
+ }
214
+
215
+ async function main(){
216
+ console.log("Starting MCP server proxy...");
217
+ await startMCPServerProxy();
218
+ console.log("MCP server proxy started successfully.");
219
+ start();
220
+ }
221
+ await main();
@@ -0,0 +1,173 @@
1
+ // server.js
2
+ import Fastify from "fastify";
3
+ import {Readable } from 'node:stream';
4
+ import {parseOpenAIResponse,parseOpenAIChatCompletion} from './api-openai.js'
5
+ import LoggerManage from "./logger-manager.js"
6
+ let logger = LoggerManage.getLogger("codex");
7
+
8
+ //是否为 chat 模式调用
9
+ const wire_api = process.env.wire_api;
10
+ //访问的 Base URL 地址
11
+ const base_url = process.env.base_url;
12
+
13
+ logger.system.debug("Base URL:" + base_url);
14
+ logger.system.debug("Wire API:" + wire_api);
15
+
16
+ logger.system.debug(process.env);
17
+
18
+ function toSimple(full , wire_api){
19
+ let log = {
20
+ request:{},
21
+ response:{}
22
+ }
23
+ if(wire_api === "chat"){
24
+ log.request.model = full.request.body.model;
25
+ log.request.messages = full.request.body.messages;
26
+ log.response.choices = full.response.body.choices;
27
+ }else{
28
+ log.request.session_id = full.request.headers.session_id;
29
+ log.request.model = full.request.body.model;
30
+ log.request.instructions = full.request.body.instructions;
31
+ log.request.input = full.request.body.input;
32
+ log.response.output = full.response.body.output;
33
+ }
34
+
35
+ return log;
36
+ }
37
+
38
+ function logAPI(fullLog,wire_api){
39
+ logger.simple.debug(toSimple(fullLog,wire_api));
40
+ logger.full.debug(fullLog);
41
+ }
42
+
43
+ const fastify = Fastify(
44
+ {
45
+ requestTimeout: 0, // never time out request (or set e.g. 10 * 60 * 1000)
46
+ connectionTimeout: 0, // no connection timeout
47
+ keepAliveTimeout: 120000, // 120s
48
+ }
49
+ );
50
+
51
+ // 注册一个 POST 接口
52
+ fastify.all("/*", async (request, reply) => {
53
+ //console.log("处理请求:", request.url);
54
+ return await handel(request, reply, request.url);
55
+ });
56
+
57
+ /**
58
+ * 判断是否为流式响应
59
+ * @returns
60
+ */
61
+ function isStream(response){
62
+ let contentType = response.headers.get('content-type') || '';
63
+ const streamTypes = [
64
+ 'text/event-stream',
65
+ 'chunked'
66
+ ];
67
+ if(streamTypes.some(t => contentType.includes(t))){
68
+ return true;
69
+ }
70
+ contentType = response.headers.get('transfer-encoding') || '';
71
+ if(streamTypes.some(t => contentType.includes(t))){
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ function headersToObject(headers) {
78
+ const obj = {};
79
+ try {
80
+ for (const [k, v] of headers.entries()) obj[k] = v;
81
+ } catch {}
82
+ return obj;
83
+ }
84
+
85
+ function joinUrl(base, ...paths) {
86
+ return [base.replace(/\/+$/, ''), ...paths.map(p => p.replace(/^\/+/, ''))]
87
+ .join('/');
88
+ }
89
+ async function handel(request, reply, endpoint){
90
+ try {
91
+ const body = request.body;
92
+ // 取出原始请求头
93
+ let incomingHeaders = { ...request.headers };
94
+
95
+ // 删除或覆盖一些不适合直接转发的头
96
+ delete incomingHeaders["host"]; // Host 由 fetch 自动设置
97
+ delete incomingHeaders["content-length"]; // 长度 fetch 会重新计算
98
+ delete incomingHeaders["accept"];
99
+
100
+ let url = joinUrl(base_url,endpoint);
101
+ logger.system.debug("向endpoint 发送请求:" + url);
102
+
103
+ let response = await fetch(url, {
104
+ method: "POST",
105
+ //headers: incomingHeaders,
106
+ //使用白名单机制,mac 有问题
107
+ headers:{
108
+ 'authorization': incomingHeaders['authorization'],
109
+ 'content-type': incomingHeaders['content-type']
110
+ },
111
+ body: JSON.stringify(body)
112
+ });
113
+
114
+ let responseToClient = response.clone();
115
+
116
+ //完整的请求日志,保护请求和响应
117
+ let fullLog = {request:{
118
+ headers: incomingHeaders,
119
+ body: body
120
+ },response:{
121
+ status: response.status,
122
+ statusText: response.statusText,
123
+ headers: headersToObject(response.headers)
124
+ }};
125
+
126
+
127
+ // 同时在后台解析日志(不影响直通)
128
+ (async () => {
129
+ if(wire_api == "chat"){
130
+ fullLog.response.body = await parseOpenAIChatCompletion(await response.text());
131
+ }else if(wire_api == "responses"){
132
+ fullLog.response.body = await parseOpenAIResponse(await response.text());
133
+ }
134
+ //其他类型是错误的
135
+ logAPI(fullLog,wire_api);
136
+
137
+ })().catch(err => console.error('日志解析错误:', err));
138
+
139
+ const HOP_BY_HOP = new Set([
140
+ 'connection', 'keep-alive', 'proxy-connection', 'transfer-encoding',
141
+ 'te', 'trailer', 'upgrade', 'proxy-authenticate', 'proxy-authorization', 'expect'
142
+ ]);
143
+
144
+ reply.code(response.status);
145
+ response.headers.forEach((value, name) => {
146
+ const lower = name.toLowerCase();
147
+ if (HOP_BY_HOP.has(lower)) return;
148
+ reply.header(name, value);
149
+ });
150
+
151
+ if (response.body) {
152
+ const nodeStream = Readable.fromWeb(responseToClient.body);
153
+ return reply.send(nodeStream);
154
+ } else {
155
+ return reply.send();
156
+ }
157
+ } catch (err) {
158
+ console.error("处理流式响应异常",err);
159
+ return reply.status(500).send({ error: "请求失败" });
160
+ }
161
+ }
162
+
163
+ // 启动服务
164
+ const startServer = async () => {
165
+ try {
166
+ await fastify.listen({ port: 3000, host: "0.0.0.0" });
167
+ logger.system.debug("✅ Server started");
168
+ } catch (err) {
169
+ fastify.log.error(err);
170
+ process.exit(1);
171
+ }
172
+ };
173
+ startServer();
package/ucodex.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ import TOML from '@iarna/toml';
4
+ import path from "path";
5
+ import { fileURLToPath, pathToFileURL } from "url";
6
+ import os from 'os';
7
+ import fs from "fs";
8
+ import { spawn } from 'child_process';
9
+ import {getCodexPath} from './untils.js';
10
+ import {startMCPServerProxy} from "./codex/mcpserver.js"
11
+ import LogManager from "./logger-manager.js";
12
+ const logger = LogManager.getSystemLogger();
13
+
14
+
15
+ /**
16
+ * codex 是 rust 开发,只能使用代理模式进行日志获取
17
+ */
18
+ const startServer = async () => {
19
+ let tomlPath = path.join(os.homedir(), ".codex", "config.toml");
20
+ const tomlString = fs.readFileSync(tomlPath, 'utf-8');
21
+ const config = TOML.parse(tomlString);
22
+ let env = {};
23
+ //是否配置了三方模型
24
+ if(config.model_provider){
25
+ let base_url = config["model_providers"][config.model_provider]["base_url"];
26
+ let wire_api = config["model_providers"][config.model_provider]["wire_api"]? config["model_providers"][config.model_provider]["wire_api"]:"chat"
27
+ env = {
28
+ base_url,
29
+ wire_api
30
+ }
31
+ }else{
32
+ env = {
33
+ //默认访问只能是 这个地址
34
+ base_url:"https://chatgpt.com/backend-api/codex",
35
+ wire_api:"responses"
36
+ }
37
+ }
38
+
39
+ let dir = path.dirname(fileURLToPath(import.meta.url));
40
+ const child = spawn('node ' + path.join(dir, 'ucodex-proxy.js'), [],{
41
+ stdio: ['ignore', 'pipe', 'pipe'],
42
+ shell: true,
43
+ env:{
44
+ ...process.env,
45
+ ...env
46
+ }
47
+ });
48
+
49
+ // 监听标准输出
50
+ child.stdout.on('data', (data) => {
51
+ //console.log(`子进程 stdout: ${data}`);
52
+ });
53
+
54
+ // 监听错误输出
55
+ child.stderr.on('data', (data) => {
56
+ logger.error(`子进程 stderr: ${data}`);
57
+ });
58
+
59
+ child.on('close', (code) => {
60
+ logger.debug(`codex 退出,退出码: ${code}`);
61
+ });
62
+ };
63
+ /**
64
+ * 启动 codex cli
65
+ **/
66
+ function startCodexcli(){
67
+ // 设置 base url 的方式
68
+ let env = {};
69
+ let configCmd = [];
70
+
71
+ let tomlPath = path.join(os.homedir(), ".codex", "config.toml");
72
+ const tomlString = fs.readFileSync(tomlPath, 'utf-8');
73
+ const config = TOML.parse(tomlString);
74
+ //是否配置了三方模型
75
+ if(config.model_provider){
76
+ let base_url = config["model_providers"][config.model_provider]["base_url"];
77
+ let wire_api = config["model_providers"][config.model_provider]["wire_api"]? config["model_providers"][config.model_provider]["wire_api"]:"chat"
78
+ configCmd = ["--config", "model_providers." + config.model_provider+".base_url="+"http://127.0.0.1:3000"];
79
+
80
+ }else{
81
+ env = {
82
+ "OPENAI_BASE_URL":"http://127.0.0.1:3000",
83
+ }
84
+ }
85
+
86
+ const child = spawn("node "+getCodexPath(), configCmd,{
87
+ stdio: 'inherit', // 继承父进程 stdio,方便交互
88
+ shell: true,
89
+ env:{
90
+ ...process.env,
91
+ ...env
92
+ }
93
+ });
94
+
95
+
96
+ child.on('close', (code) => {
97
+ logger.debug(`codex 退出,退出码: ${code}`);
98
+ });
99
+
100
+ }
101
+ /**
102
+ function startMCPServerProxy(){
103
+ let dir = path.dirname(fileURLToPath(import.meta.url));
104
+ // 启动 MCP 代理服务
105
+ const child = spawn("node " + (path.join(dir, "mcp" ,'claude-mcpproxy-launcher.js')), [], {
106
+ stdio: 'inherit',
107
+ shell: true,
108
+ env: {
109
+ // PIPE_PATH_PRE: process.pid
110
+ }
111
+ });
112
+
113
+ child.on("error", (error) => {
114
+ console.error("Failed to start MCP server proxy:", error.message);
115
+ process.exit(1);
116
+ });
117
+
118
+ child.on("close", (code) => {
119
+ process.exit(code || 0);
120
+ });
121
+ }
122
+ */
123
+
124
+ function main(){
125
+ startServer();
126
+ startMCPServerProxy()
127
+ startCodexcli();
128
+ }
129
+ main();