aniclaude 1.0.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/README.md +40 -0
- package/assets/cli-window.html +602 -0
- package/assets/idle.mp4 +0 -0
- package/assets/speak.mp4 +0 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +332 -0
- package/dist/pty.d.ts +38 -0
- package/dist/pty.js +146 -0
- package/dist/server.d.ts +23 -0
- package/dist/server.js +175 -0
- package/package.json +48 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AniClaude Local Servers
|
|
4
|
+
* - HTTP server: serves the avatar window HTML and assets
|
|
5
|
+
* - WebSocket server: relays messages between browser and Claude PTY
|
|
6
|
+
*/
|
|
7
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.suppressLogging = suppressLogging;
|
|
12
|
+
exports.startHTTPServer = startHTTPServer;
|
|
13
|
+
exports.startWebSocketServer = startWebSocketServer;
|
|
14
|
+
exports.getAvatarWindowURL = getAvatarWindowURL;
|
|
15
|
+
const http_1 = __importDefault(require("http"));
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const ws_1 = require("ws");
|
|
19
|
+
const config_1 = require("./config");
|
|
20
|
+
// Global state
|
|
21
|
+
let browserSocket = null;
|
|
22
|
+
let voiceInputCallback = null;
|
|
23
|
+
let loggingEnabled = true;
|
|
24
|
+
/**
|
|
25
|
+
* Suppress logging (call after Claude starts to avoid TUI glitches)
|
|
26
|
+
*/
|
|
27
|
+
function suppressLogging() {
|
|
28
|
+
loggingEnabled = false;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Log message only if logging is enabled
|
|
32
|
+
*/
|
|
33
|
+
function log(message) {
|
|
34
|
+
if (loggingEnabled) {
|
|
35
|
+
console.log(message);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// MIME types for static files
|
|
39
|
+
const MIME_TYPES = {
|
|
40
|
+
'.html': 'text/html',
|
|
41
|
+
'.js': 'text/javascript',
|
|
42
|
+
'.css': 'text/css',
|
|
43
|
+
'.json': 'application/json',
|
|
44
|
+
'.mp4': 'video/mp4',
|
|
45
|
+
'.png': 'image/png',
|
|
46
|
+
'.jpg': 'image/jpeg',
|
|
47
|
+
'.svg': 'image/svg+xml',
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Get the assets directory path
|
|
51
|
+
*/
|
|
52
|
+
function getAssetsDir() {
|
|
53
|
+
// Check if running from dist or src
|
|
54
|
+
const distAssets = path_1.default.join(__dirname, '..', 'assets');
|
|
55
|
+
const srcAssets = path_1.default.join(__dirname, '..', '..', 'assets');
|
|
56
|
+
if (fs_1.default.existsSync(distAssets))
|
|
57
|
+
return distAssets;
|
|
58
|
+
if (fs_1.default.existsSync(srcAssets))
|
|
59
|
+
return srcAssets;
|
|
60
|
+
// Fall back to parent directory
|
|
61
|
+
return path_1.default.join(__dirname, '..');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Start HTTP server for serving static files
|
|
65
|
+
*/
|
|
66
|
+
function startHTTPServer() {
|
|
67
|
+
const assetsDir = getAssetsDir();
|
|
68
|
+
const server = http_1.default.createServer((req, res) => {
|
|
69
|
+
// Handle CORS
|
|
70
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
71
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
72
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
73
|
+
if (req.method === 'OPTIONS') {
|
|
74
|
+
res.writeHead(200);
|
|
75
|
+
res.end();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Handle POST to /relay (from TTS hook)
|
|
79
|
+
if (req.method === 'POST' && req.url === '/relay') {
|
|
80
|
+
let body = '';
|
|
81
|
+
req.on('data', chunk => body += chunk);
|
|
82
|
+
req.on('end', () => {
|
|
83
|
+
try {
|
|
84
|
+
const data = JSON.parse(body);
|
|
85
|
+
if (data.text && browserSocket?.readyState === ws_1.WebSocket.OPEN) {
|
|
86
|
+
browserSocket.send(JSON.stringify({
|
|
87
|
+
type: 'claude_response',
|
|
88
|
+
text: data.text
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
92
|
+
res.end(JSON.stringify({ success: true }));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
96
|
+
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
// Serve static files
|
|
102
|
+
let filePath = req.url === '/' ? '/cli-window.html' : req.url || '/cli-window.html';
|
|
103
|
+
// Remove query string
|
|
104
|
+
filePath = filePath.split('?')[0];
|
|
105
|
+
// Try assets directory first
|
|
106
|
+
let fullPath = path_1.default.join(assetsDir, filePath);
|
|
107
|
+
if (!fs_1.default.existsSync(fullPath)) {
|
|
108
|
+
// Try parent directory as fallback
|
|
109
|
+
fullPath = path_1.default.join(__dirname, '..', '..', filePath);
|
|
110
|
+
}
|
|
111
|
+
if (!fs_1.default.existsSync(fullPath)) {
|
|
112
|
+
res.writeHead(404);
|
|
113
|
+
res.end('Not found');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const ext = path_1.default.extname(fullPath);
|
|
117
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
118
|
+
try {
|
|
119
|
+
const content = fs_1.default.readFileSync(fullPath);
|
|
120
|
+
res.writeHead(200, {
|
|
121
|
+
'Content-Type': contentType,
|
|
122
|
+
'Cache-Control': 'no-cache'
|
|
123
|
+
});
|
|
124
|
+
res.end(content);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
res.writeHead(500);
|
|
128
|
+
res.end('Error reading file');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
server.listen(config_1.HTTP_PORT, () => {
|
|
132
|
+
log(`[AniClaude] HTTP server: http://localhost:${config_1.HTTP_PORT}`);
|
|
133
|
+
});
|
|
134
|
+
return server;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Start WebSocket server for browser relay
|
|
138
|
+
*/
|
|
139
|
+
function startWebSocketServer(onVoiceInput) {
|
|
140
|
+
voiceInputCallback = onVoiceInput;
|
|
141
|
+
const wss = new ws_1.WebSocketServer({ port: config_1.WS_PORT });
|
|
142
|
+
wss.on('connection', (ws) => {
|
|
143
|
+
log('[AniClaude] Browser connected');
|
|
144
|
+
browserSocket = ws;
|
|
145
|
+
ws.on('message', (data) => {
|
|
146
|
+
try {
|
|
147
|
+
const message = JSON.parse(data.toString());
|
|
148
|
+
if (message.type === 'voice_input' && message.text) {
|
|
149
|
+
log(`[AniClaude] Voice: "${message.text.substring(0, 50)}..."`);
|
|
150
|
+
voiceInputCallback?.(message.text);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// Ignore parse errors
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
ws.on('close', () => {
|
|
158
|
+
log('[AniClaude] Browser disconnected');
|
|
159
|
+
if (browserSocket === ws) {
|
|
160
|
+
browserSocket = null;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
ws.on('error', () => {
|
|
164
|
+
// Ignore errors
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
log(`[AniClaude] WebSocket server: ws://localhost:${config_1.WS_PORT}`);
|
|
168
|
+
return wss;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get the URL for the avatar window
|
|
172
|
+
*/
|
|
173
|
+
function getAvatarWindowURL() {
|
|
174
|
+
return `http://localhost:${config_1.HTTP_PORT}/cli-window.html?port=${config_1.WS_PORT}&server=${encodeURIComponent(config_1.TTS_SERVER_URL)}`;
|
|
175
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aniclaude",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Voice-enabled Claude Code - talk to Claude in your terminal",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"aniclaude": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc && node dist/index.js",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"ai",
|
|
18
|
+
"voice",
|
|
19
|
+
"tts",
|
|
20
|
+
"stt",
|
|
21
|
+
"cli",
|
|
22
|
+
"terminal"
|
|
23
|
+
],
|
|
24
|
+
"author": "zcrypto",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/zcrypto8888/aniclaudecli"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/zcrypto8888/aniclaudecli#readme",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"node-pty": "0.10.1",
|
|
33
|
+
"open": "^10.1.0",
|
|
34
|
+
"ws": "^8.18.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"@types/ws": "^8.5.13",
|
|
39
|
+
"typescript": "^5.7.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"assets"
|
|
47
|
+
]
|
|
48
|
+
}
|