cc-peer 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cc-peer.cjs +348 -2
- package/dist/bin/cc-peer.mjs +325 -2
- package/dist/cc-peer-BpNw-MoF.mjs +945 -0
- package/dist/cc-peer-CH38-mD2.cjs +974 -0
- package/dist/cc-peer.cjs +3 -4
- package/dist/cc-peer.d.cts +301 -0
- package/dist/cc-peer.d.mts +301 -0
- package/dist/cc-peer.mjs +2 -4
- package/package.json +1 -1
package/dist/cc-peer.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
exports.CC_PEER_VERSION = CC_PEER_VERSION;
|
|
2
|
+
const require_cc_peer = require("./cc-peer-CH38-mD2.cjs");
|
|
3
|
+
exports.CC_PEER_VERSION = require_cc_peer.CC_PEER_VERSION;
|
|
4
|
+
exports.CcPeer = require_cc_peer.CcPeer;
|
package/dist/cc-peer.d.cts
CHANGED
|
@@ -1,3 +1,304 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/adapters/node/paths.d.ts
|
|
4
|
+
interface PathConfig {
|
|
5
|
+
homeDir?: string;
|
|
6
|
+
socketDir?: string;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/ports/transport.d.ts
|
|
10
|
+
/** Transport port: per-exchange connections and the listening inbox. */
|
|
11
|
+
interface Transport {
|
|
12
|
+
/**
|
|
13
|
+
* One connection, one write of the given lines (each newline-terminated), then close after a short linger. Mirrors the reference client's `Pe`.
|
|
14
|
+
*/
|
|
15
|
+
connectWrite: (socketPath: string, lines: readonly string[], lingerMs?: number) => Promise<void>;
|
|
16
|
+
/** Probe whether a socket accepts connections (roster admission uses this). */
|
|
17
|
+
probe: (socketPath: string) => Promise<boolean>;
|
|
18
|
+
listen: (socketPath: string, onConnection: (conn: Readonly<InboundConnection>) => void) => Promise<ListeningSocket>;
|
|
19
|
+
}
|
|
20
|
+
interface InboundConnection {
|
|
21
|
+
/** Resolves once the kernel reports the connecting process's pid (macOS). */
|
|
22
|
+
peerPid: () => number | undefined;
|
|
23
|
+
readLines: () => AsyncIterable<string>;
|
|
24
|
+
close: () => void;
|
|
25
|
+
}
|
|
26
|
+
interface ListeningSocket {
|
|
27
|
+
socketPath: string;
|
|
28
|
+
close: () => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/schemas/registry.d.ts
|
|
32
|
+
/** One entry of the ~/.claude/sessions/<pid>.json registry. */
|
|
33
|
+
declare const RegistryEntrySchema: z.ZodObject<{
|
|
34
|
+
pid: z.ZodNumber;
|
|
35
|
+
sessionId: z.ZodString;
|
|
36
|
+
cwd: z.ZodString;
|
|
37
|
+
startedAt: z.ZodNumber;
|
|
38
|
+
procStart: z.ZodString;
|
|
39
|
+
version: z.ZodString;
|
|
40
|
+
peerProtocol: z.ZodNumber;
|
|
41
|
+
peerFeatures: z.ZodArray<z.ZodEnum<{
|
|
42
|
+
notify_idle: "notify_idle";
|
|
43
|
+
reply_across_default_dirs: "reply_across_default_dirs";
|
|
44
|
+
artifact_yield: "artifact_yield";
|
|
45
|
+
}> & {
|
|
46
|
+
is(value: unknown): value is "notify_idle" | "reply_across_default_dirs" | "artifact_yield";
|
|
47
|
+
}>;
|
|
48
|
+
kind: z.ZodEnum<{
|
|
49
|
+
interactive: "interactive";
|
|
50
|
+
bg: "bg";
|
|
51
|
+
daemon: "daemon";
|
|
52
|
+
"daemon-worker": "daemon-worker";
|
|
53
|
+
}> & {
|
|
54
|
+
is(value: unknown): value is "interactive" | "bg" | "daemon" | "daemon-worker";
|
|
55
|
+
};
|
|
56
|
+
entrypoint: z.ZodString;
|
|
57
|
+
pidDomain: z.ZodString;
|
|
58
|
+
messagingSocketPath: z.ZodString;
|
|
59
|
+
name: z.ZodOptional<z.ZodString>;
|
|
60
|
+
nameSource: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
user: "user";
|
|
62
|
+
peer: "peer";
|
|
63
|
+
derived: "derived";
|
|
64
|
+
collision: "collision";
|
|
65
|
+
auto: "auto";
|
|
66
|
+
hook: "hook";
|
|
67
|
+
}> & {
|
|
68
|
+
is(value: unknown): value is "user" | "peer" | "derived" | "collision" | "auto" | "hook";
|
|
69
|
+
}>;
|
|
70
|
+
nameSince: z.ZodOptional<z.ZodNumber>;
|
|
71
|
+
updatedAt: z.ZodNumber;
|
|
72
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
busy: "busy";
|
|
74
|
+
shell: "shell";
|
|
75
|
+
idle: "idle";
|
|
76
|
+
waiting: "waiting";
|
|
77
|
+
}> & {
|
|
78
|
+
is(value: unknown): value is "busy" | "shell" | "idle" | "waiting";
|
|
79
|
+
}>;
|
|
80
|
+
statusUpdatedAt: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
bridgeSessionId: z.ZodOptional<z.ZodString>;
|
|
82
|
+
}, z.core.$strip> & {
|
|
83
|
+
is(value: unknown): value is {
|
|
84
|
+
pid: number;
|
|
85
|
+
sessionId: string;
|
|
86
|
+
cwd: string;
|
|
87
|
+
startedAt: number;
|
|
88
|
+
procStart: string;
|
|
89
|
+
version: string;
|
|
90
|
+
peerProtocol: number;
|
|
91
|
+
peerFeatures: ("notify_idle" | "reply_across_default_dirs" | "artifact_yield")[];
|
|
92
|
+
kind: "interactive" | "bg" | "daemon" | "daemon-worker";
|
|
93
|
+
entrypoint: string;
|
|
94
|
+
pidDomain: string;
|
|
95
|
+
messagingSocketPath: string;
|
|
96
|
+
updatedAt: number;
|
|
97
|
+
name?: string | undefined;
|
|
98
|
+
nameSource?: "user" | "peer" | "derived" | "collision" | "auto" | "hook" | undefined;
|
|
99
|
+
nameSince?: number | undefined;
|
|
100
|
+
status?: "busy" | "shell" | "idle" | "waiting" | undefined;
|
|
101
|
+
statusUpdatedAt?: number | undefined;
|
|
102
|
+
bridgeSessionId?: string | undefined;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/ports/registry-store.d.ts
|
|
108
|
+
/** Registry port: the ~/.claude/sessions/<pid>.json store. */
|
|
109
|
+
interface RegistryStore {
|
|
110
|
+
list: () => Promise<RegistryEntry[]>;
|
|
111
|
+
read: (pid: number) => Promise<RegistryEntry | undefined>;
|
|
112
|
+
write: (entry: RegistryEntry) => Promise<void>;
|
|
113
|
+
/** Heartbeat: bump updatedAt/statusUpdatedAt without rewriting the rest. */
|
|
114
|
+
touch: (pid: number) => Promise<void>;
|
|
115
|
+
remove: (pid: number) => Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/schemas/keyfile.d.ts
|
|
119
|
+
/** The auth key a session publishes next to its socket. */
|
|
120
|
+
declare const PeerKeyFileSchema: z.ZodObject<{
|
|
121
|
+
peerToken: z.ZodString;
|
|
122
|
+
procStart: z.ZodString;
|
|
123
|
+
pidDomain: z.ZodString;
|
|
124
|
+
}, z.core.$strip> & {
|
|
125
|
+
is(value: unknown): value is {
|
|
126
|
+
peerToken: string;
|
|
127
|
+
procStart: string;
|
|
128
|
+
pidDomain: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
type PeerKeyFile = z.infer<typeof PeerKeyFileSchema>;
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/ports/key-store.d.ts
|
|
134
|
+
/** Key-store port: auth keys published next to each session socket. */
|
|
135
|
+
interface KeyStore {
|
|
136
|
+
readForSocket: (socketPath: string) => Promise<PeerKeyFile | undefined>;
|
|
137
|
+
/** Write our own key file for the socket we own. */
|
|
138
|
+
writeForSocket: (socketPath: string, keyFile: Readonly<PeerKeyFile>) => Promise<void>;
|
|
139
|
+
removeForSocket: (socketPath: string) => Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/ports/proc-info.d.ts
|
|
143
|
+
/** Process-info port: the liveness and proc-start facts the roster needs. */
|
|
144
|
+
interface ProcInfo {
|
|
145
|
+
alive: (pid: number) => Promise<boolean>;
|
|
146
|
+
/**
|
|
147
|
+
* The process start string, byte-equal to `LC_ALL=C TZ=UTC ps -o lstart=` output — the exact value the receiver's registry byte-compares against.
|
|
148
|
+
*/
|
|
149
|
+
lstart: (pid: number) => Promise<string | undefined>;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/ports/clock.d.ts
|
|
153
|
+
/** Clock port: injectable time for pacer/refill and freshness windows. */
|
|
154
|
+
interface Clock {
|
|
155
|
+
nowMs: () => number;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/schemas/wire.d.ts
|
|
159
|
+
declare const PeerMessageStatusSchema: z.ZodObject<{
|
|
160
|
+
type: z.ZodLiteral<"control">;
|
|
161
|
+
action: z.ZodLiteral<"peer_message_status">;
|
|
162
|
+
status: z.ZodEnum<{
|
|
163
|
+
held: "held";
|
|
164
|
+
delivered: "delivered";
|
|
165
|
+
denied: "denied";
|
|
166
|
+
expired: "expired";
|
|
167
|
+
dropped: "dropped";
|
|
168
|
+
}>;
|
|
169
|
+
reason: z.ZodString;
|
|
170
|
+
from: z.ZodString;
|
|
171
|
+
orig_msg_id: z.ZodString;
|
|
172
|
+
status_detail: z.ZodOptional<z.ZodString>;
|
|
173
|
+
drop_reason: z.ZodOptional<z.ZodEnum<{
|
|
174
|
+
"rate-limited": "rate-limited";
|
|
175
|
+
duplicate: "duplicate";
|
|
176
|
+
"hop-loop": "hop-loop";
|
|
177
|
+
"hop-runaway": "hop-runaway";
|
|
178
|
+
"queue-full": "queue-full";
|
|
179
|
+
}> & {
|
|
180
|
+
is(value: unknown): value is "rate-limited" | "duplicate" | "hop-loop" | "hop-runaway" | "queue-full";
|
|
181
|
+
}>;
|
|
182
|
+
dropped_msg_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
183
|
+
msgV: z.ZodNumber;
|
|
184
|
+
msg_id: z.ZodString;
|
|
185
|
+
}, z.core.$strip> & {
|
|
186
|
+
is(value: unknown): value is {
|
|
187
|
+
type: "control";
|
|
188
|
+
action: "peer_message_status";
|
|
189
|
+
status: "held" | "delivered" | "denied" | "expired" | "dropped";
|
|
190
|
+
reason: string;
|
|
191
|
+
from: string;
|
|
192
|
+
orig_msg_id: string;
|
|
193
|
+
msgV: number;
|
|
194
|
+
msg_id: string;
|
|
195
|
+
status_detail?: string | undefined;
|
|
196
|
+
drop_reason?: "rate-limited" | "duplicate" | "hop-loop" | "hop-runaway" | "queue-full" | undefined;
|
|
197
|
+
dropped_msg_ids?: string[] | undefined;
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
type PeerMessageStatus = z.infer<typeof PeerMessageStatusSchema>;
|
|
201
|
+
declare const PeerIdleNoticeSchema: z.ZodObject<{
|
|
202
|
+
type: z.ZodLiteral<"control">;
|
|
203
|
+
action: z.ZodLiteral<"peer_idle_notice">;
|
|
204
|
+
orig_msg_id: z.ZodString;
|
|
205
|
+
state: z.ZodEnum<{
|
|
206
|
+
idle: "idle";
|
|
207
|
+
exited: "exited";
|
|
208
|
+
}>;
|
|
209
|
+
finished_at: z.ZodNumber;
|
|
210
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
211
|
+
from: z.ZodString;
|
|
212
|
+
from_mode: z.ZodOptional<z.ZodEnum<{
|
|
213
|
+
bypass: "bypass";
|
|
214
|
+
prompting: "prompting";
|
|
215
|
+
}>>;
|
|
216
|
+
msgV: z.ZodNumber;
|
|
217
|
+
msg_id: z.ZodString;
|
|
218
|
+
}, z.core.$strip> & {
|
|
219
|
+
is(value: unknown): value is {
|
|
220
|
+
type: "control";
|
|
221
|
+
action: "peer_idle_notice";
|
|
222
|
+
orig_msg_id: string;
|
|
223
|
+
state: "idle" | "exited";
|
|
224
|
+
finished_at: number;
|
|
225
|
+
from: string;
|
|
226
|
+
msgV: number;
|
|
227
|
+
msg_id: string;
|
|
228
|
+
detail?: string | undefined;
|
|
229
|
+
from_mode?: "bypass" | "prompting" | undefined;
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
type PeerIdleNotice = z.infer<typeof PeerIdleNoticeSchema>;
|
|
233
|
+
//#endregion
|
|
1
234
|
//#region src/cc-peer.d.ts
|
|
235
|
+
/** Package version placeholder until semantic-release owns it. */
|
|
2
236
|
export declare const CC_PEER_VERSION = "0.0.0";
|
|
237
|
+
/** How a target can be addressed. */
|
|
238
|
+
export type PeerRef = {
|
|
239
|
+
pid: number;
|
|
240
|
+
} | {
|
|
241
|
+
name: string;
|
|
242
|
+
} | {
|
|
243
|
+
address: string;
|
|
244
|
+
};
|
|
245
|
+
export interface CcPeerOptions extends PathConfig {
|
|
246
|
+
name?: string;
|
|
247
|
+
sessionId?: string;
|
|
248
|
+
heartbeatMs?: number;
|
|
249
|
+
logger?: (message: string) => void;
|
|
250
|
+
}
|
|
251
|
+
export interface SendOptions {
|
|
252
|
+
priority?: "next" | "later";
|
|
253
|
+
/** Attaches from-mode attestation; false deliberately sends unattested (recipient holds it). */
|
|
254
|
+
fromMode?: "bypass" | "prompting" | false;
|
|
255
|
+
/** When set, must match the receiver's sessionId or the frame is silently dropped. */
|
|
256
|
+
sessionId?: string;
|
|
257
|
+
}
|
|
258
|
+
export interface InboundMessage {
|
|
259
|
+
from?: string;
|
|
260
|
+
fromSession?: string;
|
|
261
|
+
fromName?: string;
|
|
262
|
+
fromMode?: "bypass" | "prompting";
|
|
263
|
+
hopChain?: string[];
|
|
264
|
+
body: string;
|
|
265
|
+
msgId: string;
|
|
266
|
+
}
|
|
267
|
+
export type Receipt = PeerMessageStatus;
|
|
268
|
+
export type IdleNotice = PeerIdleNotice;
|
|
269
|
+
interface Deps {
|
|
270
|
+
transport: Transport;
|
|
271
|
+
registry: RegistryStore;
|
|
272
|
+
keys: KeyStore;
|
|
273
|
+
procInfo: ProcInfo;
|
|
274
|
+
clock: Clock;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* A registered peer on Claude Code's cross-session mesh. Receipts and idle notices only reach the process that owns the listening socket (the protocol verifies return addresses via kernel peer pids), so do not split sending and listening across differently-owned processes.
|
|
278
|
+
*/
|
|
279
|
+
export declare class CcPeer extends EventEmitter {
|
|
280
|
+
private readonly options;
|
|
281
|
+
private readonly deps;
|
|
282
|
+
private listening;
|
|
283
|
+
private heartbeatTimer;
|
|
284
|
+
private ownKey;
|
|
285
|
+
private readonly pacer;
|
|
286
|
+
private readonly log;
|
|
287
|
+
private stopped;
|
|
288
|
+
constructor(options: Readonly<CcPeerOptions>, deps: Deps);
|
|
289
|
+
static create(options?: Readonly<CcPeerOptions>): Promise<CcPeer>;
|
|
290
|
+
private start;
|
|
291
|
+
private buildRegistryEntry;
|
|
292
|
+
roster(): Promise<RegistryEntry[]>;
|
|
293
|
+
send(target: PeerRef, body: string, options?: Readonly<SendOptions>): Promise<{
|
|
294
|
+
msgId: string;
|
|
295
|
+
}>;
|
|
296
|
+
private pacedSend;
|
|
297
|
+
subscribeIdle(target: PeerRef): Promise<{
|
|
298
|
+
msgId: string;
|
|
299
|
+
}>;
|
|
300
|
+
private resolveTarget;
|
|
301
|
+
private handleConnection;
|
|
302
|
+
stop(): Promise<void>;
|
|
303
|
+
}
|
|
3
304
|
//#endregion
|
package/dist/cc-peer.d.mts
CHANGED
|
@@ -1,3 +1,304 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/adapters/node/paths.d.ts
|
|
4
|
+
interface PathConfig {
|
|
5
|
+
homeDir?: string;
|
|
6
|
+
socketDir?: string;
|
|
7
|
+
}
|
|
8
|
+
//#endregion
|
|
9
|
+
//#region src/ports/transport.d.ts
|
|
10
|
+
/** Transport port: per-exchange connections and the listening inbox. */
|
|
11
|
+
interface Transport {
|
|
12
|
+
/**
|
|
13
|
+
* One connection, one write of the given lines (each newline-terminated), then close after a short linger. Mirrors the reference client's `Pe`.
|
|
14
|
+
*/
|
|
15
|
+
connectWrite: (socketPath: string, lines: readonly string[], lingerMs?: number) => Promise<void>;
|
|
16
|
+
/** Probe whether a socket accepts connections (roster admission uses this). */
|
|
17
|
+
probe: (socketPath: string) => Promise<boolean>;
|
|
18
|
+
listen: (socketPath: string, onConnection: (conn: Readonly<InboundConnection>) => void) => Promise<ListeningSocket>;
|
|
19
|
+
}
|
|
20
|
+
interface InboundConnection {
|
|
21
|
+
/** Resolves once the kernel reports the connecting process's pid (macOS). */
|
|
22
|
+
peerPid: () => number | undefined;
|
|
23
|
+
readLines: () => AsyncIterable<string>;
|
|
24
|
+
close: () => void;
|
|
25
|
+
}
|
|
26
|
+
interface ListeningSocket {
|
|
27
|
+
socketPath: string;
|
|
28
|
+
close: () => Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/schemas/registry.d.ts
|
|
32
|
+
/** One entry of the ~/.claude/sessions/<pid>.json registry. */
|
|
33
|
+
declare const RegistryEntrySchema: z.ZodObject<{
|
|
34
|
+
pid: z.ZodNumber;
|
|
35
|
+
sessionId: z.ZodString;
|
|
36
|
+
cwd: z.ZodString;
|
|
37
|
+
startedAt: z.ZodNumber;
|
|
38
|
+
procStart: z.ZodString;
|
|
39
|
+
version: z.ZodString;
|
|
40
|
+
peerProtocol: z.ZodNumber;
|
|
41
|
+
peerFeatures: z.ZodArray<z.ZodEnum<{
|
|
42
|
+
notify_idle: "notify_idle";
|
|
43
|
+
reply_across_default_dirs: "reply_across_default_dirs";
|
|
44
|
+
artifact_yield: "artifact_yield";
|
|
45
|
+
}> & {
|
|
46
|
+
is(value: unknown): value is "notify_idle" | "reply_across_default_dirs" | "artifact_yield";
|
|
47
|
+
}>;
|
|
48
|
+
kind: z.ZodEnum<{
|
|
49
|
+
interactive: "interactive";
|
|
50
|
+
bg: "bg";
|
|
51
|
+
daemon: "daemon";
|
|
52
|
+
"daemon-worker": "daemon-worker";
|
|
53
|
+
}> & {
|
|
54
|
+
is(value: unknown): value is "interactive" | "bg" | "daemon" | "daemon-worker";
|
|
55
|
+
};
|
|
56
|
+
entrypoint: z.ZodString;
|
|
57
|
+
pidDomain: z.ZodString;
|
|
58
|
+
messagingSocketPath: z.ZodString;
|
|
59
|
+
name: z.ZodOptional<z.ZodString>;
|
|
60
|
+
nameSource: z.ZodOptional<z.ZodEnum<{
|
|
61
|
+
user: "user";
|
|
62
|
+
peer: "peer";
|
|
63
|
+
derived: "derived";
|
|
64
|
+
collision: "collision";
|
|
65
|
+
auto: "auto";
|
|
66
|
+
hook: "hook";
|
|
67
|
+
}> & {
|
|
68
|
+
is(value: unknown): value is "user" | "peer" | "derived" | "collision" | "auto" | "hook";
|
|
69
|
+
}>;
|
|
70
|
+
nameSince: z.ZodOptional<z.ZodNumber>;
|
|
71
|
+
updatedAt: z.ZodNumber;
|
|
72
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
busy: "busy";
|
|
74
|
+
shell: "shell";
|
|
75
|
+
idle: "idle";
|
|
76
|
+
waiting: "waiting";
|
|
77
|
+
}> & {
|
|
78
|
+
is(value: unknown): value is "busy" | "shell" | "idle" | "waiting";
|
|
79
|
+
}>;
|
|
80
|
+
statusUpdatedAt: z.ZodOptional<z.ZodNumber>;
|
|
81
|
+
bridgeSessionId: z.ZodOptional<z.ZodString>;
|
|
82
|
+
}, z.core.$strip> & {
|
|
83
|
+
is(value: unknown): value is {
|
|
84
|
+
pid: number;
|
|
85
|
+
sessionId: string;
|
|
86
|
+
cwd: string;
|
|
87
|
+
startedAt: number;
|
|
88
|
+
procStart: string;
|
|
89
|
+
version: string;
|
|
90
|
+
peerProtocol: number;
|
|
91
|
+
peerFeatures: ("notify_idle" | "reply_across_default_dirs" | "artifact_yield")[];
|
|
92
|
+
kind: "interactive" | "bg" | "daemon" | "daemon-worker";
|
|
93
|
+
entrypoint: string;
|
|
94
|
+
pidDomain: string;
|
|
95
|
+
messagingSocketPath: string;
|
|
96
|
+
updatedAt: number;
|
|
97
|
+
name?: string | undefined;
|
|
98
|
+
nameSource?: "user" | "peer" | "derived" | "collision" | "auto" | "hook" | undefined;
|
|
99
|
+
nameSince?: number | undefined;
|
|
100
|
+
status?: "busy" | "shell" | "idle" | "waiting" | undefined;
|
|
101
|
+
statusUpdatedAt?: number | undefined;
|
|
102
|
+
bridgeSessionId?: string | undefined;
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
type RegistryEntry = z.infer<typeof RegistryEntrySchema>;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/ports/registry-store.d.ts
|
|
108
|
+
/** Registry port: the ~/.claude/sessions/<pid>.json store. */
|
|
109
|
+
interface RegistryStore {
|
|
110
|
+
list: () => Promise<RegistryEntry[]>;
|
|
111
|
+
read: (pid: number) => Promise<RegistryEntry | undefined>;
|
|
112
|
+
write: (entry: RegistryEntry) => Promise<void>;
|
|
113
|
+
/** Heartbeat: bump updatedAt/statusUpdatedAt without rewriting the rest. */
|
|
114
|
+
touch: (pid: number) => Promise<void>;
|
|
115
|
+
remove: (pid: number) => Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/schemas/keyfile.d.ts
|
|
119
|
+
/** The auth key a session publishes next to its socket. */
|
|
120
|
+
declare const PeerKeyFileSchema: z.ZodObject<{
|
|
121
|
+
peerToken: z.ZodString;
|
|
122
|
+
procStart: z.ZodString;
|
|
123
|
+
pidDomain: z.ZodString;
|
|
124
|
+
}, z.core.$strip> & {
|
|
125
|
+
is(value: unknown): value is {
|
|
126
|
+
peerToken: string;
|
|
127
|
+
procStart: string;
|
|
128
|
+
pidDomain: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
type PeerKeyFile = z.infer<typeof PeerKeyFileSchema>;
|
|
132
|
+
//#endregion
|
|
133
|
+
//#region src/ports/key-store.d.ts
|
|
134
|
+
/** Key-store port: auth keys published next to each session socket. */
|
|
135
|
+
interface KeyStore {
|
|
136
|
+
readForSocket: (socketPath: string) => Promise<PeerKeyFile | undefined>;
|
|
137
|
+
/** Write our own key file for the socket we own. */
|
|
138
|
+
writeForSocket: (socketPath: string, keyFile: Readonly<PeerKeyFile>) => Promise<void>;
|
|
139
|
+
removeForSocket: (socketPath: string) => Promise<void>;
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/ports/proc-info.d.ts
|
|
143
|
+
/** Process-info port: the liveness and proc-start facts the roster needs. */
|
|
144
|
+
interface ProcInfo {
|
|
145
|
+
alive: (pid: number) => Promise<boolean>;
|
|
146
|
+
/**
|
|
147
|
+
* The process start string, byte-equal to `LC_ALL=C TZ=UTC ps -o lstart=` output — the exact value the receiver's registry byte-compares against.
|
|
148
|
+
*/
|
|
149
|
+
lstart: (pid: number) => Promise<string | undefined>;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/ports/clock.d.ts
|
|
153
|
+
/** Clock port: injectable time for pacer/refill and freshness windows. */
|
|
154
|
+
interface Clock {
|
|
155
|
+
nowMs: () => number;
|
|
156
|
+
}
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/schemas/wire.d.ts
|
|
159
|
+
declare const PeerMessageStatusSchema: z.ZodObject<{
|
|
160
|
+
type: z.ZodLiteral<"control">;
|
|
161
|
+
action: z.ZodLiteral<"peer_message_status">;
|
|
162
|
+
status: z.ZodEnum<{
|
|
163
|
+
held: "held";
|
|
164
|
+
delivered: "delivered";
|
|
165
|
+
denied: "denied";
|
|
166
|
+
expired: "expired";
|
|
167
|
+
dropped: "dropped";
|
|
168
|
+
}>;
|
|
169
|
+
reason: z.ZodString;
|
|
170
|
+
from: z.ZodString;
|
|
171
|
+
orig_msg_id: z.ZodString;
|
|
172
|
+
status_detail: z.ZodOptional<z.ZodString>;
|
|
173
|
+
drop_reason: z.ZodOptional<z.ZodEnum<{
|
|
174
|
+
"rate-limited": "rate-limited";
|
|
175
|
+
duplicate: "duplicate";
|
|
176
|
+
"hop-loop": "hop-loop";
|
|
177
|
+
"hop-runaway": "hop-runaway";
|
|
178
|
+
"queue-full": "queue-full";
|
|
179
|
+
}> & {
|
|
180
|
+
is(value: unknown): value is "rate-limited" | "duplicate" | "hop-loop" | "hop-runaway" | "queue-full";
|
|
181
|
+
}>;
|
|
182
|
+
dropped_msg_ids: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
183
|
+
msgV: z.ZodNumber;
|
|
184
|
+
msg_id: z.ZodString;
|
|
185
|
+
}, z.core.$strip> & {
|
|
186
|
+
is(value: unknown): value is {
|
|
187
|
+
type: "control";
|
|
188
|
+
action: "peer_message_status";
|
|
189
|
+
status: "held" | "delivered" | "denied" | "expired" | "dropped";
|
|
190
|
+
reason: string;
|
|
191
|
+
from: string;
|
|
192
|
+
orig_msg_id: string;
|
|
193
|
+
msgV: number;
|
|
194
|
+
msg_id: string;
|
|
195
|
+
status_detail?: string | undefined;
|
|
196
|
+
drop_reason?: "rate-limited" | "duplicate" | "hop-loop" | "hop-runaway" | "queue-full" | undefined;
|
|
197
|
+
dropped_msg_ids?: string[] | undefined;
|
|
198
|
+
};
|
|
199
|
+
};
|
|
200
|
+
type PeerMessageStatus = z.infer<typeof PeerMessageStatusSchema>;
|
|
201
|
+
declare const PeerIdleNoticeSchema: z.ZodObject<{
|
|
202
|
+
type: z.ZodLiteral<"control">;
|
|
203
|
+
action: z.ZodLiteral<"peer_idle_notice">;
|
|
204
|
+
orig_msg_id: z.ZodString;
|
|
205
|
+
state: z.ZodEnum<{
|
|
206
|
+
idle: "idle";
|
|
207
|
+
exited: "exited";
|
|
208
|
+
}>;
|
|
209
|
+
finished_at: z.ZodNumber;
|
|
210
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
211
|
+
from: z.ZodString;
|
|
212
|
+
from_mode: z.ZodOptional<z.ZodEnum<{
|
|
213
|
+
bypass: "bypass";
|
|
214
|
+
prompting: "prompting";
|
|
215
|
+
}>>;
|
|
216
|
+
msgV: z.ZodNumber;
|
|
217
|
+
msg_id: z.ZodString;
|
|
218
|
+
}, z.core.$strip> & {
|
|
219
|
+
is(value: unknown): value is {
|
|
220
|
+
type: "control";
|
|
221
|
+
action: "peer_idle_notice";
|
|
222
|
+
orig_msg_id: string;
|
|
223
|
+
state: "idle" | "exited";
|
|
224
|
+
finished_at: number;
|
|
225
|
+
from: string;
|
|
226
|
+
msgV: number;
|
|
227
|
+
msg_id: string;
|
|
228
|
+
detail?: string | undefined;
|
|
229
|
+
from_mode?: "bypass" | "prompting" | undefined;
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
type PeerIdleNotice = z.infer<typeof PeerIdleNoticeSchema>;
|
|
233
|
+
//#endregion
|
|
1
234
|
//#region src/cc-peer.d.ts
|
|
235
|
+
/** Package version placeholder until semantic-release owns it. */
|
|
2
236
|
export declare const CC_PEER_VERSION = "0.0.0";
|
|
237
|
+
/** How a target can be addressed. */
|
|
238
|
+
export type PeerRef = {
|
|
239
|
+
pid: number;
|
|
240
|
+
} | {
|
|
241
|
+
name: string;
|
|
242
|
+
} | {
|
|
243
|
+
address: string;
|
|
244
|
+
};
|
|
245
|
+
export interface CcPeerOptions extends PathConfig {
|
|
246
|
+
name?: string;
|
|
247
|
+
sessionId?: string;
|
|
248
|
+
heartbeatMs?: number;
|
|
249
|
+
logger?: (message: string) => void;
|
|
250
|
+
}
|
|
251
|
+
export interface SendOptions {
|
|
252
|
+
priority?: "next" | "later";
|
|
253
|
+
/** Attaches from-mode attestation; false deliberately sends unattested (recipient holds it). */
|
|
254
|
+
fromMode?: "bypass" | "prompting" | false;
|
|
255
|
+
/** When set, must match the receiver's sessionId or the frame is silently dropped. */
|
|
256
|
+
sessionId?: string;
|
|
257
|
+
}
|
|
258
|
+
export interface InboundMessage {
|
|
259
|
+
from?: string;
|
|
260
|
+
fromSession?: string;
|
|
261
|
+
fromName?: string;
|
|
262
|
+
fromMode?: "bypass" | "prompting";
|
|
263
|
+
hopChain?: string[];
|
|
264
|
+
body: string;
|
|
265
|
+
msgId: string;
|
|
266
|
+
}
|
|
267
|
+
export type Receipt = PeerMessageStatus;
|
|
268
|
+
export type IdleNotice = PeerIdleNotice;
|
|
269
|
+
interface Deps {
|
|
270
|
+
transport: Transport;
|
|
271
|
+
registry: RegistryStore;
|
|
272
|
+
keys: KeyStore;
|
|
273
|
+
procInfo: ProcInfo;
|
|
274
|
+
clock: Clock;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* A registered peer on Claude Code's cross-session mesh. Receipts and idle notices only reach the process that owns the listening socket (the protocol verifies return addresses via kernel peer pids), so do not split sending and listening across differently-owned processes.
|
|
278
|
+
*/
|
|
279
|
+
export declare class CcPeer extends EventEmitter {
|
|
280
|
+
private readonly options;
|
|
281
|
+
private readonly deps;
|
|
282
|
+
private listening;
|
|
283
|
+
private heartbeatTimer;
|
|
284
|
+
private ownKey;
|
|
285
|
+
private readonly pacer;
|
|
286
|
+
private readonly log;
|
|
287
|
+
private stopped;
|
|
288
|
+
constructor(options: Readonly<CcPeerOptions>, deps: Deps);
|
|
289
|
+
static create(options?: Readonly<CcPeerOptions>): Promise<CcPeer>;
|
|
290
|
+
private start;
|
|
291
|
+
private buildRegistryEntry;
|
|
292
|
+
roster(): Promise<RegistryEntry[]>;
|
|
293
|
+
send(target: PeerRef, body: string, options?: Readonly<SendOptions>): Promise<{
|
|
294
|
+
msgId: string;
|
|
295
|
+
}>;
|
|
296
|
+
private pacedSend;
|
|
297
|
+
subscribeIdle(target: PeerRef): Promise<{
|
|
298
|
+
msgId: string;
|
|
299
|
+
}>;
|
|
300
|
+
private resolveTarget;
|
|
301
|
+
private handleConnection;
|
|
302
|
+
stop(): Promise<void>;
|
|
303
|
+
}
|
|
3
304
|
//#endregion
|
package/dist/cc-peer.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-peer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Communicate with local Claude Code instances over their native cross-session peer messaging: send and receive messages, register as a named discoverable peer, receipts, idle subscriptions, and a REST facade via `npx cc-peer`.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|