@claudexor/daemon 1.0.0 → 2.1.2
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/client.d.ts +29 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +24 -4
- package/dist/client.js.map +1 -1
- package/dist/command-authority.d.ts +11 -0
- package/dist/command-authority.d.ts.map +1 -0
- package/dist/command-authority.js +15 -0
- package/dist/command-authority.js.map +1 -0
- package/dist/command-retention.d.ts +5 -0
- package/dist/command-retention.d.ts.map +1 -0
- package/dist/command-retention.js +18 -0
- package/dist/command-retention.js.map +1 -0
- package/dist/command-store.d.ts +41 -0
- package/dist/command-store.d.ts.map +1 -0
- package/dist/command-store.js +197 -0
- package/dist/command-store.js.map +1 -0
- package/dist/events.d.ts +2 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -3
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/interactions.d.ts +32 -22
- package/dist/interactions.d.ts.map +1 -1
- package/dist/interactions.js +183 -56
- package/dist/interactions.js.map +1 -1
- package/dist/journal-events.d.ts +4 -0
- package/dist/journal-events.d.ts.map +1 -0
- package/dist/journal-events.js +23 -0
- package/dist/journal-events.js.map +1 -0
- package/dist/journal-manager.d.ts +72 -0
- package/dist/journal-manager.d.ts.map +1 -0
- package/dist/journal-manager.js +463 -0
- package/dist/journal-manager.js.map +1 -0
- package/dist/journal-projection.d.ts +11 -0
- package/dist/journal-projection.d.ts.map +1 -0
- package/dist/journal-projection.js +2 -0
- package/dist/journal-projection.js.map +1 -0
- package/dist/journal-recovery-files.d.ts +27 -0
- package/dist/journal-recovery-files.d.ts.map +1 -0
- package/dist/journal-recovery-files.js +209 -0
- package/dist/journal-recovery-files.js.map +1 -0
- package/dist/operator-decisions.d.ts +30 -0
- package/dist/operator-decisions.d.ts.map +1 -0
- package/dist/operator-decisions.js +137 -0
- package/dist/operator-decisions.js.map +1 -0
- package/dist/project-partitions.d.ts +167 -0
- package/dist/project-partitions.d.ts.map +1 -0
- package/dist/project-partitions.js +365 -0
- package/dist/project-partitions.js.map +1 -0
- package/dist/projects.d.ts +29 -0
- package/dist/projects.d.ts.map +1 -0
- package/dist/projects.js +180 -0
- package/dist/projects.js.map +1 -0
- package/dist/quota-registry.d.ts +75 -0
- package/dist/quota-registry.d.ts.map +1 -0
- package/dist/quota-registry.js +208 -0
- package/dist/quota-registry.js.map +1 -0
- package/dist/resource-store.d.ts +22 -0
- package/dist/resource-store.d.ts.map +1 -0
- package/dist/resource-store.js +272 -0
- package/dist/resource-store.js.map +1 -0
- package/dist/run-events.d.ts +15 -0
- package/dist/run-events.d.ts.map +1 -0
- package/dist/run-events.js +29 -0
- package/dist/run-events.js.map +1 -0
- package/dist/server.d.ts +33 -33
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +231 -238
- package/dist/server.js.map +1 -1
- package/dist/spec-sessions.d.ts +72 -0
- package/dist/spec-sessions.d.ts.map +1 -0
- package/dist/spec-sessions.js +301 -0
- package/dist/spec-sessions.js.map +1 -0
- package/dist/terminate.d.ts +52 -0
- package/dist/terminate.d.ts.map +1 -0
- package/dist/terminate.js +111 -0
- package/dist/terminate.js.map +1 -0
- package/dist/thread-head-ping.d.ts +35 -0
- package/dist/thread-head-ping.d.ts.map +1 -0
- package/dist/thread-head-ping.js +67 -0
- package/dist/thread-head-ping.js.map +1 -0
- package/dist/thread-lifecycle.d.ts +5 -0
- package/dist/thread-lifecycle.d.ts.map +1 -0
- package/dist/thread-lifecycle.js +57 -0
- package/dist/thread-lifecycle.js.map +1 -0
- package/dist/thread-store-support.d.ts +37 -0
- package/dist/thread-store-support.d.ts.map +1 -0
- package/dist/thread-store-support.js +94 -0
- package/dist/thread-store-support.js.map +1 -0
- package/dist/threads.d.ts +59 -13
- package/dist/threads.d.ts.map +1 -1
- package/dist/threads.js +254 -141
- package/dist/threads.js.map +1 -1
- package/dist/token.d.ts +13 -4
- package/dist/token.d.ts.map +1 -1
- package/dist/token.js +134 -29
- package/dist/token.js.map +1 -1
- package/dist/writer-lease.d.ts +21 -0
- package/dist/writer-lease.d.ts.map +1 -0
- package/dist/writer-lease.js +94 -0
- package/dist/writer-lease.js.map +1 -0
- package/package.json +5 -4
package/dist/token.js
CHANGED
|
@@ -1,63 +1,168 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { userConfigDir } from "@claudexor/util";
|
|
2
|
+
import { closeSync, constants, existsSync, fchmodSync, fstatSync, fsyncSync, lstatSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeSync, } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { ensureCanonicalPrivateDirectory, userConfigDir, userHomeDir } from "@claudexor/util";
|
|
5
5
|
export function daemonDir() {
|
|
6
6
|
return join(userConfigDir(), "daemon");
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Prove every daemon-owned root component before any token/log/lock writer is
|
|
10
|
+
* allowed to touch it. On first launch the default `~/.claudexor` parent may
|
|
11
|
+
* not exist yet, so create that one owned root before proving the v2 subtree.
|
|
12
|
+
* A custom root must still have a canonical parent supplied by its operator.
|
|
13
|
+
*/
|
|
14
|
+
export function ensureDaemonRuntimeRoot() {
|
|
15
|
+
if (!process.env.CLAUDEXOR_CONFIG_DIR?.trim()) {
|
|
16
|
+
const defaultParent = join(userHomeDir(), ".claudexor");
|
|
17
|
+
if (!existsSync(defaultParent))
|
|
18
|
+
ensureCanonicalPrivateDirectory(defaultParent);
|
|
19
|
+
}
|
|
20
|
+
const configRoot = ensureCanonicalPrivateDirectory(userConfigDir());
|
|
21
|
+
const root = ensureCanonicalPrivateDirectory(join(configRoot, "daemon"));
|
|
22
|
+
if (root !== resolve(daemonDir()))
|
|
23
|
+
throw new Error("daemon runtime root is not canonical");
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
8
26
|
export function defaultSocketPath() {
|
|
9
27
|
return process.env.CLAUDEXOR_DAEMON_SOCK || join(daemonDir(), "claudexord.sock");
|
|
10
28
|
}
|
|
11
29
|
export function logPath() {
|
|
12
30
|
return join(daemonDir(), "claudexord.log");
|
|
13
31
|
}
|
|
14
|
-
/** Read or generate a per-user local auth token (0600). */
|
|
32
|
+
/** Read or generate a per-user local auth token (0600), never through links. */
|
|
15
33
|
export function ensureToken() {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const path = join(dir, "token");
|
|
34
|
+
const root = ensureDaemonRuntimeRoot();
|
|
35
|
+
const path = join(root, "token");
|
|
19
36
|
try {
|
|
20
|
-
|
|
21
|
-
if (existing)
|
|
22
|
-
return existing;
|
|
37
|
+
return readValidatedToken(path, true);
|
|
23
38
|
}
|
|
24
|
-
catch {
|
|
25
|
-
|
|
39
|
+
catch (error) {
|
|
40
|
+
if (error?.code !== "ENOENT")
|
|
41
|
+
throw error;
|
|
26
42
|
}
|
|
27
43
|
const token = randomUUID();
|
|
28
|
-
|
|
44
|
+
let fd;
|
|
29
45
|
try {
|
|
30
|
-
|
|
46
|
+
fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
47
|
+
const stat = fstatSync(fd);
|
|
48
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
49
|
+
throw new Error("daemon token target is unsafe");
|
|
50
|
+
fchmodSync(fd, 0o600);
|
|
51
|
+
const bytes = Buffer.from(`${token}\n`, "utf8");
|
|
52
|
+
let offset = 0;
|
|
53
|
+
while (offset < bytes.length)
|
|
54
|
+
offset += writeSync(fd, bytes, offset, bytes.length - offset);
|
|
55
|
+
fsyncSync(fd);
|
|
31
56
|
}
|
|
32
|
-
catch {
|
|
33
|
-
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error?.code === "EEXIST")
|
|
59
|
+
return readValidatedToken(path, true);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
if (fd !== undefined)
|
|
64
|
+
closeSync(fd);
|
|
34
65
|
}
|
|
66
|
+
fsyncDirectory(root);
|
|
35
67
|
return token;
|
|
36
68
|
}
|
|
37
69
|
export function readToken() {
|
|
38
70
|
try {
|
|
39
|
-
const
|
|
40
|
-
return
|
|
71
|
+
const root = validateDaemonRuntimeRootReadOnly();
|
|
72
|
+
return readValidatedToken(join(root, "token"), false);
|
|
41
73
|
}
|
|
42
74
|
catch {
|
|
43
75
|
return null;
|
|
44
76
|
}
|
|
45
77
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
78
|
+
function validateDaemonRuntimeRootReadOnly() {
|
|
79
|
+
const root = resolve(daemonDir());
|
|
80
|
+
const stat = lstatSync(root);
|
|
81
|
+
if (stat.isSymbolicLink() ||
|
|
82
|
+
!stat.isDirectory() ||
|
|
83
|
+
realpathSync.native(root) !== root ||
|
|
84
|
+
(stat.mode & 0o077) !== 0 ||
|
|
85
|
+
(typeof process.getuid === "function" && stat.uid !== process.getuid())) {
|
|
86
|
+
throw new Error(`daemon runtime root is not canonical and private: ${root}`);
|
|
87
|
+
}
|
|
88
|
+
return root;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Rotate only a proven owner-controlled token inode. The caller already fences
|
|
92
|
+
* a live daemon; this writer adds no compatibility path that would chmod or
|
|
93
|
+
* overwrite a symlink/hardlink/foreign file.
|
|
94
|
+
*/
|
|
49
95
|
export function rotateToken() {
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
const root = ensureDaemonRuntimeRoot();
|
|
97
|
+
const path = join(root, "token");
|
|
98
|
+
if (existsSync(path))
|
|
99
|
+
void readValidatedToken(path, false);
|
|
53
100
|
const token = randomUUID();
|
|
54
|
-
|
|
101
|
+
const temp = join(root, `.token-${process.pid}-${randomUUID()}.tmp`);
|
|
102
|
+
let fd;
|
|
55
103
|
try {
|
|
56
|
-
|
|
104
|
+
fd = openSync(temp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
105
|
+
const bytes = Buffer.from(`${token}\n`, "utf8");
|
|
106
|
+
let offset = 0;
|
|
107
|
+
while (offset < bytes.length)
|
|
108
|
+
offset += writeSync(fd, bytes, offset, bytes.length - offset);
|
|
109
|
+
fchmodSync(fd, 0o600);
|
|
110
|
+
fsyncSync(fd);
|
|
111
|
+
closeSync(fd);
|
|
112
|
+
fd = undefined;
|
|
113
|
+
if (existsSync(path))
|
|
114
|
+
void readValidatedToken(path, false);
|
|
115
|
+
renameSync(temp, path);
|
|
116
|
+
fsyncDirectory(root);
|
|
117
|
+
return token;
|
|
57
118
|
}
|
|
58
|
-
|
|
59
|
-
|
|
119
|
+
finally {
|
|
120
|
+
if (fd !== undefined)
|
|
121
|
+
closeSync(fd);
|
|
122
|
+
try {
|
|
123
|
+
unlinkSync(temp);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* renamed or never created */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function readValidatedToken(path, repairMode) {
|
|
131
|
+
let fd;
|
|
132
|
+
try {
|
|
133
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
134
|
+
const opened = fstatSync(fd);
|
|
135
|
+
if (!opened.isFile() ||
|
|
136
|
+
opened.nlink !== 1 ||
|
|
137
|
+
opened.size > 4 * 1024 ||
|
|
138
|
+
(typeof process.getuid === "function" && opened.uid !== process.getuid()))
|
|
139
|
+
throw new Error("daemon token is not an owner-controlled singly-linked regular file");
|
|
140
|
+
if ((opened.mode & 0o077) !== 0) {
|
|
141
|
+
if (!repairMode)
|
|
142
|
+
throw new Error("daemon token permissions are not private");
|
|
143
|
+
// The inode/path identity was fully proven above; only now may startup
|
|
144
|
+
// repair permissions inherited from an older Claudexor version.
|
|
145
|
+
fchmodSync(fd, 0o600);
|
|
146
|
+
fsyncSync(fd);
|
|
147
|
+
}
|
|
148
|
+
const token = readFileSync(fd, "utf8").trim();
|
|
149
|
+
if (!token || token.includes("\n") || token.includes("\r")) {
|
|
150
|
+
throw new Error("daemon token is empty or malformed");
|
|
151
|
+
}
|
|
152
|
+
return token;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
if (fd !== undefined)
|
|
156
|
+
closeSync(fd);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function fsyncDirectory(path) {
|
|
160
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
161
|
+
try {
|
|
162
|
+
fsyncSync(fd);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
closeSync(fd);
|
|
60
166
|
}
|
|
61
|
-
return token;
|
|
62
167
|
}
|
|
63
168
|
//# sourceMappingURL=token.js.map
|
package/dist/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../src/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type KnownProcessIdentity, type ProcessIdentityReader } from "@claudexor/core";
|
|
2
|
+
export interface DaemonWriterLease {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
release(): void;
|
|
5
|
+
}
|
|
6
|
+
export interface DaemonLeaseOwner {
|
|
7
|
+
pid: number;
|
|
8
|
+
token: string;
|
|
9
|
+
/** Birth identity of the owning daemon, recorded at acquisition so a later
|
|
10
|
+
* terminator can verify it never signals a recycled pid (W3.5/sol #5).
|
|
11
|
+
* Absent on legacy leases or when the platform cannot observe identity. */
|
|
12
|
+
identity?: KnownProcessIdentity;
|
|
13
|
+
}
|
|
14
|
+
/** Claim single-writer authority before any daemon journal is opened. */
|
|
15
|
+
export declare function acquireDaemonWriterLease(socketPath: string, deps?: {
|
|
16
|
+
identity?: ProcessIdentityReader;
|
|
17
|
+
}): DaemonWriterLease;
|
|
18
|
+
/** Read the current writer-lease owner for a socket path (null when free). */
|
|
19
|
+
export declare function daemonLeaseOwner(socketPath: string): DaemonLeaseOwner | null;
|
|
20
|
+
export declare function processIsAlive(pid: number): boolean;
|
|
21
|
+
//# sourceMappingURL=writer-lease.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer-lease.d.ts","sourceRoot":"","sources":["../src/writer-lease.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC3B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd;;+EAE2E;IAC3E,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED,yEAAyE;AACzE,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,qBAAqB,CAAA;CAAO,GAC9C,iBAAiB,CA4CnB;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAE5E;AAiCD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOnD"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { defaultProcessIdentityService, isKnownProcessIdentity, } from "@claudexor/core";
|
|
4
|
+
/** Claim single-writer authority before any daemon journal is opened. */
|
|
5
|
+
export function acquireDaemonWriterLease(socketPath, deps = {}) {
|
|
6
|
+
const path = `${socketPath}.writer`;
|
|
7
|
+
const token = randomUUID();
|
|
8
|
+
const ownerPath = `${path}/owner.json`;
|
|
9
|
+
const self = (deps.identity ?? defaultProcessIdentityService).self();
|
|
10
|
+
const owner = {
|
|
11
|
+
pid: process.pid,
|
|
12
|
+
token,
|
|
13
|
+
...(self.status === "known" ? { identity: self } : {}),
|
|
14
|
+
};
|
|
15
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
16
|
+
try {
|
|
17
|
+
mkdirSync(path, { mode: 0o700 });
|
|
18
|
+
writeFileSync(ownerPath, `${JSON.stringify(owner)}\n`, {
|
|
19
|
+
mode: 0o600,
|
|
20
|
+
flag: "wx",
|
|
21
|
+
});
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code !== "EEXIST")
|
|
26
|
+
throw error;
|
|
27
|
+
const existing = readLeaseOwner(ownerPath);
|
|
28
|
+
if (!existing || processIsAlive(existing.pid))
|
|
29
|
+
throw writerBusy(path);
|
|
30
|
+
const stale = `${path}.stale-${process.pid}-${randomUUID()}`;
|
|
31
|
+
try {
|
|
32
|
+
renameSync(path, stale);
|
|
33
|
+
rmSync(stale, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
catch (cleanupError) {
|
|
36
|
+
if (cleanupError.code !== "ENOENT")
|
|
37
|
+
throw cleanupError;
|
|
38
|
+
}
|
|
39
|
+
if (attempt === 1)
|
|
40
|
+
throw new Error(`could not replace stale daemon writer lease ${path}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let released = false;
|
|
44
|
+
return {
|
|
45
|
+
path,
|
|
46
|
+
release: () => {
|
|
47
|
+
if (released)
|
|
48
|
+
return;
|
|
49
|
+
released = true;
|
|
50
|
+
const current = readLeaseOwner(ownerPath);
|
|
51
|
+
if (current?.token === token && current.pid === process.pid) {
|
|
52
|
+
rmSync(path, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/** Read the current writer-lease owner for a socket path (null when free). */
|
|
58
|
+
export function daemonLeaseOwner(socketPath) {
|
|
59
|
+
return readLeaseOwner(`${socketPath}.writer/owner.json`);
|
|
60
|
+
}
|
|
61
|
+
function writerBusy(path) {
|
|
62
|
+
return Object.assign(new Error(`another claudexor daemon owns ${path}`), {
|
|
63
|
+
code: "daemon_writer_busy",
|
|
64
|
+
status: 409,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function readLeaseOwner(path) {
|
|
68
|
+
try {
|
|
69
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
70
|
+
if (!Number.isSafeInteger(value.pid) ||
|
|
71
|
+
Number(value.pid) <= 0 ||
|
|
72
|
+
typeof value.token !== "string") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
pid: Number(value.pid),
|
|
77
|
+
token: value.token,
|
|
78
|
+
...(isKnownProcessIdentity(value.identity) ? { identity: value.identity } : {}),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export function processIsAlive(pid) {
|
|
86
|
+
try {
|
|
87
|
+
process.kill(pid, 0);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return error.code === "EPERM";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=writer-lease.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"writer-lease.js","sourceRoot":"","sources":["../src/writer-lease.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudexor/daemon",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Optional local daemon: Unix-socket JSON-RPC, token auth, task queue. Runner injected (no second scheduler).",
|
|
6
6
|
"type": "module",
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@claudexor/
|
|
20
|
-
"@claudexor/
|
|
21
|
-
"@claudexor/
|
|
19
|
+
"@claudexor/core": "2.1.2",
|
|
20
|
+
"@claudexor/journal": "2.1.2",
|
|
21
|
+
"@claudexor/schema": "2.1.2",
|
|
22
|
+
"@claudexor/util": "2.1.2"
|
|
22
23
|
},
|
|
23
24
|
"repository": {
|
|
24
25
|
"type": "git",
|