@linkright.in/agent 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.
Files changed (2) hide show
  1. package/package.json +22 -0
  2. package/src/index.js +237 -0
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@linkright.in/agent",
3
+ "version": "1.0.0",
4
+ "description": "LinkRight local agent — runs AI CLIs on your machine, connects to linkright.in",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "linkright-agent": "src/index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node src/index.js",
11
+ "build": "npx esbuild src/index.js --bundle --platform=node --outfile=dist/linkright-agent.js --format=cjs"
12
+ },
13
+ "dependencies": {
14
+ "ws": "^8.0.0"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ }
22
+ }
package/src/index.js ADDED
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * LinkRight Agent — Local AI Terminal Bridge
4
+ *
5
+ * Runs on the user's machine. Exposes ws://localhost:7777
6
+ * LinkRight UI connects to this and sends prompts.
7
+ * Agent runs AI CLIs locally and streams output back.
8
+ *
9
+ * Zero API cost for Satvik — all AI calls use user's local tools.
10
+ *
11
+ * Usage:
12
+ * npx linkright-agent # auto-detects best available CLI
13
+ * npx linkright-agent --port 7777
14
+ * npx linkright-agent --cli opencode
15
+ */
16
+
17
+ const { WebSocketServer, WebSocket } = require('ws');
18
+ const { spawn, execSync } = require('child_process');
19
+ const os = require('os');
20
+ const path = require('path');
21
+
22
+ const PORT = parseInt(process.argv.find(a => a.startsWith('--port='))?.split('=')[1] || '7777');
23
+ const PREFERRED_CLI = process.argv.find(a => a.startsWith('--cli='))?.split('=')[1] || null;
24
+
25
+ // ── CLI Detection ─────────────────────────────────────────────────────────────
26
+
27
+ const CLI_OPTIONS = [
28
+ {
29
+ name: 'opencode',
30
+ check: () => which('opencode'),
31
+ install: 'npm install -g @opencode-ai/cli',
32
+ run: (prompt) => ['opencode', ['--print', prompt]],
33
+ streaming: true,
34
+ },
35
+ {
36
+ name: 'gemini',
37
+ check: () => which('gemini'),
38
+ install: 'npm install -g @google/gemini-cli',
39
+ run: (prompt) => ['gemini', [prompt]],
40
+ streaming: true,
41
+ },
42
+ {
43
+ name: 'claude',
44
+ check: () => which('claude'),
45
+ install: 'npm install -g @anthropic-ai/claude-code',
46
+ run: (prompt) => ['claude', ['--print', prompt]],
47
+ streaming: true,
48
+ },
49
+ ];
50
+
51
+ function which(cmd) {
52
+ try {
53
+ execSync(`which ${cmd}`, { stdio: 'ignore' });
54
+ return true;
55
+ } catch {
56
+ // Try common install paths
57
+ const paths = [
58
+ path.join(os.homedir(), '.npm-global', 'bin', cmd),
59
+ path.join(os.homedir(), '.local', 'bin', cmd),
60
+ `/usr/local/bin/${cmd}`,
61
+ ];
62
+ return paths.some(p => {
63
+ try { execSync(`test -f ${p}`, { stdio: 'ignore' }); return true; } catch { return false; }
64
+ });
65
+ }
66
+ }
67
+
68
+ function detectCli() {
69
+ if (PREFERRED_CLI) {
70
+ const cli = CLI_OPTIONS.find(c => c.name === PREFERRED_CLI);
71
+ if (cli && cli.check()) return cli;
72
+ console.log(`⚠️ Preferred CLI '${PREFERRED_CLI}' not found. Auto-detecting...`);
73
+ }
74
+ return CLI_OPTIONS.find(c => c.check()) || null;
75
+ }
76
+
77
+ function getCliPath(name) {
78
+ const paths = [
79
+ name,
80
+ path.join(os.homedir(), '.npm-global', 'bin', name),
81
+ path.join(os.homedir(), '.local', 'bin', name),
82
+ `/usr/local/bin/${name}`,
83
+ ];
84
+ for (const p of paths) {
85
+ try { execSync(`test -f ${p} || which ${name}`, { stdio: 'ignore' }); return name; } catch {}
86
+ }
87
+ return name;
88
+ }
89
+
90
+ // ── Agent info ────────────────────────────────────────────────────────────────
91
+
92
+ function getAgentInfo() {
93
+ const available = CLI_OPTIONS.filter(c => c.check()).map(c => c.name);
94
+ const active = detectCli();
95
+ return {
96
+ type: 'agent_info',
97
+ version: '1.0.0',
98
+ platform: `${os.type()} ${os.arch()}`,
99
+ available_clis: available,
100
+ active_cli: active?.name || null,
101
+ ready: !!active,
102
+ install_hint: active ? null : `Install a CLI: ${CLI_OPTIONS.map(c => c.install).join(' OR ')}`,
103
+ };
104
+ }
105
+
106
+ // ── WebSocket Server ──────────────────────────────────────────────────────────
107
+
108
+ const wss = new WebSocketServer({
109
+ port: PORT,
110
+ // Allow connections from linkright.in and localhost
111
+ verifyClient: ({ origin }) => {
112
+ const allowed = [
113
+ 'https://linkright.in',
114
+ 'https://www.linkright.in',
115
+ 'http://localhost:3000',
116
+ 'http://localhost:5173',
117
+ ];
118
+ if (!origin || allowed.some(o => origin.startsWith(o))) return true;
119
+ console.log(`⚠️ Rejected origin: ${origin}`);
120
+ return false;
121
+ }
122
+ });
123
+
124
+ const activeProcs = new Map(); // id → child process
125
+
126
+ wss.on('connection', (ws, req) => {
127
+ console.log(`🔗 Connected from ${req.headers.origin || 'unknown'}`);
128
+
129
+ // Send agent info immediately
130
+ ws.send(JSON.stringify(getAgentInfo()));
131
+
132
+ ws.on('message', async (raw) => {
133
+ let msg;
134
+ try { msg = JSON.parse(raw); } catch { return; }
135
+
136
+ if (msg.type === 'ping') {
137
+ ws.send(JSON.stringify({ type: 'pong', id: msg.id }));
138
+ return;
139
+ }
140
+
141
+ if (msg.type === 'info') {
142
+ ws.send(JSON.stringify(getAgentInfo()));
143
+ return;
144
+ }
145
+
146
+ if (msg.type === 'run') {
147
+ const { id, prompt, cli: preferredCli } = msg;
148
+
149
+ // Pick CLI
150
+ const selectedCli = preferredCli
151
+ ? CLI_OPTIONS.find(c => c.name === preferredCli && c.check())
152
+ : detectCli();
153
+
154
+ if (!selectedCli) {
155
+ ws.send(JSON.stringify({
156
+ type: 'error',
157
+ id,
158
+ error: `No AI CLI found. Install one:\n${CLI_OPTIONS.map(c => ` ${c.install}`).join('\n')}`,
159
+ }));
160
+ return;
161
+ }
162
+
163
+ console.log(`▶ Running ${selectedCli.name}: ${prompt.substring(0, 60)}...`);
164
+
165
+ const [cmd, args] = selectedCli.run(prompt);
166
+ const cliPath = getCliPath(cmd);
167
+
168
+ const proc = spawn(cliPath, args, {
169
+ env: { ...process.env },
170
+ shell: false,
171
+ });
172
+
173
+ activeProcs.set(id, proc);
174
+ ws.send(JSON.stringify({ type: 'started', id, cli: selectedCli.name }));
175
+
176
+ let outputBuffer = '';
177
+
178
+ proc.stdout.on('data', (chunk) => {
179
+ outputBuffer += chunk.toString();
180
+ });
181
+
182
+ proc.stderr.on('data', (chunk) => {
183
+ // Silently discard stderr (MCP errors, warnings etc.)
184
+ });
185
+
186
+ proc.on('close', (code) => {
187
+ activeProcs.delete(id);
188
+ if (ws.readyState === WebSocket.OPEN) {
189
+ // Send clean final response only
190
+ const cleanOutput = outputBuffer.trim();
191
+ ws.send(JSON.stringify({ type: 'result', id, code, data: cleanOutput }));
192
+ }
193
+ console.log(`✓ ${selectedCli.name} exited (code ${code})`);
194
+ });
195
+
196
+ proc.on('error', (err) => {
197
+ activeProcs.delete(id);
198
+ if (ws.readyState === WebSocket.OPEN) {
199
+ ws.send(JSON.stringify({ type: 'error', id, error: err.message }));
200
+ }
201
+ });
202
+ }
203
+
204
+ if (msg.type === 'kill') {
205
+ const proc = activeProcs.get(msg.id);
206
+ if (proc) {
207
+ proc.kill();
208
+ activeProcs.delete(msg.id);
209
+ }
210
+ }
211
+ });
212
+
213
+ ws.on('close', () => {
214
+ console.log('🔌 Disconnected');
215
+ // Kill all processes for this connection
216
+ activeProcs.forEach((proc) => proc.kill());
217
+ activeProcs.clear();
218
+ });
219
+ });
220
+
221
+ // ── Startup ───────────────────────────────────────────────────────────────────
222
+
223
+ const info = getAgentInfo();
224
+
225
+ console.log('\n╔════════════════════════════════════════╗');
226
+ console.log('║ LinkRight Agent v1.0.0 ║');
227
+ console.log('╚════════════════════════════════════════╝\n');
228
+ console.log(`🖥️ Platform: ${info.platform}`);
229
+ console.log(`🔍 Available CLIs: ${info.available_clis.join(', ') || 'none'}`);
230
+ console.log(`✅ Active CLI: ${info.active_cli || 'none'}`);
231
+ if (!info.ready) {
232
+ console.log(`\n⚠️ No AI CLI found! Install one:`);
233
+ CLI_OPTIONS.forEach(c => console.log(` ${c.install}`));
234
+ }
235
+ console.log(`\n🌐 Listening on ws://localhost:${PORT}`);
236
+ console.log(`📡 Open linkright.in/sync → AI Terminal\n`);
237
+ console.log('Press Ctrl+C to stop\n');