@cmdctrl/aider 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/adapter/agentapi.d.ts +100 -0
- package/dist/adapter/agentapi.d.ts.map +1 -0
- package/dist/adapter/agentapi.js +578 -0
- package/dist/adapter/agentapi.js.map +1 -0
- package/dist/client/messages.d.ts +89 -0
- package/dist/client/messages.d.ts.map +1 -0
- package/dist/client/messages.js +6 -0
- package/dist/client/messages.js.map +1 -0
- package/dist/client/websocket.d.ts +66 -0
- package/dist/client/websocket.d.ts.map +1 -0
- package/dist/client/websocket.js +276 -0
- package/dist/client/websocket.js.map +1 -0
- package/dist/commands/register.d.ts +10 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +175 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/commands/start.d.ts +9 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +54 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +37 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +5 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +59 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/config/config.d.ts +60 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +176 -0
- package/dist/config/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
- package/src/adapter/agentapi.ts +656 -0
- package/src/client/messages.ts +125 -0
- package/src/client/websocket.ts +317 -0
- package/src/commands/register.ts +201 -0
- package/src/commands/start.ts +70 -0
- package/src/commands/status.ts +45 -0
- package/src/commands/stop.ts +58 -0
- package/src/config/config.ts +146 -0
- package/src/index.ts +39 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readConfig,
|
|
3
|
+
readCredentials,
|
|
4
|
+
isRegistered,
|
|
5
|
+
isDaemonRunning,
|
|
6
|
+
readPidFile
|
|
7
|
+
} from '../config/config';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Status command - check daemon and connection status
|
|
11
|
+
*/
|
|
12
|
+
export async function status(): Promise<void> {
|
|
13
|
+
console.log('CmdCtrl Aider Daemon Status\n');
|
|
14
|
+
|
|
15
|
+
// Registration status
|
|
16
|
+
if (!isRegistered()) {
|
|
17
|
+
console.log('Registration: Not registered');
|
|
18
|
+
console.log('\nRun "cmdctrl-aider register" to register this device.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const config = readConfig()!;
|
|
23
|
+
const credentials = readCredentials()!;
|
|
24
|
+
|
|
25
|
+
console.log('Registration: Registered');
|
|
26
|
+
console.log(` Server: ${config.serverUrl}`);
|
|
27
|
+
console.log(` Device: ${config.deviceName}`);
|
|
28
|
+
console.log(` Device ID: ${config.deviceId}`);
|
|
29
|
+
|
|
30
|
+
// Token status
|
|
31
|
+
const tokenExpired = credentials.expiresAt < Date.now();
|
|
32
|
+
console.log(`\nToken: ${tokenExpired ? 'Expired' : 'Valid'}`);
|
|
33
|
+
if (!tokenExpired) {
|
|
34
|
+
const expiresIn = Math.round((credentials.expiresAt - Date.now()) / 1000 / 60);
|
|
35
|
+
console.log(` Expires in: ${expiresIn} minutes`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Daemon status
|
|
39
|
+
const running = isDaemonRunning();
|
|
40
|
+
console.log(`\nDaemon: ${running ? 'Running' : 'Stopped'}`);
|
|
41
|
+
if (running) {
|
|
42
|
+
const pid = readPidFile();
|
|
43
|
+
console.log(` PID: ${pid}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { isDaemonRunning, readPidFile, deletePidFile } from '../config/config';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stop command - stop the running daemon
|
|
5
|
+
*/
|
|
6
|
+
export async function stop(): Promise<void> {
|
|
7
|
+
if (!isDaemonRunning()) {
|
|
8
|
+
console.log('Daemon is not running.');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const pid = readPidFile();
|
|
13
|
+
if (pid === null) {
|
|
14
|
+
console.log('No PID file found.');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
console.log(`Stopping daemon (PID ${pid})...`);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// Send SIGTERM for graceful shutdown
|
|
22
|
+
process.kill(pid, 'SIGTERM');
|
|
23
|
+
|
|
24
|
+
// Wait for process to exit (up to 5 seconds)
|
|
25
|
+
let attempts = 0;
|
|
26
|
+
while (attempts < 50) {
|
|
27
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 0); // Check if still running
|
|
30
|
+
attempts++;
|
|
31
|
+
} catch {
|
|
32
|
+
// Process has exited
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If still running after 5 seconds, force kill
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 0);
|
|
40
|
+
console.log('Daemon did not stop gracefully, sending SIGKILL...');
|
|
41
|
+
process.kill(pid, 'SIGKILL');
|
|
42
|
+
} catch {
|
|
43
|
+
// Already dead, good
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
deletePidFile();
|
|
47
|
+
console.log('Daemon stopped.');
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
|
|
50
|
+
// Process doesn't exist
|
|
51
|
+
deletePidFile();
|
|
52
|
+
console.log('Daemon was not running (stale PID file removed).');
|
|
53
|
+
} else {
|
|
54
|
+
console.error('Failed to stop daemon:', err);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
|
|
5
|
+
export interface CmdCtrlConfig {
|
|
6
|
+
serverUrl: string;
|
|
7
|
+
deviceId: string;
|
|
8
|
+
deviceName: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Credentials {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken: string;
|
|
14
|
+
expiresAt: number; // Unix timestamp
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const CONFIG_DIR = path.join(os.homedir(), '.cmdctrl-aider');
|
|
18
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
19
|
+
const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials');
|
|
20
|
+
const PID_FILE = path.join(CONFIG_DIR, 'daemon.pid');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ensure the config directory exists with proper permissions
|
|
24
|
+
*/
|
|
25
|
+
export function ensureConfigDir(): void {
|
|
26
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
27
|
+
fs.mkdirSync(CONFIG_DIR, { mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read the config file
|
|
33
|
+
*/
|
|
34
|
+
export function readConfig(): CmdCtrlConfig | null {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
40
|
+
return JSON.parse(content) as CmdCtrlConfig;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Write the config file
|
|
48
|
+
*/
|
|
49
|
+
export function writeConfig(config: CmdCtrlConfig): void {
|
|
50
|
+
ensureConfigDir();
|
|
51
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Read credentials (access/refresh tokens)
|
|
56
|
+
*/
|
|
57
|
+
export function readCredentials(): Credentials | null {
|
|
58
|
+
try {
|
|
59
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const content = fs.readFileSync(CREDENTIALS_FILE, 'utf-8');
|
|
63
|
+
return JSON.parse(content) as Credentials;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Write credentials with restrictive permissions (600)
|
|
71
|
+
*/
|
|
72
|
+
export function writeCredentials(creds: Credentials): void {
|
|
73
|
+
ensureConfigDir();
|
|
74
|
+
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Delete credentials (for logout/revoke)
|
|
79
|
+
*/
|
|
80
|
+
export function deleteCredentials(): void {
|
|
81
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
82
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if device is registered
|
|
88
|
+
*/
|
|
89
|
+
export function isRegistered(): boolean {
|
|
90
|
+
const config = readConfig();
|
|
91
|
+
const creds = readCredentials();
|
|
92
|
+
return config !== null && creds !== null && config.deviceId !== '';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Write daemon PID file
|
|
97
|
+
*/
|
|
98
|
+
export function writePidFile(pid: number): void {
|
|
99
|
+
ensureConfigDir();
|
|
100
|
+
fs.writeFileSync(PID_FILE, pid.toString(), { mode: 0o600 });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Read daemon PID
|
|
105
|
+
*/
|
|
106
|
+
export function readPidFile(): number | null {
|
|
107
|
+
try {
|
|
108
|
+
if (!fs.existsSync(PID_FILE)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const content = fs.readFileSync(PID_FILE, 'utf-8');
|
|
112
|
+
return parseInt(content, 10);
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Delete PID file
|
|
120
|
+
*/
|
|
121
|
+
export function deletePidFile(): void {
|
|
122
|
+
if (fs.existsSync(PID_FILE)) {
|
|
123
|
+
fs.unlinkSync(PID_FILE);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Check if daemon is running
|
|
129
|
+
*/
|
|
130
|
+
export function isDaemonRunning(): boolean {
|
|
131
|
+
const pid = readPidFile();
|
|
132
|
+
if (pid === null) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
// Signal 0 doesn't kill, just checks if process exists
|
|
137
|
+
process.kill(pid, 0);
|
|
138
|
+
return true;
|
|
139
|
+
} catch {
|
|
140
|
+
// Process doesn't exist, clean up stale PID file
|
|
141
|
+
deletePidFile();
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { CONFIG_DIR, CONFIG_FILE, CREDENTIALS_FILE, PID_FILE };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { register } from './commands/register';
|
|
5
|
+
import { start } from './commands/start';
|
|
6
|
+
import { status } from './commands/status';
|
|
7
|
+
import { stop } from './commands/stop';
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('cmdctrl-aider')
|
|
13
|
+
.description('Aider daemon - connects your workstation to the CmdCtrl orchestration server via AgentAPI')
|
|
14
|
+
.version('0.1.0');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('register')
|
|
18
|
+
.description('Register this device with a CmdCtrl server')
|
|
19
|
+
.option('-s, --server <url>', 'CmdCtrl server URL', 'http://localhost:4000')
|
|
20
|
+
.option('-n, --name <name>', 'Device name (defaults to hostname-aider)')
|
|
21
|
+
.action(register);
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('start')
|
|
25
|
+
.description('Start the daemon and connect to the CmdCtrl server')
|
|
26
|
+
.option('-f, --foreground', 'Run in foreground (don\'t daemonize)')
|
|
27
|
+
.action(start);
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command('status')
|
|
31
|
+
.description('Check daemon connection status')
|
|
32
|
+
.action(status);
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command('stop')
|
|
36
|
+
.description('Stop the running daemon')
|
|
37
|
+
.action(stop);
|
|
38
|
+
|
|
39
|
+
program.parse();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|