@agentrific/server 0.1.5 → 0.2.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/CHANGELOG.md +14 -0
- package/README.md +2 -0
- package/cli.js +439 -261
- package/cli.js.map +4 -4
- package/index.js +435 -264
- package/index.js.map +4 -4
- package/package.json +2 -1
package/cli.js
CHANGED
|
@@ -30,7 +30,7 @@ var init_package = __esm({
|
|
|
30
30
|
"package.json"() {
|
|
31
31
|
package_default = {
|
|
32
32
|
name: "@agentrific/server",
|
|
33
|
-
version: "0.
|
|
33
|
+
version: "0.2.0",
|
|
34
34
|
description: "Self-hosted control plane for long-running coding agents",
|
|
35
35
|
keywords: [
|
|
36
36
|
"agentrific",
|
|
@@ -67,6 +67,7 @@ var init_package = __esm({
|
|
|
67
67
|
"@fastify/cors": "^11.3.0",
|
|
68
68
|
"@fastify/multipart": "^10.1.2",
|
|
69
69
|
"@inquirer/select": "^5.2.5",
|
|
70
|
+
diff: "^9.0.0",
|
|
70
71
|
effect: "4.0.0-rc.117",
|
|
71
72
|
fastify: "^5.6.2",
|
|
72
73
|
"pino-pretty": "^13.1.3",
|
|
@@ -245,7 +246,7 @@ function createPairingLink(payload) {
|
|
|
245
246
|
link.searchParams.set("data", JSON.stringify(PairingPayloadSchema.parse(payload)));
|
|
246
247
|
return link.toString();
|
|
247
248
|
}
|
|
248
|
-
var cronBounds, AutomationConfigSchema, AutomationStateSchema, SessionStatusSchema, AgentKindSchema, ModelReferenceSchema, ModelIdentitySchema, ModelOptionSchema, ModelCatalogSchema, GitFlowSchema, GitDeliverySchema, DeliveryRequestSchema, RunPreparationSchema, SessionSchema, MAX_ATTACHMENTS, MAX_ATTACHMENT_BYTES, AttachmentSchema, AttachmentIdsSchema, CreateSessionSchema, SendMessageSchema, SessionEventSchema, RecordedEventSchema, EventHistorySchema, ServerEventSchema, ServerSettingsSchema, ConnectionsStatusSchema, UpdateSettingsSchema, ProjectSchema, TaskStatusSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, PROTOCOL_VERSION, ServerUpdateStatusSchema, ServerHealthSchema, PairingPayloadSchema, PairingRequestSchema, DeviceSchema, PairingResultSchema, HandshakeSchema, HandshakeAckSchema;
|
|
249
|
+
var cronBounds, AutomationConfigSchema, AutomationStateSchema, SessionStatusSchema, AgentKindSchema, ModelReferenceSchema, ModelIdentitySchema, ModelOptionSchema, ModelCatalogSchema, GitFlowSchema, GitDeliverySchema, DeliveryRequestSchema, RunPreparationSchema, SessionSchema, MAX_ATTACHMENTS, MAX_ATTACHMENT_BYTES, AttachmentSchema, AttachmentIdsSchema, CreateSessionSchema, SendMessageSchema, FileChangeSchema, SessionEventSchema, RecordedEventSchema, EventHistorySchema, ServerEventSchema, ServerSettingsSchema, ConnectionsStatusSchema, UpdateSettingsSchema, ProjectSchema, TaskStatusSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, PROTOCOL_VERSION, ServerUpdateStatusSchema, ServerHealthSchema, PairingPayloadSchema, PairingInvitationSchema, PairingRequestSchema, DeviceSchema, PairingResultSchema, HandshakeSchema, HandshakeAckSchema;
|
|
249
250
|
var init_src = __esm({
|
|
250
251
|
"../../packages/contracts/src/index.ts"() {
|
|
251
252
|
cronBounds = [
|
|
@@ -381,11 +382,18 @@ var init_src = __esm({
|
|
|
381
382
|
SendMessageSchema = z2.object({
|
|
382
383
|
message: z2.string().trim().max(2e4),
|
|
383
384
|
attachmentIds: AttachmentIdsSchema.optional(),
|
|
384
|
-
clientMessageId: z2.string().min(1).max(120).optional()
|
|
385
|
+
clientMessageId: z2.string().min(1).max(120).optional(),
|
|
386
|
+
mode: z2.enum(["followUp", "steer"]).optional()
|
|
385
387
|
}).refine(
|
|
386
388
|
(input) => input.message.length > 0 || (input.attachmentIds?.length ?? 0) > 0,
|
|
387
389
|
"Write a message or attach a file."
|
|
388
390
|
);
|
|
391
|
+
FileChangeSchema = z2.object({
|
|
392
|
+
path: z2.string(),
|
|
393
|
+
patch: z2.string().optional(),
|
|
394
|
+
content: z2.string().optional(),
|
|
395
|
+
omitted: z2.boolean().optional()
|
|
396
|
+
});
|
|
389
397
|
SessionEventSchema = z2.discriminatedUnion("type", [
|
|
390
398
|
z2.object({ type: z2.literal("session.updated"), session: SessionSchema }),
|
|
391
399
|
z2.object({
|
|
@@ -420,7 +428,8 @@ var init_src = __esm({
|
|
|
420
428
|
sessionId: z2.string(),
|
|
421
429
|
toolCallId: z2.string().optional(),
|
|
422
430
|
tool: z2.string(),
|
|
423
|
-
input: z2.string()
|
|
431
|
+
input: z2.string(),
|
|
432
|
+
filePath: z2.string().optional()
|
|
424
433
|
}),
|
|
425
434
|
z2.object({
|
|
426
435
|
type: z2.literal("tool.completed"),
|
|
@@ -428,6 +437,7 @@ var init_src = __esm({
|
|
|
428
437
|
toolCallId: z2.string().optional(),
|
|
429
438
|
tool: z2.string(),
|
|
430
439
|
output: z2.string(),
|
|
440
|
+
fileChange: FileChangeSchema.optional(),
|
|
431
441
|
isError: z2.boolean().default(false)
|
|
432
442
|
}),
|
|
433
443
|
z2.object({
|
|
@@ -519,6 +529,11 @@ var init_src = __esm({
|
|
|
519
529
|
serverId: z2.string().uuid(),
|
|
520
530
|
pairingId: z2.string().regex(/^[a-f0-9]{64}$/)
|
|
521
531
|
});
|
|
532
|
+
PairingInvitationSchema = z2.object({
|
|
533
|
+
payload: PairingPayloadSchema,
|
|
534
|
+
pin: z2.string().regex(/^\d{6}$/),
|
|
535
|
+
expiresAt: z2.number()
|
|
536
|
+
});
|
|
522
537
|
PairingRequestSchema = z2.object({
|
|
523
538
|
pairingId: z2.string().regex(/^[a-f0-9]{64}$/),
|
|
524
539
|
pin: z2.string().regex(/^\d{6}$/),
|
|
@@ -727,6 +742,202 @@ var init_control = __esm({
|
|
|
727
742
|
}
|
|
728
743
|
});
|
|
729
744
|
|
|
745
|
+
// src/cli-output.ts
|
|
746
|
+
import { styleText } from "node:util";
|
|
747
|
+
function printHeading(title) {
|
|
748
|
+
console.log();
|
|
749
|
+
console.log(styleText("bold", " ####### ##"));
|
|
750
|
+
console.log(styleText("bold", " ### #####"));
|
|
751
|
+
console.log(` ${styleText("bold", "## ####")} ${styleText("bold", "AGENTRIFIC")}`);
|
|
752
|
+
console.log(
|
|
753
|
+
` ${styleText("bold", "## ")}${styleText(["bold", "yellow"], "o")}${styleText("bold", " ####")} ${styleText("dim", `${title} \xB7 v${serverVersion}`)}`
|
|
754
|
+
);
|
|
755
|
+
console.log(styleText("bold", " ## ####"));
|
|
756
|
+
console.log(styleText("bold", " ### #####"));
|
|
757
|
+
console.log(styleText("bold", " ####### ##"));
|
|
758
|
+
console.log();
|
|
759
|
+
}
|
|
760
|
+
function printDetail(label2, value) {
|
|
761
|
+
console.log(` ${styleText("dim", "\u203A")} ${styleText("dim", label2.padEnd(10))} ${value}`);
|
|
762
|
+
}
|
|
763
|
+
function printShortcuts(pairingAvailable = false) {
|
|
764
|
+
console.log();
|
|
765
|
+
console.log(` ${styleText("dim", "\u203A")} Press ${styleText("bold", "p")} \u2502 pair a device`);
|
|
766
|
+
if (pairingAvailable)
|
|
767
|
+
console.log(
|
|
768
|
+
` ${styleText("dim", "\u203A")} Press ${styleText("bold", "j")} \u2502 show pairing JSON`
|
|
769
|
+
);
|
|
770
|
+
console.log(` ${styleText("dim", "\u203A")} Press ${styleText("bold", "?")} \u2502 show shortcuts`);
|
|
771
|
+
console.log(
|
|
772
|
+
` ${styleText("dim", "\u203A")} Press ${styleText("bold", "Ctrl+C")} \u2502 stop the server
|
|
773
|
+
`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
function printError(message) {
|
|
777
|
+
console.error(`
|
|
778
|
+
${styleText(["bold", "red"], "Error")} ${message}
|
|
779
|
+
`);
|
|
780
|
+
}
|
|
781
|
+
function printHelp() {
|
|
782
|
+
printHeading("server");
|
|
783
|
+
console.log(` ${styleText("bold", "Usage")} agentrific <command> [options]
|
|
784
|
+
`);
|
|
785
|
+
for (const [command, description] of [
|
|
786
|
+
["init", "Configure the server"],
|
|
787
|
+
["start", "Start the server \xB7 press p to pair"],
|
|
788
|
+
["pair", "Print a fresh pairing QR code and PIN"],
|
|
789
|
+
["devices", "List enrolled devices"],
|
|
790
|
+
["revoke <id>", "Revoke a device's access"],
|
|
791
|
+
["status", "Show server status"],
|
|
792
|
+
["maintenance enter/resume/drain", "Pause new work, resume, or wait for work to finish"],
|
|
793
|
+
["service install/uninstall/restart", "Manage the background service"],
|
|
794
|
+
["logs", "Follow server logs"],
|
|
795
|
+
["update --check", "Check for a newer release"]
|
|
796
|
+
])
|
|
797
|
+
console.log(` ${styleText("cyan", command.padEnd(34))}${description}`);
|
|
798
|
+
console.log(`
|
|
799
|
+
${styleText("bold", "Options")}
|
|
800
|
+
`);
|
|
801
|
+
for (const [option, description] of [
|
|
802
|
+
["--data-dir <path>", "Use a specific server data directory"],
|
|
803
|
+
["--public-url <https-url>", "Use your tunnel URL without prompting"],
|
|
804
|
+
["--no-interactive", "Disable prompts and keyboard shortcuts"],
|
|
805
|
+
["--show-payload", "Print manual JSON with the pairing QR code"],
|
|
806
|
+
["--timeout-ms <0-60000>", "Set the maintenance drain timeout"],
|
|
807
|
+
["--version", "Print the server version"],
|
|
808
|
+
["--help", "Show this help"]
|
|
809
|
+
])
|
|
810
|
+
console.log(` ${styleText("cyan", option.padEnd(34))}${description}`);
|
|
811
|
+
console.log("\n Connect using Tailscale Serve, cloudflared, or another HTTPS tunnel.\n");
|
|
812
|
+
}
|
|
813
|
+
var init_cli_output = __esm({
|
|
814
|
+
"src/cli-output.ts"() {
|
|
815
|
+
"use strict";
|
|
816
|
+
init_version();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// src/pairing.ts
|
|
821
|
+
import { z as z4 } from "zod";
|
|
822
|
+
import { styleText as styleText2 } from "node:util";
|
|
823
|
+
import { emitKeypressEvents } from "node:readline";
|
|
824
|
+
import qrcode from "qrcode-terminal";
|
|
825
|
+
function printManualPairing(invitation) {
|
|
826
|
+
if (Date.now() >= invitation.expiresAt) {
|
|
827
|
+
printError("This invitation expired. Generate a fresh pairing QR code.");
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
console.log(styleText2("dim", "\n Manual entry \xB7 paste this JSON into Pairing payload:\n"));
|
|
831
|
+
console.log(` ${invitation.payload}
|
|
832
|
+
`);
|
|
833
|
+
}
|
|
834
|
+
function waitForManualPairing(invitation) {
|
|
835
|
+
console.log(styleText2("dim", " Press j to show manual entry \xB7 Enter or q to finish.\n"));
|
|
836
|
+
return new Promise((resolve) => {
|
|
837
|
+
const input = process.stdin;
|
|
838
|
+
const previousRaw = input.isRaw;
|
|
839
|
+
const timeout = setTimeout(
|
|
840
|
+
() => {
|
|
841
|
+
console.log(
|
|
842
|
+
styleText2("dim", "\n Pairing invitation expired. Run agentrific pair again.\n")
|
|
843
|
+
);
|
|
844
|
+
close();
|
|
845
|
+
},
|
|
846
|
+
Math.max(0, invitation.expiresAt - Date.now())
|
|
847
|
+
);
|
|
848
|
+
function close() {
|
|
849
|
+
clearTimeout(timeout);
|
|
850
|
+
input.removeListener("keypress", pressed);
|
|
851
|
+
input.removeListener("end", close);
|
|
852
|
+
process.removeListener("SIGINT", interrupted);
|
|
853
|
+
process.removeListener("SIGTERM", terminated);
|
|
854
|
+
input.setRawMode(previousRaw);
|
|
855
|
+
input.pause();
|
|
856
|
+
resolve();
|
|
857
|
+
}
|
|
858
|
+
function interrupted() {
|
|
859
|
+
process.exitCode = 130;
|
|
860
|
+
close();
|
|
861
|
+
}
|
|
862
|
+
function terminated() {
|
|
863
|
+
process.exitCode = 143;
|
|
864
|
+
close();
|
|
865
|
+
}
|
|
866
|
+
function pressed(text, key) {
|
|
867
|
+
if (key.ctrl && key.name === "c") {
|
|
868
|
+
interrupted();
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
if (key.ctrl || key.meta) return;
|
|
872
|
+
if (text?.toLowerCase() === "j") {
|
|
873
|
+
printManualPairing(invitation);
|
|
874
|
+
close();
|
|
875
|
+
} else if (text?.toLowerCase() === "q" || key.name === "return" || key.name === "escape") {
|
|
876
|
+
close();
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
emitKeypressEvents(input);
|
|
880
|
+
input.setRawMode(true);
|
|
881
|
+
input.on("keypress", pressed);
|
|
882
|
+
input.once("end", close);
|
|
883
|
+
process.once("SIGINT", interrupted);
|
|
884
|
+
process.once("SIGTERM", terminated);
|
|
885
|
+
input.resume();
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
function pairingOrigin(url) {
|
|
889
|
+
const endpoint = new URL(url);
|
|
890
|
+
if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash || endpoint.pathname !== "/")
|
|
891
|
+
throw new Error("Use the HTTPS server origin without a path or credentials.");
|
|
892
|
+
if (endpoint.protocol !== "https:" && !(process.env.NODE_ENV !== "production" && endpoint.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(endpoint.hostname)))
|
|
893
|
+
throw new Error("Use HTTPS. Localhost HTTP is for development builds only.");
|
|
894
|
+
return endpoint.origin;
|
|
895
|
+
}
|
|
896
|
+
async function printPairing(dataDir, url) {
|
|
897
|
+
if (!url)
|
|
898
|
+
throw new Error(
|
|
899
|
+
"Specify --public-url with your Tailscale Serve or Cloudflare Tunnel HTTPS address."
|
|
900
|
+
);
|
|
901
|
+
const origin = pairingOrigin(url);
|
|
902
|
+
const grant = z4.object({
|
|
903
|
+
pairingId: z4.string(),
|
|
904
|
+
pin: z4.string(),
|
|
905
|
+
expiresAt: z4.number(),
|
|
906
|
+
serverId: z4.string()
|
|
907
|
+
}).parse(await controlRequest(dataDir, { command: "pair" }));
|
|
908
|
+
const invitation = {
|
|
909
|
+
v: 1,
|
|
910
|
+
url: origin,
|
|
911
|
+
serverId: grant.serverId,
|
|
912
|
+
pairingId: grant.pairingId
|
|
913
|
+
};
|
|
914
|
+
const payload = JSON.stringify(invitation);
|
|
915
|
+
const link = createPairingLink(invitation);
|
|
916
|
+
printHeading("pairing");
|
|
917
|
+
console.log(" 1. Scan below with your phone camera and tap Open Agentrific if offered.");
|
|
918
|
+
console.log(" 2. Or open Agentrific and choose Scan pairing QR code.");
|
|
919
|
+
console.log(" 3. Enter the PIN, then confirm pairing.\n");
|
|
920
|
+
qrcode.generate(link, { small: true }, (code) => {
|
|
921
|
+
console.log(
|
|
922
|
+
code.split("\n").map((line) => ` ${line}`).join("\n")
|
|
923
|
+
);
|
|
924
|
+
});
|
|
925
|
+
console.log();
|
|
926
|
+
printDetail("PIN:", styleText2(["bold", "yellow"], grant.pin));
|
|
927
|
+
printDetail("Server", styleText2("cyan", origin));
|
|
928
|
+
printDetail("Expires", "In five minutes \xB7 one device per code");
|
|
929
|
+
console.log("\n Pairing grants this device access to run agents as your host user.\n");
|
|
930
|
+
return { payload, expiresAt: grant.expiresAt };
|
|
931
|
+
}
|
|
932
|
+
var init_pairing = __esm({
|
|
933
|
+
"src/pairing.ts"() {
|
|
934
|
+
"use strict";
|
|
935
|
+
init_src();
|
|
936
|
+
init_control();
|
|
937
|
+
init_cli_output();
|
|
938
|
+
}
|
|
939
|
+
});
|
|
940
|
+
|
|
730
941
|
// ../../packages/contracts/src/push.ts
|
|
731
942
|
import { z as z6 } from "zod";
|
|
732
943
|
function pushSigningText(installationId, operation, request) {
|
|
@@ -740,7 +951,7 @@ function pushSigningText(installationId, operation, request) {
|
|
|
740
951
|
request.payload
|
|
741
952
|
].join("\n");
|
|
742
953
|
}
|
|
743
|
-
var PUSH_API_URL, PushKindSchema, PushPreferencesSchema, PushPublicKeySchema, PushInstallationSchema, PushRegistrationSchema, PushEnrollmentSchema, PushGrantSchema, PushEventSchema, PushSignedRequestSchema, PushEntitlementSchema, PushServerStatusSchema;
|
|
954
|
+
var PUSH_API_URL, PushKindSchema, PushPreferencesSchema, PushPublicKeySchema, PushInstallationSchema, PushRegistrationSchema, PushEnrollmentSchema, PushGrantSchema, PushPullRequestUrlSchema, PushEventSchema, PushSignedRequestSchema, PushEntitlementSchema, PushServerStatusSchema;
|
|
744
955
|
var init_push = __esm({
|
|
745
956
|
"../../packages/contracts/src/push.ts"() {
|
|
746
957
|
"use strict";
|
|
@@ -768,7 +979,14 @@ var init_push = __esm({
|
|
|
768
979
|
grant: z6.string().regex(/^[a-f0-9]{64}$/),
|
|
769
980
|
expiresAt: z6.number()
|
|
770
981
|
}).strict();
|
|
771
|
-
|
|
982
|
+
PushPullRequestUrlSchema = z6.string().max(512).regex(/^https:\/\/github\.com\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/pull\/[1-9][0-9]*$/);
|
|
983
|
+
PushEventSchema = z6.object({
|
|
984
|
+
eventId: z6.string().uuid(),
|
|
985
|
+
sessionId: z6.string().uuid(),
|
|
986
|
+
kind: PushKindSchema,
|
|
987
|
+
taskTitle: z6.string().trim().min(1).max(160).optional(),
|
|
988
|
+
prUrl: PushPullRequestUrlSchema.optional()
|
|
989
|
+
}).strict();
|
|
772
990
|
PushSignedRequestSchema = z6.object({
|
|
773
991
|
bindingId: z6.string().uuid(),
|
|
774
992
|
timestamp: z6.number().int(),
|
|
@@ -855,10 +1073,11 @@ var init_push2 = __esm({
|
|
|
855
1073
|
"use strict";
|
|
856
1074
|
init_push();
|
|
857
1075
|
ServerPush = class {
|
|
858
|
-
constructor(store2, serverId, directory, transport = fetch) {
|
|
1076
|
+
constructor(store2, serverId, directory, transport = fetch, apiUrl = PUSH_API_URL) {
|
|
859
1077
|
this.store = store2;
|
|
860
1078
|
this.serverId = serverId;
|
|
861
1079
|
this.transport = transport;
|
|
1080
|
+
this.apiUrl = apiUrl;
|
|
862
1081
|
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
863
1082
|
const filename = path4.join(directory, "push-signing-key.pem");
|
|
864
1083
|
if (!existsSync(filename))
|
|
@@ -873,6 +1092,7 @@ var init_push2 = __esm({
|
|
|
873
1092
|
store;
|
|
874
1093
|
serverId;
|
|
875
1094
|
transport;
|
|
1095
|
+
apiUrl;
|
|
876
1096
|
key;
|
|
877
1097
|
publicKey;
|
|
878
1098
|
draining;
|
|
@@ -886,6 +1106,8 @@ var init_push2 = __esm({
|
|
|
886
1106
|
eventId: randomUUID(),
|
|
887
1107
|
sessionId: session.id,
|
|
888
1108
|
kind,
|
|
1109
|
+
taskTitle: session.title.replace(/[\r\n\t]+/g, " ").trim().slice(0, 160) || void 0,
|
|
1110
|
+
prUrl: kind === "pr_created" ? PushPullRequestUrlSchema.safeParse(session.gitDelivery?.url).data : void 0,
|
|
889
1111
|
remaining: state.bindings,
|
|
890
1112
|
createdAt: Date.now(),
|
|
891
1113
|
attempts: 0,
|
|
@@ -908,7 +1130,7 @@ var init_push2 = __esm({
|
|
|
908
1130
|
Buffer.from(pushSigningText(installationId, operation, request)),
|
|
909
1131
|
this.key
|
|
910
1132
|
).toString("hex");
|
|
911
|
-
return this.transport(`${
|
|
1133
|
+
return this.transport(`${this.apiUrl}/v1/installations/${installationId}/${operation}`, {
|
|
912
1134
|
method: "POST",
|
|
913
1135
|
headers: { "Content-Type": "application/json" },
|
|
914
1136
|
body: JSON.stringify({ ...request, signature }),
|
|
@@ -982,7 +1204,13 @@ var init_push2 = __esm({
|
|
|
982
1204
|
binding.installationId,
|
|
983
1205
|
binding.bindingId,
|
|
984
1206
|
"events",
|
|
985
|
-
JSON.stringify({
|
|
1207
|
+
JSON.stringify({
|
|
1208
|
+
eventId: job.eventId,
|
|
1209
|
+
sessionId: job.sessionId,
|
|
1210
|
+
kind: job.kind,
|
|
1211
|
+
taskTitle: job.taskTitle,
|
|
1212
|
+
prUrl: job.prUrl
|
|
1213
|
+
})
|
|
986
1214
|
);
|
|
987
1215
|
if (response.ok) continue;
|
|
988
1216
|
if ([401, 403, 404].includes(response.status)) {
|
|
@@ -1332,6 +1560,29 @@ async function registerSecurity(app2, security2, version, allowedOrigins2) {
|
|
|
1332
1560
|
const result = await security2.pair(input.data, request.ip);
|
|
1333
1561
|
return result ?? reply.code(401).send({ error: "Pairing expired, locked, or invalid. Run agentrific pair again." });
|
|
1334
1562
|
});
|
|
1563
|
+
app2.post("/api/auth/pairing-invitation", (_request, reply) => {
|
|
1564
|
+
const publicUrl = process.env.AGENTRIFIC_PUBLIC_URL;
|
|
1565
|
+
if (!publicUrl)
|
|
1566
|
+
return reply.code(503).send({
|
|
1567
|
+
error: "Configure the server\u2019s public HTTPS URL and restart the server before pairing a device."
|
|
1568
|
+
});
|
|
1569
|
+
let url;
|
|
1570
|
+
try {
|
|
1571
|
+
url = pairingOrigin(publicUrl);
|
|
1572
|
+
} catch {
|
|
1573
|
+
return reply.code(503).send({ error: "The server\u2019s public URL is invalid. Configure a public HTTPS origin." });
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
const grant = security2.createPairing();
|
|
1577
|
+
return {
|
|
1578
|
+
payload: { v: 1, url, serverId: grant.serverId, pairingId: grant.pairingId },
|
|
1579
|
+
pin: grant.pin,
|
|
1580
|
+
expiresAt: grant.expiresAt
|
|
1581
|
+
};
|
|
1582
|
+
} catch {
|
|
1583
|
+
return reply.code(429).send({ error: "Too many pending pairing codes. Wait five minutes and try again." });
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1335
1586
|
app2.get("/api/auth/session", (request) => ({
|
|
1336
1587
|
serverId: security2.serverId,
|
|
1337
1588
|
device: security2.authenticate((request.headers.authorization ?? "").slice(7))
|
|
@@ -1354,6 +1605,7 @@ var init_security = __esm({
|
|
|
1354
1605
|
"src/security.ts"() {
|
|
1355
1606
|
"use strict";
|
|
1356
1607
|
init_src();
|
|
1608
|
+
init_pairing();
|
|
1357
1609
|
secret = () => randomBytes(32).toString("hex");
|
|
1358
1610
|
digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
1359
1611
|
ServerSecurity = class {
|
|
@@ -1883,7 +2135,7 @@ var init_runtime = __esm({
|
|
|
1883
2135
|
Effect8.ensuring(Effect8.sync(() => this.working.delete(entry.session.id)))
|
|
1884
2136
|
);
|
|
1885
2137
|
}
|
|
1886
|
-
send(session, message, attachments2 = [], messageId = randomUUID4()) {
|
|
2138
|
+
send(session, message, attachments2 = [], messageId = randomUUID4(), mode = "followUp") {
|
|
1887
2139
|
return this.lock(session.id).withPermit(
|
|
1888
2140
|
Effect8.gen({ self: this }, function* () {
|
|
1889
2141
|
if (this.closed)
|
|
@@ -1893,6 +2145,16 @@ var init_runtime = __esm({
|
|
|
1893
2145
|
if (this.hasMessage(session.id, messageId)) return;
|
|
1894
2146
|
const entry = yield* this.open(session);
|
|
1895
2147
|
yield* this.operation(async () => entry.agent.validateAttachments?.(attachments2));
|
|
2148
|
+
const steering = entry.running && mode === "steer";
|
|
2149
|
+
if (steering) {
|
|
2150
|
+
if (!entry.agent.steer)
|
|
2151
|
+
return yield* Effect8.fail(
|
|
2152
|
+
new AgentError({ message: "This agent does not support steering." })
|
|
2153
|
+
);
|
|
2154
|
+
yield* this.operation(
|
|
2155
|
+
() => entry.agent.steer(gitFlowPrompt(entry.session, message), attachments2)
|
|
2156
|
+
);
|
|
2157
|
+
}
|
|
1896
2158
|
this.emit({
|
|
1897
2159
|
type: "message.user",
|
|
1898
2160
|
sessionId: session.id,
|
|
@@ -1900,6 +2162,7 @@ var init_runtime = __esm({
|
|
|
1900
2162
|
text: message,
|
|
1901
2163
|
attachments: attachments2
|
|
1902
2164
|
});
|
|
2165
|
+
if (steering) return;
|
|
1903
2166
|
if (entry.running) {
|
|
1904
2167
|
entry.pending.push({ text: message, attachments: attachments2 });
|
|
1905
2168
|
return;
|
|
@@ -2874,6 +3137,74 @@ var init_delivery_tool = __esm({
|
|
|
2874
3137
|
}
|
|
2875
3138
|
});
|
|
2876
3139
|
|
|
3140
|
+
// src/file-change.ts
|
|
3141
|
+
import { createWriteToolDefinition, defineTool as defineTool2 } from "@earendil-works/pi-coding-agent";
|
|
3142
|
+
import { createTwoFilesPatch } from "diff";
|
|
3143
|
+
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
3144
|
+
import { z as z13 } from "zod";
|
|
3145
|
+
function boundFileChange(change) {
|
|
3146
|
+
if ((change.patch?.length ?? 0) + (change.content?.length ?? 0) > previewLimit)
|
|
3147
|
+
return { path: change.path, omitted: true };
|
|
3148
|
+
return change;
|
|
3149
|
+
}
|
|
3150
|
+
function completedFileChange(result, path13) {
|
|
3151
|
+
const parsed = EditResultSchema.safeParse(result);
|
|
3152
|
+
if (!parsed.success) return;
|
|
3153
|
+
const details = parsed.data.details;
|
|
3154
|
+
if (details?.fileChange) return boundFileChange(details.fileChange);
|
|
3155
|
+
if (path13 && details?.patch) return boundFileChange({ path: path13, patch: details.patch });
|
|
3156
|
+
}
|
|
3157
|
+
function fileWriteTool(cwd) {
|
|
3158
|
+
const base = createWriteToolDefinition(cwd);
|
|
3159
|
+
return defineTool2({
|
|
3160
|
+
name: base.name,
|
|
3161
|
+
label: base.label,
|
|
3162
|
+
description: base.description,
|
|
3163
|
+
promptSnippet: base.promptSnippet,
|
|
3164
|
+
promptGuidelines: base.promptGuidelines,
|
|
3165
|
+
parameters: base.parameters,
|
|
3166
|
+
constrainedSampling: base.constrainedSampling,
|
|
3167
|
+
async execute(toolCallId, args, signal, _onUpdate, ctx) {
|
|
3168
|
+
let fileChange;
|
|
3169
|
+
const tool = createWriteToolDefinition(cwd, {
|
|
3170
|
+
operations: {
|
|
3171
|
+
mkdir: async (dir) => {
|
|
3172
|
+
await mkdir5(dir, { recursive: true });
|
|
3173
|
+
},
|
|
3174
|
+
// Pi invokes this inside its per-file mutation queue, so the snapshot
|
|
3175
|
+
// and write cannot race another built-in edit/write on the same file.
|
|
3176
|
+
writeFile: async (path13, content) => {
|
|
3177
|
+
const previous = await readFile4(path13, "utf8").catch((error) => {
|
|
3178
|
+
if (error.code === "ENOENT") return void 0;
|
|
3179
|
+
throw error;
|
|
3180
|
+
});
|
|
3181
|
+
await writeFile3(path13, content, "utf8");
|
|
3182
|
+
fileChange = boundFileChange(
|
|
3183
|
+
previous === void 0 ? { path: args.path, content } : {
|
|
3184
|
+
path: args.path,
|
|
3185
|
+
patch: createTwoFilesPatch(args.path, args.path, previous, content)
|
|
3186
|
+
}
|
|
3187
|
+
);
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
});
|
|
3191
|
+
const result = await tool.execute(toolCallId, args, signal, void 0, ctx);
|
|
3192
|
+
return { ...result, details: { fileChange } };
|
|
3193
|
+
}
|
|
3194
|
+
});
|
|
3195
|
+
}
|
|
3196
|
+
var previewLimit, EditResultSchema;
|
|
3197
|
+
var init_file_change = __esm({
|
|
3198
|
+
"src/file-change.ts"() {
|
|
3199
|
+
"use strict";
|
|
3200
|
+
init_src();
|
|
3201
|
+
previewLimit = 256e3;
|
|
3202
|
+
EditResultSchema = z13.object({
|
|
3203
|
+
details: z13.object({ patch: z13.string().optional(), fileChange: FileChangeSchema.optional() }).optional()
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
});
|
|
3207
|
+
|
|
2877
3208
|
// src/pi.ts
|
|
2878
3209
|
import { Effect as Effect12 } from "effect";
|
|
2879
3210
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -2881,7 +3212,7 @@ import {
|
|
|
2881
3212
|
createAgentSession as createAgentSession2,
|
|
2882
3213
|
SessionManager as SessionManager2
|
|
2883
3214
|
} from "@earendil-works/pi-coding-agent";
|
|
2884
|
-
import { z as
|
|
3215
|
+
import { z as z14 } from "zod";
|
|
2885
3216
|
function toolOutput(value) {
|
|
2886
3217
|
const parsed = ToolResultSchema.safeParse(value);
|
|
2887
3218
|
const text = parsed.success ? parsed.data.content?.filter((part) => part.type === "text").map((part) => part.text || "").join("\n") || "Completed without text output." : boundedJson(value, 4e3);
|
|
@@ -2896,7 +3227,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2896
3227
|
model,
|
|
2897
3228
|
modelRuntime: models2.runtime,
|
|
2898
3229
|
sessionManager: SessionManager2.continueRecent(cwd, sessionDirectory),
|
|
2899
|
-
customTools:
|
|
3230
|
+
customTools: [
|
|
3231
|
+
fileWriteTool(cwd),
|
|
3232
|
+
...run?.gitFlow && delivery2 ? [deliveryTool(sessionId, delivery2)] : []
|
|
3233
|
+
]
|
|
2900
3234
|
});
|
|
2901
3235
|
return {
|
|
2902
3236
|
get model() {
|
|
@@ -2920,6 +3254,12 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2920
3254
|
throw new Error(last.errorMessage ?? "The model request failed");
|
|
2921
3255
|
}
|
|
2922
3256
|
},
|
|
3257
|
+
async steer(message, attachments2 = []) {
|
|
3258
|
+
const context = await Effect12.runPromise(files.prepare(attachments2));
|
|
3259
|
+
if (!session.isStreaming)
|
|
3260
|
+
throw new Error("The response has finished. Send this message as a follow-up instead.");
|
|
3261
|
+
await session.steer(message + context.context, context.images);
|
|
3262
|
+
},
|
|
2923
3263
|
async abort() {
|
|
2924
3264
|
session.clearQueue();
|
|
2925
3265
|
await session.abort();
|
|
@@ -2984,16 +3324,23 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2984
3324
|
}
|
|
2985
3325
|
return false;
|
|
2986
3326
|
}
|
|
3327
|
+
const filePaths = /* @__PURE__ */ new Map();
|
|
3328
|
+
const FileArgsSchema = z14.object({ path: z14.string() });
|
|
2987
3329
|
function handleTool(event) {
|
|
2988
3330
|
if (event.type === "tool_execution_start") {
|
|
2989
3331
|
flushText();
|
|
2990
3332
|
setActivity("thinking");
|
|
3333
|
+
if (event.toolName === "edit" || event.toolName === "write") {
|
|
3334
|
+
const args = FileArgsSchema.safeParse(event.args);
|
|
3335
|
+
if (args.success) filePaths.set(event.toolCallId, args.data.path);
|
|
3336
|
+
}
|
|
2991
3337
|
emit({
|
|
2992
3338
|
type: "tool.started",
|
|
2993
3339
|
sessionId,
|
|
2994
3340
|
toolCallId: event.toolCallId,
|
|
2995
3341
|
tool: event.toolName,
|
|
2996
|
-
input: boundedJson(event.args, 1200)
|
|
3342
|
+
input: boundedJson(event.args, 1200),
|
|
3343
|
+
filePath: filePaths.get(event.toolCallId)
|
|
2997
3344
|
});
|
|
2998
3345
|
return true;
|
|
2999
3346
|
}
|
|
@@ -3005,8 +3352,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3005
3352
|
toolCallId: event.toolCallId,
|
|
3006
3353
|
tool: event.toolName,
|
|
3007
3354
|
output: toolOutput(event.result),
|
|
3355
|
+
fileChange: event.isError ? void 0 : completedFileChange(event.result, filePaths.get(event.toolCallId)),
|
|
3008
3356
|
isError: event.isError
|
|
3009
3357
|
});
|
|
3358
|
+
filePaths.delete(event.toolCallId);
|
|
3010
3359
|
setActivity("thinking");
|
|
3011
3360
|
return true;
|
|
3012
3361
|
}
|
|
@@ -3039,8 +3388,9 @@ var init_pi = __esm({
|
|
|
3039
3388
|
"use strict";
|
|
3040
3389
|
init_models();
|
|
3041
3390
|
init_delivery_tool();
|
|
3042
|
-
|
|
3043
|
-
|
|
3391
|
+
init_file_change();
|
|
3392
|
+
ToolResultSchema = z14.object({
|
|
3393
|
+
content: z14.array(z14.object({ type: z14.string(), text: z14.string().optional() })).optional()
|
|
3044
3394
|
}).passthrough();
|
|
3045
3395
|
boundedJson = (value, limit) => {
|
|
3046
3396
|
let text;
|
|
@@ -3055,9 +3405,9 @@ var init_pi = __esm({
|
|
|
3055
3405
|
});
|
|
3056
3406
|
|
|
3057
3407
|
// src/projects.ts
|
|
3058
|
-
import { access as access2, readdir as readdir2, realpath, stat as stat3, readFile as
|
|
3408
|
+
import { access as access2, readdir as readdir2, realpath, stat as stat3, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
3059
3409
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
3060
|
-
import { z as
|
|
3410
|
+
import { z as z15 } from "zod";
|
|
3061
3411
|
import { constants as constants2 } from "node:fs";
|
|
3062
3412
|
import { homedir as homedir3 } from "node:os";
|
|
3063
3413
|
import path8 from "node:path";
|
|
@@ -3092,7 +3442,7 @@ async function gitDirectory(directory) {
|
|
|
3092
3442
|
const location = path8.join(directory, ".git");
|
|
3093
3443
|
const info = await stat3(location);
|
|
3094
3444
|
if (info.isDirectory()) return location;
|
|
3095
|
-
const contents = await
|
|
3445
|
+
const contents = await readFile5(location, "utf8");
|
|
3096
3446
|
const match = /^gitdir: (.+)\s*$/m.exec(contents);
|
|
3097
3447
|
if (!match) throw new Error("Invalid Git worktree metadata.");
|
|
3098
3448
|
const resolved = await realpath(path8.resolve(directory, match[1].trim()));
|
|
@@ -3102,21 +3452,21 @@ async function gitDirectory(directory) {
|
|
|
3102
3452
|
async function identity(directory, preferred) {
|
|
3103
3453
|
const filename = path8.join(await gitDirectory(directory), "agentrific-project-id");
|
|
3104
3454
|
try {
|
|
3105
|
-
return ProjectId.parse(await
|
|
3455
|
+
return ProjectId.parse(await readFile5(filename, "utf8"));
|
|
3106
3456
|
} catch (error) {
|
|
3107
|
-
if (!
|
|
3457
|
+
if (!z15.object({ code: z15.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
3108
3458
|
const legacyFilename = path8.join(await gitDirectory(directory), "chatplans-project-id");
|
|
3109
|
-
const legacy = await
|
|
3110
|
-
if (!
|
|
3459
|
+
const legacy = await readFile5(legacyFilename, "utf8").catch((cause) => {
|
|
3460
|
+
if (!z15.object({ code: z15.literal("ENOENT") }).safeParse(cause).success) throw cause;
|
|
3111
3461
|
return null;
|
|
3112
3462
|
});
|
|
3113
3463
|
const id = legacy === null ? preferred ?? randomUUID6() : ProjectId.parse(legacy);
|
|
3114
3464
|
try {
|
|
3115
|
-
await
|
|
3465
|
+
await writeFile4(filename, id + "\n", { flag: "wx" });
|
|
3116
3466
|
return id;
|
|
3117
3467
|
} catch (writeError) {
|
|
3118
|
-
if (!
|
|
3119
|
-
return ProjectId.parse(await
|
|
3468
|
+
if (!z15.object({ code: z15.literal("EEXIST") }).safeParse(writeError).success) throw writeError;
|
|
3469
|
+
return ProjectId.parse(await readFile5(filename, "utf8"));
|
|
3120
3470
|
}
|
|
3121
3471
|
}
|
|
3122
3472
|
}
|
|
@@ -3136,7 +3486,7 @@ function scanProjects(root, store2) {
|
|
|
3136
3486
|
let existing = registered.find((project2) => project2.id === id);
|
|
3137
3487
|
if (found.some((project2) => project2.id === id) || existing && existing.path !== directory && await stat3(path8.join(existing.path, ".git")).catch(() => null)) {
|
|
3138
3488
|
id = randomUUID6();
|
|
3139
|
-
await
|
|
3489
|
+
await writeFile4(
|
|
3140
3490
|
path8.join(await gitDirectory(directory), "agentrific-project-id"),
|
|
3141
3491
|
id + "\n"
|
|
3142
3492
|
);
|
|
@@ -3175,11 +3525,11 @@ function reconnectProject(root, id, input, store2) {
|
|
|
3175
3525
|
const filename = path8.join(await gitDirectory(directory), "agentrific-project-id");
|
|
3176
3526
|
const replacedId = await identity(directory);
|
|
3177
3527
|
const project = { id, name: path8.basename(directory), path: directory, available: true };
|
|
3178
|
-
await
|
|
3528
|
+
await writeFile4(filename, id + "\n");
|
|
3179
3529
|
try {
|
|
3180
3530
|
await Effect13.runPromise(store2.relocateProject(project, previous.path, replacedId));
|
|
3181
3531
|
} catch (error) {
|
|
3182
|
-
await
|
|
3532
|
+
await writeFile4(filename, replacedId + "\n");
|
|
3183
3533
|
throw error;
|
|
3184
3534
|
}
|
|
3185
3535
|
return project;
|
|
@@ -3192,31 +3542,31 @@ var init_projects = __esm({
|
|
|
3192
3542
|
"src/projects.ts"() {
|
|
3193
3543
|
"use strict";
|
|
3194
3544
|
locks2 = /* @__PURE__ */ new WeakMap();
|
|
3195
|
-
ProjectId =
|
|
3545
|
+
ProjectId = z15.string().trim().min(1).max(255);
|
|
3196
3546
|
}
|
|
3197
3547
|
});
|
|
3198
3548
|
|
|
3199
3549
|
// src/push-schema.ts
|
|
3200
|
-
import { z as
|
|
3550
|
+
import { z as z16 } from "zod";
|
|
3201
3551
|
var PushBindingSchema, PushStateSchema, PendingPushSchema;
|
|
3202
3552
|
var init_push_schema = __esm({
|
|
3203
3553
|
"src/push-schema.ts"() {
|
|
3204
3554
|
"use strict";
|
|
3205
3555
|
init_push();
|
|
3206
|
-
PushBindingSchema =
|
|
3207
|
-
bindingId:
|
|
3208
|
-
installationId:
|
|
3209
|
-
deviceId:
|
|
3556
|
+
PushBindingSchema = z16.object({
|
|
3557
|
+
bindingId: z16.string().uuid(),
|
|
3558
|
+
installationId: z16.string().uuid(),
|
|
3559
|
+
deviceId: z16.string().uuid()
|
|
3210
3560
|
});
|
|
3211
|
-
PushStateSchema =
|
|
3212
|
-
bindings:
|
|
3213
|
-
error:
|
|
3561
|
+
PushStateSchema = z16.object({
|
|
3562
|
+
bindings: z16.array(PushBindingSchema).max(10),
|
|
3563
|
+
error: z16.string().nullable()
|
|
3214
3564
|
});
|
|
3215
3565
|
PendingPushSchema = PushEventSchema.extend({
|
|
3216
|
-
remaining:
|
|
3217
|
-
createdAt:
|
|
3218
|
-
attempts:
|
|
3219
|
-
nextAt:
|
|
3566
|
+
remaining: z16.array(PushBindingSchema),
|
|
3567
|
+
createdAt: z16.number(),
|
|
3568
|
+
attempts: z16.number().int().nonnegative(),
|
|
3569
|
+
nextAt: z16.number()
|
|
3220
3570
|
});
|
|
3221
3571
|
}
|
|
3222
3572
|
});
|
|
@@ -3226,7 +3576,7 @@ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "n
|
|
|
3226
3576
|
import path9 from "node:path";
|
|
3227
3577
|
import { DatabaseSync } from "node:sqlite";
|
|
3228
3578
|
import { Context as Context3, Data as Data3, Effect as Effect14, Layer as Layer3 } from "effect";
|
|
3229
|
-
import { z as
|
|
3579
|
+
import { z as z17 } from "zod";
|
|
3230
3580
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
3231
3581
|
var StorageError, PayloadRow, EventRow, SqliteSessionStore, SessionStore;
|
|
3232
3582
|
var init_store = __esm({
|
|
@@ -3239,8 +3589,8 @@ var init_store = __esm({
|
|
|
3239
3589
|
init_src();
|
|
3240
3590
|
StorageError = class extends Data3.TaggedError("StorageError") {
|
|
3241
3591
|
};
|
|
3242
|
-
PayloadRow =
|
|
3243
|
-
EventRow =
|
|
3592
|
+
PayloadRow = z17.object({ payload: z17.string() });
|
|
3593
|
+
EventRow = z17.object({ sequence: z17.number(), recorded_at: z17.string(), payload: z17.string() });
|
|
3244
3594
|
SqliteSessionStore = class {
|
|
3245
3595
|
constructor(db) {
|
|
3246
3596
|
this.db = db;
|
|
@@ -3291,7 +3641,7 @@ var init_store = __esm({
|
|
|
3291
3641
|
const row = PayloadRow.parse(
|
|
3292
3642
|
this.db.prepare("SELECT payload FROM settings WHERE id = 'server-id'").get()
|
|
3293
3643
|
);
|
|
3294
|
-
return
|
|
3644
|
+
return z17.string().uuid().parse(JSON.parse(row.payload));
|
|
3295
3645
|
});
|
|
3296
3646
|
}
|
|
3297
3647
|
saveDevice(device, tokenHash) {
|
|
@@ -3379,7 +3729,7 @@ var init_store = __esm({
|
|
|
3379
3729
|
`).all();
|
|
3380
3730
|
const insert = this.db.prepare("INSERT OR IGNORE INTO projects(id, payload) VALUES (?, ?)");
|
|
3381
3731
|
for (const row of legacy) {
|
|
3382
|
-
const id =
|
|
3732
|
+
const id = z17.object({ id: z17.string() }).parse(row).id;
|
|
3383
3733
|
insert.run(
|
|
3384
3734
|
id,
|
|
3385
3735
|
JSON.stringify({ id, name: id, path: path9.join(root, id), available: false })
|
|
@@ -3589,7 +3939,7 @@ var init_store = __esm({
|
|
|
3589
3939
|
if (event.type === "message.user") this.claimAttachments(event.attachments ?? []);
|
|
3590
3940
|
const result = this.db.prepare("INSERT INTO events(session_id, recorded_at, payload) VALUES (?, ?, ?)").run(sessionId, recordedAt, JSON.stringify(event));
|
|
3591
3941
|
if (notification) {
|
|
3592
|
-
const count =
|
|
3942
|
+
const count = z17.object({ total: z17.number() }).parse(this.db.prepare("SELECT COUNT(*) AS total FROM push_outbox").get());
|
|
3593
3943
|
if (count.total >= 1e3)
|
|
3594
3944
|
this.db.prepare(
|
|
3595
3945
|
"DELETE FROM push_outbox WHERE rowid IN (SELECT rowid FROM push_outbox ORDER BY rowid LIMIT 1)"
|
|
@@ -3641,7 +3991,7 @@ var init_store = __esm({
|
|
|
3641
3991
|
for (const suffix of ["-wal", "-shm"])
|
|
3642
3992
|
if (existsSync2(filename + suffix)) chmodSync(filename + suffix, 384);
|
|
3643
3993
|
try {
|
|
3644
|
-
const schema =
|
|
3994
|
+
const schema = z17.object({ user_version: z17.number() }).parse(db.prepare("PRAGMA user_version").get());
|
|
3645
3995
|
if (schema.user_version > 1)
|
|
3646
3996
|
throw new Error("This database requires a newer Agentrific server.");
|
|
3647
3997
|
db.exec(`
|
|
@@ -3762,7 +4112,7 @@ import { homedir as homedir4 } from "node:os";
|
|
|
3762
4112
|
import path11 from "node:path";
|
|
3763
4113
|
import { WebSocket } from "ws";
|
|
3764
4114
|
import { Effect as Effect16, ManagedRuntime } from "effect";
|
|
3765
|
-
import { z as
|
|
4115
|
+
import { z as z18 } from "zod";
|
|
3766
4116
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
3767
4117
|
function publish(event, workflowUpdate = false) {
|
|
3768
4118
|
const previous = event.type === "session.updated" ? Effect16.runSync(store.get(event.session.id)) : void 0;
|
|
@@ -3968,7 +4318,7 @@ async function withSessionTaskLock(id, operation) {
|
|
|
3968
4318
|
const lockId = session ? await sessionTaskLockId(store, session) : `session-${id}`;
|
|
3969
4319
|
return withTaskLock(lockId, operation);
|
|
3970
4320
|
}
|
|
3971
|
-
var host, port, defaultProjectsRoot, worktreesRoot, execFileAsync, app, dataRoot, releaseLock, effects, store, security, push, allowedOrigins, attachmentEffects, attachments, attachmentCleanup, models, delivery, preparation, agentEffects, runtime, worktreeCleanup, maxWorktreeAgeDays, pruneWorktrees, worktreeCleanupTimer, updates, maintenance, stopUpdateDiscovery, HistoryQuery, StartTaskBody, automation, automationTimer, closeControl;
|
|
4321
|
+
var host, port, defaultProjectsRoot, worktreesRoot, execFileAsync, app, dataRoot, releaseLock, effects, store, security, testPushUrl, push, allowedOrigins, attachmentEffects, attachments, attachmentCleanup, models, delivery, preparation, agentEffects, runtime, worktreeCleanup, maxWorktreeAgeDays, pruneWorktrees, worktreeCleanupTimer, updates, maintenance, stopUpdateDiscovery, HistoryQuery, StartTaskBody, automation, automationTimer, closeControl;
|
|
3972
4322
|
var init_index = __esm({
|
|
3973
4323
|
async "src/index.ts"() {
|
|
3974
4324
|
"use strict";
|
|
@@ -4025,9 +4375,13 @@ var init_index = __esm({
|
|
|
4025
4375
|
effects = ManagedRuntime.make(SessionStore.layer(path11.join(dataRoot, "sessions.sqlite")));
|
|
4026
4376
|
store = await effects.runPromise(SessionStore);
|
|
4027
4377
|
security = new ServerSecurity(store, await Effect16.runPromise(store.serverIdentity()));
|
|
4028
|
-
|
|
4378
|
+
testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
|
|
4379
|
+
if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
|
|
4380
|
+
throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
|
|
4381
|
+
push = new ServerPush(store, security.serverId, dataRoot, fetch, testPushUrl);
|
|
4029
4382
|
allowedOrigins = [
|
|
4030
4383
|
.../* @__PURE__ */ new Set([
|
|
4384
|
+
"agentrific-desktop://app",
|
|
4031
4385
|
...(process.env.AGENTRIFIC_ALLOWED_ORIGINS ?? "http://localhost:8081,http://127.0.0.1:8081").split(","),
|
|
4032
4386
|
`http://${host === "::1" ? "[::1]" : host}:${port}`,
|
|
4033
4387
|
...process.env.AGENTRIFIC_PUBLIC_URL ? [new URL(process.env.AGENTRIFIC_PUBLIC_URL).origin] : []
|
|
@@ -4249,7 +4603,7 @@ var init_index = __esm({
|
|
|
4249
4603
|
return project ?? reply.code(404).send({ error: "Project not found on this server." });
|
|
4250
4604
|
});
|
|
4251
4605
|
app.post("/api/projects/:id/reconnect", async (request, reply) => {
|
|
4252
|
-
const parsed =
|
|
4606
|
+
const parsed = z18.object({ path: z18.string().trim().min(1).max(4096) }).safeParse(request.body);
|
|
4253
4607
|
if (!parsed.success)
|
|
4254
4608
|
return reply.code(400).send({ error: "Enter the project's new folder path." });
|
|
4255
4609
|
const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
@@ -4381,9 +4735,9 @@ var init_index = __esm({
|
|
|
4381
4735
|
}
|
|
4382
4736
|
})
|
|
4383
4737
|
);
|
|
4384
|
-
HistoryQuery =
|
|
4385
|
-
after:
|
|
4386
|
-
limit:
|
|
4738
|
+
HistoryQuery = z18.object({
|
|
4739
|
+
after: z18.coerce.number().int().nonnegative().default(0),
|
|
4740
|
+
limit: z18.coerce.number().int().min(1).max(1e3).default(500)
|
|
4387
4741
|
});
|
|
4388
4742
|
app.get("/api/sessions/:id/events", async (request, reply) => {
|
|
4389
4743
|
const query = HistoryQuery.safeParse(request.query);
|
|
@@ -4402,7 +4756,7 @@ var init_index = __esm({
|
|
|
4402
4756
|
if (!result.session) return reply.code(result.status).send({ error: result.error });
|
|
4403
4757
|
return reply.code(201).send(result.session);
|
|
4404
4758
|
});
|
|
4405
|
-
StartTaskBody =
|
|
4759
|
+
StartTaskBody = z18.object({ prompt: z18.string().trim().max(2e4).optional() }).default({});
|
|
4406
4760
|
app.post("/api/tasks/:id/start", async (request, reply) => {
|
|
4407
4761
|
const body = StartTaskBody.safeParse(request.body ?? {});
|
|
4408
4762
|
if (!body.success) return reply.code(400).send({ error: "The task prompt is too long." });
|
|
@@ -4437,7 +4791,7 @@ var init_index = __esm({
|
|
|
4437
4791
|
return automation.configure(parsed.data);
|
|
4438
4792
|
});
|
|
4439
4793
|
app.patch("/api/automation/queue", async (request, reply) => {
|
|
4440
|
-
const parsed =
|
|
4794
|
+
const parsed = z18.object({ taskId: z18.string().min(1), beforeId: z18.string().min(1).nullable() }).safeParse(request.body);
|
|
4441
4795
|
if (!parsed.success)
|
|
4442
4796
|
return reply.code(400).send({ error: "Choose a task and a queue position." });
|
|
4443
4797
|
try {
|
|
@@ -4467,7 +4821,13 @@ var init_index = __esm({
|
|
|
4467
4821
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4468
4822
|
);
|
|
4469
4823
|
await Effect16.runPromise(
|
|
4470
|
-
runtime.send(
|
|
4824
|
+
runtime.send(
|
|
4825
|
+
session,
|
|
4826
|
+
parsed.data.message,
|
|
4827
|
+
files,
|
|
4828
|
+
parsed.data.clientMessageId,
|
|
4829
|
+
parsed.data.mode
|
|
4830
|
+
)
|
|
4471
4831
|
);
|
|
4472
4832
|
return reply.code(202).send({ accepted: true });
|
|
4473
4833
|
} catch (error) {
|
|
@@ -4537,7 +4897,7 @@ var init_index = __esm({
|
|
|
4537
4897
|
|
|
4538
4898
|
// src/cli.ts
|
|
4539
4899
|
init_warnings();
|
|
4540
|
-
import { mkdir as
|
|
4900
|
+
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
4541
4901
|
import { homedir as homedir5 } from "node:os";
|
|
4542
4902
|
import path12 from "node:path";
|
|
4543
4903
|
import { fileURLToPath } from "node:url";
|
|
@@ -4635,204 +4995,21 @@ async function installService(platform, directory, filename, domain, dataDir, cl
|
|
|
4635
4995
|
init_updates();
|
|
4636
4996
|
init_control();
|
|
4637
4997
|
init_version();
|
|
4998
|
+
init_pairing();
|
|
4638
4999
|
import { spawn as spawn2 } from "node:child_process";
|
|
4639
5000
|
import { parseArgs } from "node:util";
|
|
4640
|
-
import { z as
|
|
4641
|
-
|
|
4642
|
-
// src/pairing.ts
|
|
4643
|
-
init_src();
|
|
4644
|
-
init_control();
|
|
4645
|
-
import { z as z4 } from "zod";
|
|
4646
|
-
import { styleText as styleText2 } from "node:util";
|
|
4647
|
-
import { emitKeypressEvents } from "node:readline";
|
|
4648
|
-
import qrcode from "qrcode-terminal";
|
|
4649
|
-
|
|
4650
|
-
// src/cli-output.ts
|
|
4651
|
-
init_version();
|
|
4652
|
-
import { styleText } from "node:util";
|
|
4653
|
-
function printHeading(title) {
|
|
4654
|
-
console.log();
|
|
4655
|
-
console.log(styleText("bold", " ####### ##"));
|
|
4656
|
-
console.log(styleText("bold", " ### #####"));
|
|
4657
|
-
console.log(` ${styleText("bold", "## ####")} ${styleText("bold", "AGENTRIFIC")}`);
|
|
4658
|
-
console.log(
|
|
4659
|
-
` ${styleText("bold", "## ")}${styleText(["bold", "yellow"], "o")}${styleText("bold", " ####")} ${styleText("dim", `${title} \xB7 v${serverVersion}`)}`
|
|
4660
|
-
);
|
|
4661
|
-
console.log(styleText("bold", " ## ####"));
|
|
4662
|
-
console.log(styleText("bold", " ### #####"));
|
|
4663
|
-
console.log(styleText("bold", " ####### ##"));
|
|
4664
|
-
console.log();
|
|
4665
|
-
}
|
|
4666
|
-
function printDetail(label2, value) {
|
|
4667
|
-
console.log(` ${styleText("dim", "\u203A")} ${styleText("dim", label2.padEnd(10))} ${value}`);
|
|
4668
|
-
}
|
|
4669
|
-
function printShortcuts(pairingAvailable = false) {
|
|
4670
|
-
console.log();
|
|
4671
|
-
console.log(` ${styleText("dim", "\u203A")} Press ${styleText("bold", "p")} \u2502 pair a device`);
|
|
4672
|
-
if (pairingAvailable)
|
|
4673
|
-
console.log(
|
|
4674
|
-
` ${styleText("dim", "\u203A")} Press ${styleText("bold", "j")} \u2502 show pairing JSON`
|
|
4675
|
-
);
|
|
4676
|
-
console.log(` ${styleText("dim", "\u203A")} Press ${styleText("bold", "?")} \u2502 show shortcuts`);
|
|
4677
|
-
console.log(
|
|
4678
|
-
` ${styleText("dim", "\u203A")} Press ${styleText("bold", "Ctrl+C")} \u2502 stop the server
|
|
4679
|
-
`
|
|
4680
|
-
);
|
|
4681
|
-
}
|
|
4682
|
-
function printError(message) {
|
|
4683
|
-
console.error(`
|
|
4684
|
-
${styleText(["bold", "red"], "Error")} ${message}
|
|
4685
|
-
`);
|
|
4686
|
-
}
|
|
4687
|
-
function printHelp() {
|
|
4688
|
-
printHeading("server");
|
|
4689
|
-
console.log(` ${styleText("bold", "Usage")} agentrific <command> [options]
|
|
4690
|
-
`);
|
|
4691
|
-
for (const [command, description] of [
|
|
4692
|
-
["init", "Configure the server"],
|
|
4693
|
-
["start", "Start the server \xB7 press p to pair"],
|
|
4694
|
-
["pair", "Print a fresh pairing QR code and PIN"],
|
|
4695
|
-
["devices", "List enrolled devices"],
|
|
4696
|
-
["revoke <id>", "Revoke a device's access"],
|
|
4697
|
-
["status", "Show server status"],
|
|
4698
|
-
["maintenance enter/resume/drain", "Pause new work, resume, or wait for work to finish"],
|
|
4699
|
-
["service install/uninstall/restart", "Manage the background service"],
|
|
4700
|
-
["logs", "Follow server logs"],
|
|
4701
|
-
["update --check", "Check for a newer release"]
|
|
4702
|
-
])
|
|
4703
|
-
console.log(` ${styleText("cyan", command.padEnd(34))}${description}`);
|
|
4704
|
-
console.log(`
|
|
4705
|
-
${styleText("bold", "Options")}
|
|
4706
|
-
`);
|
|
4707
|
-
for (const [option, description] of [
|
|
4708
|
-
["--data-dir <path>", "Use a specific server data directory"],
|
|
4709
|
-
["--public-url <https-url>", "Use your tunnel URL without prompting"],
|
|
4710
|
-
["--no-interactive", "Disable prompts and keyboard shortcuts"],
|
|
4711
|
-
["--show-payload", "Print manual JSON with the pairing QR code"],
|
|
4712
|
-
["--timeout-ms <0-60000>", "Set the maintenance drain timeout"],
|
|
4713
|
-
["--version", "Print the server version"],
|
|
4714
|
-
["--help", "Show this help"]
|
|
4715
|
-
])
|
|
4716
|
-
console.log(` ${styleText("cyan", option.padEnd(34))}${description}`);
|
|
4717
|
-
console.log("\n Connect using Tailscale Serve, cloudflared, or another HTTPS tunnel.\n");
|
|
4718
|
-
}
|
|
4719
|
-
|
|
4720
|
-
// src/pairing.ts
|
|
4721
|
-
function printManualPairing(invitation) {
|
|
4722
|
-
if (Date.now() >= invitation.expiresAt) {
|
|
4723
|
-
printError("This invitation expired. Generate a fresh pairing QR code.");
|
|
4724
|
-
return;
|
|
4725
|
-
}
|
|
4726
|
-
console.log(styleText2("dim", "\n Manual entry \xB7 paste this JSON into Pairing payload:\n"));
|
|
4727
|
-
console.log(` ${invitation.payload}
|
|
4728
|
-
`);
|
|
4729
|
-
}
|
|
4730
|
-
function waitForManualPairing(invitation) {
|
|
4731
|
-
console.log(styleText2("dim", " Press j to show manual entry \xB7 Enter or q to finish.\n"));
|
|
4732
|
-
return new Promise((resolve) => {
|
|
4733
|
-
const input = process.stdin;
|
|
4734
|
-
const previousRaw = input.isRaw;
|
|
4735
|
-
const timeout = setTimeout(
|
|
4736
|
-
() => {
|
|
4737
|
-
console.log(
|
|
4738
|
-
styleText2("dim", "\n Pairing invitation expired. Run agentrific pair again.\n")
|
|
4739
|
-
);
|
|
4740
|
-
close();
|
|
4741
|
-
},
|
|
4742
|
-
Math.max(0, invitation.expiresAt - Date.now())
|
|
4743
|
-
);
|
|
4744
|
-
function close() {
|
|
4745
|
-
clearTimeout(timeout);
|
|
4746
|
-
input.removeListener("keypress", pressed);
|
|
4747
|
-
input.removeListener("end", close);
|
|
4748
|
-
process.removeListener("SIGINT", interrupted);
|
|
4749
|
-
process.removeListener("SIGTERM", terminated);
|
|
4750
|
-
input.setRawMode(previousRaw);
|
|
4751
|
-
input.pause();
|
|
4752
|
-
resolve();
|
|
4753
|
-
}
|
|
4754
|
-
function interrupted() {
|
|
4755
|
-
process.exitCode = 130;
|
|
4756
|
-
close();
|
|
4757
|
-
}
|
|
4758
|
-
function terminated() {
|
|
4759
|
-
process.exitCode = 143;
|
|
4760
|
-
close();
|
|
4761
|
-
}
|
|
4762
|
-
function pressed(text, key) {
|
|
4763
|
-
if (key.ctrl && key.name === "c") {
|
|
4764
|
-
interrupted();
|
|
4765
|
-
return;
|
|
4766
|
-
}
|
|
4767
|
-
if (key.ctrl || key.meta) return;
|
|
4768
|
-
if (text?.toLowerCase() === "j") {
|
|
4769
|
-
printManualPairing(invitation);
|
|
4770
|
-
close();
|
|
4771
|
-
} else if (text?.toLowerCase() === "q" || key.name === "return" || key.name === "escape") {
|
|
4772
|
-
close();
|
|
4773
|
-
}
|
|
4774
|
-
}
|
|
4775
|
-
emitKeypressEvents(input);
|
|
4776
|
-
input.setRawMode(true);
|
|
4777
|
-
input.on("keypress", pressed);
|
|
4778
|
-
input.once("end", close);
|
|
4779
|
-
process.once("SIGINT", interrupted);
|
|
4780
|
-
process.once("SIGTERM", terminated);
|
|
4781
|
-
input.resume();
|
|
4782
|
-
});
|
|
4783
|
-
}
|
|
4784
|
-
function pairingOrigin(url) {
|
|
4785
|
-
const endpoint = new URL(url);
|
|
4786
|
-
if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash || endpoint.pathname !== "/")
|
|
4787
|
-
throw new Error("Use the HTTPS server origin without a path or credentials.");
|
|
4788
|
-
if (endpoint.protocol !== "https:" && !(process.env.NODE_ENV !== "production" && endpoint.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(endpoint.hostname)))
|
|
4789
|
-
throw new Error("Use HTTPS. Localhost HTTP is for development builds only.");
|
|
4790
|
-
return endpoint.origin;
|
|
4791
|
-
}
|
|
4792
|
-
async function printPairing(dataDir, url) {
|
|
4793
|
-
if (!url)
|
|
4794
|
-
throw new Error(
|
|
4795
|
-
"Specify --public-url with your Tailscale Serve or Cloudflare Tunnel HTTPS address."
|
|
4796
|
-
);
|
|
4797
|
-
const origin = pairingOrigin(url);
|
|
4798
|
-
const grant = z4.object({
|
|
4799
|
-
pairingId: z4.string(),
|
|
4800
|
-
pin: z4.string(),
|
|
4801
|
-
expiresAt: z4.number(),
|
|
4802
|
-
serverId: z4.string()
|
|
4803
|
-
}).parse(await controlRequest(dataDir, { command: "pair" }));
|
|
4804
|
-
const invitation = {
|
|
4805
|
-
v: 1,
|
|
4806
|
-
url: origin,
|
|
4807
|
-
serverId: grant.serverId,
|
|
4808
|
-
pairingId: grant.pairingId
|
|
4809
|
-
};
|
|
4810
|
-
const payload = JSON.stringify(invitation);
|
|
4811
|
-
const link = createPairingLink(invitation);
|
|
4812
|
-
printHeading("pairing");
|
|
4813
|
-
console.log(" 1. Scan below with your phone camera and tap Open Agentrific if offered.");
|
|
4814
|
-
console.log(" 2. Or open Agentrific and choose Scan pairing QR code.");
|
|
4815
|
-
console.log(" 3. Enter the PIN, then confirm pairing.\n");
|
|
4816
|
-
qrcode.generate(link, { small: true }, (code) => {
|
|
4817
|
-
console.log(
|
|
4818
|
-
code.split("\n").map((line) => ` ${line}`).join("\n")
|
|
4819
|
-
);
|
|
4820
|
-
});
|
|
4821
|
-
console.log();
|
|
4822
|
-
printDetail("PIN:", styleText2(["bold", "yellow"], grant.pin));
|
|
4823
|
-
printDetail("Server", styleText2("cyan", origin));
|
|
4824
|
-
printDetail("Expires", "In five minutes \xB7 one device per code");
|
|
4825
|
-
console.log("\n Pairing grants this device access to run agents as your host user.\n");
|
|
4826
|
-
return { payload, expiresAt: grant.expiresAt };
|
|
4827
|
-
}
|
|
5001
|
+
import { z as z19 } from "zod";
|
|
4828
5002
|
|
|
4829
5003
|
// src/terminal.ts
|
|
5004
|
+
init_pairing();
|
|
5005
|
+
init_cli_output();
|
|
4830
5006
|
import { createInterface } from "node:readline/promises";
|
|
4831
5007
|
import select from "@inquirer/select";
|
|
4832
5008
|
import { emitKeypressEvents as emitKeypressEvents2 } from "node:readline";
|
|
4833
5009
|
import { styleText as styleText3 } from "node:util";
|
|
4834
5010
|
|
|
4835
5011
|
// src/tunnels.ts
|
|
5012
|
+
init_cli_output();
|
|
4836
5013
|
import { spawn } from "node:child_process";
|
|
4837
5014
|
import { access, stat } from "node:fs/promises";
|
|
4838
5015
|
import { constants } from "node:fs";
|
|
@@ -5301,10 +5478,11 @@ function startTerminal(dataDir, publicUrl, tunnel) {
|
|
|
5301
5478
|
}
|
|
5302
5479
|
|
|
5303
5480
|
// src/cli.ts
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5481
|
+
init_cli_output();
|
|
5482
|
+
var ConfigSchema = z19.object({
|
|
5483
|
+
dataDir: z19.string(),
|
|
5484
|
+
projectsDir: z19.string(),
|
|
5485
|
+
publicUrl: z19.string().url().optional()
|
|
5308
5486
|
});
|
|
5309
5487
|
function parseCommandArgs() {
|
|
5310
5488
|
return parseArgs({
|
|
@@ -5338,8 +5516,8 @@ async function main() {
|
|
|
5338
5516
|
const filename = path12.join(dataDir, "config.json");
|
|
5339
5517
|
const config = ConfigSchema.parse(
|
|
5340
5518
|
JSON.parse(
|
|
5341
|
-
await
|
|
5342
|
-
if (!
|
|
5519
|
+
await readFile6(filename, "utf8").catch((error) => {
|
|
5520
|
+
if (!z19.object({ code: z19.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
5343
5521
|
return JSON.stringify({ dataDir, projectsDir: path12.join(homedir5(), "projects") });
|
|
5344
5522
|
})
|
|
5345
5523
|
)
|
|
@@ -5357,11 +5535,11 @@ async function main() {
|
|
|
5357
5535
|
throw new Error("Unknown command. Run agentrific --help.");
|
|
5358
5536
|
}
|
|
5359
5537
|
async function initCommand({ values, dataDir, filename, config }) {
|
|
5360
|
-
await
|
|
5538
|
+
await mkdir6(dataDir, { recursive: true, mode: 448 });
|
|
5361
5539
|
const publicUrl = values["public-url"] ?? config.publicUrl;
|
|
5362
5540
|
if (publicUrl && new URL(publicUrl).protocol !== "https:")
|
|
5363
5541
|
throw new Error("Configure an HTTPS URL from Tailscale Serve or Cloudflare Tunnel.");
|
|
5364
|
-
await
|
|
5542
|
+
await writeFile5(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
5365
5543
|
mode: 384
|
|
5366
5544
|
});
|
|
5367
5545
|
console.log(`Configuration saved to ${filename}. Start with agentrific start.`);
|
|
@@ -5389,8 +5567,8 @@ async function startCommand({ values, dataDir, filename, config }) {
|
|
|
5389
5567
|
const publicUrl = connection.url;
|
|
5390
5568
|
try {
|
|
5391
5569
|
if (connection.remember && publicUrl) {
|
|
5392
|
-
await
|
|
5393
|
-
await
|
|
5570
|
+
await mkdir6(dataDir, { recursive: true, mode: 448 });
|
|
5571
|
+
await writeFile5(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
5394
5572
|
mode: 384
|
|
5395
5573
|
});
|
|
5396
5574
|
}
|
|
@@ -5446,11 +5624,11 @@ async function pairCommand({ values, dataDir, config }) {
|
|
|
5446
5624
|
return;
|
|
5447
5625
|
}
|
|
5448
5626
|
async function maintenanceCommand({ values, positionals, dataDir }) {
|
|
5449
|
-
const action =
|
|
5627
|
+
const action = z19.enum(["enter", "resume", "drain"]).parse(positionals[1]);
|
|
5450
5628
|
const result = await controlRequest(dataDir, {
|
|
5451
5629
|
command: "maintenance",
|
|
5452
5630
|
action,
|
|
5453
|
-
timeoutMs:
|
|
5631
|
+
timeoutMs: z19.coerce.number().int().min(0).max(6e4).parse(values["timeout-ms"] ?? 3e4)
|
|
5454
5632
|
});
|
|
5455
5633
|
console.log(JSON.stringify(result, null, 2));
|
|
5456
5634
|
if (action === "drain" && "drained" in result && !result.drained) process.exitCode = 1;
|
|
@@ -5459,7 +5637,7 @@ async function maintenanceCommand({ values, positionals, dataDir }) {
|
|
|
5459
5637
|
async function devicesCommand({ dataDir, command }) {
|
|
5460
5638
|
console.log(
|
|
5461
5639
|
JSON.stringify(
|
|
5462
|
-
await controlRequest(dataDir, { command:
|
|
5640
|
+
await controlRequest(dataDir, { command: z19.enum(["devices", "status"]).parse(command) }),
|
|
5463
5641
|
null,
|
|
5464
5642
|
2
|
|
5465
5643
|
)
|
|
@@ -5467,7 +5645,7 @@ async function devicesCommand({ dataDir, command }) {
|
|
|
5467
5645
|
return;
|
|
5468
5646
|
}
|
|
5469
5647
|
async function revokeCommand({ positionals, dataDir }) {
|
|
5470
|
-
const id =
|
|
5648
|
+
const id = z19.string().uuid().parse(positionals[1]);
|
|
5471
5649
|
console.log(JSON.stringify(await controlRequest(dataDir, { command: "revoke", id })));
|
|
5472
5650
|
return;
|
|
5473
5651
|
}
|