@rederjs/daemon 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/README.md +21 -0
- package/dist/adapter-host.d.ts +32 -0
- package/dist/adapter-host.d.ts.map +1 -0
- package/dist/adapter-host.js +95 -0
- package/dist/adapter-host.js.map +1 -0
- package/dist/bootstrap.d.ts +29 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +222 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/lifecycle.d.ts +17 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +91 -0
- package/dist/lifecycle.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edward de Groot
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @rederjs/daemon
|
|
2
|
+
|
|
3
|
+
The reder daemon (`rederd`) — a long-running process that hosts the Unix socket every Claude Code shim connects to, owns the SQLite store, runs the configured adapters, and serves the web dashboard. Part of the [reder](https://github.com/RederJS/rederjs) project.
|
|
4
|
+
|
|
5
|
+
You typically don't install this package directly. It comes along with the CLI:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install -g rederjs
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
That ships the `rederd` binary on your `PATH` alongside `reder` and `reder-shim`.
|
|
12
|
+
|
|
13
|
+
Run it via the CLI rather than invoking it by hand:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
reder start # start the daemon (writes a systemd user unit on Linux)
|
|
17
|
+
reder status # query its HTTP API
|
|
18
|
+
reder restart # restart after a config change
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
See also: the [main repo README](https://github.com/RederJS/rederjs#readme) for the architecture diagram and configuration reference.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Database as Db } from 'better-sqlite3';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
import { Adapter, type RouterHandle } from '@rederjs/core/adapter';
|
|
4
|
+
import type { AuditLog } from '@rederjs/core/audit';
|
|
5
|
+
import type { Config } from '@rederjs/core/config';
|
|
6
|
+
export interface AdapterFactory {
|
|
7
|
+
(cfg: unknown): Promise<Adapter> | Adapter;
|
|
8
|
+
}
|
|
9
|
+
export interface AdapterHostDeps {
|
|
10
|
+
db: Db;
|
|
11
|
+
config: Config;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
audit: AuditLog;
|
|
14
|
+
router: RouterHandle;
|
|
15
|
+
dataDir: string;
|
|
16
|
+
configDir: string;
|
|
17
|
+
resolveModule: (spec: string) => Promise<AdapterFactory>;
|
|
18
|
+
healthSnapshot?: () => Promise<unknown>;
|
|
19
|
+
}
|
|
20
|
+
export interface LoadedAdapter {
|
|
21
|
+
name: string;
|
|
22
|
+
module: string;
|
|
23
|
+
adapter: Adapter;
|
|
24
|
+
}
|
|
25
|
+
export declare function loadAdapter(spec: string): Promise<AdapterFactory>;
|
|
26
|
+
export interface AdapterHost {
|
|
27
|
+
readonly loaded: readonly LoadedAdapter[];
|
|
28
|
+
startAll(register: (name: string, adapter: Adapter) => void): Promise<void>;
|
|
29
|
+
stopAll(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare function createAdapterHost(deps: AdapterHostDeps): Promise<AdapterHost>;
|
|
32
|
+
//# sourceMappingURL=adapter-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-host.d.ts","sourceRoot":"","sources":["../src/adapter-host.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,EAAE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAExF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC5C;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,EAAE,CAAC;IACP,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACzD,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBvE;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC,CA2EnF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { createAdapterStorage } from '@rederjs/core/storage/kv';
|
|
3
|
+
export async function loadAdapter(spec) {
|
|
4
|
+
const imported = (await import(spec));
|
|
5
|
+
if (typeof imported.createAdapter === 'function') {
|
|
6
|
+
return imported.createAdapter;
|
|
7
|
+
}
|
|
8
|
+
const exported = imported.default;
|
|
9
|
+
if (typeof exported === 'function') {
|
|
10
|
+
return (async (cfg) => {
|
|
11
|
+
const AdapterClass = exported;
|
|
12
|
+
return new AdapterClass(cfg);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`adapter module '${spec}' must export either a default Adapter class or a createAdapter() function`);
|
|
16
|
+
}
|
|
17
|
+
export async function createAdapterHost(deps) {
|
|
18
|
+
const loaded = [];
|
|
19
|
+
for (const [name, cfg] of Object.entries(deps.config.adapters)) {
|
|
20
|
+
if (!cfg.enabled)
|
|
21
|
+
continue;
|
|
22
|
+
const adapterLogger = deps.logger.child({
|
|
23
|
+
component: `adapter.${name}`,
|
|
24
|
+
adapter_module: cfg.module,
|
|
25
|
+
});
|
|
26
|
+
try {
|
|
27
|
+
const factory = await deps.resolveModule(cfg.module);
|
|
28
|
+
const adapter = await factory(cfg.config);
|
|
29
|
+
loaded.push({ name, module: cfg.module, adapter });
|
|
30
|
+
if (!cfg.module.startsWith('@rederjs/')) {
|
|
31
|
+
adapterLogger.warn({ module: cfg.module }, 'third-party adapter loaded');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
adapterLogger.error({ err }, 'failed to load adapter; skipping');
|
|
36
|
+
deps.audit.write({
|
|
37
|
+
kind: 'adapter_start',
|
|
38
|
+
adapter: name,
|
|
39
|
+
details: { error: String(err), failed: true },
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
get loaded() {
|
|
45
|
+
return loaded;
|
|
46
|
+
},
|
|
47
|
+
async startAll(register) {
|
|
48
|
+
for (const entry of loaded) {
|
|
49
|
+
const logger = deps.logger.child({ component: `adapter.${entry.name}` });
|
|
50
|
+
const ctx = {
|
|
51
|
+
logger,
|
|
52
|
+
config: deps.config.adapters[entry.name]?.config,
|
|
53
|
+
storage: createAdapterStorage(deps.db, entry.name),
|
|
54
|
+
router: deps.router,
|
|
55
|
+
dataDir: deps.dataDir,
|
|
56
|
+
sessions: deps.config.sessions.map((s) => ({
|
|
57
|
+
session_id: s.session_id,
|
|
58
|
+
display_name: s.display_name,
|
|
59
|
+
...(s.workspace_dir !== undefined ? { workspace_dir: s.workspace_dir } : {}),
|
|
60
|
+
...(s.avatar !== undefined ? { avatar_path: resolve(deps.configDir, s.avatar) } : {}),
|
|
61
|
+
auto_start: s.auto_start,
|
|
62
|
+
})),
|
|
63
|
+
db: deps.db,
|
|
64
|
+
...(deps.healthSnapshot ? { healthSnapshot: deps.healthSnapshot } : {}),
|
|
65
|
+
};
|
|
66
|
+
try {
|
|
67
|
+
await entry.adapter.start(ctx);
|
|
68
|
+
register(entry.name, entry.adapter);
|
|
69
|
+
deps.audit.write({ kind: 'adapter_start', adapter: entry.name });
|
|
70
|
+
logger.info('adapter started');
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
logger.error({ err }, 'adapter start failed');
|
|
74
|
+
deps.audit.write({
|
|
75
|
+
kind: 'adapter_start',
|
|
76
|
+
adapter: entry.name,
|
|
77
|
+
details: { error: String(err), failed: true },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
async stopAll() {
|
|
83
|
+
for (const entry of loaded) {
|
|
84
|
+
try {
|
|
85
|
+
await entry.adapter.stop();
|
|
86
|
+
deps.audit.write({ kind: 'adapter_stop', adapter: entry.name });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
deps.logger.error({ err, adapter: entry.name }, 'adapter stop failed');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=adapter-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter-host.js","sourceRoot":"","sources":["../src/adapter-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Logger } from 'pino';
|
|
2
|
+
import { type Config } from '@rederjs/core/config';
|
|
3
|
+
import { type DatabaseHandle } from '@rederjs/core/storage/db';
|
|
4
|
+
import { type IpcServer } from '@rederjs/core/ipc/server';
|
|
5
|
+
import { type Router } from '@rederjs/core/router';
|
|
6
|
+
import { type AuditLog } from '@rederjs/core/audit';
|
|
7
|
+
import { type HealthEndpoint } from '@rederjs/core/health';
|
|
8
|
+
import { type AdapterHost, loadAdapter } from './adapter-host.js';
|
|
9
|
+
export type { AdapterHost };
|
|
10
|
+
export interface BootstrapResult {
|
|
11
|
+
config: Config;
|
|
12
|
+
configPath: string;
|
|
13
|
+
logger: Logger;
|
|
14
|
+
db: DatabaseHandle;
|
|
15
|
+
ipcServer: IpcServer;
|
|
16
|
+
router: Router;
|
|
17
|
+
audit: AuditLog;
|
|
18
|
+
health: HealthEndpoint | null;
|
|
19
|
+
adapterHost: AdapterHost;
|
|
20
|
+
startedAt: Date;
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export interface BootstrapOptions {
|
|
24
|
+
configPath: string;
|
|
25
|
+
overrideResolveModule?: (spec: string) => Promise<Awaited<ReturnType<typeof loadAdapter>>>;
|
|
26
|
+
daemonVersion?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function bootstrap(opts: BootstrapOptions): Promise<BootstrapResult>;
|
|
29
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,EAAgB,KAAK,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAC3E,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAEL,KAAK,cAAc,EAEpB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAqB,KAAK,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrF,YAAY,EAAE,WAAW,EAAE,CAAC;AAuB5B,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,cAAc,CAAC;IACnB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;IAChB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAQD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkOhF"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
import { createLogger } from '@rederjs/core/logger';
|
|
5
|
+
import { loadConfig } from '@rederjs/core/config';
|
|
6
|
+
import { openDatabase } from '@rederjs/core/storage/db';
|
|
7
|
+
import { createSession } from '@rederjs/core/sessions';
|
|
8
|
+
import { createIpcServer } from '@rederjs/core/ipc/server';
|
|
9
|
+
import { createRouter } from '@rederjs/core/router';
|
|
10
|
+
import { createAuditLog } from '@rederjs/core/audit';
|
|
11
|
+
import { startHealthEndpoint, } from '@rederjs/core/health';
|
|
12
|
+
import { startSession as startTmuxSession, getPaneCommand } from '@rederjs/core/tmux';
|
|
13
|
+
import { createAdapterHost, loadAdapter } from './adapter-host.js';
|
|
14
|
+
/**
|
|
15
|
+
* Best-effort check that the Claude Code hooks were installed in a session's
|
|
16
|
+
* workspace. Lives in the `rederjs` (CLI) package — daemon dynamically imports
|
|
17
|
+
* it so the daemon can be installed/run without the CLI present (and to avoid
|
|
18
|
+
* a circular npm dep). If the CLI isn't on the module path, the check is
|
|
19
|
+
* silently skipped — it only drives a warning log, not behavior.
|
|
20
|
+
*/
|
|
21
|
+
async function checkClaudeHooks(args) {
|
|
22
|
+
try {
|
|
23
|
+
const mod = (await import('rederjs/commands/claude-hooks'));
|
|
24
|
+
return mod.hasClaudeHooks(args);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function expandHome(p) {
|
|
31
|
+
if (p.startsWith('~/'))
|
|
32
|
+
return join(homedir(), p.slice(2));
|
|
33
|
+
if (p === '~')
|
|
34
|
+
return homedir();
|
|
35
|
+
return resolve(p);
|
|
36
|
+
}
|
|
37
|
+
export async function bootstrap(opts) {
|
|
38
|
+
const startedAt = new Date();
|
|
39
|
+
const configPath = resolve(opts.configPath);
|
|
40
|
+
const configDir = dirname(configPath);
|
|
41
|
+
const config = loadConfig(configPath);
|
|
42
|
+
const runtimeDir = expandHome(config.runtime.runtime_dir);
|
|
43
|
+
const dataDir = expandHome(config.runtime.data_dir);
|
|
44
|
+
mkdirSync(runtimeDir, { recursive: true, mode: 0o700 });
|
|
45
|
+
mkdirSync(dataDir, { recursive: true, mode: 0o700 });
|
|
46
|
+
const logger = createLogger({ level: config.logging.level });
|
|
47
|
+
logger.info({ configPath, runtimeDir, dataDir, component: 'daemon.bootstrap' }, 'starting rederd');
|
|
48
|
+
const audit = createAuditLog(runtimeDir);
|
|
49
|
+
const db = openDatabase(join(dataDir, 'reder.db'));
|
|
50
|
+
// Ensure sessions declared in config exist in DB (registered state).
|
|
51
|
+
for (const s of config.sessions) {
|
|
52
|
+
const existing = db.raw
|
|
53
|
+
.prepare('SELECT session_id FROM sessions WHERE session_id = ?')
|
|
54
|
+
.get(s.session_id);
|
|
55
|
+
if (!existing) {
|
|
56
|
+
const { token } = await createSession(db.raw, s.session_id, s.display_name);
|
|
57
|
+
logger.warn({ session_id: s.session_id, component: 'daemon.bootstrap' }, 'session not previously registered; generated fresh token (run `reder sessions add` to retrieve it)');
|
|
58
|
+
// Token is logged only at trace to avoid persistent leakage
|
|
59
|
+
logger.trace({ token, session_id: s.session_id }, 'generated session token');
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const ipcSocket = join(runtimeDir, 'rederd.sock');
|
|
63
|
+
const ipcServer = await createIpcServer({
|
|
64
|
+
db: db.raw,
|
|
65
|
+
socketPath: ipcSocket,
|
|
66
|
+
logger: logger.child({ component: 'ipc.server' }),
|
|
67
|
+
});
|
|
68
|
+
const router = createRouter({
|
|
69
|
+
db: db.raw,
|
|
70
|
+
ipcServer,
|
|
71
|
+
logger: logger.child({ component: 'core.router' }),
|
|
72
|
+
audit,
|
|
73
|
+
permissions: {
|
|
74
|
+
timeoutSeconds: config.security.permission_timeout_seconds,
|
|
75
|
+
defaultOnTimeout: config.security.permission_default_on_timeout,
|
|
76
|
+
},
|
|
77
|
+
dataDir,
|
|
78
|
+
});
|
|
79
|
+
const daemonVersion = opts.daemonVersion ?? '0.1.0';
|
|
80
|
+
// Forward-reference to adapter host — set after we construct it below.
|
|
81
|
+
let adapterHostRef = null;
|
|
82
|
+
const snapshotFn = async () => {
|
|
83
|
+
const inbound = db.raw
|
|
84
|
+
.prepare(`SELECT COUNT(*) AS c FROM inbound_messages WHERE state IN ('received','delivered')`)
|
|
85
|
+
.get().c;
|
|
86
|
+
const outbound = db.raw
|
|
87
|
+
.prepare(`SELECT COUNT(*) AS c FROM outbound_messages WHERE state = 'pending'`)
|
|
88
|
+
.get().c;
|
|
89
|
+
const sessions = db.raw
|
|
90
|
+
.prepare(`SELECT session_id, state, last_seen_at FROM sessions ORDER BY session_id`)
|
|
91
|
+
.all().map((r) => ({ session_id: r.session_id, state: r.state, last_seen_at: r.last_seen_at }));
|
|
92
|
+
const adapterSnaps = [];
|
|
93
|
+
const entries = adapterHostRef?.loaded ?? [];
|
|
94
|
+
for (const entry of entries) {
|
|
95
|
+
const h = entry.adapter.healthCheck ? await entry.adapter.healthCheck() : null;
|
|
96
|
+
adapterSnaps.push({
|
|
97
|
+
name: entry.name,
|
|
98
|
+
healthy: h?.healthy ?? true,
|
|
99
|
+
connected_since: h?.connectedSince?.toISOString() ?? null,
|
|
100
|
+
last_inbound_at: h?.lastInboundAt?.toISOString() ?? null,
|
|
101
|
+
last_outbound_at: h?.lastOutboundAt?.toISOString() ?? null,
|
|
102
|
+
details: h?.details ?? {},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
daemon: {
|
|
107
|
+
uptime_s: Math.round((Date.now() - startedAt.getTime()) / 1000),
|
|
108
|
+
started_at: startedAt.toISOString(),
|
|
109
|
+
config_path: configPath,
|
|
110
|
+
version: daemonVersion,
|
|
111
|
+
},
|
|
112
|
+
adapters: adapterSnaps,
|
|
113
|
+
outbox: { inbound_pending: inbound, outbound_pending: outbound },
|
|
114
|
+
sessions,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
const adapterHost = await createAdapterHost({
|
|
118
|
+
db: db.raw,
|
|
119
|
+
config,
|
|
120
|
+
logger,
|
|
121
|
+
audit,
|
|
122
|
+
router,
|
|
123
|
+
dataDir,
|
|
124
|
+
configDir,
|
|
125
|
+
resolveModule: opts.overrideResolveModule ?? loadAdapter,
|
|
126
|
+
healthSnapshot: snapshotFn,
|
|
127
|
+
});
|
|
128
|
+
adapterHostRef = adapterHost;
|
|
129
|
+
await adapterHost.startAll((name, adapter) => {
|
|
130
|
+
router.registerAdapter(name, { adapter });
|
|
131
|
+
});
|
|
132
|
+
// Auto-start tmux sessions for entries with auto_start:true + workspace_dir.
|
|
133
|
+
// Non-fatal; log each start/skip/failure.
|
|
134
|
+
for (const s of config.sessions) {
|
|
135
|
+
if (!s.auto_start || !s.workspace_dir)
|
|
136
|
+
continue;
|
|
137
|
+
const result = startTmuxSession({
|
|
138
|
+
session_id: s.session_id,
|
|
139
|
+
workspace_dir: s.workspace_dir,
|
|
140
|
+
permission_mode: s.permission_mode,
|
|
141
|
+
logger: logger.child({ component: 'core.tmux' }),
|
|
142
|
+
});
|
|
143
|
+
logger.info({
|
|
144
|
+
session_id: s.session_id,
|
|
145
|
+
workspace_dir: s.workspace_dir,
|
|
146
|
+
...result,
|
|
147
|
+
component: 'daemon.bootstrap',
|
|
148
|
+
}, result.started ? 'auto-started tmux session' : 'tmux auto-start skipped');
|
|
149
|
+
// A tmux session can outlive its `claude` process (manual ctrl+D, crash,
|
|
150
|
+
// etc.). `isRunning` only checks that the session exists, so auto-start
|
|
151
|
+
// silently skips stale sessions. Detect and warn.
|
|
152
|
+
if (result.reason === 'already_running') {
|
|
153
|
+
const paneCmd = getPaneCommand(s.session_id);
|
|
154
|
+
if (paneCmd !== null && paneCmd !== 'claude') {
|
|
155
|
+
logger.warn({
|
|
156
|
+
session_id: s.session_id,
|
|
157
|
+
workspace_dir: s.workspace_dir,
|
|
158
|
+
pane_current_command: paneCmd,
|
|
159
|
+
component: 'daemon.bootstrap',
|
|
160
|
+
}, `tmux session '${s.session_id}' is running but pane is '${paneCmd}' (not claude). ` +
|
|
161
|
+
`Run \`reder sessions restart ${s.session_id}\` to relaunch.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// Warn about sessions that are auto-started but missing their Claude hook
|
|
166
|
+
// config. Best-effort — if the CLI package isn't installed alongside the
|
|
167
|
+
// daemon, the check returns null and we skip the warning.
|
|
168
|
+
for (const s of config.sessions) {
|
|
169
|
+
if (!s.auto_start || !s.workspace_dir)
|
|
170
|
+
continue;
|
|
171
|
+
const present = await checkClaudeHooks({
|
|
172
|
+
projectDir: s.workspace_dir,
|
|
173
|
+
sessionId: s.session_id,
|
|
174
|
+
});
|
|
175
|
+
if (present === false) {
|
|
176
|
+
logger.warn({
|
|
177
|
+
session_id: s.session_id,
|
|
178
|
+
workspace_dir: s.workspace_dir,
|
|
179
|
+
component: 'daemon.bootstrap',
|
|
180
|
+
}, "claude hook config missing — dashboard activity status will show 'unknown'. Run `reder sessions repair " +
|
|
181
|
+
s.session_id +
|
|
182
|
+
'`');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Legacy health endpoint: only start if the web adapter isn't enabled.
|
|
186
|
+
// When enabled, adapter-web serves `/health` on the same port as the dashboard.
|
|
187
|
+
const webAdapterEnabled = Boolean(config.adapters['web']?.enabled);
|
|
188
|
+
let health = null;
|
|
189
|
+
if (config.health.enabled && !webAdapterEnabled) {
|
|
190
|
+
health = await startHealthEndpoint({
|
|
191
|
+
bind: config.health.bind,
|
|
192
|
+
port: config.health.port,
|
|
193
|
+
snapshot: snapshotFn,
|
|
194
|
+
logger: logger.child({ component: 'core.health' }),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const stop = async () => {
|
|
198
|
+
logger.info({ component: 'daemon.bootstrap' }, 'stopping rederd');
|
|
199
|
+
if (health)
|
|
200
|
+
await health.close();
|
|
201
|
+
await adapterHost.stopAll();
|
|
202
|
+
await router.stop();
|
|
203
|
+
await ipcServer.close();
|
|
204
|
+
db.close();
|
|
205
|
+
audit.close();
|
|
206
|
+
logger.info({ component: 'daemon.bootstrap' }, 'rederd stopped');
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
config,
|
|
210
|
+
configPath,
|
|
211
|
+
logger,
|
|
212
|
+
db,
|
|
213
|
+
ipcServer,
|
|
214
|
+
router,
|
|
215
|
+
audit,
|
|
216
|
+
health,
|
|
217
|
+
adapterHost,
|
|
218
|
+
startedAt,
|
|
219
|
+
stop,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { bootstrap } from './bootstrap.js';
|
|
6
|
+
import { acquirePidLock, installSignalHandlers, AlreadyRunningError } from './lifecycle.js';
|
|
7
|
+
const VERSION = '0.1.0';
|
|
8
|
+
async function main() {
|
|
9
|
+
const { values } = parseArgs({
|
|
10
|
+
options: {
|
|
11
|
+
config: { type: 'string' },
|
|
12
|
+
help: { type: 'boolean', short: 'h' },
|
|
13
|
+
version: { type: 'boolean', short: 'v' },
|
|
14
|
+
},
|
|
15
|
+
strict: false,
|
|
16
|
+
});
|
|
17
|
+
if (values.help) {
|
|
18
|
+
process.stdout.write('Usage: rederd [--config PATH]\n\n' +
|
|
19
|
+
'Environment:\n' +
|
|
20
|
+
' REDER_CONFIG path to reder.config.yaml\n');
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
if (values.version) {
|
|
24
|
+
process.stdout.write(`${VERSION}\n`);
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
const configPath = values.config ??
|
|
28
|
+
process.env['REDER_CONFIG'] ??
|
|
29
|
+
join(homedir(), '.config', 'reder', 'reder.config.yaml');
|
|
30
|
+
let result;
|
|
31
|
+
try {
|
|
32
|
+
result = await bootstrap({ configPath, daemonVersion: VERSION });
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
process.stderr.write(`rederd: bootstrap failed: ${err.message}\n`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const runtimeDir = result.config.runtime.runtime_dir.startsWith('~/')
|
|
39
|
+
? join(homedir(), result.config.runtime.runtime_dir.slice(2))
|
|
40
|
+
: result.config.runtime.runtime_dir;
|
|
41
|
+
let releaseLock;
|
|
42
|
+
try {
|
|
43
|
+
releaseLock = acquirePidLock(join(runtimeDir, 'rederd.pid'));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err instanceof AlreadyRunningError) {
|
|
47
|
+
process.stderr.write(`rederd: ${err.message}\n`);
|
|
48
|
+
await result.stop();
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
installSignalHandlers(async () => {
|
|
54
|
+
await result.stop();
|
|
55
|
+
releaseLock();
|
|
56
|
+
}, { logger: result.logger, timeoutMs: 10_000 });
|
|
57
|
+
result.logger.info({ version: VERSION }, 'rederd ready');
|
|
58
|
+
// Block main task; signal handlers will exit.
|
|
59
|
+
await new Promise(() => { });
|
|
60
|
+
}
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
process.stderr.write(`rederd: fatal: ${err.stack ?? err}\n`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Logger } from 'pino';
|
|
2
|
+
export declare class AlreadyRunningError extends Error {
|
|
3
|
+
readonly existingPid: number;
|
|
4
|
+
readonly pidPath: string;
|
|
5
|
+
readonly name = "AlreadyRunningError";
|
|
6
|
+
constructor(existingPid: number, pidPath: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Acquire an exclusive PID lock file. Fails loudly if another process already holds it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function acquirePidLock(pidPath: string): () => void;
|
|
12
|
+
export interface ShutdownOptions {
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
logger?: Logger;
|
|
15
|
+
}
|
|
16
|
+
export declare function installSignalHandlers(stop: () => Promise<void>, opts?: ShutdownOptions): () => void;
|
|
17
|
+
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,qBAAa,mBAAoB,SAAQ,KAAK;aAG1B,WAAW,EAAE,MAAM;aACnB,OAAO,EAAE,MAAM;IAHjC,SAAkB,IAAI,yBAAyB;gBAE7B,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM;CAIlC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,IAAI,CAqC1D;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,EACzB,IAAI,GAAE,eAAoB,GACzB,MAAM,IAAI,CAmCZ"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { mkdirSync, openSync, writeSync, closeSync, readFileSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
export class AlreadyRunningError extends Error {
|
|
4
|
+
existingPid;
|
|
5
|
+
pidPath;
|
|
6
|
+
name = 'AlreadyRunningError';
|
|
7
|
+
constructor(existingPid, pidPath) {
|
|
8
|
+
super(`rederd already running (pid ${existingPid}) per ${pidPath}`);
|
|
9
|
+
this.existingPid = existingPid;
|
|
10
|
+
this.pidPath = pidPath;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Acquire an exclusive PID lock file. Fails loudly if another process already holds it.
|
|
15
|
+
*/
|
|
16
|
+
export function acquirePidLock(pidPath) {
|
|
17
|
+
mkdirSync(dirname(pidPath), { recursive: true });
|
|
18
|
+
try {
|
|
19
|
+
const fd = openSync(pidPath, 'wx'); // O_CREAT | O_EXCL
|
|
20
|
+
writeSync(fd, `${process.pid}\n`);
|
|
21
|
+
closeSync(fd);
|
|
22
|
+
return () => {
|
|
23
|
+
try {
|
|
24
|
+
unlinkSync(pidPath);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// ignore
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
const code = err.code;
|
|
33
|
+
if (code !== 'EEXIST')
|
|
34
|
+
throw err;
|
|
35
|
+
const raw = readFileSync(pidPath, 'utf8').trim();
|
|
36
|
+
const existing = Number(raw);
|
|
37
|
+
if (!Number.isFinite(existing) || existing <= 0) {
|
|
38
|
+
// stale lock file with bad content — remove and retry
|
|
39
|
+
unlinkSync(pidPath);
|
|
40
|
+
return acquirePidLock(pidPath);
|
|
41
|
+
}
|
|
42
|
+
// Check if process is alive
|
|
43
|
+
try {
|
|
44
|
+
process.kill(existing, 0);
|
|
45
|
+
}
|
|
46
|
+
catch (sigErr) {
|
|
47
|
+
const sigCode = sigErr.code;
|
|
48
|
+
if (sigCode === 'ESRCH') {
|
|
49
|
+
// stale — process is gone, remove and retry
|
|
50
|
+
unlinkSync(pidPath);
|
|
51
|
+
return acquirePidLock(pidPath);
|
|
52
|
+
}
|
|
53
|
+
// EPERM: process exists but not ours — still means it's running
|
|
54
|
+
}
|
|
55
|
+
throw new AlreadyRunningError(existing, pidPath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function installSignalHandlers(stop, opts = {}) {
|
|
59
|
+
const { timeoutMs = 10_000, logger } = opts;
|
|
60
|
+
let shuttingDown = false;
|
|
61
|
+
const handler = (signal) => {
|
|
62
|
+
if (shuttingDown) {
|
|
63
|
+
logger?.warn({ signal }, 'second signal received; forcing exit');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
shuttingDown = true;
|
|
67
|
+
logger?.info({ signal }, 'shutdown signal received');
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
logger?.error({ signal, timeoutMs }, 'graceful shutdown timed out; exiting 1');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}, timeoutMs);
|
|
72
|
+
timer.unref();
|
|
73
|
+
stop()
|
|
74
|
+
.then(() => {
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
})
|
|
78
|
+
.catch((err) => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
logger?.error({ err }, 'error during shutdown');
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
process.on('SIGTERM', handler);
|
|
85
|
+
process.on('SIGINT', handler);
|
|
86
|
+
return () => {
|
|
87
|
+
process.off('SIGTERM', handler);
|
|
88
|
+
process.off('SIGINT', handler);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../src/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC9F,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAG1B;IACA;IAHA,IAAI,GAAG,qBAAqB,CAAC;IAC/C,YACkB,WAAmB,EACnB,OAAe;QAE/B,KAAK,CAAC,+BAA+B,WAAW,SAAS,OAAO,EAAE,CAAC,CAAC;QAHpD,gBAAW,GAAX,WAAW,CAAQ;QACnB,YAAO,GAAP,OAAO,CAAQ;IAGjC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,mBAAmB;QACvD,SAAS,CAAC,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;QAClC,SAAS,CAAC,EAAE,CAAC,CAAC;QACd,OAAO,GAAG,EAAE;YACV,IAAI,CAAC;gBACH,UAAU,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ;YAAE,MAAM,GAAG,CAAC;QACjC,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACjD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAChD,sDAAsD;YACtD,UAAU,CAAC,OAAO,CAAC,CAAC;YACpB,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QACD,4BAA4B;QAC5B,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC5B,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,MAAM,OAAO,GAAI,MAAgC,CAAC,IAAI,CAAC;YACvD,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;gBACxB,4CAA4C;gBAC5C,UAAU,CAAC,OAAO,CAAC,CAAC;gBACpB,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;YACjC,CAAC;YACD,gEAAgE;QAClE,CAAC;QACD,MAAM,IAAI,mBAAmB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnD,CAAC;AACH,CAAC;AAOD,MAAM,UAAU,qBAAqB,CACnC,IAAyB,EACzB,OAAwB,EAAE;IAE1B,MAAM,EAAE,SAAS,GAAG,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,MAAM,OAAO,GAAG,CAAC,MAAsB,EAAQ,EAAE;QAC/C,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,sCAAsC,CAAC,CAAC;YACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,YAAY,GAAG,IAAI,CAAC;QACpB,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,0BAA0B,CAAC,CAAC;QACrD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,wCAAwC,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,EAAE,SAAS,CAAC,CAAC;QACd,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,EAAE;aACH,IAAI,CAAC,GAAG,EAAE;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,EAAE,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,uBAAuB,CAAC,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE9B,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rederjs/daemon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reder daemon (rederd) — bridges Claude Code sessions to remote adapters.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Edward de Groot <ed@degrootventures.com>",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"reder",
|
|
9
|
+
"claude-code",
|
|
10
|
+
"claude",
|
|
11
|
+
"daemon",
|
|
12
|
+
"tmux"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/RederJS/rederjs.git",
|
|
17
|
+
"directory": "packages/daemon"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://rederjs.com",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/RederJS/rederjs/issues"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"type": "module",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"bin": {
|
|
33
|
+
"rederd": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@rederjs/core": "^0.1.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.0.0"
|
|
48
|
+
}
|
|
49
|
+
}
|