@siftd/connect-agent 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/dist/agent.d.ts +3 -0
- package/dist/agent.js +90 -0
- package/dist/api.d.ts +22 -0
- package/dist/api.js +48 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +93 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +45 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/package.json +46 -0
package/dist/agent.d.ts
ADDED
package/dist/agent.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { pollMessages, sendResponse } from './api.js';
|
|
3
|
+
// Strip ANSI escape codes for clean output
|
|
4
|
+
function stripAnsi(str) {
|
|
5
|
+
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Send a message to Claude Code and get the response
|
|
9
|
+
* Uses --continue to maintain conversation context
|
|
10
|
+
*/
|
|
11
|
+
async function sendToClaude(input) {
|
|
12
|
+
return new Promise((resolve, reject) => {
|
|
13
|
+
console.log(`\n[CLAUDE] Sending: ${input.substring(0, 80)}...`);
|
|
14
|
+
// Use -p (print mode) with --continue to maintain context
|
|
15
|
+
const claude = spawn('claude', ['-p', '--continue'], {
|
|
16
|
+
cwd: process.env.HOME,
|
|
17
|
+
env: process.env,
|
|
18
|
+
shell: true,
|
|
19
|
+
});
|
|
20
|
+
let output = '';
|
|
21
|
+
let errorOutput = '';
|
|
22
|
+
claude.stdout?.on('data', (data) => {
|
|
23
|
+
const text = data.toString();
|
|
24
|
+
output += text;
|
|
25
|
+
process.stdout.write(text);
|
|
26
|
+
});
|
|
27
|
+
claude.stderr?.on('data', (data) => {
|
|
28
|
+
const text = data.toString();
|
|
29
|
+
errorOutput += text;
|
|
30
|
+
// Only print stderr if it's not just status info
|
|
31
|
+
if (!text.includes('Checking') && !text.includes('Connected')) {
|
|
32
|
+
process.stderr.write(text);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
claude.on('close', (code) => {
|
|
36
|
+
if (code === 0) {
|
|
37
|
+
const response = stripAnsi(output).trim();
|
|
38
|
+
console.log(`\n[CLAUDE] Response: ${response.substring(0, 80)}...`);
|
|
39
|
+
resolve(response || 'No response from Claude');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const error = errorOutput || `Claude exited with code ${code}`;
|
|
43
|
+
console.error(`\n[CLAUDE] Error: ${error}`);
|
|
44
|
+
resolve(`Error: ${stripAnsi(error).trim()}`);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
claude.on('error', (err) => {
|
|
48
|
+
console.error(`\n[CLAUDE] Failed to start: ${err.message}`);
|
|
49
|
+
resolve(`Error: Failed to start Claude - ${err.message}`);
|
|
50
|
+
});
|
|
51
|
+
// Send the input to claude's stdin
|
|
52
|
+
if (claude.stdin) {
|
|
53
|
+
claude.stdin.write(input);
|
|
54
|
+
claude.stdin.end();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export async function processMessage(message) {
|
|
59
|
+
console.log(`\n[WEB] >>> ${message.content}`);
|
|
60
|
+
try {
|
|
61
|
+
const response = await sendToClaude(message.content);
|
|
62
|
+
return response;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error('Error:', error);
|
|
66
|
+
return `Error: ${error instanceof Error ? error.message : 'Unknown error'}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export async function runAgent(pollInterval = 2000) {
|
|
70
|
+
console.log('╔══════════════════════════════════════════╗');
|
|
71
|
+
console.log('║ Connect Agent - Claude Code Relay ║');
|
|
72
|
+
console.log('╚══════════════════════════════════════════╝\n');
|
|
73
|
+
console.log('[AGENT] Polling for web messages...\n');
|
|
74
|
+
while (true) {
|
|
75
|
+
try {
|
|
76
|
+
const { messages } = await pollMessages();
|
|
77
|
+
for (const msg of messages) {
|
|
78
|
+
const response = await processMessage(msg);
|
|
79
|
+
await sendResponse(msg.id, response);
|
|
80
|
+
console.log(`[AGENT] Response sent (${response.length} chars)`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof Error && !error.message.includes('ECONNREFUSED')) {
|
|
85
|
+
console.error('[AGENT] Poll error:', error.message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
89
|
+
}
|
|
90
|
+
}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface Message {
|
|
2
|
+
id: string;
|
|
3
|
+
content: string;
|
|
4
|
+
timestamp: number;
|
|
5
|
+
}
|
|
6
|
+
export interface ConnectResponse {
|
|
7
|
+
success: boolean;
|
|
8
|
+
agentToken?: string;
|
|
9
|
+
userId?: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MessagesResponse {
|
|
13
|
+
messages: Message[];
|
|
14
|
+
}
|
|
15
|
+
export interface RespondResponse {
|
|
16
|
+
success: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function connectWithPairingCode(code: string): Promise<ConnectResponse>;
|
|
20
|
+
export declare function pollMessages(): Promise<MessagesResponse>;
|
|
21
|
+
export declare function sendResponse(messageId: string, response: string): Promise<RespondResponse>;
|
|
22
|
+
export declare function checkConnection(): Promise<boolean>;
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getServerUrl, getAgentToken } from './config.js';
|
|
2
|
+
async function fetchWithAuth(path, options = {}) {
|
|
3
|
+
const serverUrl = getServerUrl();
|
|
4
|
+
const agentToken = getAgentToken();
|
|
5
|
+
const headers = {
|
|
6
|
+
'Content-Type': 'application/json',
|
|
7
|
+
...options.headers,
|
|
8
|
+
};
|
|
9
|
+
if (agentToken) {
|
|
10
|
+
headers['Authorization'] = `Bearer ${agentToken}`;
|
|
11
|
+
}
|
|
12
|
+
return fetch(`${serverUrl}${path}`, {
|
|
13
|
+
...options,
|
|
14
|
+
headers,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function connectWithPairingCode(code) {
|
|
18
|
+
const serverUrl = getServerUrl();
|
|
19
|
+
const res = await fetch(`${serverUrl}/api/agent/connect`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({ pairingCode: code }),
|
|
23
|
+
});
|
|
24
|
+
return res.json();
|
|
25
|
+
}
|
|
26
|
+
export async function pollMessages() {
|
|
27
|
+
const res = await fetchWithAuth('/api/agent/messages');
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
throw new Error(`Failed to poll messages: ${res.status}`);
|
|
30
|
+
}
|
|
31
|
+
return res.json();
|
|
32
|
+
}
|
|
33
|
+
export async function sendResponse(messageId, response) {
|
|
34
|
+
const res = await fetchWithAuth('/api/agent/respond', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body: JSON.stringify({ messageId, response }),
|
|
37
|
+
});
|
|
38
|
+
return res.json();
|
|
39
|
+
}
|
|
40
|
+
export async function checkConnection() {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetchWithAuth('/api/agent/messages');
|
|
43
|
+
return res.ok;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { program } from 'commander';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getServerUrl, setServerUrl, setAgentToken, setUserId, isConfigured, clearConfig, getConfigPath, } from './config.js';
|
|
5
|
+
import { connectWithPairingCode, checkConnection } from './api.js';
|
|
6
|
+
import { runAgent } from './agent.js';
|
|
7
|
+
program
|
|
8
|
+
.name('connect-agent')
|
|
9
|
+
.description('Local agent for Connect web app')
|
|
10
|
+
.version('0.1.0');
|
|
11
|
+
program
|
|
12
|
+
.command('pair')
|
|
13
|
+
.description('Pair with Connect web app using a pairing code')
|
|
14
|
+
.argument('<code>', 'The 6-character pairing code from the web app')
|
|
15
|
+
.option('-s, --server <url>', 'Server URL', getServerUrl())
|
|
16
|
+
.action(async (code, options) => {
|
|
17
|
+
const spinner = ora('Connecting to server...').start();
|
|
18
|
+
try {
|
|
19
|
+
if (options.server !== getServerUrl()) {
|
|
20
|
+
setServerUrl(options.server);
|
|
21
|
+
}
|
|
22
|
+
const result = await connectWithPairingCode(code.toUpperCase());
|
|
23
|
+
if (!result.success) {
|
|
24
|
+
spinner.fail(`Pairing failed: ${result.error}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
setAgentToken(result.agentToken);
|
|
28
|
+
setUserId(result.userId);
|
|
29
|
+
spinner.succeed('Paired successfully!');
|
|
30
|
+
console.log(`\nAgent is now connected to ${getServerUrl()}`);
|
|
31
|
+
console.log('Run "connect-agent start" to begin processing messages.');
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
spinner.fail(`Connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
program
|
|
39
|
+
.command('start')
|
|
40
|
+
.description('Start the agent and begin processing messages')
|
|
41
|
+
.option('-i, --interval <ms>', 'Poll interval in milliseconds', '2000')
|
|
42
|
+
.action(async (options) => {
|
|
43
|
+
if (!isConfigured()) {
|
|
44
|
+
console.error('Agent not configured. Run "connect-agent pair <code>" first.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const spinner = ora('Checking connection...').start();
|
|
48
|
+
const connected = await checkConnection();
|
|
49
|
+
if (!connected) {
|
|
50
|
+
spinner.fail('Cannot connect to server. Check your configuration.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
spinner.succeed('Connected to server');
|
|
54
|
+
console.log(`\nServer: ${getServerUrl()}`);
|
|
55
|
+
console.log(`Poll interval: ${options.interval}ms`);
|
|
56
|
+
console.log('\nPress Ctrl+C to stop.\n');
|
|
57
|
+
await runAgent(parseInt(options.interval, 10));
|
|
58
|
+
});
|
|
59
|
+
program
|
|
60
|
+
.command('status')
|
|
61
|
+
.description('Show agent configuration status')
|
|
62
|
+
.action(async () => {
|
|
63
|
+
console.log('Connect Agent Status\n');
|
|
64
|
+
console.log(`Config file: ${getConfigPath()}`);
|
|
65
|
+
console.log(`Server URL: ${getServerUrl()}`);
|
|
66
|
+
console.log(`Configured: ${isConfigured() ? 'Yes' : 'No'}`);
|
|
67
|
+
if (isConfigured()) {
|
|
68
|
+
const spinner = ora('Checking connection...').start();
|
|
69
|
+
const connected = await checkConnection();
|
|
70
|
+
if (connected) {
|
|
71
|
+
spinner.succeed('Server connection: OK');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
spinner.fail('Server connection: Failed');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
program
|
|
79
|
+
.command('logout')
|
|
80
|
+
.description('Clear agent configuration')
|
|
81
|
+
.action(() => {
|
|
82
|
+
clearConfig();
|
|
83
|
+
console.log('Agent configuration cleared.');
|
|
84
|
+
});
|
|
85
|
+
program
|
|
86
|
+
.command('server')
|
|
87
|
+
.description('Set the server URL')
|
|
88
|
+
.argument('<url>', 'The server URL')
|
|
89
|
+
.action((url) => {
|
|
90
|
+
setServerUrl(url);
|
|
91
|
+
console.log(`Server URL set to: ${url}`);
|
|
92
|
+
});
|
|
93
|
+
program.parse();
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function getServerUrl(): string;
|
|
2
|
+
export declare function setServerUrl(url: string): void;
|
|
3
|
+
export declare function getAgentToken(): string | null;
|
|
4
|
+
export declare function setAgentToken(token: string | null): void;
|
|
5
|
+
export declare function getUserId(): string | null;
|
|
6
|
+
export declare function setUserId(id: string | null): void;
|
|
7
|
+
export declare function isConfigured(): boolean;
|
|
8
|
+
export declare function clearConfig(): void;
|
|
9
|
+
export declare function getConfigPath(): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
const config = new Conf({
|
|
3
|
+
projectName: 'connect-agent',
|
|
4
|
+
defaults: {
|
|
5
|
+
serverUrl: 'https://connect.siftd.app',
|
|
6
|
+
},
|
|
7
|
+
});
|
|
8
|
+
export function getServerUrl() {
|
|
9
|
+
return config.get('serverUrl');
|
|
10
|
+
}
|
|
11
|
+
export function setServerUrl(url) {
|
|
12
|
+
config.set('serverUrl', url);
|
|
13
|
+
}
|
|
14
|
+
export function getAgentToken() {
|
|
15
|
+
return config.get('agentToken') ?? null;
|
|
16
|
+
}
|
|
17
|
+
export function setAgentToken(token) {
|
|
18
|
+
if (token === null) {
|
|
19
|
+
config.delete('agentToken');
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
config.set('agentToken', token);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function getUserId() {
|
|
26
|
+
return config.get('userId') ?? null;
|
|
27
|
+
}
|
|
28
|
+
export function setUserId(id) {
|
|
29
|
+
if (id === null) {
|
|
30
|
+
config.delete('userId');
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
config.set('userId', id);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function isConfigured() {
|
|
37
|
+
return !!getAgentToken() && !!getUserId();
|
|
38
|
+
}
|
|
39
|
+
export function clearConfig() {
|
|
40
|
+
config.delete('agentToken');
|
|
41
|
+
config.delete('userId');
|
|
42
|
+
}
|
|
43
|
+
export function getConfigPath() {
|
|
44
|
+
return config.path;
|
|
45
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@siftd/connect-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local agent that connects to Connect web app - control Claude Code remotely",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"connect-agent": "dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsc --watch",
|
|
16
|
+
"start": "node dist/cli.js",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"agent",
|
|
21
|
+
"cli",
|
|
22
|
+
"connect",
|
|
23
|
+
"claude",
|
|
24
|
+
"remote",
|
|
25
|
+
"terminal"
|
|
26
|
+
],
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/james-m-jordan/connect-app.git",
|
|
30
|
+
"directory": "packages/agent"
|
|
31
|
+
},
|
|
32
|
+
"author": "Jim Jordan",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"commander": "^12.1.0",
|
|
39
|
+
"conf": "^13.0.1",
|
|
40
|
+
"ora": "^8.1.1"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^22.10.2",
|
|
44
|
+
"typescript": "^5.7.2"
|
|
45
|
+
}
|
|
46
|
+
}
|