@agentrific/server 0.1.0 → 0.1.4
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 +25 -0
- package/README.md +39 -5
- package/cli.js +534 -364
- package/cli.js.map +3 -3
- package/index.js +236 -185
- package/index.js.map +2 -2
- package/package.json +14 -2
package/cli.js
CHANGED
|
@@ -30,8 +30,20 @@ var init_package = __esm({
|
|
|
30
30
|
"package.json"() {
|
|
31
31
|
package_default = {
|
|
32
32
|
name: "@agentrific/server",
|
|
33
|
-
version: "0.1.
|
|
33
|
+
version: "0.1.4",
|
|
34
34
|
description: "Self-hosted control plane for long-running coding agents",
|
|
35
|
+
keywords: [
|
|
36
|
+
"agentrific",
|
|
37
|
+
"ai",
|
|
38
|
+
"android",
|
|
39
|
+
"cli",
|
|
40
|
+
"coding-agents",
|
|
41
|
+
"ios",
|
|
42
|
+
"pi",
|
|
43
|
+
"remote-control",
|
|
44
|
+
"self-hosted"
|
|
45
|
+
],
|
|
46
|
+
homepage: "https://agentrific.com",
|
|
35
47
|
repository: {
|
|
36
48
|
type: "git",
|
|
37
49
|
url: "git+https://github.com/devular/agentrific.git",
|
|
@@ -51,7 +63,7 @@ var init_package = __esm({
|
|
|
51
63
|
cli: "AGENTRIFIC_DATA_DIR=./data tsx src/cli.ts"
|
|
52
64
|
},
|
|
53
65
|
dependencies: {
|
|
54
|
-
"@earendil-works/pi-coding-agent": "
|
|
66
|
+
"@earendil-works/pi-coding-agent": "1.0.0",
|
|
55
67
|
"@fastify/cors": "^11.3.0",
|
|
56
68
|
"@fastify/multipart": "^10.1.2",
|
|
57
69
|
"@inquirer/select": "^5.2.5",
|
|
@@ -192,6 +204,9 @@ var init_updates = __esm({
|
|
|
192
204
|
|
|
193
205
|
// ../../packages/contracts/src/index.ts
|
|
194
206
|
import { z as z2 } from "zod";
|
|
207
|
+
function cronRange(base, min, max, stepped) {
|
|
208
|
+
return base === "*" ? [min, max] : base.includes("-") ? base.split("-").map(Number) : [Number(base), stepped ? max : Number(base)];
|
|
209
|
+
}
|
|
195
210
|
function cronFields(expression) {
|
|
196
211
|
const fields = expression.trim().split(/\s+/);
|
|
197
212
|
if (fields.length !== 5)
|
|
@@ -204,7 +219,7 @@ function cronFields(expression) {
|
|
|
204
219
|
if (!match) throw new Error("Use numeric cron fields, *, ranges, lists and steps.");
|
|
205
220
|
const base = match[1];
|
|
206
221
|
const step = Number(match[2] ?? 1);
|
|
207
|
-
const [start, end] = base
|
|
222
|
+
const [start, end] = cronRange(base, min, max, Boolean(match[2]));
|
|
208
223
|
if (!Number.isInteger(step) || step < 1 || step > max - min + 1 || start < min || end > max || start > end)
|
|
209
224
|
throw new Error("Cron field is outside its allowed range.");
|
|
210
225
|
for (let value = start; value <= end; value += step) values.add(value);
|
|
@@ -225,6 +240,11 @@ function cronMatches(expression, date) {
|
|
|
225
240
|
const day = fields[2].wildcard || fields[4].wildcard ? matches[2] && matches[4] : matches[2] || matches[4];
|
|
226
241
|
return Boolean(matches[0] && matches[1] && matches[3] && day);
|
|
227
242
|
}
|
|
243
|
+
function createPairingLink(payload) {
|
|
244
|
+
const link = new URL("agentrific://pair");
|
|
245
|
+
link.searchParams.set("data", JSON.stringify(PairingPayloadSchema.parse(payload)));
|
|
246
|
+
return link.toString();
|
|
247
|
+
}
|
|
228
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;
|
|
229
249
|
var init_src = __esm({
|
|
230
250
|
"../../packages/contracts/src/index.ts"() {
|
|
@@ -456,6 +476,7 @@ var init_src = __esm({
|
|
|
456
476
|
gitFlow: GitFlowSchema.optional(),
|
|
457
477
|
status: TaskStatusSchema,
|
|
458
478
|
sessionId: z2.string().nullable(),
|
|
479
|
+
pullRequestUrl: z2.string().url().optional(),
|
|
459
480
|
createdAt: z2.string().datetime(),
|
|
460
481
|
updatedAt: z2.string().datetime(),
|
|
461
482
|
deletedAt: z2.string().datetime().optional()
|
|
@@ -473,7 +494,7 @@ var init_src = __esm({
|
|
|
473
494
|
prompt: true,
|
|
474
495
|
attachmentIds: true,
|
|
475
496
|
gitFlow: true
|
|
476
|
-
}).strict();
|
|
497
|
+
}).extend({ gitFlow: GitFlowSchema.nullable().optional() }).strict();
|
|
477
498
|
PROTOCOL_VERSION = 1;
|
|
478
499
|
ServerUpdateStatusSchema = z2.object({
|
|
479
500
|
currentVersion: z2.string(),
|
|
@@ -1223,6 +1244,9 @@ function taskStatusForSession(status, session) {
|
|
|
1223
1244
|
return "needs_user_input";
|
|
1224
1245
|
}
|
|
1225
1246
|
}
|
|
1247
|
+
function pullRequestUrl(session) {
|
|
1248
|
+
return session.gitDelivery?.status === "pr_opened" ? session.gitDelivery.url : void 0;
|
|
1249
|
+
}
|
|
1226
1250
|
function sessionTask(session) {
|
|
1227
1251
|
return TaskSchema.parse({
|
|
1228
1252
|
id: `session-${session.id}`,
|
|
@@ -1233,6 +1257,7 @@ function sessionTask(session) {
|
|
|
1233
1257
|
gitFlow: session.gitFlow,
|
|
1234
1258
|
status: taskStatusForSession(session.status, session),
|
|
1235
1259
|
sessionId: session.id,
|
|
1260
|
+
pullRequestUrl: pullRequestUrl(session),
|
|
1236
1261
|
createdAt: session.createdAt,
|
|
1237
1262
|
updatedAt: session.updatedAt
|
|
1238
1263
|
});
|
|
@@ -1244,12 +1269,13 @@ async function boardTasks(store2, projectId) {
|
|
|
1244
1269
|
tasks.flatMap((task) => task.sessionId ? [task.sessionId] : [])
|
|
1245
1270
|
);
|
|
1246
1271
|
const linkedTasks = tasks.filter((task) => !task.deletedAt).map((task) => {
|
|
1247
|
-
const session = sessions.find((item) => item.
|
|
1248
|
-
if (!session) return { ...task, status: "backlog" };
|
|
1272
|
+
const session = sessions.find((item) => item.id === task.sessionId) ?? sessions.find((item) => item.taskId === task.id);
|
|
1273
|
+
if (!session) return { ...task, status: "backlog", pullRequestUrl: void 0 };
|
|
1249
1274
|
linkedSessionIds.add(session.id);
|
|
1250
1275
|
return {
|
|
1251
1276
|
...task,
|
|
1252
1277
|
sessionId: session.id,
|
|
1278
|
+
pullRequestUrl: pullRequestUrl(session),
|
|
1253
1279
|
status: taskStatusForSession(session.status, session)
|
|
1254
1280
|
};
|
|
1255
1281
|
});
|
|
@@ -1267,7 +1293,9 @@ async function findTask(store2, id) {
|
|
|
1267
1293
|
function editTask(store2, id, input, resolveAttachments) {
|
|
1268
1294
|
return withTaskLock(id, async () => {
|
|
1269
1295
|
const task = await findTask(store2, id);
|
|
1270
|
-
const { attachmentIds, ...updates2 } = input;
|
|
1296
|
+
const { attachmentIds, gitFlow, ...updates2 } = input;
|
|
1297
|
+
const configuration = { gitFlow: task.gitFlow };
|
|
1298
|
+
if (gitFlow !== void 0) configuration.gitFlow = gitFlow ?? void 0;
|
|
1271
1299
|
let files = task.attachments;
|
|
1272
1300
|
if (attachmentIds) {
|
|
1273
1301
|
if (!resolveAttachments)
|
|
@@ -1278,6 +1306,7 @@ function editTask(store2, id, input, resolveAttachments) {
|
|
|
1278
1306
|
store2.saveTask({
|
|
1279
1307
|
...task,
|
|
1280
1308
|
...updates2,
|
|
1309
|
+
...configuration,
|
|
1281
1310
|
attachments: files,
|
|
1282
1311
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1283
1312
|
})
|
|
@@ -1554,7 +1583,7 @@ var init_runtime = __esm({
|
|
|
1554
1583
|
Effect7.gen({ self: this }, function* () {
|
|
1555
1584
|
if (this.closed)
|
|
1556
1585
|
return yield* Effect7.fail(new AgentError({ message: "Server is shutting down" }));
|
|
1557
|
-
if (!session.branch)
|
|
1586
|
+
if (!session.branch && (session.gitFlow || !session.workspacePath))
|
|
1558
1587
|
return yield* Effect7.fail(new AgentError({ message: "Session checkout is not ready" }));
|
|
1559
1588
|
if (this.hasMessage(session.id, messageId)) return;
|
|
1560
1589
|
const entry = yield* this.open(session);
|
|
@@ -1882,45 +1911,7 @@ ${error ? workflowError(error instanceof Error ? error : String(error)) : messag
|
|
|
1882
1911
|
if (await this.git(session, ["branch", "--show-current"], signal) !== session.branch)
|
|
1883
1912
|
throw new Error("Restore the assigned run branch before requesting delivery.");
|
|
1884
1913
|
if (dirty) {
|
|
1885
|
-
await this.
|
|
1886
|
-
await this.stage(job, "drafting");
|
|
1887
|
-
const diff = await this.git(session, ["diff", "--cached", "HEAD", "--"], signal);
|
|
1888
|
-
const files = await this.git(
|
|
1889
|
-
session,
|
|
1890
|
-
["ls-files", "--others", "--exclude-standard"],
|
|
1891
|
-
signal
|
|
1892
|
-
);
|
|
1893
|
-
if (this.options.draft) {
|
|
1894
|
-
try {
|
|
1895
|
-
const draft = await this.options.draft(
|
|
1896
|
-
session,
|
|
1897
|
-
input,
|
|
1898
|
-
`${diff}
|
|
1899
|
-
New files:
|
|
1900
|
-
${files}`.slice(0, 4e4),
|
|
1901
|
-
signal
|
|
1902
|
-
);
|
|
1903
|
-
job.title = draft.title;
|
|
1904
|
-
job.body = draft.body;
|
|
1905
|
-
} catch (error) {
|
|
1906
|
-
job.logs = workflowLog(
|
|
1907
|
-
`${job.logs}
|
|
1908
|
-
Message helper unavailable; using task summary. ${workflowError(error instanceof Error ? error : String(error))}`
|
|
1909
|
-
);
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
await this.stage(job, "committing");
|
|
1913
|
-
if (await this.git(session, ["branch", "--show-current"], signal) !== session.branch)
|
|
1914
|
-
throw new Error("The workspace changed branches while preparing delivery.");
|
|
1915
|
-
const output = await this.git(
|
|
1916
|
-
session,
|
|
1917
|
-
["commit", "-m", `${job.title}
|
|
1918
|
-
|
|
1919
|
-
Agentrific run ${id}.`],
|
|
1920
|
-
signal
|
|
1921
|
-
);
|
|
1922
|
-
job.logs = workflowLog(`${job.logs}
|
|
1923
|
-
${output}`);
|
|
1914
|
+
await this.commitChanges(session, job, input, signal);
|
|
1924
1915
|
} else {
|
|
1925
1916
|
await this.git(session, ["hook", "run", "--ignore-missing", "pre-commit"], signal);
|
|
1926
1917
|
}
|
|
@@ -1947,6 +1938,44 @@ ${workflowError(error instanceof Error ? error : String(error))}`
|
|
|
1947
1938
|
}
|
|
1948
1939
|
});
|
|
1949
1940
|
}
|
|
1941
|
+
async commitChanges(session, job, input, signal) {
|
|
1942
|
+
const id = session.id;
|
|
1943
|
+
await this.git(session, ["add", "--all"], signal);
|
|
1944
|
+
await this.stage(job, "drafting");
|
|
1945
|
+
const diff = await this.git(session, ["diff", "--cached", "HEAD", "--"], signal);
|
|
1946
|
+
const files = await this.git(session, ["ls-files", "--others", "--exclude-standard"], signal);
|
|
1947
|
+
if (this.options.draft) {
|
|
1948
|
+
try {
|
|
1949
|
+
const draft = await this.options.draft(
|
|
1950
|
+
session,
|
|
1951
|
+
input,
|
|
1952
|
+
`${diff}
|
|
1953
|
+
New files:
|
|
1954
|
+
${files}`.slice(0, 4e4),
|
|
1955
|
+
signal
|
|
1956
|
+
);
|
|
1957
|
+
job.title = draft.title;
|
|
1958
|
+
job.body = draft.body;
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
job.logs = workflowLog(
|
|
1961
|
+
`${job.logs}
|
|
1962
|
+
Message helper unavailable; using task summary. ${workflowError(error instanceof Error ? error : String(error))}`
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
await this.stage(job, "committing");
|
|
1967
|
+
if (await this.git(session, ["branch", "--show-current"], signal) !== session.branch)
|
|
1968
|
+
throw new Error("The workspace changed branches while preparing delivery.");
|
|
1969
|
+
const output = await this.git(
|
|
1970
|
+
session,
|
|
1971
|
+
["commit", "-m", `${job.title}
|
|
1972
|
+
|
|
1973
|
+
Agentrific run ${id}.`],
|
|
1974
|
+
signal
|
|
1975
|
+
);
|
|
1976
|
+
job.logs = workflowLog(`${job.logs}
|
|
1977
|
+
${output}`);
|
|
1978
|
+
}
|
|
1950
1979
|
fallbackBody(id, input) {
|
|
1951
1980
|
return `${input.summary}
|
|
1952
1981
|
|
|
@@ -2019,6 +2048,31 @@ Agentrific run ${id}.`;
|
|
|
2019
2048
|
);
|
|
2020
2049
|
}
|
|
2021
2050
|
}
|
|
2051
|
+
async mergeChanges(session, job, projectPath, commit) {
|
|
2052
|
+
await this.stage(job, "integrating");
|
|
2053
|
+
if (session.gitFlow.workspace === "checkout" && await this.command(projectPath, "git", ["branch", "--show-current"]) === session.branch)
|
|
2054
|
+
await this.command(projectPath, "git", ["switch", "main"]);
|
|
2055
|
+
if (await this.command(projectPath, "git", ["branch", "--show-current"]) !== "main")
|
|
2056
|
+
throw new Error("Switch the project checkout to main, then retry delivery.");
|
|
2057
|
+
if (await this.command(projectPath, "git", ["status", "--porcelain"]))
|
|
2058
|
+
throw new Error(
|
|
2059
|
+
"Waiting for local changes in the main checkout. Commit or move them, then retry delivery."
|
|
2060
|
+
);
|
|
2061
|
+
const included = await this.command(projectPath, "git", ["merge-base", "main", commit]) === commit;
|
|
2062
|
+
if (!included) {
|
|
2063
|
+
try {
|
|
2064
|
+
await this.command(projectPath, "git", ["merge", "--no-edit", commit]);
|
|
2065
|
+
} catch (error) {
|
|
2066
|
+
await this.command(projectPath, "git", ["merge", "--abort"]).catch(() => void 0);
|
|
2067
|
+
throw new Error(
|
|
2068
|
+
`Integration failed. Resolve conflicts against main in the run workspace, then call deliver_changes again.
|
|
2069
|
+
${workflowError(error instanceof Error ? error : String(error))}`
|
|
2070
|
+
);
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
job.result = "merged";
|
|
2074
|
+
job.message = "Committed changes are on main.";
|
|
2075
|
+
}
|
|
2022
2076
|
async integrate(id) {
|
|
2023
2077
|
const job = await this.job(id);
|
|
2024
2078
|
if (!job?.commitSha) return;
|
|
@@ -2032,29 +2086,7 @@ Agentrific run ${id}.`;
|
|
|
2032
2086
|
"The workspace changed after requesting delivery. Call deliver_changes again for the new changes."
|
|
2033
2087
|
);
|
|
2034
2088
|
if (session.gitFlow.delivery === "merge") {
|
|
2035
|
-
await this.
|
|
2036
|
-
if (session.gitFlow.workspace === "checkout" && await this.command(projectPath, "git", ["branch", "--show-current"]) === session.branch)
|
|
2037
|
-
await this.command(projectPath, "git", ["switch", "main"]);
|
|
2038
|
-
if (await this.command(projectPath, "git", ["branch", "--show-current"]) !== "main")
|
|
2039
|
-
throw new Error("Switch the project checkout to main, then retry delivery.");
|
|
2040
|
-
if (await this.command(projectPath, "git", ["status", "--porcelain"]))
|
|
2041
|
-
throw new Error(
|
|
2042
|
-
"Waiting for local changes in the main checkout. Commit or move them, then retry delivery."
|
|
2043
|
-
);
|
|
2044
|
-
const included = await this.command(projectPath, "git", ["merge-base", "main", commit]) === commit;
|
|
2045
|
-
if (!included) {
|
|
2046
|
-
try {
|
|
2047
|
-
await this.command(projectPath, "git", ["merge", "--no-edit", commit]);
|
|
2048
|
-
} catch (error) {
|
|
2049
|
-
await this.command(projectPath, "git", ["merge", "--abort"]).catch(() => void 0);
|
|
2050
|
-
throw new Error(
|
|
2051
|
-
`Integration failed. Resolve conflicts against main in the run workspace, then call deliver_changes again.
|
|
2052
|
-
${workflowError(error instanceof Error ? error : String(error))}`
|
|
2053
|
-
);
|
|
2054
|
-
}
|
|
2055
|
-
}
|
|
2056
|
-
job.result = "merged";
|
|
2057
|
-
job.message = "Committed changes are on main.";
|
|
2089
|
+
await this.mergeChanges(session, job, projectPath, commit);
|
|
2058
2090
|
} else {
|
|
2059
2091
|
await this.stage(job, "pushing");
|
|
2060
2092
|
await this.git(session, ["push", "origin", `${commit}:refs/heads/${session.branch}`]);
|
|
@@ -2141,6 +2173,7 @@ var init_preparation = __esm({
|
|
|
2141
2173
|
return [...this.preparing.values()].map((id) => `preparation:${id}`);
|
|
2142
2174
|
}
|
|
2143
2175
|
async prepare(session, projectPath, input) {
|
|
2176
|
+
if (!session.gitFlow) return { ...session, branch: null, workspacePath: projectPath };
|
|
2144
2177
|
const preparation2 = Symbol(session.id);
|
|
2145
2178
|
this.preparing.set(preparation2, session.id);
|
|
2146
2179
|
return withTaskLock(`prepare:${session.id}`, async () => {
|
|
@@ -2173,56 +2206,7 @@ ${logs}` : ""}`);
|
|
|
2173
2206
|
try {
|
|
2174
2207
|
await record("creating", "Creating the run workspace\u2026");
|
|
2175
2208
|
const workspace = session.workspacePath;
|
|
2176
|
-
|
|
2177
|
-
const branchAtWorkspace = await command(workspace, "git", [
|
|
2178
|
-
"branch",
|
|
2179
|
-
"--show-current"
|
|
2180
|
-
]).catch(() => void 0);
|
|
2181
|
-
if (branchAtWorkspace !== session.branch) {
|
|
2182
|
-
if (checkout) {
|
|
2183
|
-
if (branchAtWorkspace !== "main" || await command(projectPath, "git", ["status", "--porcelain"]))
|
|
2184
|
-
throw new Error(
|
|
2185
|
-
"The project checkout must be clean and on main before starting a checkout run."
|
|
2186
|
-
);
|
|
2187
|
-
await command(
|
|
2188
|
-
projectPath,
|
|
2189
|
-
"git",
|
|
2190
|
-
["switch", "-c", session.branch, "main"],
|
|
2191
|
-
void 0,
|
|
2192
|
-
true
|
|
2193
|
-
);
|
|
2194
|
-
} else {
|
|
2195
|
-
if (branchAtWorkspace !== void 0)
|
|
2196
|
-
throw new Error("The run workspace already exists on a different branch.");
|
|
2197
|
-
await mkdir4(path6.dirname(workspace), { recursive: true });
|
|
2198
|
-
try {
|
|
2199
|
-
await command(
|
|
2200
|
-
projectPath,
|
|
2201
|
-
"git",
|
|
2202
|
-
[
|
|
2203
|
-
"worktree",
|
|
2204
|
-
"add",
|
|
2205
|
-
"-b",
|
|
2206
|
-
session.branch,
|
|
2207
|
-
workspace,
|
|
2208
|
-
session.gitFlow ? "main" : "HEAD"
|
|
2209
|
-
],
|
|
2210
|
-
void 0,
|
|
2211
|
-
true
|
|
2212
|
-
);
|
|
2213
|
-
} catch (error) {
|
|
2214
|
-
if (await command(workspace, "git", ["branch", "--show-current"]).catch(
|
|
2215
|
-
() => void 0
|
|
2216
|
-
) !== session.branch)
|
|
2217
|
-
throw error;
|
|
2218
|
-
await record(
|
|
2219
|
-
"creating",
|
|
2220
|
-
"Workspace created; a checkout hook failed. Preparing dependencies\u2026",
|
|
2221
|
-
workflowError(error instanceof Error ? error : String(error))
|
|
2222
|
-
);
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
}
|
|
2209
|
+
await this.createWorkspace(session, projectPath, command, record);
|
|
2226
2210
|
await record("installing", "Installing run dependencies\u2026");
|
|
2227
2211
|
await this.install(workspace, command);
|
|
2228
2212
|
await record("ready", "Workspace ready.");
|
|
@@ -2238,6 +2222,57 @@ ${logs}` : ""}`);
|
|
|
2238
2222
|
}
|
|
2239
2223
|
}).finally(() => this.preparing.delete(preparation2));
|
|
2240
2224
|
}
|
|
2225
|
+
async createWorkspace(session, projectPath, command, record) {
|
|
2226
|
+
const workspace = session.workspacePath;
|
|
2227
|
+
const checkout = session.gitFlow?.workspace === "checkout";
|
|
2228
|
+
if (!await command(projectPath, "git", ["rev-parse", "--verify", "HEAD"]).catch(() => void 0))
|
|
2229
|
+
throw new Error(
|
|
2230
|
+
"Git Flow needs an initial commit. Create one in the project, or turn off Git Flow for a plain chat."
|
|
2231
|
+
);
|
|
2232
|
+
const branchAtWorkspace = await command(workspace, "git", ["branch", "--show-current"]).catch(
|
|
2233
|
+
() => void 0
|
|
2234
|
+
);
|
|
2235
|
+
if (branchAtWorkspace !== session.branch) {
|
|
2236
|
+
if (checkout) {
|
|
2237
|
+
if (branchAtWorkspace !== "main" || await command(projectPath, "git", ["status", "--porcelain"]))
|
|
2238
|
+
throw new Error(
|
|
2239
|
+
"The project checkout must be clean and on main before starting a checkout run."
|
|
2240
|
+
);
|
|
2241
|
+
await command(
|
|
2242
|
+
projectPath,
|
|
2243
|
+
"git",
|
|
2244
|
+
["switch", "-c", session.branch, "main"],
|
|
2245
|
+
void 0,
|
|
2246
|
+
true
|
|
2247
|
+
);
|
|
2248
|
+
} else {
|
|
2249
|
+
await this.createWorktree(session, projectPath, command, record, branchAtWorkspace);
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
async createWorktree(session, projectPath, command, record, branchAtWorkspace) {
|
|
2254
|
+
const workspace = session.workspacePath;
|
|
2255
|
+
if (branchAtWorkspace !== void 0)
|
|
2256
|
+
throw new Error("The run workspace already exists on a different branch.");
|
|
2257
|
+
await mkdir4(path6.dirname(workspace), { recursive: true });
|
|
2258
|
+
try {
|
|
2259
|
+
await command(
|
|
2260
|
+
projectPath,
|
|
2261
|
+
"git",
|
|
2262
|
+
["worktree", "add", "-b", session.branch, workspace, "main"],
|
|
2263
|
+
void 0,
|
|
2264
|
+
true
|
|
2265
|
+
);
|
|
2266
|
+
} catch (error) {
|
|
2267
|
+
if (await command(workspace, "git", ["branch", "--show-current"]).catch(() => void 0) !== session.branch)
|
|
2268
|
+
throw error;
|
|
2269
|
+
await record(
|
|
2270
|
+
"creating",
|
|
2271
|
+
"Workspace created; a checkout hook failed. Preparing dependencies\u2026",
|
|
2272
|
+
workflowError(error instanceof Error ? error : String(error))
|
|
2273
|
+
);
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2241
2276
|
async install(workspace, command) {
|
|
2242
2277
|
const packageFile = await readFile3(path6.join(workspace, "package.json"), "utf8").catch(
|
|
2243
2278
|
() => void 0
|
|
@@ -2328,10 +2363,10 @@ ${logs}` : ""}`);
|
|
|
2328
2363
|
// src/delivery-writer.ts
|
|
2329
2364
|
import { createAgentSession, SessionManager } from "@earendil-works/pi-coding-agent";
|
|
2330
2365
|
import { z as z11 } from "zod";
|
|
2331
|
-
function deliveryWriter(models2) {
|
|
2366
|
+
function deliveryWriter(models2, createSession = createAgentSession) {
|
|
2332
2367
|
return async (run, input, diff, signal) => {
|
|
2333
2368
|
signal?.throwIfAborted();
|
|
2334
|
-
const { session } = await
|
|
2369
|
+
const { session } = await createSession({
|
|
2335
2370
|
cwd: run.workspacePath,
|
|
2336
2371
|
model: await models2.resolve(run.model ?? await models2.defaultReference()),
|
|
2337
2372
|
modelRuntime: models2.runtime,
|
|
@@ -2535,11 +2570,11 @@ function toolOutput(value) {
|
|
|
2535
2570
|
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);
|
|
2536
2571
|
return text.length > 4e3 ? `${text.slice(0, 4e3)}\u2026 [truncated]` : text;
|
|
2537
2572
|
}
|
|
2538
|
-
function createPiFactory(files, models2, delivery2) {
|
|
2573
|
+
function createPiFactory(files, models2, delivery2, createSession = createAgentSession2) {
|
|
2539
2574
|
return {
|
|
2540
2575
|
async open(sessionId, cwd, sessionDirectory, selected = legacyModel, run) {
|
|
2541
2576
|
const model = await models2.resolve(selected);
|
|
2542
|
-
const { session } = await
|
|
2577
|
+
const { session } = await createSession({
|
|
2543
2578
|
cwd,
|
|
2544
2579
|
model,
|
|
2545
2580
|
modelRuntime: models2.runtime,
|
|
@@ -2998,31 +3033,7 @@ var init_store = __esm({
|
|
|
2998
3033
|
this.db.prepare("DELETE FROM projects WHERE id = ?").run(replacedId);
|
|
2999
3034
|
}
|
|
3000
3035
|
if (previousPath && previousPath !== project.path) {
|
|
3001
|
-
this.
|
|
3002
|
-
"UPDATE workflows SET payload = json_set(payload, '$.projectPath', ?) WHERE json_extract(payload, '$.kind') = 'preparation' AND json_extract(payload, '$.projectPath') = ?"
|
|
3003
|
-
).run(project.path, previousPath);
|
|
3004
|
-
const relocate = (workspace) => workspace === previousPath || workspace.startsWith(`${previousPath}${path8.sep}`) ? project.path + workspace.slice(previousPath.length) : workspace;
|
|
3005
|
-
for (const row of this.db.prepare(
|
|
3006
|
-
"SELECT id, payload FROM sessions WHERE json_extract(payload, '$.projectId') = ?"
|
|
3007
|
-
).all(project.id)) {
|
|
3008
|
-
const session = SessionSchema.parse(JSON.parse(PayloadRow.parse(row).payload));
|
|
3009
|
-
if (session.workspacePath) {
|
|
3010
|
-
session.workspacePath = String(relocate(session.workspacePath));
|
|
3011
|
-
this.db.prepare("UPDATE sessions SET payload = ? WHERE id = ?").run(JSON.stringify(session), session.id);
|
|
3012
|
-
}
|
|
3013
|
-
}
|
|
3014
|
-
for (const row of this.db.prepare(
|
|
3015
|
-
"SELECT sequence, payload FROM events WHERE json_extract(payload, '$.session.projectId') = ?"
|
|
3016
|
-
).all(project.id)) {
|
|
3017
|
-
const event = SessionSchema.parse(JSON.parse(PayloadRow.parse(row).payload).session);
|
|
3018
|
-
if (event.workspacePath) {
|
|
3019
|
-
event.workspacePath = String(relocate(event.workspacePath));
|
|
3020
|
-
this.db.prepare("UPDATE events SET payload = ? WHERE sequence = ?").run(
|
|
3021
|
-
JSON.stringify({ type: "session.updated", session: event }),
|
|
3022
|
-
Number(row.sequence)
|
|
3023
|
-
);
|
|
3024
|
-
}
|
|
3025
|
-
}
|
|
3036
|
+
this.relocateProjectHistory(project, previousPath);
|
|
3026
3037
|
}
|
|
3027
3038
|
this.db.prepare(
|
|
3028
3039
|
"INSERT INTO projects(id, payload) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET payload = excluded.payload"
|
|
@@ -3035,6 +3046,28 @@ var init_store = __esm({
|
|
|
3035
3046
|
}
|
|
3036
3047
|
});
|
|
3037
3048
|
}
|
|
3049
|
+
relocateProjectHistory(project, previousPath) {
|
|
3050
|
+
this.db.prepare(
|
|
3051
|
+
"UPDATE workflows SET payload = json_set(payload, '$.projectPath', ?) WHERE json_extract(payload, '$.kind') = 'preparation' AND json_extract(payload, '$.projectPath') = ?"
|
|
3052
|
+
).run(project.path, previousPath);
|
|
3053
|
+
const relocate = (workspace) => workspace === previousPath || workspace.startsWith(`${previousPath}${path8.sep}`) ? project.path + workspace.slice(previousPath.length) : workspace;
|
|
3054
|
+
for (const row of this.db.prepare("SELECT id, payload FROM sessions WHERE json_extract(payload, '$.projectId') = ?").all(project.id)) {
|
|
3055
|
+
const session = SessionSchema.parse(JSON.parse(PayloadRow.parse(row).payload));
|
|
3056
|
+
if (session.workspacePath) {
|
|
3057
|
+
session.workspacePath = String(relocate(session.workspacePath));
|
|
3058
|
+
this.db.prepare("UPDATE sessions SET payload = ? WHERE id = ?").run(JSON.stringify(session), session.id);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
for (const row of this.db.prepare(
|
|
3062
|
+
"SELECT sequence, payload FROM events WHERE json_extract(payload, '$.session.projectId') = ?"
|
|
3063
|
+
).all(project.id)) {
|
|
3064
|
+
const event = SessionSchema.parse(JSON.parse(PayloadRow.parse(row).payload).session);
|
|
3065
|
+
if (event.workspacePath) {
|
|
3066
|
+
event.workspacePath = String(relocate(event.workspacePath));
|
|
3067
|
+
this.db.prepare("UPDATE events SET payload = ? WHERE sequence = ?").run(JSON.stringify({ type: "session.updated", session: event }), Number(row.sequence));
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3038
3071
|
defaultModel() {
|
|
3039
3072
|
return this.query(() => {
|
|
3040
3073
|
const row = this.db.prepare("SELECT payload FROM settings WHERE id = 'default-model'").get();
|
|
@@ -3286,16 +3319,27 @@ async function githubCliStatus() {
|
|
|
3286
3319
|
return { githubCli: { installed: true, authenticated: false } };
|
|
3287
3320
|
}
|
|
3288
3321
|
}
|
|
3289
|
-
async function
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
const
|
|
3293
|
-
if (!
|
|
3294
|
-
return {
|
|
3322
|
+
async function selectSessionModel(input, hasImages) {
|
|
3323
|
+
try {
|
|
3324
|
+
const reference = input.model ?? await models.defaultReference();
|
|
3325
|
+
const selected = await models.resolve(reference);
|
|
3326
|
+
if (hasImages && !selected.input.includes("image"))
|
|
3327
|
+
return {
|
|
3328
|
+
error: "This model cannot read images. Choose a vision model in Settings.",
|
|
3329
|
+
status: 400
|
|
3330
|
+
};
|
|
3331
|
+
return { model: models.identity(selected.provider, selected.id) };
|
|
3332
|
+
} catch {
|
|
3333
|
+
return {
|
|
3334
|
+
error: "The default model is unavailable. Choose an available model in Settings.",
|
|
3335
|
+
status: 400
|
|
3336
|
+
};
|
|
3295
3337
|
}
|
|
3338
|
+
}
|
|
3339
|
+
async function resolveSessionProject(projectId) {
|
|
3296
3340
|
const projectsRoot = await Effect14.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
3297
3341
|
const project = (await Effect14.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
3298
|
-
(item) => item.id ===
|
|
3342
|
+
(item) => item.id === projectId && item.available
|
|
3299
3343
|
);
|
|
3300
3344
|
if (!project)
|
|
3301
3345
|
return { error: "Project folder is missing. Reconnect it before starting a run.", status: 404 };
|
|
@@ -3303,6 +3347,37 @@ async function createAndStartSession(input) {
|
|
|
3303
3347
|
const resolvedRoot = await realpath2(projectsRoot);
|
|
3304
3348
|
if (path9.dirname(projectPath) !== resolvedRoot)
|
|
3305
3349
|
return { error: "Project path is outside projects root.", status: 400 };
|
|
3350
|
+
return { projectPath };
|
|
3351
|
+
}
|
|
3352
|
+
async function resolveSessionWorkspace(id, gitFlow, projectPath) {
|
|
3353
|
+
const worktreePath = path9.join(worktreesRoot, id);
|
|
3354
|
+
const branch = gitFlow ? `agentrific/${id.slice(0, 8)}` : null;
|
|
3355
|
+
const useProjectCheckout = gitFlow?.workspace === "checkout";
|
|
3356
|
+
const workspacePath = !gitFlow || useProjectCheckout ? projectPath : worktreePath;
|
|
3357
|
+
if (useProjectCheckout) {
|
|
3358
|
+
const serverPath = await realpath2(process.cwd());
|
|
3359
|
+
if (serverPath === projectPath || serverPath.startsWith(`${projectPath}${path9.sep}`))
|
|
3360
|
+
return {
|
|
3361
|
+
error: "The server runs from this project. Choose a worktree for this task.",
|
|
3362
|
+
status: 409
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
return { branch, workspacePath };
|
|
3366
|
+
}
|
|
3367
|
+
function sessionTaskUnavailable(task, projectId) {
|
|
3368
|
+
return !task || Boolean(task.deletedAt) || task.projectId !== projectId;
|
|
3369
|
+
}
|
|
3370
|
+
async function sessionDeliveryAvailable(gitFlow) {
|
|
3371
|
+
if (gitFlow?.delivery !== "pull_request") return true;
|
|
3372
|
+
return (await githubCliStatus()).githubCli.authenticated;
|
|
3373
|
+
}
|
|
3374
|
+
async function createAndStartSession(input) {
|
|
3375
|
+
maintenance.assertReady();
|
|
3376
|
+
if (!await sessionDeliveryAvailable(input.gitFlow))
|
|
3377
|
+
return { error: "GitHub CLI must be authenticated on the server to open a PR.", status: 400 };
|
|
3378
|
+
const location = await resolveSessionProject(input.projectId);
|
|
3379
|
+
if (location.error !== void 0) return { error: location.error, status: location.status };
|
|
3380
|
+
const projectPath = location.projectPath;
|
|
3306
3381
|
let files;
|
|
3307
3382
|
try {
|
|
3308
3383
|
files = await Effect14.runPromise(
|
|
@@ -3311,25 +3386,15 @@ async function createAndStartSession(input) {
|
|
|
3311
3386
|
} catch (error) {
|
|
3312
3387
|
return { error: String(error), status: 400 };
|
|
3313
3388
|
}
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
error: "This model cannot read images. Choose a vision model in Settings.",
|
|
3321
|
-
status: 400
|
|
3322
|
-
};
|
|
3323
|
-
selectedModel = models.identity(selected.provider, selected.id);
|
|
3324
|
-
} catch {
|
|
3325
|
-
return {
|
|
3326
|
-
error: "The default model is unavailable. Choose an available model in Settings.",
|
|
3327
|
-
status: 400
|
|
3328
|
-
};
|
|
3329
|
-
}
|
|
3389
|
+
const selection = await selectSessionModel(
|
|
3390
|
+
input,
|
|
3391
|
+
files.some((file) => file.kind === "image")
|
|
3392
|
+
);
|
|
3393
|
+
if (selection.error !== void 0) return { error: selection.error, status: selection.status };
|
|
3394
|
+
const selectedModel = selection.model;
|
|
3330
3395
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3331
3396
|
const task = input.taskId ? await Effect14.runPromise(store.getTask(input.taskId)) : void 0;
|
|
3332
|
-
if (input.taskId && (
|
|
3397
|
+
if (input.taskId && sessionTaskUnavailable(task, input.projectId))
|
|
3333
3398
|
return { error: "Task not found.", status: 404 };
|
|
3334
3399
|
const session = SessionSchema.parse({
|
|
3335
3400
|
id: randomUUID7(),
|
|
@@ -3344,18 +3409,9 @@ async function createAndStartSession(input) {
|
|
|
3344
3409
|
createdAt: now,
|
|
3345
3410
|
updatedAt: now
|
|
3346
3411
|
});
|
|
3347
|
-
const
|
|
3348
|
-
|
|
3349
|
-
const
|
|
3350
|
-
const workspacePath = useProjectCheckout ? projectPath : worktreePath;
|
|
3351
|
-
if (useProjectCheckout) {
|
|
3352
|
-
const serverPath = await realpath2(process.cwd());
|
|
3353
|
-
if (serverPath === projectPath || serverPath.startsWith(`${projectPath}${path9.sep}`))
|
|
3354
|
-
return {
|
|
3355
|
-
error: "The server runs from this project. Choose a worktree for this task.",
|
|
3356
|
-
status: 409
|
|
3357
|
-
};
|
|
3358
|
-
}
|
|
3412
|
+
const workspace = await resolveSessionWorkspace(session.id, input.gitFlow, projectPath);
|
|
3413
|
+
if (workspace.error !== void 0) return { error: workspace.error, status: workspace.status };
|
|
3414
|
+
const { branch, workspacePath } = workspace;
|
|
3359
3415
|
const initial = { ...session, branch, workspacePath };
|
|
3360
3416
|
publish({ type: "session.updated", session: initial });
|
|
3361
3417
|
let running;
|
|
@@ -4026,17 +4082,7 @@ async function manageService(action, dataDir, cli) {
|
|
|
4026
4082
|
);
|
|
4027
4083
|
const domain = `gui/${process.getuid?.()}`;
|
|
4028
4084
|
if (action === "install") {
|
|
4029
|
-
await
|
|
4030
|
-
await mkdir(path.join(dataDir, "logs"), { recursive: true, mode: 448 });
|
|
4031
|
-
await writeFile(filename, serviceDefinition(platform, process.execPath, cli, dataDir), {
|
|
4032
|
-
mode: 384,
|
|
4033
|
-
flag: "wx"
|
|
4034
|
-
});
|
|
4035
|
-
if (platform === "darwin") await execute("launchctl", ["bootstrap", domain, filename]);
|
|
4036
|
-
else {
|
|
4037
|
-
await execute("systemctl", ["--user", "daemon-reload"]);
|
|
4038
|
-
await execute("systemctl", ["--user", "enable", "--now", "agentrific.service"]);
|
|
4039
|
-
}
|
|
4085
|
+
await installService(platform, directory, filename, domain, dataDir, cli);
|
|
4040
4086
|
return;
|
|
4041
4087
|
}
|
|
4042
4088
|
if (action === "restart") {
|
|
@@ -4050,6 +4096,19 @@ async function manageService(action, dataDir, cli) {
|
|
|
4050
4096
|
await unlink(filename);
|
|
4051
4097
|
if (platform === "linux") await execute("systemctl", ["--user", "daemon-reload"]);
|
|
4052
4098
|
}
|
|
4099
|
+
async function installService(platform, directory, filename, domain, dataDir, cli) {
|
|
4100
|
+
await mkdir(directory, { recursive: true, mode: 448 });
|
|
4101
|
+
await mkdir(path.join(dataDir, "logs"), { recursive: true, mode: 448 });
|
|
4102
|
+
await writeFile(filename, serviceDefinition(platform, process.execPath, cli, dataDir), {
|
|
4103
|
+
mode: 384,
|
|
4104
|
+
flag: "wx"
|
|
4105
|
+
});
|
|
4106
|
+
if (platform === "darwin") await execute("launchctl", ["bootstrap", domain, filename]);
|
|
4107
|
+
else {
|
|
4108
|
+
await execute("systemctl", ["--user", "daemon-reload"]);
|
|
4109
|
+
await execute("systemctl", ["--user", "enable", "--now", "agentrific.service"]);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4053
4112
|
|
|
4054
4113
|
// src/cli.ts
|
|
4055
4114
|
init_updates();
|
|
@@ -4060,9 +4119,11 @@ import { parseArgs } from "node:util";
|
|
|
4060
4119
|
import { z as z16 } from "zod";
|
|
4061
4120
|
|
|
4062
4121
|
// src/pairing.ts
|
|
4122
|
+
init_src();
|
|
4063
4123
|
init_control();
|
|
4064
4124
|
import { z as z4 } from "zod";
|
|
4065
4125
|
import { styleText as styleText2 } from "node:util";
|
|
4126
|
+
import { emitKeypressEvents } from "node:readline";
|
|
4066
4127
|
import qrcode from "qrcode-terminal";
|
|
4067
4128
|
|
|
4068
4129
|
// src/cli-output.ts
|
|
@@ -4084,9 +4145,13 @@ function printHeading(title) {
|
|
|
4084
4145
|
function printDetail(label2, value) {
|
|
4085
4146
|
console.log(` ${styleText("dim", "\u203A")} ${styleText("dim", label2.padEnd(10))} ${value}`);
|
|
4086
4147
|
}
|
|
4087
|
-
function printShortcuts() {
|
|
4148
|
+
function printShortcuts(pairingAvailable = false) {
|
|
4088
4149
|
console.log();
|
|
4089
4150
|
console.log(` ${styleText("dim", "\u203A")} Press ${styleText("bold", "p")} \u2502 pair a device`);
|
|
4151
|
+
if (pairingAvailable)
|
|
4152
|
+
console.log(
|
|
4153
|
+
` ${styleText("dim", "\u203A")} Press ${styleText("bold", "j")} \u2502 show pairing JSON`
|
|
4154
|
+
);
|
|
4090
4155
|
console.log(` ${styleText("dim", "\u203A")} Press ${styleText("bold", "?")} \u2502 show shortcuts`);
|
|
4091
4156
|
console.log(
|
|
4092
4157
|
` ${styleText("dim", "\u203A")} Press ${styleText("bold", "Ctrl+C")} \u2502 stop the server
|
|
@@ -4122,6 +4187,7 @@ function printHelp() {
|
|
|
4122
4187
|
["--data-dir <path>", "Use a specific server data directory"],
|
|
4123
4188
|
["--public-url <https-url>", "Use your tunnel URL without prompting"],
|
|
4124
4189
|
["--no-interactive", "Disable prompts and keyboard shortcuts"],
|
|
4190
|
+
["--show-payload", "Print manual JSON with the pairing QR code"],
|
|
4125
4191
|
["--timeout-ms <0-60000>", "Set the maintenance drain timeout"],
|
|
4126
4192
|
["--version", "Print the server version"],
|
|
4127
4193
|
["--help", "Show this help"]
|
|
@@ -4131,6 +4197,69 @@ function printHelp() {
|
|
|
4131
4197
|
}
|
|
4132
4198
|
|
|
4133
4199
|
// src/pairing.ts
|
|
4200
|
+
function printManualPairing(invitation) {
|
|
4201
|
+
if (Date.now() >= invitation.expiresAt) {
|
|
4202
|
+
printError("This invitation expired. Generate a fresh pairing QR code.");
|
|
4203
|
+
return;
|
|
4204
|
+
}
|
|
4205
|
+
console.log(styleText2("dim", "\n Manual entry \xB7 paste this JSON into Pairing payload:\n"));
|
|
4206
|
+
console.log(` ${invitation.payload}
|
|
4207
|
+
`);
|
|
4208
|
+
}
|
|
4209
|
+
function waitForManualPairing(invitation) {
|
|
4210
|
+
console.log(styleText2("dim", " Press j to show manual entry \xB7 Enter or q to finish.\n"));
|
|
4211
|
+
return new Promise((resolve) => {
|
|
4212
|
+
const input = process.stdin;
|
|
4213
|
+
const previousRaw = input.isRaw;
|
|
4214
|
+
const timeout = setTimeout(
|
|
4215
|
+
() => {
|
|
4216
|
+
console.log(
|
|
4217
|
+
styleText2("dim", "\n Pairing invitation expired. Run agentrific pair again.\n")
|
|
4218
|
+
);
|
|
4219
|
+
close();
|
|
4220
|
+
},
|
|
4221
|
+
Math.max(0, invitation.expiresAt - Date.now())
|
|
4222
|
+
);
|
|
4223
|
+
function close() {
|
|
4224
|
+
clearTimeout(timeout);
|
|
4225
|
+
input.removeListener("keypress", pressed);
|
|
4226
|
+
input.removeListener("end", close);
|
|
4227
|
+
process.removeListener("SIGINT", interrupted);
|
|
4228
|
+
process.removeListener("SIGTERM", terminated);
|
|
4229
|
+
input.setRawMode(previousRaw);
|
|
4230
|
+
input.pause();
|
|
4231
|
+
resolve();
|
|
4232
|
+
}
|
|
4233
|
+
function interrupted() {
|
|
4234
|
+
process.exitCode = 130;
|
|
4235
|
+
close();
|
|
4236
|
+
}
|
|
4237
|
+
function terminated() {
|
|
4238
|
+
process.exitCode = 143;
|
|
4239
|
+
close();
|
|
4240
|
+
}
|
|
4241
|
+
function pressed(text, key) {
|
|
4242
|
+
if (key.ctrl && key.name === "c") {
|
|
4243
|
+
interrupted();
|
|
4244
|
+
return;
|
|
4245
|
+
}
|
|
4246
|
+
if (key.ctrl || key.meta) return;
|
|
4247
|
+
if (text?.toLowerCase() === "j") {
|
|
4248
|
+
printManualPairing(invitation);
|
|
4249
|
+
close();
|
|
4250
|
+
} else if (text?.toLowerCase() === "q" || key.name === "return" || key.name === "escape") {
|
|
4251
|
+
close();
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
emitKeypressEvents(input);
|
|
4255
|
+
input.setRawMode(true);
|
|
4256
|
+
input.on("keypress", pressed);
|
|
4257
|
+
input.once("end", close);
|
|
4258
|
+
process.once("SIGINT", interrupted);
|
|
4259
|
+
process.once("SIGTERM", terminated);
|
|
4260
|
+
input.resume();
|
|
4261
|
+
});
|
|
4262
|
+
}
|
|
4134
4263
|
function pairingOrigin(url) {
|
|
4135
4264
|
const endpoint = new URL(url);
|
|
4136
4265
|
if (endpoint.username || endpoint.password || endpoint.search || endpoint.hash || endpoint.pathname !== "/")
|
|
@@ -4151,17 +4280,19 @@ async function printPairing(dataDir, url) {
|
|
|
4151
4280
|
expiresAt: z4.number(),
|
|
4152
4281
|
serverId: z4.string()
|
|
4153
4282
|
}).parse(await controlRequest(dataDir, { command: "pair" }));
|
|
4154
|
-
const
|
|
4283
|
+
const invitation = {
|
|
4155
4284
|
v: 1,
|
|
4156
4285
|
url: origin,
|
|
4157
4286
|
serverId: grant.serverId,
|
|
4158
4287
|
pairingId: grant.pairingId
|
|
4159
|
-
}
|
|
4288
|
+
};
|
|
4289
|
+
const payload = JSON.stringify(invitation);
|
|
4290
|
+
const link = createPairingLink(invitation);
|
|
4160
4291
|
printHeading("pairing");
|
|
4161
|
-
console.log(" 1.
|
|
4162
|
-
console.log(" 2.
|
|
4292
|
+
console.log(" 1. Scan below with your phone camera and tap Open Agentrific if offered.");
|
|
4293
|
+
console.log(" 2. Or open Agentrific and choose Scan pairing QR code.");
|
|
4163
4294
|
console.log(" 3. Enter the PIN, then confirm pairing.\n");
|
|
4164
|
-
qrcode.generate(
|
|
4295
|
+
qrcode.generate(link, { small: true }, (code) => {
|
|
4165
4296
|
console.log(
|
|
4166
4297
|
code.split("\n").map((line) => ` ${line}`).join("\n")
|
|
4167
4298
|
);
|
|
@@ -4171,16 +4302,13 @@ async function printPairing(dataDir, url) {
|
|
|
4171
4302
|
printDetail("Server", styleText2("cyan", origin));
|
|
4172
4303
|
printDetail("Expires", "In five minutes \xB7 one device per code");
|
|
4173
4304
|
console.log("\n Pairing grants this device access to run agents as your host user.\n");
|
|
4174
|
-
|
|
4175
|
-
console.log(`Pairing payload:
|
|
4176
|
-
${payload}`);
|
|
4177
|
-
console.log();
|
|
4305
|
+
return { payload, expiresAt: grant.expiresAt };
|
|
4178
4306
|
}
|
|
4179
4307
|
|
|
4180
4308
|
// src/terminal.ts
|
|
4181
4309
|
import { createInterface } from "node:readline/promises";
|
|
4182
4310
|
import select from "@inquirer/select";
|
|
4183
|
-
import { emitKeypressEvents } from "node:readline";
|
|
4311
|
+
import { emitKeypressEvents as emitKeypressEvents2 } from "node:readline";
|
|
4184
4312
|
import { styleText as styleText3 } from "node:util";
|
|
4185
4313
|
|
|
4186
4314
|
// src/tunnels.ts
|
|
@@ -4489,6 +4617,60 @@ async function promptPublicUrl(signal, saved) {
|
|
|
4489
4617
|
prompt.close();
|
|
4490
4618
|
}
|
|
4491
4619
|
}
|
|
4620
|
+
async function selectConnectionMethod(options, signal, saved) {
|
|
4621
|
+
return select(
|
|
4622
|
+
{
|
|
4623
|
+
message: "Connection method",
|
|
4624
|
+
default: saved ? "existing" : void 0,
|
|
4625
|
+
loop: false,
|
|
4626
|
+
choices: [
|
|
4627
|
+
...options.filter((option) => option.available).map((option) => ({
|
|
4628
|
+
value: option.provider,
|
|
4629
|
+
name: option.provider === "tailscale" ? "Tailscale" : "Cloudflared",
|
|
4630
|
+
description: option.reason
|
|
4631
|
+
})),
|
|
4632
|
+
{
|
|
4633
|
+
value: "existing",
|
|
4634
|
+
name: "Existing HTTPS address",
|
|
4635
|
+
description: saved ? `Paste an address, or keep ${saved}` : "Paste the public address from your tunnel provider."
|
|
4636
|
+
},
|
|
4637
|
+
{
|
|
4638
|
+
value: "local",
|
|
4639
|
+
name: "Local only",
|
|
4640
|
+
description: "Start without a public address."
|
|
4641
|
+
}
|
|
4642
|
+
],
|
|
4643
|
+
theme: {
|
|
4644
|
+
prefix: " ",
|
|
4645
|
+
style: {
|
|
4646
|
+
message: (text) => styleText3("bold", text),
|
|
4647
|
+
highlight: (text) => styleText3("cyan", text),
|
|
4648
|
+
answer: (text) => styleText3("cyan", text),
|
|
4649
|
+
description: (text) => styleText3("dim", ` ${text}`),
|
|
4650
|
+
keysHelpTip: () => styleText3("dim", " \u2191/\u2193 to move \xB7 Enter to select")
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
},
|
|
4654
|
+
{ signal }
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
async function startSelectedTunnel(options, choice, origin, signal) {
|
|
4658
|
+
const option = options.find((candidate) => candidate.provider === choice);
|
|
4659
|
+
if (!option?.available) throw new Error("The selected tunnel is unavailable.");
|
|
4660
|
+
const tunnel = new ManagedTunnel(option);
|
|
4661
|
+
try {
|
|
4662
|
+
console.log(styleText3("dim", `
|
|
4663
|
+
Starting ${option.provider}\u2026
|
|
4664
|
+
`));
|
|
4665
|
+
const url = await tunnel.start(origin, signal);
|
|
4666
|
+
return { url, tunnel, remember: false };
|
|
4667
|
+
} catch (error) {
|
|
4668
|
+
await tunnel.close();
|
|
4669
|
+
if (signal.aborted) throw error;
|
|
4670
|
+
printError(error instanceof Error ? error.message : String(error));
|
|
4671
|
+
return void 0;
|
|
4672
|
+
}
|
|
4673
|
+
}
|
|
4492
4674
|
async function selectPublicConnection(origin, saved) {
|
|
4493
4675
|
const controller = new AbortController();
|
|
4494
4676
|
const cancel = () => controller.abort();
|
|
@@ -4504,60 +4686,14 @@ async function selectPublicConnection(origin, saved) {
|
|
|
4504
4686
|
}
|
|
4505
4687
|
console.log();
|
|
4506
4688
|
while (true) {
|
|
4507
|
-
const choice = await
|
|
4508
|
-
{
|
|
4509
|
-
message: "Connection method",
|
|
4510
|
-
default: saved ? "existing" : void 0,
|
|
4511
|
-
loop: false,
|
|
4512
|
-
choices: [
|
|
4513
|
-
...options.filter((option2) => option2.available).map((option2) => ({
|
|
4514
|
-
value: option2.provider,
|
|
4515
|
-
name: option2.provider === "tailscale" ? "Tailscale" : "Cloudflared",
|
|
4516
|
-
description: option2.reason
|
|
4517
|
-
})),
|
|
4518
|
-
{
|
|
4519
|
-
value: "existing",
|
|
4520
|
-
name: "Existing HTTPS address",
|
|
4521
|
-
description: saved ? `Paste an address, or keep ${saved}` : "Paste the public address from your tunnel provider."
|
|
4522
|
-
},
|
|
4523
|
-
{
|
|
4524
|
-
value: "local",
|
|
4525
|
-
name: "Local only",
|
|
4526
|
-
description: "Start without a public address."
|
|
4527
|
-
}
|
|
4528
|
-
],
|
|
4529
|
-
theme: {
|
|
4530
|
-
prefix: " ",
|
|
4531
|
-
style: {
|
|
4532
|
-
message: (text) => styleText3("bold", text),
|
|
4533
|
-
highlight: (text) => styleText3("cyan", text),
|
|
4534
|
-
answer: (text) => styleText3("cyan", text),
|
|
4535
|
-
description: (text) => styleText3("dim", ` ${text}`),
|
|
4536
|
-
keysHelpTip: () => styleText3("dim", " \u2191/\u2193 to move \xB7 Enter to select")
|
|
4537
|
-
}
|
|
4538
|
-
}
|
|
4539
|
-
},
|
|
4540
|
-
{ signal: controller.signal }
|
|
4541
|
-
);
|
|
4689
|
+
const choice = await selectConnectionMethod(options, controller.signal, saved);
|
|
4542
4690
|
if (choice === "local") return { remember: false };
|
|
4543
4691
|
if (choice === "existing") {
|
|
4544
4692
|
console.log();
|
|
4545
4693
|
return { url: await promptPublicUrl(controller.signal, saved), remember: true };
|
|
4546
4694
|
}
|
|
4547
|
-
const
|
|
4548
|
-
if (
|
|
4549
|
-
const tunnel = new ManagedTunnel(option);
|
|
4550
|
-
try {
|
|
4551
|
-
console.log(styleText3("dim", `
|
|
4552
|
-
Starting ${option.provider}\u2026
|
|
4553
|
-
`));
|
|
4554
|
-
const url = await tunnel.start(origin, controller.signal);
|
|
4555
|
-
return { url, tunnel, remember: false };
|
|
4556
|
-
} catch (error) {
|
|
4557
|
-
await tunnel.close();
|
|
4558
|
-
if (controller.signal.aborted) throw error;
|
|
4559
|
-
printError(error instanceof Error ? error.message : String(error));
|
|
4560
|
-
}
|
|
4695
|
+
const connection = await startSelectedTunnel(options, choice, origin, controller.signal);
|
|
4696
|
+
if (connection) return connection;
|
|
4561
4697
|
}
|
|
4562
4698
|
} finally {
|
|
4563
4699
|
process.removeListener("SIGINT", cancel);
|
|
@@ -4569,6 +4705,7 @@ function startTerminal(dataDir, publicUrl, tunnel) {
|
|
|
4569
4705
|
const previousRaw = input.isRaw;
|
|
4570
4706
|
let busy = false;
|
|
4571
4707
|
let closed = false;
|
|
4708
|
+
let invitation;
|
|
4572
4709
|
const close = () => {
|
|
4573
4710
|
if (closed) return;
|
|
4574
4711
|
closed = true;
|
|
@@ -4590,10 +4727,15 @@ function startTerminal(dataDir, publicUrl, tunnel) {
|
|
|
4590
4727
|
return;
|
|
4591
4728
|
}
|
|
4592
4729
|
if (text === "?" && !busy) {
|
|
4593
|
-
printShortcuts();
|
|
4730
|
+
printShortcuts(Boolean(invitation));
|
|
4731
|
+
return;
|
|
4732
|
+
}
|
|
4733
|
+
if (busy || key.ctrl || key.meta) return;
|
|
4734
|
+
if (text?.toLowerCase() === "j" && invitation) {
|
|
4735
|
+
printManualPairing(invitation);
|
|
4594
4736
|
return;
|
|
4595
4737
|
}
|
|
4596
|
-
if (text?.toLowerCase() !== "p"
|
|
4738
|
+
if (text?.toLowerCase() !== "p") return;
|
|
4597
4739
|
if (tunnel && !tunnel.running()) {
|
|
4598
4740
|
printError(
|
|
4599
4741
|
"The managed tunnel is no longer running. Restart the server to reconnect your phone."
|
|
@@ -4601,12 +4743,15 @@ function startTerminal(dataDir, publicUrl, tunnel) {
|
|
|
4601
4743
|
return;
|
|
4602
4744
|
}
|
|
4603
4745
|
busy = true;
|
|
4604
|
-
|
|
4746
|
+
invitation = void 0;
|
|
4747
|
+
void printPairing(dataDir, publicUrl).then((result) => {
|
|
4748
|
+
invitation = result;
|
|
4749
|
+
}).catch((error) => printError(error.message)).finally(() => {
|
|
4605
4750
|
busy = false;
|
|
4606
|
-
if (!closed) printShortcuts();
|
|
4751
|
+
if (!closed) printShortcuts(Boolean(invitation));
|
|
4607
4752
|
});
|
|
4608
4753
|
}
|
|
4609
|
-
|
|
4754
|
+
emitKeypressEvents2(input);
|
|
4610
4755
|
input.setRawMode(true);
|
|
4611
4756
|
input.on("keypress", pressed);
|
|
4612
4757
|
input.once("end", close);
|
|
@@ -4640,8 +4785,8 @@ var ConfigSchema = z16.object({
|
|
|
4640
4785
|
projectsDir: z16.string(),
|
|
4641
4786
|
publicUrl: z16.string().url().optional()
|
|
4642
4787
|
});
|
|
4643
|
-
|
|
4644
|
-
|
|
4788
|
+
function parseCommandArgs() {
|
|
4789
|
+
return parseArgs({
|
|
4645
4790
|
allowPositionals: true,
|
|
4646
4791
|
options: {
|
|
4647
4792
|
"data-dir": { type: "string" },
|
|
@@ -4650,9 +4795,13 @@ async function main() {
|
|
|
4650
4795
|
help: { type: "boolean" },
|
|
4651
4796
|
check: { type: "boolean" },
|
|
4652
4797
|
"timeout-ms": { type: "string" },
|
|
4653
|
-
"no-interactive": { type: "boolean" }
|
|
4798
|
+
"no-interactive": { type: "boolean" },
|
|
4799
|
+
"show-payload": { type: "boolean" }
|
|
4654
4800
|
}
|
|
4655
4801
|
});
|
|
4802
|
+
}
|
|
4803
|
+
async function main() {
|
|
4804
|
+
const { values, positionals } = parseCommandArgs();
|
|
4656
4805
|
if (values.version) {
|
|
4657
4806
|
console.log(serverVersion);
|
|
4658
4807
|
return;
|
|
@@ -4674,111 +4823,132 @@ async function main() {
|
|
|
4674
4823
|
})
|
|
4675
4824
|
)
|
|
4676
4825
|
);
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4826
|
+
const context = { values, positionals, dataDir, filename, config, command };
|
|
4827
|
+
if (command === "init") return initCommand(context);
|
|
4828
|
+
if (command === "start") return startCommand(context);
|
|
4829
|
+
if (command === "service") return serviceCommand(context);
|
|
4830
|
+
if (command === "logs") return logsCommand(context);
|
|
4831
|
+
if (command === "update") return updateCommand(context);
|
|
4832
|
+
if (command === "pair") return pairCommand(context);
|
|
4833
|
+
if (command === "maintenance") return maintenanceCommand(context);
|
|
4834
|
+
if (command === "devices" || command === "status") return devicesCommand(context);
|
|
4835
|
+
if (command === "revoke") return revokeCommand(context);
|
|
4836
|
+
throw new Error("Unknown command. Run agentrific --help.");
|
|
4837
|
+
}
|
|
4838
|
+
async function initCommand({ values, dataDir, filename, config }) {
|
|
4839
|
+
await mkdir5(dataDir, { recursive: true, mode: 448 });
|
|
4840
|
+
const publicUrl = values["public-url"] ?? config.publicUrl;
|
|
4841
|
+
if (publicUrl && new URL(publicUrl).protocol !== "https:")
|
|
4842
|
+
throw new Error("Configure an HTTPS URL from Tailscale Serve or Cloudflare Tunnel.");
|
|
4843
|
+
await writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
4844
|
+
mode: 384
|
|
4845
|
+
});
|
|
4846
|
+
console.log(`Configuration saved to ${filename}. Start with agentrific start.`);
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
4849
|
+
async function startCommand({ values, dataDir, filename, config }) {
|
|
4850
|
+
process.env.AGENTRIFIC_DATA_DIR = dataDir;
|
|
4851
|
+
process.env.AGENTRIFIC_PROJECTS_DIR ??= config.projectsDir;
|
|
4852
|
+
const interactive = Boolean(
|
|
4853
|
+
process.stdin.isTTY && process.stdout.isTTY && !values["no-interactive"]
|
|
4854
|
+
);
|
|
4855
|
+
const explicitUrl = values["public-url"] ?? process.env.AGENTRIFIC_PUBLIC_URL;
|
|
4856
|
+
if (interactive) {
|
|
4857
|
+
process.env.AGENTRIFIC_INTERACTIVE = "1";
|
|
4858
|
+
printHeading("server");
|
|
4687
4859
|
}
|
|
4688
|
-
|
|
4689
|
-
process.env.
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
url: explicitUrl ? pairingOrigin(explicitUrl) : config.publicUrl,
|
|
4704
|
-
remember: false,
|
|
4705
|
-
tunnel: void 0
|
|
4706
|
-
};
|
|
4707
|
-
const publicUrl = connection.url;
|
|
4708
|
-
try {
|
|
4709
|
-
if (connection.remember && publicUrl) {
|
|
4710
|
-
await mkdir5(dataDir, { recursive: true, mode: 448 });
|
|
4711
|
-
await writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
4712
|
-
mode: 384
|
|
4713
|
-
});
|
|
4714
|
-
}
|
|
4715
|
-
if (publicUrl) process.env.AGENTRIFIC_PUBLIC_URL = pairingOrigin(publicUrl);
|
|
4716
|
-
await init_index().then(() => index_exports);
|
|
4717
|
-
if (interactive) startTerminal(dataDir, publicUrl, connection.tunnel);
|
|
4718
|
-
} catch (error) {
|
|
4719
|
-
await connection.tunnel?.close();
|
|
4720
|
-
throw error;
|
|
4860
|
+
const connection = interactive && !explicitUrl ? await selectPublicConnection(
|
|
4861
|
+
`http://${process.env.AGENTRIFIC_HOST ?? "127.0.0.1"}:${process.env.AGENTRIFIC_PORT ?? "4318"}`,
|
|
4862
|
+
config.publicUrl
|
|
4863
|
+
) : {
|
|
4864
|
+
url: explicitUrl ? pairingOrigin(explicitUrl) : config.publicUrl,
|
|
4865
|
+
remember: false,
|
|
4866
|
+
tunnel: void 0
|
|
4867
|
+
};
|
|
4868
|
+
const publicUrl = connection.url;
|
|
4869
|
+
try {
|
|
4870
|
+
if (connection.remember && publicUrl) {
|
|
4871
|
+
await mkdir5(dataDir, { recursive: true, mode: 448 });
|
|
4872
|
+
await writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
4873
|
+
mode: 384
|
|
4874
|
+
});
|
|
4721
4875
|
}
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
"Build and install the compiled server package before registering a service."
|
|
4729
|
-
);
|
|
4730
|
-
await manageService(positionals[1] ?? "", dataDir, cli);
|
|
4731
|
-
console.log(`Service ${positionals[1]} completed.`);
|
|
4732
|
-
return;
|
|
4733
|
-
}
|
|
4734
|
-
if (command === "logs") {
|
|
4735
|
-
const child = process.platform === "linux" ? spawn2("journalctl", ["--user", "-u", "agentrific.service", "-f"], { stdio: "inherit" }) : spawn2("tail", ["-F", path10.join(dataDir, "logs", "server.log")], { stdio: "inherit" });
|
|
4736
|
-
child.on("error", (error) => {
|
|
4737
|
-
console.error(error.message);
|
|
4738
|
-
process.exitCode = 1;
|
|
4739
|
-
});
|
|
4740
|
-
child.on("exit", (code) => {
|
|
4741
|
-
process.exitCode = code ?? 0;
|
|
4742
|
-
});
|
|
4743
|
-
return;
|
|
4744
|
-
}
|
|
4745
|
-
if (command === "update") {
|
|
4746
|
-
if (!values.check)
|
|
4747
|
-
throw new Error(
|
|
4748
|
-
"Use update --check. Automatic installation is unavailable; stop the server and install a reviewed package before restarting."
|
|
4749
|
-
);
|
|
4750
|
-
console.log(JSON.stringify(await checkForUpdates(), null, 2));
|
|
4751
|
-
return;
|
|
4876
|
+
if (publicUrl) process.env.AGENTRIFIC_PUBLIC_URL = pairingOrigin(publicUrl);
|
|
4877
|
+
await init_index().then(() => index_exports);
|
|
4878
|
+
if (interactive) startTerminal(dataDir, publicUrl, connection.tunnel);
|
|
4879
|
+
} catch (error) {
|
|
4880
|
+
await connection.tunnel?.close();
|
|
4881
|
+
throw error;
|
|
4752
4882
|
}
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4885
|
+
async function serviceCommand({ positionals, dataDir }) {
|
|
4886
|
+
const cli = fileURLToPath(import.meta.url);
|
|
4887
|
+
if (!cli.endsWith(".js"))
|
|
4888
|
+
throw new Error("Build and install the compiled server package before registering a service.");
|
|
4889
|
+
await manageService(positionals[1] ?? "", dataDir, cli);
|
|
4890
|
+
console.log(`Service ${positionals[1]} completed.`);
|
|
4891
|
+
return;
|
|
4892
|
+
}
|
|
4893
|
+
async function logsCommand({ dataDir }) {
|
|
4894
|
+
const child = process.platform === "linux" ? spawn2("journalctl", ["--user", "-u", "agentrific.service", "-f"], { stdio: "inherit" }) : spawn2("tail", ["-F", path10.join(dataDir, "logs", "server.log")], { stdio: "inherit" });
|
|
4895
|
+
child.on("error", (error) => {
|
|
4896
|
+
console.error(error.message);
|
|
4897
|
+
process.exitCode = 1;
|
|
4898
|
+
});
|
|
4899
|
+
child.on("exit", (code) => {
|
|
4900
|
+
process.exitCode = code ?? 0;
|
|
4901
|
+
});
|
|
4902
|
+
return;
|
|
4903
|
+
}
|
|
4904
|
+
async function updateCommand({ values }) {
|
|
4905
|
+
if (!values.check)
|
|
4906
|
+
throw new Error(
|
|
4907
|
+
"Use update --check. Automatic installation is unavailable; stop the server and install a reviewed package before restarting."
|
|
4758
4908
|
);
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
console.log(
|
|
4774
|
-
return;
|
|
4775
|
-
}
|
|
4776
|
-
if (command === "revoke") {
|
|
4777
|
-
const id = z16.string().uuid().parse(positionals[1]);
|
|
4778
|
-
console.log(JSON.stringify(await controlRequest(dataDir, { command, id })));
|
|
4779
|
-
return;
|
|
4909
|
+
console.log(JSON.stringify(await checkForUpdates(), null, 2));
|
|
4910
|
+
return;
|
|
4911
|
+
}
|
|
4912
|
+
async function pairCommand({ values, dataDir, config }) {
|
|
4913
|
+
const status = await controlRequest(dataDir, { command: "status" });
|
|
4914
|
+
const invitation = await printPairing(
|
|
4915
|
+
dataDir,
|
|
4916
|
+
values["public-url"] ?? process.env.AGENTRIFIC_PUBLIC_URL ?? ("publicUrl" in status ? status.publicUrl : void 0) ?? config.publicUrl
|
|
4917
|
+
);
|
|
4918
|
+
if (values["show-payload"]) {
|
|
4919
|
+
printManualPairing(invitation);
|
|
4920
|
+
} else if (process.stdin.isTTY && process.stdout.isTTY && !values["no-interactive"]) {
|
|
4921
|
+
await waitForManualPairing(invitation);
|
|
4922
|
+
} else {
|
|
4923
|
+
console.log(" For manual entry, run agentrific pair --show-payload.\n");
|
|
4780
4924
|
}
|
|
4781
|
-
|
|
4925
|
+
return;
|
|
4926
|
+
}
|
|
4927
|
+
async function maintenanceCommand({ values, positionals, dataDir }) {
|
|
4928
|
+
const action = z16.enum(["enter", "resume", "drain"]).parse(positionals[1]);
|
|
4929
|
+
const result = await controlRequest(dataDir, {
|
|
4930
|
+
command: "maintenance",
|
|
4931
|
+
action,
|
|
4932
|
+
timeoutMs: z16.coerce.number().int().min(0).max(6e4).parse(values["timeout-ms"] ?? 3e4)
|
|
4933
|
+
});
|
|
4934
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4935
|
+
if (action === "drain" && "drained" in result && !result.drained) process.exitCode = 1;
|
|
4936
|
+
return;
|
|
4937
|
+
}
|
|
4938
|
+
async function devicesCommand({ dataDir, command }) {
|
|
4939
|
+
console.log(
|
|
4940
|
+
JSON.stringify(
|
|
4941
|
+
await controlRequest(dataDir, { command: z16.enum(["devices", "status"]).parse(command) }),
|
|
4942
|
+
null,
|
|
4943
|
+
2
|
|
4944
|
+
)
|
|
4945
|
+
);
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4948
|
+
async function revokeCommand({ positionals, dataDir }) {
|
|
4949
|
+
const id = z16.string().uuid().parse(positionals[1]);
|
|
4950
|
+
console.log(JSON.stringify(await controlRequest(dataDir, { command: "revoke", id })));
|
|
4951
|
+
return;
|
|
4782
4952
|
}
|
|
4783
4953
|
void main().catch((error) => {
|
|
4784
4954
|
if (["ExitPromptError", "AbortPromptError", "AbortError"].includes(error.name)) {
|