@paradow/ledge 0.14.0 → 0.15.1
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/README.md +2 -2
- package/dist/cli/{attach-3U4A44ZE.js → attach-Y4OMZXAH.js} +7 -7
- package/dist/cli/{bridge-7JT6P53L.js → bridge-6K523KUX.js} +2 -2
- package/dist/cli/{chunk-FPW7MF5X.js → chunk-2H5FJH4Q.js} +2 -2
- package/dist/cli/{chunk-5LVPB3ET.js → chunk-2LQITRTR.js} +178 -39
- package/dist/cli/{chunk-D3JQV5AA.js → chunk-4IBQ4PG6.js} +6 -6
- package/dist/cli/{chunk-5DDBCJX2.js → chunk-BNKPXSUY.js} +1 -1
- package/dist/cli/{chunk-2P2SUT7A.js → chunk-IMSPQUFC.js} +2 -2
- package/dist/cli/{chunk-ULWVO3TH.js → chunk-KGEF6OIU.js} +2 -2
- package/dist/cli/chunk-LKQ5DDQ4.js +6 -0
- package/dist/cli/{chunk-IIXALRFM.js → chunk-NKYJF3VT.js} +2 -2
- package/dist/cli/{chunk-PFDZ6VYG.js → chunk-PXO4FFJR.js} +1 -1
- package/dist/cli/{chunk-UWANRALT.js → chunk-QCEVJMXI.js} +4 -3
- package/dist/cli/{chunk-QBJEUZYW.js → chunk-R5462SQM.js} +1 -1
- package/dist/cli/{chunk-6A7JPTHV.js → chunk-ZANOMSLW.js} +22 -12
- package/dist/cli/{daemon-KHGAO6BC.js → daemon-TBYCUHC3.js} +1243 -189
- package/dist/cli/{hook-R6OWCFAQ.js → hook-PRPEB3T7.js} +8 -8
- package/dist/cli/index.js +11 -11
- package/dist/cli/{launch-PPTQEWYD.js → launch-JS74JGW6.js} +7 -7
- package/dist/cli/send-JX6O7AQR.js +14 -0
- package/dist/cli/{setup-FPQK3NA7.js → setup-LWYZEVNE.js} +10 -8
- package/dist/cli/{status-QLTB6T3U.js → status-Q6TQXVLF.js} +3 -3
- package/dist/cli/{uninstall-QC5WEWDH.js → uninstall-TFC4LALT.js} +7 -6
- package/dist/cli/{version-AELAEF6T.js → version-5BLH6OA6.js} +1 -1
- package/dist/cli/who-KAAEASAA.js +11 -0
- package/package.json +1 -1
- package/dist/cli/chunk-QUUZSAPI.js +0 -6
- package/dist/cli/send-74LTIRSN.js +0 -14
- package/dist/cli/who-5F6CHRA3.js +0 -11
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ pnpm --filter @paradow/ledge build
|
|
|
30
30
|
chmod +x packages/ledge/dist/cli/index.js
|
|
31
31
|
mkdir -p ~/.local/bin
|
|
32
32
|
ln -sf "$PWD/packages/ledge/dist/cli/index.js" ~/.local/bin/ledge # ~/.local/bin must be on the PATH Claude Code uses for hooks
|
|
33
|
-
ledge setup
|
|
33
|
+
ledge setup # the link becomes a script that runs the CLI with this node (a session Ledge launches may have no node on its PATH: nvm)
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
The daemon is spawned from the built CLI (`dist/cli/index.js`), so rebuild after changes (`pnpm --filter @paradow/ledge build`).
|
|
@@ -43,7 +43,7 @@ ledge claude --name max # pick the bot's name
|
|
|
43
43
|
ledge status # list bots and what they wait for
|
|
44
44
|
ledge attach <id> # put this terminal on a running session (what the overlay opens a tab with)
|
|
45
45
|
ledge daemon # run the daemon in the foreground (normally run by the systemd service)
|
|
46
|
-
ledge uninstall # undo ledge setup: hooks, service and
|
|
46
|
+
ledge uninstall # undo ledge setup: hooks, service and ~/.local/bin/ledge (--purge: ~/.ledge too)
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
`ledge setup` installs `ledge-daemon.service` as a systemd user unit, starts it and enables linger, so the daemon runs from WSL boot and restarts after a crash. Rerun it after a node upgrade or a repository move: the unit is rewritten and the service restarted. Useful commands:
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
attachEnv
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4IBQ4PG6.js";
|
|
4
4
|
import {
|
|
5
5
|
connect
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-PXO4FFJR.js";
|
|
7
|
+
import "./chunk-KGEF6OIU.js";
|
|
8
8
|
import {
|
|
9
9
|
PROTOCOL_PORT,
|
|
10
10
|
backendOf
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2LQITRTR.js";
|
|
12
12
|
import {
|
|
13
13
|
realTmux
|
|
14
|
-
} from "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
14
|
+
} from "./chunk-2H5FJH4Q.js";
|
|
15
|
+
import "./chunk-BNKPXSUY.js";
|
|
16
|
+
import "./chunk-R5462SQM.js";
|
|
17
17
|
|
|
18
18
|
// src/cli/attach.ts
|
|
19
19
|
import { spawn } from "child_process";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
paths
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-R5462SQM.js";
|
|
4
4
|
|
|
5
5
|
// ../protocol/src/session.ts
|
|
6
6
|
import { z } from "zod";
|
|
@@ -458,7 +458,7 @@ function pendingKey(p) {
|
|
|
458
458
|
|
|
459
459
|
// ../protocol/src/mobile.ts
|
|
460
460
|
import { z as z14 } from "zod";
|
|
461
|
-
var DEFAULT_RELAY_URL = "https://
|
|
461
|
+
var DEFAULT_RELAY_URL = "https://app.kl-ledge.dev";
|
|
462
462
|
var RelayUrlSchema = z14.string().transform((v, ctx) => {
|
|
463
463
|
const bad = () => {
|
|
464
464
|
ctx.addIssue({ code: z14.ZodIssueCode.custom, message: "invalid relay URL" });
|
|
@@ -973,7 +973,53 @@ var MeResponseSchema = z18.object({
|
|
|
973
973
|
});
|
|
974
974
|
|
|
975
975
|
// ../protocol/src/phone.ts
|
|
976
|
+
import { z as z20 } from "zod";
|
|
977
|
+
|
|
978
|
+
// ../protocol/src/push.ts
|
|
976
979
|
import { z as z19 } from "zod";
|
|
980
|
+
var B64U = z19.string().regex(/^[A-Za-z0-9_-]*$/);
|
|
981
|
+
var PUSH_HOSTS = ["*.push.apple.com", "fcm.googleapis.com", "updates.push.services.mozilla.com", "*.notify.windows.com"];
|
|
982
|
+
function pushEndpointAllowed(url) {
|
|
983
|
+
let u;
|
|
984
|
+
try {
|
|
985
|
+
u = new URL(url);
|
|
986
|
+
} catch {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
if (u.protocol !== "https:" || u.port !== "" || u.username !== "" || u.password !== "") return false;
|
|
990
|
+
const host = u.hostname.toLowerCase();
|
|
991
|
+
return PUSH_HOSTS.some((h) => {
|
|
992
|
+
if (!h.startsWith("*.")) return host === h;
|
|
993
|
+
const suffix = h.slice(1);
|
|
994
|
+
return host.length > suffix.length && host.endsWith(suffix);
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
var PushSendSchema = z19.object({
|
|
998
|
+
endpoint: z19.string().url().max(1024),
|
|
999
|
+
body: B64U.max(5462),
|
|
1000
|
+
ttl: z19.number().int().min(0).max(86400),
|
|
1001
|
+
urgency: z19.enum(["normal", "high"]),
|
|
1002
|
+
topic: z19.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
|
|
1003
|
+
});
|
|
1004
|
+
var PushSendResultSchema = z19.object({
|
|
1005
|
+
result: z19.enum(["sent", "gone", "failed"]),
|
|
1006
|
+
status: z19.number().int().optional()
|
|
1007
|
+
});
|
|
1008
|
+
var PushSubscriptionSchema = z19.object({
|
|
1009
|
+
endpoint: z19.string().url().max(1024).refine(pushEndpointAllowed),
|
|
1010
|
+
keys: z19.object({ p256dh: B64U.length(87), auth: B64U.length(22) })
|
|
1011
|
+
});
|
|
1012
|
+
var PushPayloadSchema = z19.object({
|
|
1013
|
+
v: z19.literal(1),
|
|
1014
|
+
kind: z19.enum(["request", "question", "finished", "chat"]),
|
|
1015
|
+
title: z19.string().max(80),
|
|
1016
|
+
body: z19.string().max(120),
|
|
1017
|
+
url: z19.string().regex(/^\/#\/[A-Za-z0-9/%._~-]*$/).max(300),
|
|
1018
|
+
badge: z19.number().int().min(0).max(999),
|
|
1019
|
+
tag: z19.string().regex(/^[A-Za-z0-9_-]{1,32}$/)
|
|
1020
|
+
});
|
|
1021
|
+
|
|
1022
|
+
// ../protocol/src/phone.ts
|
|
977
1023
|
var PHONE_PROTOCOL = 1;
|
|
978
1024
|
var PAD_TIERS = [1024, 4096, 16384, 65536, 262144, 655360];
|
|
979
1025
|
var PLAIN_MAX = 655360;
|
|
@@ -991,18 +1037,22 @@ var THREAD_TEXT_MAX = 8192;
|
|
|
991
1037
|
var TOOL_TEXT_MAX = 2048;
|
|
992
1038
|
var PENDING_BODY_MAX = 65536;
|
|
993
1039
|
var SNAPSHOT_BODY_MAX = 2048;
|
|
994
|
-
var RiskSchema =
|
|
995
|
-
var RiskReasonSchema =
|
|
996
|
-
var OperationIdSchema =
|
|
997
|
-
var OperationStatusSchema =
|
|
998
|
-
var OperationReasonSchema =
|
|
999
|
-
var CapabilitiesSchema =
|
|
1040
|
+
var RiskSchema = z20.enum(["low", "high"]);
|
|
1041
|
+
var RiskReasonSchema = z20.enum(["question", "read_only_command", "edit_inside_folder", "pushes", "destroys", "deploys", "publishes", "pipes_to_shell", "network", "writes_outside_folder", "sensitive_path", "unsafe_flag", "shell_syntax", "script", "plan", "unknown"]);
|
|
1042
|
+
var OperationIdSchema = z20.string().uuid();
|
|
1043
|
+
var OperationStatusSchema = z20.enum(["received", "applied", "rejected", "failed"]);
|
|
1044
|
+
var OperationReasonSchema = z20.enum([...RefusalCodeSchema.options, "untypable", "confirm_required", "conflict", "assertion_invalid", "assertion_expired"]);
|
|
1045
|
+
var CapabilitiesSchema = z20.object({
|
|
1000
1046
|
/** A question can be answered in free text. */
|
|
1001
|
-
freeTextAnswer:
|
|
1047
|
+
freeTextAnswer: z20.boolean(),
|
|
1048
|
+
/** The session's accept/deny can be typed; absent = true. */
|
|
1049
|
+
decide: z20.boolean().optional(),
|
|
1050
|
+
/** OMP's dialog questions can be answered from the phone; absent = false. */
|
|
1051
|
+
questions: z20.boolean().optional()
|
|
1002
1052
|
});
|
|
1003
1053
|
var UNTYPABLE = /[\u0000-\u001f\u007f-\u009f\u200b\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/;
|
|
1004
|
-
var CapabilitiesByToolSchema =
|
|
1005
|
-
var PhonePendingSchema = PendingSchema.extend({ key:
|
|
1054
|
+
var CapabilitiesByToolSchema = z20.object({ claude: CapabilitiesSchema, codex: CapabilitiesSchema, cursor: CapabilitiesSchema, omp: CapabilitiesSchema });
|
|
1055
|
+
var PhonePendingSchema = PendingSchema.extend({ key: z20.string().max(64), risk: RiskSchema, riskReason: RiskReasonSchema, cut: z20.literal(true).optional() });
|
|
1006
1056
|
var PhoneSessionSchema = SessionSchema.pick({
|
|
1007
1057
|
id: true,
|
|
1008
1058
|
name: true,
|
|
@@ -1018,49 +1068,133 @@ var PhoneSessionSchema = SessionSchema.pick({
|
|
|
1018
1068
|
lastReply: true,
|
|
1019
1069
|
startedAt: true,
|
|
1020
1070
|
lastActivityAt: true
|
|
1021
|
-
}).extend({ pending: PhonePendingSchema.optional(), unread:
|
|
1022
|
-
var
|
|
1023
|
-
var
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1071
|
+
}).extend({ pending: PhonePendingSchema.optional(), unread: z20.boolean() });
|
|
1072
|
+
var B64U2 = z20.string().regex(/^[A-Za-z0-9_-]*$/);
|
|
1073
|
+
var CredentialSchema = z20.object({
|
|
1074
|
+
id: B64U2.max(1366),
|
|
1075
|
+
publicKey: B64U2.max(2048),
|
|
1076
|
+
alg: z20.union([z20.literal(-7), z20.literal(-257)]),
|
|
1077
|
+
clientDataJSON: B64U2.max(4096),
|
|
1078
|
+
authenticatorData: B64U2.max(4096),
|
|
1079
|
+
transports: z20.array(z20.string().max(20)).max(8).optional()
|
|
1080
|
+
});
|
|
1081
|
+
var AssertionSchema = z20.object({
|
|
1082
|
+
id: B64U2.max(1366),
|
|
1083
|
+
clientDataJSON: B64U2.max(4096),
|
|
1084
|
+
authenticatorData: B64U2.max(4096),
|
|
1085
|
+
signature: B64U2.max(1024)
|
|
1086
|
+
});
|
|
1087
|
+
var ConfirmPurposeSchema = z20.enum(["register", "decide", "launch"]);
|
|
1088
|
+
var PhoneChatStepSchema = ChatStepSchema.pick({ kind: true, id: true, name: true }).strict();
|
|
1089
|
+
var PhoneChatProposalSchema = z20.object({
|
|
1090
|
+
project: z20.string().max(80),
|
|
1091
|
+
tool: ToolSchema,
|
|
1092
|
+
prompt: z20.string().max(TOOL_TEXT_MAX).optional(),
|
|
1093
|
+
launchedId: z20.string().optional(),
|
|
1094
|
+
failed: z20.literal(true).optional()
|
|
1095
|
+
}).strict();
|
|
1096
|
+
var PhoneChatRelaySchema = z20.object({
|
|
1097
|
+
id: z20.string().min(1),
|
|
1098
|
+
name: z20.string(),
|
|
1099
|
+
tool: ToolSchema,
|
|
1100
|
+
text: z20.string().max(TOOL_TEXT_MAX),
|
|
1101
|
+
status: z20.enum(["queued", "sent"]).optional(),
|
|
1102
|
+
sentAt: z20.string().datetime().optional(),
|
|
1103
|
+
failed: z20.literal(true).optional()
|
|
1104
|
+
}).strict();
|
|
1105
|
+
var ChatErrorCodeSchema = z20.enum(["busy", "timeout", "cancelled", "failed"]);
|
|
1106
|
+
var PhoneChatTurnSchema = z20.object({
|
|
1107
|
+
qid: OperationIdSchema,
|
|
1108
|
+
question: z20.string().max(MESSAGE_MAX),
|
|
1109
|
+
askedAt: z20.string().datetime(),
|
|
1110
|
+
steps: z20.array(PhoneChatStepSchema).max(50),
|
|
1111
|
+
answer: z20.string().max(THREAD_TEXT_MAX).optional(),
|
|
1112
|
+
answeredAt: z20.string().datetime().optional(),
|
|
1113
|
+
error: ChatErrorCodeSchema.optional(),
|
|
1114
|
+
proposals: z20.array(PhoneChatProposalSchema).max(MAX_PROPOSALS).optional(),
|
|
1115
|
+
relay: PhoneChatRelaySchema.optional(),
|
|
1116
|
+
cut: z20.literal(true).optional()
|
|
1117
|
+
}).strict();
|
|
1118
|
+
var CHAT_HISTORY_BUDGET = 393216;
|
|
1119
|
+
var sessionId = z20.string().min(1).max(100);
|
|
1120
|
+
var PhoneExpectSchema = PendingSchema.pick({ kind: true, text: true }).extend({ key: z20.string().min(1).max(64) });
|
|
1121
|
+
var PhoneClientMessageSchema = z20.discriminatedUnion("type", [
|
|
1122
|
+
z20.object({ type: z20.literal("snapshot.request") }),
|
|
1123
|
+
z20.object({ type: z20.literal("session.thread"), id: sessionId }),
|
|
1027
1124
|
/** The session's request with its whole body (up to `PENDING_BODY_MAX`). */
|
|
1028
|
-
|
|
1125
|
+
z20.object({ type: z20.literal("session.pending"), id: sessionId }),
|
|
1029
1126
|
/** The session whose terminal the phone shows; null when it shows none. */
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1127
|
+
z20.object({ type: z20.literal("session.watch"), id: sessionId.nullable() }),
|
|
1128
|
+
z20.object({ type: z20.literal("session.read"), id: sessionId, at: z20.string().datetime() }),
|
|
1129
|
+
/** Asks for a challenge. One object for both purposes: the gateway refuses a `decide` without `id` and `expect`. */
|
|
1130
|
+
z20.object({ type: z20.literal("confirm.prepare"), purpose: ConfirmPurposeSchema, id: sessionId.optional(), expect: PhoneExpectSchema.optional(), qid: OperationIdSchema.optional() }),
|
|
1131
|
+
z20.object({ type: z20.literal("credential.register"), op: OperationIdSchema, credential: CredentialSchema }),
|
|
1132
|
+
z20.object({
|
|
1133
|
+
type: z20.literal("session.decide"),
|
|
1034
1134
|
op: OperationIdSchema,
|
|
1035
1135
|
id: sessionId,
|
|
1036
1136
|
decision: DecisionSchema,
|
|
1037
|
-
note:
|
|
1137
|
+
note: z20.string().trim().min(1).max(NOTE_MAX).optional(),
|
|
1038
1138
|
/** The request the phone showed: refused unless it is still the pending one. */
|
|
1039
|
-
expect: PhoneExpectSchema
|
|
1139
|
+
expect: PhoneExpectSchema,
|
|
1140
|
+
/** The device check over the challenge of `confirm.prepare`; required by the daemon for a high-risk accept. */
|
|
1141
|
+
assertion: AssertionSchema.optional()
|
|
1040
1142
|
}),
|
|
1041
|
-
|
|
1042
|
-
type:
|
|
1143
|
+
z20.object({
|
|
1144
|
+
type: z20.literal("session.reply"),
|
|
1043
1145
|
op: OperationIdSchema,
|
|
1044
1146
|
id: sessionId,
|
|
1045
|
-
text:
|
|
1147
|
+
text: z20.string().min(1).max(MESSAGE_MAX),
|
|
1046
1148
|
/** The request the reply answers, or null when the phone showed none: refused if the session's request is no longer that one. */
|
|
1047
|
-
expect: PhoneExpectSchema.nullable()
|
|
1149
|
+
expect: PhoneExpectSchema.nullable(),
|
|
1150
|
+
assertion: AssertionSchema.optional()
|
|
1151
|
+
}),
|
|
1152
|
+
/** OMP's `ask`: the answers to every question of the dialog `requestId`, in order; `cancel` declines it. Bounds as the local `session.answer`. */
|
|
1153
|
+
z20.object({
|
|
1154
|
+
type: z20.literal("session.answer"),
|
|
1155
|
+
op: OperationIdSchema,
|
|
1156
|
+
id: sessionId,
|
|
1157
|
+
requestId: z20.string().min(1).max(200),
|
|
1158
|
+
answers: z20.array(z20.object({ id: z20.string().max(200), selected: z20.array(z20.string().max(2e3)).max(100), text: z20.string().max(2e4).optional() })).max(50),
|
|
1159
|
+
cancel: z20.literal(true).optional(),
|
|
1160
|
+
expect: PhoneExpectSchema
|
|
1048
1161
|
}),
|
|
1049
|
-
|
|
1162
|
+
z20.object({ type: z20.literal("session.interrupt"), op: OperationIdSchema, id: sessionId }),
|
|
1163
|
+
/** The phone drops its passkey from the daemon. */
|
|
1164
|
+
z20.object({ type: z20.literal("device.forget"), op: OperationIdSchema }),
|
|
1165
|
+
/** The phone's push subscription, with the VAPID key it was made with (so a key change is seen). */
|
|
1166
|
+
z20.object({ type: z20.literal("push.subscribe"), op: OperationIdSchema, subscription: PushSubscriptionSchema, key: B64U2.length(87) }),
|
|
1167
|
+
z20.object({ type: z20.literal("push.unsubscribe"), op: OperationIdSchema }),
|
|
1168
|
+
/** The phone's own chat with Ledge. */
|
|
1169
|
+
z20.object({ type: z20.literal("chat.ask"), qid: OperationIdSchema, text: z20.string().trim().min(1).max(MESSAGE_MAX) }),
|
|
1170
|
+
z20.object({ type: z20.literal("chat.cancel"), qid: OperationIdSchema }),
|
|
1171
|
+
z20.object({ type: z20.literal("chat.reset") }),
|
|
1172
|
+
z20.object({ type: z20.literal("chat.history") }),
|
|
1173
|
+
z20.object({ type: z20.literal("chat.relay"), op: OperationIdSchema, qid: OperationIdSchema }),
|
|
1174
|
+
z20.object({ type: z20.literal("chat.launch"), op: OperationIdSchema, qid: OperationIdSchema, rows: z20.array(z20.number().int().min(0).max(MAX_PROPOSALS - 1)).min(1).max(MAX_PROPOSALS).refine((r) => new Set(r).size === r.length, "a row twice").optional(), assertion: AssertionSchema.optional() }),
|
|
1175
|
+
/** Whether the phone's app is in front: a device that is visible gets no push. */
|
|
1176
|
+
z20.object({ type: z20.literal("presence"), visible: z20.boolean() })
|
|
1050
1177
|
]);
|
|
1051
|
-
var PhoneServerMessageSchema =
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1178
|
+
var PhoneServerMessageSchema = z20.discriminatedUnion("type", [
|
|
1179
|
+
z20.object({ type: z20.literal("snapshot"), protocol: z20.literal(PHONE_PROTOCOL), sessions: z20.array(PhoneSessionSchema), capabilities: CapabilitiesByToolSchema, daemon: z20.object({ version: z20.string(), name: z20.string().max(40), boot: z20.string().uuid().optional() }), device: z20.object({ confirm: z20.boolean(), push: z20.enum(["on", "off", "stale", "unavailable"]).optional(), chat: z20.literal(true).optional() }).optional() }),
|
|
1180
|
+
z20.object({ type: z20.literal("confirm.challenge"), purpose: ConfirmPurposeSchema, challenge: B64U2.max(64), rpId: z20.string().max(253), credentialId: B64U2.max(1366).optional(), expiresInMs: z20.number().int().positive() }),
|
|
1181
|
+
z20.object({ type: z20.literal("session.updated"), session: PhoneSessionSchema }),
|
|
1182
|
+
z20.object({ type: z20.literal("session.removed"), id: z20.string() }),
|
|
1055
1183
|
/** `pending.cut` is set when even this body was shortened. */
|
|
1056
|
-
|
|
1057
|
-
|
|
1184
|
+
z20.object({ type: z20.literal("session.pending"), id: z20.string(), pending: PhonePendingSchema }),
|
|
1185
|
+
z20.object({ type: z20.literal("session.preview"), id: z20.string(), lines: z20.array(z20.string()) }),
|
|
1058
1186
|
/** `cut`: the ids of the items shortened; `more`: older items were left out. */
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1187
|
+
z20.object({ type: z20.literal("session.thread"), id: z20.string(), items: z20.array(ThreadItemSchema), cut: z20.array(z20.string()), more: z20.boolean() }),
|
|
1188
|
+
z20.object({ type: z20.literal("session.thread.item"), id: z20.string(), item: ThreadItemSchema, cut: z20.literal(true).optional() }),
|
|
1189
|
+
z20.object({ type: z20.literal("operation.result"), op: OperationIdSchema, status: OperationStatusSchema, reason: OperationReasonSchema.optional() }),
|
|
1190
|
+
z20.object({ type: z20.literal("chat.history"), turns: z20.array(PhoneChatTurnSchema).max(50) }),
|
|
1191
|
+
z20.object({ type: z20.literal("chat.step"), qid: OperationIdSchema, step: PhoneChatStepSchema }),
|
|
1192
|
+
/** The whole text so far, not an increment. */
|
|
1193
|
+
z20.object({ type: z20.literal("chat.delta"), qid: OperationIdSchema, text: z20.string().max(THREAD_TEXT_MAX) }),
|
|
1194
|
+
z20.object({ type: z20.literal("chat.done"), qid: OperationIdSchema, turn: PhoneChatTurnSchema }),
|
|
1195
|
+
z20.object({ type: z20.literal("chat.error"), qid: OperationIdSchema, code: ChatErrorCodeSchema }),
|
|
1062
1196
|
/** Never carries free text. */
|
|
1063
|
-
|
|
1197
|
+
z20.object({ type: z20.literal("error"), code: z20.enum(["invalid", "unknown_session", "too_large"]), request: z20.string().max(40).optional() })
|
|
1064
1198
|
]);
|
|
1065
1199
|
|
|
1066
1200
|
// src/auth.ts
|
|
@@ -1225,6 +1359,9 @@ export {
|
|
|
1225
1359
|
RELAY_CLOSE_SIGNED_OUT,
|
|
1226
1360
|
DeviceStartResponseSchema,
|
|
1227
1361
|
DevicePollResponseSchema,
|
|
1362
|
+
pushEndpointAllowed,
|
|
1363
|
+
PushSendResultSchema,
|
|
1364
|
+
PushPayloadSchema,
|
|
1228
1365
|
PHONE_PROTOCOL,
|
|
1229
1366
|
padPlain,
|
|
1230
1367
|
THREAD_ITEMS_MAX,
|
|
@@ -1233,7 +1370,9 @@ export {
|
|
|
1233
1370
|
TOOL_TEXT_MAX,
|
|
1234
1371
|
PENDING_BODY_MAX,
|
|
1235
1372
|
SNAPSHOT_BODY_MAX,
|
|
1373
|
+
OperationIdSchema,
|
|
1236
1374
|
UNTYPABLE,
|
|
1375
|
+
CHAT_HISTORY_BUDGET,
|
|
1237
1376
|
PhoneClientMessageSchema,
|
|
1238
1377
|
hostAllowed,
|
|
1239
1378
|
requestAuthorized,
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DaemonRefusedError,
|
|
3
3
|
connect
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PXO4FFJR.js";
|
|
5
5
|
import {
|
|
6
6
|
exitFile,
|
|
7
7
|
spawnSession
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KGEF6OIU.js";
|
|
9
9
|
import {
|
|
10
10
|
PROTOCOL_PORT
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2LQITRTR.js";
|
|
12
12
|
import {
|
|
13
13
|
realTmux
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-2H5FJH4Q.js";
|
|
15
15
|
import {
|
|
16
16
|
log
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-BNKPXSUY.js";
|
|
18
18
|
import {
|
|
19
19
|
ensureLedgeDir
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-R5462SQM.js";
|
|
21
21
|
|
|
22
22
|
// src/cli/launch.ts
|
|
23
23
|
import { spawn } from "child_process";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
connect,
|
|
3
3
|
sendMessageAndAwaitAck
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PXO4FFJR.js";
|
|
5
5
|
import {
|
|
6
6
|
CODEX_HOOK_VAR
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-KGEF6OIU.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/send.ts
|
|
10
10
|
import { randomUUID } from "crypto";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
pickName
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2LQITRTR.js";
|
|
4
4
|
import {
|
|
5
5
|
paths
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-R5462SQM.js";
|
|
7
7
|
|
|
8
8
|
// src/daemon/transcript.ts
|
|
9
9
|
import { closeSync, fstatSync, openSync, readSync } from "fs";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cleanTopic,
|
|
3
3
|
sessionTitle
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2LQITRTR.js";
|
|
5
5
|
|
|
6
6
|
// src/daemon/crossSession.ts
|
|
7
7
|
var FIRST_LINE = /^Another Claude session sent a message:\s*\n/;
|
|
@@ -356,9 +356,10 @@ function reduceEvent(s, ev) {
|
|
|
356
356
|
case "PanePrompt": {
|
|
357
357
|
const incoming = pendingFor(ev);
|
|
358
358
|
const moved = s.pending?.source === "hook" && s.pending.kind === "question" && incoming.kind === "question" && !sameQuestion(incoming.text, s.pending.text);
|
|
359
|
-
|
|
359
|
+
const bare = s.pending?.source === "hook" && s.pending.kind === "permission" && !s.pending.tool;
|
|
360
|
+
if (s.pending?.source === "hook" && !moved && !bare) return s;
|
|
360
361
|
if (!moved && hooksReportPrompts(s) && lastHookAt && Date.parse(at) - Date.parse(lastHookAt) < HOOK_PRIORITY_MS) return s;
|
|
361
|
-
let pending = moved ? { ...incoming, ...s.pending.helperId ? { helperId: s.pending.helperId } : {} } : mergePending(s.pending, incoming);
|
|
362
|
+
let pending = moved ? { ...incoming, ...s.pending.helperId ? { helperId: s.pending.helperId } : {} } : bare ? incoming : mergePending(s.pending, incoming);
|
|
362
363
|
if (pending.kind === "plan" && !pending.body && s.tool === "codex" && s.lastReply) pending = { ...pending, body: s.lastReply.text };
|
|
363
364
|
if (s.pending && samePending(s.pending, pending)) return s;
|
|
364
365
|
const state = pending.kind === "question" ? "waiting_input" : "waiting_permission";
|
|
@@ -17,7 +17,7 @@ function ledgeDir() {
|
|
|
17
17
|
}
|
|
18
18
|
function paths() {
|
|
19
19
|
const dir = ledgeDir();
|
|
20
|
-
return { dir, tmuxConf: join(dir, "tmux.conf"), journal: join(dir, "sessions.json"), log: join(dir, "daemon.log"), pid: join(dir, "daemon.pid"), history: join(dir, "history"), machines: join(dir, "machines.json"), projects: join(dir, "projects.json"), island: join(dir, "island.json"), token: join(dir, "token"), mobile: join(dir, "mobile.json"), mobileSession: join(dir, "mobile-session.json"), mobileKey: join(dir, "mobile-key"), devices: join(dir, "devices.json") };
|
|
20
|
+
return { dir, tmuxConf: join(dir, "tmux.conf"), journal: join(dir, "sessions.json"), log: join(dir, "daemon.log"), pid: join(dir, "daemon.pid"), history: join(dir, "history"), machines: join(dir, "machines.json"), projects: join(dir, "projects.json"), island: join(dir, "island.json"), token: join(dir, "token"), mobile: join(dir, "mobile.json"), mobileSession: join(dir, "mobile-session.json"), mobileKey: join(dir, "mobile-key"), devices: join(dir, "devices.json"), revocations: join(dir, "revocations.json") };
|
|
21
21
|
}
|
|
22
22
|
async function ensureLedgeDir() {
|
|
23
23
|
const p = paths();
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
run
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-2H5FJH4Q.js";
|
|
4
4
|
import {
|
|
5
5
|
ensureLedgeDir,
|
|
6
6
|
paths
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-R5462SQM.js";
|
|
8
8
|
|
|
9
9
|
// src/cli/setup.ts
|
|
10
|
-
import { readFile as readFile2, writeFile, mkdir, rename, realpath, rm, stat, lstat, readlink
|
|
10
|
+
import { readFile as readFile2, writeFile, mkdir, rename, realpath, rm, stat, lstat, readlink } from "fs/promises";
|
|
11
11
|
import { execFile } from "child_process";
|
|
12
12
|
import { homedir, userInfo } from "os";
|
|
13
13
|
import { join, dirname } from "path";
|
|
@@ -237,14 +237,23 @@ async function isDir(path) {
|
|
|
237
237
|
return false;
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
var BIN_MARK = "#!/bin/sh\n# Written by `ledge setup`";
|
|
241
|
+
var shQuote = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
242
|
+
async function writeBin(opts) {
|
|
241
243
|
const st = await lstat(opts.link).catch(() => null);
|
|
242
|
-
if (!st
|
|
243
|
-
const
|
|
244
|
-
if (
|
|
245
|
-
await
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
if (!st) return null;
|
|
245
|
+
const was = st.isSymbolicLink() ? await readlink(opts.link) : null;
|
|
246
|
+
if (was !== null && !was.endsWith("/dist/cli/index.js")) return null;
|
|
247
|
+
const current = was === null && st.isFile() ? await readFile2(opts.link, "utf8").catch(() => "") : "";
|
|
248
|
+
if (was === null && !current.startsWith(BIN_MARK)) return null;
|
|
249
|
+
const script = `${BIN_MARK}: the hooks run \`ledge\` wherever node is not on the PATH.
|
|
250
|
+
exec ${shQuote(opts.node)} ${shQuote(opts.target)} "$@"
|
|
251
|
+
`;
|
|
252
|
+
if (current === script) return null;
|
|
253
|
+
const tmp = `${opts.link}.tmp`;
|
|
254
|
+
await writeFile(tmp, script, { mode: 493 });
|
|
255
|
+
await rename(tmp, opts.link);
|
|
256
|
+
return `${opts.link} runs ${opts.target} with ${opts.node}${was !== null ? ` (was a link to ${was})` : ""}`;
|
|
248
257
|
}
|
|
249
258
|
async function runSetup(opts = {}) {
|
|
250
259
|
const out = [];
|
|
@@ -263,7 +272,7 @@ async function runSetup(opts = {}) {
|
|
|
263
272
|
else if (await mergeJsonFile(join(cursorDir, "hooks.json"), mergeCursorHooks, "hooks.json")) out.push(`Cursor Agent hooks added in ${join(cursorDir, "hooks.json")}`);
|
|
264
273
|
else out.push("Cursor Agent hooks already present");
|
|
265
274
|
if ((opts.service ?? true) && !process.argv[1]?.endsWith(".ts")) {
|
|
266
|
-
const line = await
|
|
275
|
+
const line = await writeBin({ link: join(homedir(), ".local", "bin", "ledge"), node: process.execPath, target: await realpath(process.argv[1]) });
|
|
267
276
|
if (line) out.push(line);
|
|
268
277
|
}
|
|
269
278
|
if (opts.service ?? true) {
|
|
@@ -286,7 +295,8 @@ export {
|
|
|
286
295
|
stopDetachedDaemon,
|
|
287
296
|
installService,
|
|
288
297
|
isDir,
|
|
289
|
-
|
|
298
|
+
BIN_MARK,
|
|
299
|
+
writeBin,
|
|
290
300
|
runSetup,
|
|
291
301
|
inDaemonService,
|
|
292
302
|
tmuxServerAtRisk
|