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/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
+ }