@kraki/tentacle 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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/adapters/base.d.ts +107 -0
  3. package/dist/adapters/base.js +32 -0
  4. package/dist/adapters/base.js.map +1 -0
  5. package/dist/adapters/copilot.d.ts +57 -0
  6. package/dist/adapters/copilot.js +489 -0
  7. package/dist/adapters/copilot.js.map +1 -0
  8. package/dist/adapters/index.d.ts +5 -0
  9. package/dist/adapters/index.js +4 -0
  10. package/dist/adapters/index.js.map +1 -0
  11. package/dist/banner-data.json +1 -0
  12. package/dist/banner.d.ts +7 -0
  13. package/dist/banner.js +187 -0
  14. package/dist/banner.js.map +1 -0
  15. package/dist/checks.d.ts +26 -0
  16. package/dist/checks.js +74 -0
  17. package/dist/checks.js.map +1 -0
  18. package/dist/cli.d.ts +15 -0
  19. package/dist/cli.js +306 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/config.d.ts +38 -0
  22. package/dist/config.js +113 -0
  23. package/dist/config.js.map +1 -0
  24. package/dist/daemon-worker.d.ts +21 -0
  25. package/dist/daemon-worker.js +127 -0
  26. package/dist/daemon-worker.js.map +1 -0
  27. package/dist/daemon.d.ts +24 -0
  28. package/dist/daemon.js +163 -0
  29. package/dist/daemon.js.map +1 -0
  30. package/dist/index.d.ts +8 -0
  31. package/dist/index.js +11 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/key-manager.d.ts +28 -0
  34. package/dist/key-manager.js +61 -0
  35. package/dist/key-manager.js.map +1 -0
  36. package/dist/logger.d.ts +8 -0
  37. package/dist/logger.js +30 -0
  38. package/dist/logger.js.map +1 -0
  39. package/dist/pair.d.ts +32 -0
  40. package/dist/pair.js +131 -0
  41. package/dist/pair.js.map +1 -0
  42. package/dist/parse-permission.d.ts +25 -0
  43. package/dist/parse-permission.js +67 -0
  44. package/dist/parse-permission.js.map +1 -0
  45. package/dist/relay-client.d.ts +90 -0
  46. package/dist/relay-client.js +525 -0
  47. package/dist/relay-client.js.map +1 -0
  48. package/dist/session-manager.d.ts +85 -0
  49. package/dist/session-manager.js +218 -0
  50. package/dist/session-manager.js.map +1 -0
  51. package/dist/setup.d.ts +13 -0
  52. package/dist/setup.js +234 -0
  53. package/dist/setup.js.map +1 -0
  54. package/package.json +48 -0
@@ -0,0 +1,38 @@
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
+ export type KrakiLogVerbosity = 'normal' | 'verbose';
9
+ export interface KrakiConfig {
10
+ relay: string;
11
+ authMethod: 'github' | 'channel-key' | 'open';
12
+ device: {
13
+ name: string;
14
+ id?: string;
15
+ };
16
+ logging?: {
17
+ verbosity?: KrakiLogVerbosity;
18
+ };
19
+ }
20
+ export declare const DEFAULT_LOG_VERBOSITY: KrakiLogVerbosity;
21
+ export declare function getConfigDir(): string;
22
+ /**
23
+ * Get the stable device ID for this machine.
24
+ * Generated once and persisted at ~/.kraki/device-id.
25
+ * Sent to the head on auth so reconnections don't create ghost devices.
26
+ */
27
+ export declare function getOrCreateDeviceId(): string;
28
+ export declare function configExists(): boolean;
29
+ export declare function getLogVerbosity(config: Pick<KrakiConfig, 'logging'> | null | undefined): KrakiLogVerbosity;
30
+ export declare function loadConfig(): KrakiConfig | null;
31
+ export declare function saveConfig(config: KrakiConfig): void;
32
+ export declare function getChannelKeyPath(): string;
33
+ export declare function saveChannelKey(key: string): void;
34
+ export declare function loadChannelKey(): string | null;
35
+ export declare function getDaemonPidPath(): string;
36
+ export declare function saveDaemonPid(pid: number): void;
37
+ export declare function loadDaemonPid(): number | null;
38
+ export declare function clearDaemonPid(): void;
package/dist/config.js ADDED
@@ -0,0 +1,113 @@
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
+ export const DEFAULT_LOG_VERBOSITY = 'normal';
13
+ // ── Paths ───────────────────────────────────────────────
14
+ const CONFIG_DIR = join(homedir(), '.kraki');
15
+ const CONFIG_PATH = join(CONFIG_DIR, 'config.json');
16
+ const CHANNEL_KEY_PATH = join(CONFIG_DIR, 'channel.key');
17
+ const DAEMON_PID_PATH = join(CONFIG_DIR, 'daemon.pid');
18
+ const DEVICE_ID_PATH = join(CONFIG_DIR, 'device-id');
19
+ export function getConfigDir() {
20
+ mkdirSync(CONFIG_DIR, { recursive: true });
21
+ return CONFIG_DIR;
22
+ }
23
+ // ── Device ID ───────────────────────────────────────────
24
+ /**
25
+ * Get the stable device ID for this machine.
26
+ * Generated once and persisted at ~/.kraki/device-id.
27
+ * Sent to the head on auth so reconnections don't create ghost devices.
28
+ */
29
+ export function getOrCreateDeviceId() {
30
+ try {
31
+ const existing = readFileSync(DEVICE_ID_PATH, 'utf8').trim();
32
+ if (existing)
33
+ return existing;
34
+ }
35
+ catch {
36
+ // File doesn't exist — generate one
37
+ }
38
+ const id = `dev_${randomUUID().slice(0, 12)}`;
39
+ getConfigDir();
40
+ writeFileSync(DEVICE_ID_PATH, id, 'utf8');
41
+ return id;
42
+ }
43
+ // ── Config ──────────────────────────────────────────────
44
+ export function configExists() {
45
+ return existsSync(CONFIG_PATH);
46
+ }
47
+ export function getLogVerbosity(config) {
48
+ return config?.logging?.verbosity === 'verbose' ? 'verbose' : DEFAULT_LOG_VERBOSITY;
49
+ }
50
+ function normalizeConfig(config) {
51
+ return {
52
+ ...config,
53
+ device: { ...config.device },
54
+ logging: { verbosity: getLogVerbosity(config) },
55
+ };
56
+ }
57
+ export function loadConfig() {
58
+ try {
59
+ const raw = readFileSync(CONFIG_PATH, 'utf8');
60
+ return normalizeConfig(JSON.parse(raw));
61
+ }
62
+ catch {
63
+ return null;
64
+ }
65
+ }
66
+ export function saveConfig(config) {
67
+ getConfigDir();
68
+ writeFileSync(CONFIG_PATH, JSON.stringify(normalizeConfig(config), null, 2) + '\n', 'utf8');
69
+ }
70
+ // ── Channel key ─────────────────────────────────────────
71
+ export function getChannelKeyPath() {
72
+ return CHANNEL_KEY_PATH;
73
+ }
74
+ export function saveChannelKey(key) {
75
+ getConfigDir();
76
+ writeFileSync(CHANNEL_KEY_PATH, key, 'utf8');
77
+ chmodSync(CHANNEL_KEY_PATH, 0o600);
78
+ }
79
+ export function loadChannelKey() {
80
+ try {
81
+ return readFileSync(CHANNEL_KEY_PATH, 'utf8').trim();
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ }
87
+ // ── Daemon PID ──────────────────────────────────────────
88
+ export function getDaemonPidPath() {
89
+ return DAEMON_PID_PATH;
90
+ }
91
+ export function saveDaemonPid(pid) {
92
+ getConfigDir();
93
+ writeFileSync(DAEMON_PID_PATH, String(pid), 'utf8');
94
+ }
95
+ export function loadDaemonPid() {
96
+ try {
97
+ const raw = readFileSync(DAEMON_PID_PATH, 'utf8').trim();
98
+ const pid = parseInt(raw, 10);
99
+ return Number.isFinite(pid) ? pid : null;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ export function clearDaemonPid() {
106
+ try {
107
+ unlinkSync(DAEMON_PID_PATH);
108
+ }
109
+ catch {
110
+ // File may not exist — that's fine
111
+ }
112
+ }
113
+ //# 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.debug('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.debug('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.debug({ 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.debug({ 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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"}
@@ -0,0 +1,24 @@
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 DaemonLaunchSpec {
9
+ runtime: string;
10
+ args: string[];
11
+ cwd: string;
12
+ env: NodeJS.ProcessEnv;
13
+ workerPath: string;
14
+ }
15
+ export declare function getDaemonBootstrapLogPath(): string;
16
+ export declare function resolveDaemonLaunch(currentUrl?: string): DaemonLaunchSpec;
17
+ export interface DaemonStatus {
18
+ running: boolean;
19
+ pid: number | null;
20
+ }
21
+ export declare function isDaemonRunning(): boolean;
22
+ export declare function getDaemonStatus(): DaemonStatus;
23
+ export declare function startDaemon(config: KrakiConfig): Promise<number>;
24
+ export declare function stopDaemon(): boolean;
package/dist/daemon.js ADDED
@@ -0,0 +1,163 @@
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 { closeSync, mkdirSync, openSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join, dirname, resolve } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { getLogVerbosity, saveDaemonPid, loadDaemonPid, clearDaemonPid, } from './config.js';
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const STARTUP_GRACE_MS = 1500;
15
+ const BOOTSTRAP_LOG_PATH = join(homedir(), '.kraki', 'logs', 'daemon-bootstrap.log');
16
+ export function getDaemonBootstrapLogPath() {
17
+ return BOOTSTRAP_LOG_PATH;
18
+ }
19
+ export function resolveDaemonLaunch(currentUrl = import.meta.url) {
20
+ const moduleDir = dirname(fileURLToPath(currentUrl));
21
+ const isTsSource = currentUrl.endsWith('.ts');
22
+ const packageRoot = resolve(moduleDir, '..');
23
+ const workspaceRoot = resolve(packageRoot, '..', '..');
24
+ const workerFile = isTsSource ? 'daemon-worker.ts' : 'daemon-worker.js';
25
+ const workerPath = join(moduleDir, workerFile);
26
+ const binPaths = isTsSource
27
+ ? [join(workspaceRoot, 'node_modules', '.bin'), join(packageRoot, 'node_modules', '.bin')]
28
+ : [join(packageRoot, 'node_modules', '.bin')];
29
+ return {
30
+ runtime: process.execPath,
31
+ args: isTsSource ? ['--import', 'tsx', workerPath] : [workerPath],
32
+ cwd: isTsSource ? workspaceRoot : packageRoot,
33
+ env: {
34
+ ...process.env,
35
+ NODE_ENV: 'production',
36
+ PATH: [...binPaths, process.env.PATH ?? ''].filter(Boolean).join(':'),
37
+ },
38
+ workerPath,
39
+ };
40
+ }
41
+ function waitForDaemonBootstrap(child, timeoutMs = STARTUP_GRACE_MS) {
42
+ return new Promise((resolve, reject) => {
43
+ const cleanup = () => {
44
+ clearTimeout(timer);
45
+ child.off('error', onError);
46
+ child.off('exit', onExit);
47
+ };
48
+ const onError = (err) => {
49
+ cleanup();
50
+ reject(new Error(`Kraki failed to start: ${err.message}. Check ${getDaemonBootstrapLogPath()}`));
51
+ };
52
+ const onExit = (code, signal) => {
53
+ cleanup();
54
+ reject(new Error(`Kraki exited during startup (code ${code ?? 'null'}, signal ${signal ?? 'none'}). Check ${getDaemonBootstrapLogPath()}`));
55
+ };
56
+ const timer = setTimeout(() => {
57
+ cleanup();
58
+ resolve();
59
+ }, timeoutMs);
60
+ child.once('error', onError);
61
+ child.once('exit', onExit);
62
+ });
63
+ }
64
+ export function isDaemonRunning() {
65
+ const pid = loadDaemonPid();
66
+ if (pid === null)
67
+ return false;
68
+ try {
69
+ process.kill(pid, 0);
70
+ return true;
71
+ }
72
+ catch {
73
+ // Process doesn't exist — stale PID file
74
+ clearDaemonPid();
75
+ return false;
76
+ }
77
+ }
78
+ export function getDaemonStatus() {
79
+ const pid = loadDaemonPid();
80
+ if (pid === null)
81
+ return { running: false, pid: null };
82
+ try {
83
+ process.kill(pid, 0);
84
+ return { running: true, pid };
85
+ }
86
+ catch {
87
+ clearDaemonPid();
88
+ return { running: false, pid: null };
89
+ }
90
+ }
91
+ // ── Start / Stop ────────────────────────────────────────
92
+ export async function startDaemon(config) {
93
+ // Kill any existing daemon(s) before starting a new one
94
+ stopDaemon();
95
+ const launch = resolveDaemonLaunch();
96
+ launch.env.LOG_LEVEL = getLogVerbosity(config) === 'verbose' ? 'debug' : 'info';
97
+ mkdirSync(dirname(BOOTSTRAP_LOG_PATH), { recursive: true });
98
+ const bootstrapFd = openSync(BOOTSTRAP_LOG_PATH, 'w');
99
+ const child = spawn(launch.runtime, launch.args, {
100
+ detached: true,
101
+ stdio: ['ignore', bootstrapFd, bootstrapFd],
102
+ cwd: launch.cwd,
103
+ env: launch.env,
104
+ });
105
+ closeSync(bootstrapFd);
106
+ if (!child.pid) {
107
+ throw new Error(`Kraki failed to start: no daemon PID returned. Check ${getDaemonBootstrapLogPath()}`);
108
+ }
109
+ try {
110
+ await waitForDaemonBootstrap(child);
111
+ }
112
+ catch (err) {
113
+ try {
114
+ process.kill(child.pid, 'SIGTERM');
115
+ }
116
+ catch {
117
+ // Child may already be gone
118
+ }
119
+ throw err;
120
+ }
121
+ child.unref();
122
+ saveDaemonPid(child.pid);
123
+ return child.pid;
124
+ }
125
+ export function stopDaemon() {
126
+ const pid = loadDaemonPid();
127
+ if (pid !== null) {
128
+ try {
129
+ process.kill(pid, 'SIGTERM');
130
+ }
131
+ catch {
132
+ // Process already gone
133
+ }
134
+ clearDaemonPid();
135
+ }
136
+ // Kill any orphaned daemon-worker processes (missed by PID tracking)
137
+ killOrphanedWorkers();
138
+ return pid !== null;
139
+ }
140
+ /**
141
+ * Find and kill any daemon-worker processes not tracked by the PID file.
142
+ */
143
+ function killOrphanedWorkers() {
144
+ try {
145
+ const output = execSync('ps -eo pid,command', { encoding: 'utf8' });
146
+ for (const line of output.split('\n')) {
147
+ if (line.includes('daemon-worker') && !line.includes('grep')) {
148
+ const pidStr = line.trim().split(/\s+/)[0];
149
+ const orphanPid = parseInt(pidStr, 10);
150
+ if (orphanPid && orphanPid !== process.pid) {
151
+ try {
152
+ process.kill(orphanPid, 'SIGTERM');
153
+ }
154
+ catch { /* already gone */ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ catch {
160
+ // ps not available — skip orphan cleanup
161
+ }
162
+ }
163
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -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"}
@@ -0,0 +1,8 @@
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
+ export declare function createLogger(name: string): pino.Logger;