kraki 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/LICENSE +21 -0
- package/dist/adapters/base.d.ts +107 -0
- package/dist/adapters/base.js +32 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/copilot.d.ts +51 -0
- package/dist/adapters/copilot.js +331 -0
- package/dist/adapters/copilot.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/banner-data.json +1 -0
- package/dist/banner.d.ts +7 -0
- package/dist/banner.js +187 -0
- package/dist/banner.js.map +1 -0
- package/dist/checks.d.ts +26 -0
- package/dist/checks.js +74 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +266 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +32 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/daemon-worker.d.ts +21 -0
- package/dist/daemon-worker.js +127 -0
- package/dist/daemon-worker.js.map +1 -0
- package/dist/daemon.d.ts +15 -0
- package/dist/daemon.js +117 -0
- package/dist/daemon.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/key-manager.d.ts +28 -0
- package/dist/key-manager.js +61 -0
- package/dist/key-manager.js.map +1 -0
- package/dist/logger.d.ts +8 -0
- package/dist/logger.js +30 -0
- package/dist/logger.js.map +1 -0
- package/dist/pair.d.ts +32 -0
- package/dist/pair.js +131 -0
- package/dist/pair.js.map +1 -0
- package/dist/parse-permission.d.ts +25 -0
- package/dist/parse-permission.js +67 -0
- package/dist/parse-permission.js.map +1 -0
- package/dist/relay-client.d.ts +82 -0
- package/dist/relay-client.js +490 -0
- package/dist/relay-client.js.map +1 -0
- package/dist/session-manager.d.ts +85 -0
- package/dist/session-manager.js +218 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/setup.d.ts +13 -0
- package/dist/setup.js +232 -0
- package/dist/setup.js.map +1 -0
- package/package.json +46 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration management for Kraki tentacle.
|
|
3
|
+
*
|
|
4
|
+
* Config is stored at ~/.kraki/config.json (no secrets).
|
|
5
|
+
* Channel keys are stored separately at ~/.kraki/channel.key with 0o600 permissions.
|
|
6
|
+
* Daemon PID is tracked at ~/.kraki/daemon.pid.
|
|
7
|
+
*/
|
|
8
|
+
import { mkdirSync, readFileSync, writeFileSync, unlinkSync, existsSync, chmodSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
import { randomUUID } from 'node:crypto';
|
|
12
|
+
// ── Paths ───────────────────────────────────────────────
|
|
13
|
+
const CONFIG_DIR = join(homedir(), '.kraki');
|
|
14
|
+
const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
|
|
15
|
+
const CHANNEL_KEY_PATH = join(CONFIG_DIR, 'channel.key');
|
|
16
|
+
const DAEMON_PID_PATH = join(CONFIG_DIR, 'daemon.pid');
|
|
17
|
+
const DEVICE_ID_PATH = join(CONFIG_DIR, 'device-id');
|
|
18
|
+
export function getConfigDir() {
|
|
19
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
20
|
+
return CONFIG_DIR;
|
|
21
|
+
}
|
|
22
|
+
// ── Device ID ───────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Get the stable device ID for this machine.
|
|
25
|
+
* Generated once and persisted at ~/.kraki/device-id.
|
|
26
|
+
* Sent to the head on auth so reconnections don't create ghost devices.
|
|
27
|
+
*/
|
|
28
|
+
export function getOrCreateDeviceId() {
|
|
29
|
+
try {
|
|
30
|
+
const existing = readFileSync(DEVICE_ID_PATH, 'utf8').trim();
|
|
31
|
+
if (existing)
|
|
32
|
+
return existing;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// File doesn't exist — generate one
|
|
36
|
+
}
|
|
37
|
+
const id = `dev_${randomUUID().slice(0, 12)}`;
|
|
38
|
+
getConfigDir();
|
|
39
|
+
writeFileSync(DEVICE_ID_PATH, id, 'utf8');
|
|
40
|
+
return id;
|
|
41
|
+
}
|
|
42
|
+
// ── Config ──────────────────────────────────────────────
|
|
43
|
+
export function configExists() {
|
|
44
|
+
return existsSync(CONFIG_PATH);
|
|
45
|
+
}
|
|
46
|
+
export function loadConfig() {
|
|
47
|
+
try {
|
|
48
|
+
const raw = readFileSync(CONFIG_PATH, 'utf8');
|
|
49
|
+
return JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function saveConfig(config) {
|
|
56
|
+
getConfigDir();
|
|
57
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
58
|
+
}
|
|
59
|
+
// ── Channel key ─────────────────────────────────────────
|
|
60
|
+
export function getChannelKeyPath() {
|
|
61
|
+
return CHANNEL_KEY_PATH;
|
|
62
|
+
}
|
|
63
|
+
export function saveChannelKey(key) {
|
|
64
|
+
getConfigDir();
|
|
65
|
+
writeFileSync(CHANNEL_KEY_PATH, key, 'utf8');
|
|
66
|
+
chmodSync(CHANNEL_KEY_PATH, 0o600);
|
|
67
|
+
}
|
|
68
|
+
export function loadChannelKey() {
|
|
69
|
+
try {
|
|
70
|
+
return readFileSync(CHANNEL_KEY_PATH, 'utf8').trim();
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ── Daemon PID ──────────────────────────────────────────
|
|
77
|
+
export function getDaemonPidPath() {
|
|
78
|
+
return DAEMON_PID_PATH;
|
|
79
|
+
}
|
|
80
|
+
export function saveDaemonPid(pid) {
|
|
81
|
+
getConfigDir();
|
|
82
|
+
writeFileSync(DAEMON_PID_PATH, String(pid), 'utf8');
|
|
83
|
+
}
|
|
84
|
+
export function loadDaemonPid() {
|
|
85
|
+
try {
|
|
86
|
+
const raw = readFileSync(DAEMON_PID_PATH, 'utf8').trim();
|
|
87
|
+
const pid = parseInt(raw, 10);
|
|
88
|
+
return Number.isFinite(pid) ? pid : null;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function clearDaemonPid() {
|
|
95
|
+
try {
|
|
96
|
+
unlinkSync(DAEMON_PID_PATH);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// File may not exist — that's fine
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kraki tentacle daemon worker.
|
|
3
|
+
*
|
|
4
|
+
* This file is spawned as a detached background process by daemon.ts.
|
|
5
|
+
* It loads config, resolves authentication, starts the Copilot adapter,
|
|
6
|
+
* and connects to the head via RelayClient.
|
|
7
|
+
*
|
|
8
|
+
* RelayClient wires all adapter events to the head automatically.
|
|
9
|
+
* SessionManager handles durable session state and crash recovery.
|
|
10
|
+
* KeyManager handles E2E encryption keys.
|
|
11
|
+
*/
|
|
12
|
+
import { CopilotAdapter } from './adapters/copilot.js';
|
|
13
|
+
import { RelayClient } from './relay-client.js';
|
|
14
|
+
import { SessionManager } from './session-manager.js';
|
|
15
|
+
export interface WorkerResult {
|
|
16
|
+
adapter: CopilotAdapter;
|
|
17
|
+
relay: RelayClient;
|
|
18
|
+
sessionManager: SessionManager;
|
|
19
|
+
shutdown: () => Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare function startWorker(): Promise<WorkerResult>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kraki tentacle daemon worker.
|
|
3
|
+
*
|
|
4
|
+
* This file is spawned as a detached background process by daemon.ts.
|
|
5
|
+
* It loads config, resolves authentication, starts the Copilot adapter,
|
|
6
|
+
* and connects to the head via RelayClient.
|
|
7
|
+
*
|
|
8
|
+
* RelayClient wires all adapter events to the head automatically.
|
|
9
|
+
* SessionManager handles durable session state and crash recovery.
|
|
10
|
+
* KeyManager handles E2E encryption keys.
|
|
11
|
+
*/
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { loadConfig, loadChannelKey, getOrCreateDeviceId } from './config.js';
|
|
14
|
+
import { CopilotAdapter } from './adapters/copilot.js';
|
|
15
|
+
import { RelayClient } from './relay-client.js';
|
|
16
|
+
import { SessionManager } from './session-manager.js';
|
|
17
|
+
import { KeyManager } from './key-manager.js';
|
|
18
|
+
import { createLogger } from './logger.js';
|
|
19
|
+
const logger = createLogger('daemon');
|
|
20
|
+
// ── Uncaught exception / rejection handlers ─────────────
|
|
21
|
+
process.on('uncaughtException', (err) => {
|
|
22
|
+
logger.fatal({ err }, 'Uncaught exception');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
});
|
|
25
|
+
process.on('unhandledRejection', (reason) => {
|
|
26
|
+
logger.fatal({ reason }, 'Unhandled rejection');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
});
|
|
29
|
+
export async function startWorker() {
|
|
30
|
+
logger.info('Daemon starting…');
|
|
31
|
+
// 1. Load config
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
if (!config) {
|
|
34
|
+
logger.fatal('No config found at ~/.kraki/config.json — run `kraki` to set up');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// 2. Resolve auth token
|
|
38
|
+
let token;
|
|
39
|
+
if (config.authMethod === 'github') {
|
|
40
|
+
try {
|
|
41
|
+
token = execSync('gh auth token 2>/dev/null', { encoding: 'utf8' }).trim() || undefined;
|
|
42
|
+
if (token)
|
|
43
|
+
logger.info('Resolved GitHub token from `gh auth token`');
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
logger.warn('Could not resolve GitHub token from gh CLI');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
const channelKey = loadChannelKey();
|
|
51
|
+
if (channelKey) {
|
|
52
|
+
token = channelKey;
|
|
53
|
+
logger.info('Loaded channel key from ~/.kraki/channel.key');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
logger.warn('No channel key found at ~/.kraki/channel.key');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 3. Initialize components
|
|
60
|
+
const adapter = new CopilotAdapter();
|
|
61
|
+
const sessionManager = new SessionManager();
|
|
62
|
+
const keyManager = new KeyManager();
|
|
63
|
+
const deviceId = getOrCreateDeviceId();
|
|
64
|
+
// 4. Start Copilot adapter
|
|
65
|
+
if (token) {
|
|
66
|
+
process.env.GITHUB_TOKEN = token;
|
|
67
|
+
}
|
|
68
|
+
await adapter.start();
|
|
69
|
+
logger.info('Copilot adapter started');
|
|
70
|
+
// 5. Fetch available models for device capabilities
|
|
71
|
+
let models = [];
|
|
72
|
+
try {
|
|
73
|
+
models = await adapter.listModels();
|
|
74
|
+
logger.info({ count: models.length }, 'Fetched available models');
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
logger.warn('Could not fetch available models');
|
|
78
|
+
}
|
|
79
|
+
// 6. Connect to relay via RelayClient
|
|
80
|
+
const relay = new RelayClient(adapter, sessionManager, {
|
|
81
|
+
relayUrl: config.relay,
|
|
82
|
+
device: {
|
|
83
|
+
name: config.device.name,
|
|
84
|
+
role: 'tentacle',
|
|
85
|
+
kind: 'desktop',
|
|
86
|
+
deviceId,
|
|
87
|
+
capabilities: models.length > 0 ? { models } : undefined,
|
|
88
|
+
},
|
|
89
|
+
token,
|
|
90
|
+
reconnectDelay: 3000,
|
|
91
|
+
}, keyManager);
|
|
92
|
+
relay.onStateChange = (state) => {
|
|
93
|
+
logger.info({ state }, 'Relay connection state changed');
|
|
94
|
+
};
|
|
95
|
+
relay.onAuthenticated = (info) => {
|
|
96
|
+
logger.info({
|
|
97
|
+
channel: info.channel,
|
|
98
|
+
deviceId: info.deviceId,
|
|
99
|
+
e2e: info.e2e,
|
|
100
|
+
devices: info.devices.length,
|
|
101
|
+
sessions: info.sessions.length,
|
|
102
|
+
}, 'Connected to relay');
|
|
103
|
+
};
|
|
104
|
+
relay.onFatalError = (message) => {
|
|
105
|
+
logger.fatal({ message }, 'Relay fatal error');
|
|
106
|
+
};
|
|
107
|
+
relay.connect();
|
|
108
|
+
logger.info({ relay: config.relay, device: config.device.name }, 'Daemon running');
|
|
109
|
+
// 6. Graceful shutdown
|
|
110
|
+
const shutdown = async () => {
|
|
111
|
+
logger.info('Shutting down…');
|
|
112
|
+
relay.disconnect();
|
|
113
|
+
await adapter.stop();
|
|
114
|
+
};
|
|
115
|
+
process.on('SIGTERM', async () => { await shutdown(); process.exit(0); });
|
|
116
|
+
process.on('SIGINT', async () => { await shutdown(); process.exit(0); });
|
|
117
|
+
return { adapter, relay, sessionManager, shutdown };
|
|
118
|
+
}
|
|
119
|
+
// Auto-run when executed directly (not imported for testing)
|
|
120
|
+
const isDirectRun = process.argv[1]?.endsWith('daemon-worker.js') || process.argv[1]?.endsWith('daemon-worker.ts');
|
|
121
|
+
if (isDirectRun) {
|
|
122
|
+
startWorker().catch((err) => {
|
|
123
|
+
logger.fatal({ err }, 'Daemon failed to start');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=daemon-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon-worker.js","sourceRoot":"","sources":["../src/daemon-worker.ts"],"names":[],"mappings":"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"}
|
package/dist/daemon.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon process management for Kraki tentacle.
|
|
3
|
+
*
|
|
4
|
+
* The daemon runs as a detached child process executing daemon-worker.js.
|
|
5
|
+
* Its PID is tracked in ~/.kraki/daemon.pid.
|
|
6
|
+
*/
|
|
7
|
+
import { type KrakiConfig } from './config.js';
|
|
8
|
+
export interface DaemonStatus {
|
|
9
|
+
running: boolean;
|
|
10
|
+
pid: number | null;
|
|
11
|
+
}
|
|
12
|
+
export declare function isDaemonRunning(): boolean;
|
|
13
|
+
export declare function getDaemonStatus(): DaemonStatus;
|
|
14
|
+
export declare function startDaemon(_config: KrakiConfig): number;
|
|
15
|
+
export declare function stopDaemon(): boolean;
|
package/dist/daemon.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon process management for Kraki tentacle.
|
|
3
|
+
*
|
|
4
|
+
* The daemon runs as a detached child process executing daemon-worker.js.
|
|
5
|
+
* Its PID is tracked in ~/.kraki/daemon.pid.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn, execSync } from 'node:child_process';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { saveDaemonPid, loadDaemonPid, clearDaemonPid, } from './config.js';
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
export function isDaemonRunning() {
|
|
13
|
+
const pid = loadDaemonPid();
|
|
14
|
+
if (pid === null)
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
process.kill(pid, 0);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Process doesn't exist — stale PID file
|
|
22
|
+
clearDaemonPid();
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function getDaemonStatus() {
|
|
27
|
+
const pid = loadDaemonPid();
|
|
28
|
+
if (pid === null)
|
|
29
|
+
return { running: false, pid: null };
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return { running: true, pid };
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
clearDaemonPid();
|
|
36
|
+
return { running: false, pid: null };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// ── Start / Stop ────────────────────────────────────────
|
|
40
|
+
export function startDaemon(_config) {
|
|
41
|
+
// Kill any existing daemon(s) before starting a new one
|
|
42
|
+
stopDaemon();
|
|
43
|
+
// Detect if running from source (.ts) or built (.js)
|
|
44
|
+
const currentUrl = import.meta.url;
|
|
45
|
+
const isTsSource = currentUrl.endsWith('.ts');
|
|
46
|
+
const workerFile = isTsSource ? 'daemon-worker.ts' : 'daemon-worker.js';
|
|
47
|
+
const workerPath = join(__dirname, workerFile);
|
|
48
|
+
let runtime;
|
|
49
|
+
let args;
|
|
50
|
+
if (isTsSource) {
|
|
51
|
+
// In dev: use node with --import tsx for ESM TypeScript support
|
|
52
|
+
runtime = process.execPath; // full path to node
|
|
53
|
+
args = ['--import', 'tsx', workerPath];
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
runtime = process.execPath;
|
|
57
|
+
args = [workerPath];
|
|
58
|
+
}
|
|
59
|
+
// Ensure node_modules/.bin is in PATH for tsx resolution
|
|
60
|
+
const projectRoot = join(__dirname, '..', '..');
|
|
61
|
+
const binPath = join(projectRoot, 'node_modules', '.bin');
|
|
62
|
+
const env = {
|
|
63
|
+
...process.env,
|
|
64
|
+
NODE_ENV: 'production',
|
|
65
|
+
NODE_PATH: join(projectRoot, 'node_modules'),
|
|
66
|
+
PATH: `${binPath}:${process.env.PATH ?? ''}`,
|
|
67
|
+
};
|
|
68
|
+
const child = spawn(runtime, args, {
|
|
69
|
+
detached: true,
|
|
70
|
+
stdio: 'ignore',
|
|
71
|
+
cwd: projectRoot,
|
|
72
|
+
env,
|
|
73
|
+
});
|
|
74
|
+
child.unref();
|
|
75
|
+
const pid = child.pid;
|
|
76
|
+
saveDaemonPid(pid);
|
|
77
|
+
return pid;
|
|
78
|
+
}
|
|
79
|
+
export function stopDaemon() {
|
|
80
|
+
const pid = loadDaemonPid();
|
|
81
|
+
if (pid !== null) {
|
|
82
|
+
try {
|
|
83
|
+
process.kill(pid, 'SIGTERM');
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// Process already gone
|
|
87
|
+
}
|
|
88
|
+
clearDaemonPid();
|
|
89
|
+
}
|
|
90
|
+
// Kill any orphaned daemon-worker processes (missed by PID tracking)
|
|
91
|
+
killOrphanedWorkers();
|
|
92
|
+
return pid !== null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Find and kill any daemon-worker processes not tracked by the PID file.
|
|
96
|
+
*/
|
|
97
|
+
function killOrphanedWorkers() {
|
|
98
|
+
try {
|
|
99
|
+
const output = execSync('ps -eo pid,command', { encoding: 'utf8' });
|
|
100
|
+
for (const line of output.split('\n')) {
|
|
101
|
+
if (line.includes('daemon-worker') && !line.includes('grep')) {
|
|
102
|
+
const pidStr = line.trim().split(/\s+/)[0];
|
|
103
|
+
const orphanPid = parseInt(pidStr, 10);
|
|
104
|
+
if (orphanPid && orphanPid !== process.pid) {
|
|
105
|
+
try {
|
|
106
|
+
process.kill(orphanPid, 'SIGTERM');
|
|
107
|
+
}
|
|
108
|
+
catch { /* already gone */ }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// ps not available — skip orphan cleanup
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=daemon.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { AgentAdapter, CopilotAdapter, parsePermission } from './adapters/index.js';
|
|
2
|
+
export type { CreateSessionConfig, SessionInfo, PermissionDecision, SessionCreatedEvent, MessageEvent, MessageDeltaEvent, PermissionRequestEvent, QuestionRequestEvent, ToolStartEvent, ToolCompleteEvent, SessionEndedEvent, ErrorEvent, ParsedPermission, } from './adapters/index.js';
|
|
3
|
+
export { loadConfig, saveConfig, configExists, getOrCreateDeviceId } from './config.js';
|
|
4
|
+
export { SessionManager } from './session-manager.js';
|
|
5
|
+
export type { SessionContext, SessionMeta, RunRecord } from './session-manager.js';
|
|
6
|
+
export { RelayClient } from './relay-client.js';
|
|
7
|
+
export type { RelayClientOptions, RelayClientState } from './relay-client.js';
|
|
8
|
+
export { KeyManager } from './key-manager.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// kraki — Kraki agent bridge
|
|
2
|
+
//
|
|
3
|
+
// The tentacle connects coding agents to the Kraki relay.
|
|
4
|
+
// It wraps agent SDKs via adapters and translates their events
|
|
5
|
+
// into the @kraki/protocol message types.
|
|
6
|
+
export { AgentAdapter, CopilotAdapter, parsePermission } from './adapters/index.js';
|
|
7
|
+
export { loadConfig, saveConfig, configExists, getOrCreateDeviceId } from './config.js';
|
|
8
|
+
export { SessionManager } from './session-manager.js';
|
|
9
|
+
export { RelayClient } from './relay-client.js';
|
|
10
|
+
export { KeyManager } from './key-manager.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,0DAA0D;AAC1D,+DAA+D;AAC/D,0CAA0C;AAE1C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAkBpF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tentacle key management for E2E encryption.
|
|
3
|
+
*
|
|
4
|
+
* Generates and persists RSA keypair on first run.
|
|
5
|
+
* Provides encrypt/decrypt helpers using @kraki/crypto.
|
|
6
|
+
*/
|
|
7
|
+
import type { KeyPair, EncryptedPayload, RecipientKey } from '@kraki/crypto';
|
|
8
|
+
export declare class KeyManager {
|
|
9
|
+
private keysDir;
|
|
10
|
+
private keyPair;
|
|
11
|
+
constructor(keysDir?: string);
|
|
12
|
+
/**
|
|
13
|
+
* Get or create the device keypair. Generated once, persisted to disk.
|
|
14
|
+
*/
|
|
15
|
+
getKeyPair(): KeyPair;
|
|
16
|
+
/**
|
|
17
|
+
* Get compact public key for sending to the head during auth.
|
|
18
|
+
*/
|
|
19
|
+
getCompactPublicKey(): string;
|
|
20
|
+
/**
|
|
21
|
+
* Encrypt a message payload for a set of recipient devices.
|
|
22
|
+
*/
|
|
23
|
+
encryptForRecipients(plaintext: string, recipients: RecipientKey[]): EncryptedPayload;
|
|
24
|
+
/**
|
|
25
|
+
* Decrypt a message payload intended for this device.
|
|
26
|
+
*/
|
|
27
|
+
decryptForMe(payload: EncryptedPayload, myDeviceId: string): string;
|
|
28
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tentacle key management for E2E encryption.
|
|
3
|
+
*
|
|
4
|
+
* Generates and persists RSA keypair on first run.
|
|
5
|
+
* Provides encrypt/decrypt helpers using @kraki/crypto.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { generateKeyPair, exportPublicKey, encrypt, decrypt } from '@kraki/crypto';
|
|
10
|
+
import { getConfigDir } from './config.js';
|
|
11
|
+
const KEYS_DIR_NAME = 'keys';
|
|
12
|
+
const PRIVATE_KEY_FILE = 'private.pem';
|
|
13
|
+
const PUBLIC_KEY_FILE = 'public.pem';
|
|
14
|
+
export class KeyManager {
|
|
15
|
+
keysDir;
|
|
16
|
+
keyPair = null;
|
|
17
|
+
constructor(keysDir) {
|
|
18
|
+
this.keysDir = keysDir ?? join(getConfigDir(), KEYS_DIR_NAME);
|
|
19
|
+
mkdirSync(this.keysDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get or create the device keypair. Generated once, persisted to disk.
|
|
23
|
+
*/
|
|
24
|
+
getKeyPair() {
|
|
25
|
+
if (this.keyPair)
|
|
26
|
+
return this.keyPair;
|
|
27
|
+
const privPath = join(this.keysDir, PRIVATE_KEY_FILE);
|
|
28
|
+
const pubPath = join(this.keysDir, PUBLIC_KEY_FILE);
|
|
29
|
+
if (existsSync(privPath) && existsSync(pubPath)) {
|
|
30
|
+
this.keyPair = {
|
|
31
|
+
privateKey: readFileSync(privPath, 'utf8'),
|
|
32
|
+
publicKey: readFileSync(pubPath, 'utf8'),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
this.keyPair = generateKeyPair();
|
|
37
|
+
writeFileSync(privPath, this.keyPair.privateKey, { mode: 0o600 });
|
|
38
|
+
writeFileSync(pubPath, this.keyPair.publicKey, { mode: 0o644 });
|
|
39
|
+
}
|
|
40
|
+
return this.keyPair;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get compact public key for sending to the head during auth.
|
|
44
|
+
*/
|
|
45
|
+
getCompactPublicKey() {
|
|
46
|
+
return exportPublicKey(this.getKeyPair().publicKey);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Encrypt a message payload for a set of recipient devices.
|
|
50
|
+
*/
|
|
51
|
+
encryptForRecipients(plaintext, recipients) {
|
|
52
|
+
return encrypt(plaintext, recipients);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decrypt a message payload intended for this device.
|
|
56
|
+
*/
|
|
57
|
+
decryptForMe(payload, myDeviceId) {
|
|
58
|
+
return decrypt(payload, myDeviceId, this.getKeyPair().privateKey);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=key-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-manager.js","sourceRoot":"","sources":["../src/key-manager.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,eAAe,EAAmB,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAEpG,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,MAAM,gBAAgB,GAAG,aAAa,CAAC;AACvC,MAAM,eAAe,GAAG,YAAY,CAAC;AAErC,MAAM,OAAO,UAAU;IACb,OAAO,CAAS;IAChB,OAAO,GAAmB,IAAI,CAAC;IAEvC,YAAY,OAAgB;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,IAAI,IAAI,CAAC,YAAY,EAAE,EAAE,aAAa,CAAC,CAAC;QAC9D,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;QAEtC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;QAEpD,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC,OAAO,GAAG;gBACb,UAAU,EAAE,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC;gBAC1C,SAAS,EAAE,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC;aACzC,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,GAAG,eAAe,EAAE,CAAC;YACjC,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;YAClE,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED;;OAEG;IACH,mBAAmB;QACjB,OAAO,eAAe,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC,CAAC;IACtD,CAAC;IAED;;OAEG;IACH,oBAAoB,CAAC,SAAiB,EAAE,UAA0B;QAChE,OAAO,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACxC,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,OAAyB,EAAE,UAAkB;QACxD,OAAO,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC;IACpE,CAAC;CACF"}
|
package/dist/logger.d.ts
ADDED
package/dist/logger.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured logging for Kraki tentacle.
|
|
3
|
+
*
|
|
4
|
+
* - Development: pretty-prints to stdout
|
|
5
|
+
* - Production: writes to rotating log files under ~/.kraki/logs/
|
|
6
|
+
*/
|
|
7
|
+
import pino from 'pino';
|
|
8
|
+
import { mkdirSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homedir } from 'node:os';
|
|
11
|
+
const LOG_DIR = join(homedir(), '.kraki', 'logs');
|
|
12
|
+
export function createLogger(name) {
|
|
13
|
+
const level = process.env.LOG_LEVEL ?? 'info';
|
|
14
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
15
|
+
if (isDev) {
|
|
16
|
+
return pino({ name, level });
|
|
17
|
+
}
|
|
18
|
+
// Production: rotate log files via pino-roll
|
|
19
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
20
|
+
const transport = pino.transport({
|
|
21
|
+
target: 'pino-roll',
|
|
22
|
+
options: {
|
|
23
|
+
file: join(LOG_DIR, `${name}.log`),
|
|
24
|
+
size: '5m',
|
|
25
|
+
limit: { count: 5 },
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
return pino({ name, level }, transport);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAElD,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,MAAM,CAAC;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;IAEpD,IAAI,KAAK,EAAE,CAAC;QACV,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,6CAA6C;IAC7C,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAExC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAC/B,MAAM,EAAE,WAAW;QACnB,OAAO,EAAE;YACP,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,IAAI,MAAM,CAAC;YAClC,IAAI,EAAE,IAAI;YACV,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE;SACpB;KACF,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;AAC1C,CAAC"}
|
package/dist/pair.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pairing command — generates a QR code for mobile app pairing.
|
|
3
|
+
*
|
|
4
|
+
* Requests a one-time pairing token from the head,
|
|
5
|
+
* then displays a QR code in the terminal containing:
|
|
6
|
+
* - relay URL
|
|
7
|
+
* - pairing token (expires in 5 min)
|
|
8
|
+
* - tentacle's public key (for E2E)
|
|
9
|
+
*/
|
|
10
|
+
export interface PairingInfo {
|
|
11
|
+
relay: string;
|
|
12
|
+
pairingToken: string;
|
|
13
|
+
publicKey?: string;
|
|
14
|
+
expiresIn: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Request a pairing token from the head and return pairing info.
|
|
18
|
+
*/
|
|
19
|
+
export declare function requestPairingToken(relayUrl: string, authToken?: string): Promise<PairingInfo>;
|
|
20
|
+
/**
|
|
21
|
+
* Generate the pairing URL that will be encoded in the QR code.
|
|
22
|
+
* Phone camera scans → opens this URL → web app auto-pairs.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildPairingUrl(info: PairingInfo, appBaseUrl?: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Legacy: build compact JSON payload (for manual transfer).
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildPairingPayload(info: PairingInfo): string;
|
|
29
|
+
/**
|
|
30
|
+
* Render a QR code to the terminal. Copy link to clipboard.
|
|
31
|
+
*/
|
|
32
|
+
export declare function renderQrToTerminal(url: string): Promise<string>;
|