frontpage-host 0.1.7 → 0.1.8
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/AGENTS.md +6 -3
- package/dist/commands/deploy.js +9 -27
- package/dist/commands/push.js +55 -19
- package/dist/commands/runs.js +33 -19
- package/dist/commands/wait-agent-idle.d.ts +49 -0
- package/dist/commands/wait-agent-idle.js +181 -0
- package/dist/http/client.d.ts +11 -7
- package/dist/http/client.js +38 -51
- package/dist/registry.js +24 -10
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.js +32 -12
- package/llms.txt +3 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -122,7 +122,7 @@ frontpage prompt "make the hero shorter"
|
|
|
122
122
|
frontpage prompt "Use this logo in the header" --attach ./logo.png
|
|
123
123
|
frontpage prompt "Match this brand PDF" --attach ./guide.pdf --path /
|
|
124
124
|
frontpage prompt "Reuse uploaded art" --attach /__media/uploads/<slug>/<file>
|
|
125
|
-
frontpage runs wait # wait for the agent run to finish
|
|
125
|
+
frontpage runs wait # wait for the agent run to finish (failed/canceled → exit 2)
|
|
126
126
|
# Review https://<subdomain>.frontpage.host — do not publish unless asked.
|
|
127
127
|
```
|
|
128
128
|
|
|
@@ -130,6 +130,9 @@ frontpage runs wait # wait for the agent run to finish
|
|
|
130
130
|
- `--asset-url <https://…>` adds a legacy image URL. Repeatable.
|
|
131
131
|
- Caps match the media proxy: images / PDF / text-code. CLI video and >4 MB binaries are not supported yet (editor attach / Phase E).
|
|
132
132
|
- Prompt text is still required (API). Attachments ride on the same enqueue as the editor.
|
|
133
|
+
- `runs wait` with `--json`: failures use `ok: false` and include `data.run` (with `run.error` when present).
|
|
134
|
+
- `deploy wait` is fail-closed: exit 2 if the waited run (or latest, when already idle) is `failed`/`canceled`.
|
|
135
|
+
- `frontpage push --wait` waits for agent-run idle after push (same busy statuses; not a separate Vercel oracle).
|
|
133
136
|
|
|
134
137
|
Mid-task continuation is automatic. There is **no** `runs continue` in v1.
|
|
135
138
|
|
|
@@ -173,10 +176,10 @@ Global machine envelope (`--json`):
|
|
|
173
176
|
|
|
174
177
|
```json
|
|
175
178
|
{ "ok": true, "command": "status", "data": { } }
|
|
176
|
-
{ "ok": false, "command": "push", "error": { "message": "…", "code": "conflict", "httpStatus": 409, "exitCode": 3 } }
|
|
179
|
+
{ "ok": false, "command": "push", "error": { "message": "…", "code": "conflict", "httpStatus": 409, "exitCode": 3, "requestId": "…" } }
|
|
177
180
|
```
|
|
178
181
|
|
|
179
|
-
Failures with `--json` print the envelope on **stdout** (still use the process exit code).
|
|
182
|
+
Failures with `--json` print the envelope on **stdout** (still use the process exit code). Include `requestId` when filing support issues.
|
|
180
183
|
|
|
181
184
|
## Global flags
|
|
182
185
|
|
package/dist/commands/deploy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { resolveSlug } from "../site.js";
|
|
2
|
-
import {
|
|
3
|
-
import { emitErr,
|
|
4
|
-
import {
|
|
2
|
+
import { EXIT_USAGE } from "../http/client.js";
|
|
3
|
+
import { emitErr, emitUsage } from "../runtime.js";
|
|
4
|
+
import { emitWaitOutcome, waitForAgentIdle } from "./wait-agent-idle.js";
|
|
5
5
|
/** `frontpage deploy wait` — wait until no run is in deploying/queued/running. */
|
|
6
6
|
export async function runDeploy(args) {
|
|
7
7
|
try {
|
|
@@ -10,30 +10,12 @@ export async function runDeploy(args) {
|
|
|
10
10
|
return emitUsage("deploy", "Usage: frontpage deploy wait [--site <slug>]", EXIT_USAGE);
|
|
11
11
|
}
|
|
12
12
|
const { slug } = await resolveSlug(args.slice(1));
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (isJson()) {
|
|
20
|
-
emitOk("deploy", {
|
|
21
|
-
slug,
|
|
22
|
-
waited: true,
|
|
23
|
-
run: data.run,
|
|
24
|
-
status: data.run?.status ?? "idle",
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
console.log(data.run ? `${data.run.id} ${data.run.status}` : "No active deploy.");
|
|
29
|
-
}
|
|
30
|
-
return EXIT_OK;
|
|
31
|
-
}
|
|
32
|
-
const head = busy.find((r) => r.status === "deploying") ?? busy[0];
|
|
33
|
-
progress(`\r${head.status} ${head.statusMessage ?? ""} `);
|
|
34
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
35
|
-
}
|
|
36
|
-
return emitUsage("deploy", "Timed out waiting for deploy.", EXIT_USAGE);
|
|
13
|
+
const result = await waitForAgentIdle({
|
|
14
|
+
slug,
|
|
15
|
+
// Fail-closed: idle with latest failed/canceled is not success.
|
|
16
|
+
failClosedOnIdleTerminalFailure: true,
|
|
17
|
+
});
|
|
18
|
+
return emitWaitOutcome({ command: "deploy", slug, result });
|
|
37
19
|
}
|
|
38
20
|
catch (err) {
|
|
39
21
|
return emitErr("deploy", err);
|
package/dist/commands/push.js
CHANGED
|
@@ -4,6 +4,7 @@ import { resolveSlug } from "../site.js";
|
|
|
4
4
|
import { EXIT_OK, EXIT_USAGE, v1Put } from "../http/client.js";
|
|
5
5
|
import { readState, writeState } from "../state/frontpage-state.js";
|
|
6
6
|
import { emitErr, emitOk, emitUsage, isJson } from "../runtime.js";
|
|
7
|
+
import { emitWaitOutcome, waitForAgentIdle } from "./wait-agent-idle.js";
|
|
7
8
|
const TEXT_EXTS = new Set([
|
|
8
9
|
".astro",
|
|
9
10
|
".ts",
|
|
@@ -59,13 +60,22 @@ function isTextPath(repoPath) {
|
|
|
59
60
|
return true;
|
|
60
61
|
return TEXT_EXTS.has(repoPath.slice(i).toLowerCase());
|
|
61
62
|
}
|
|
63
|
+
function takeWaitFlag(rest) {
|
|
64
|
+
const i = rest.indexOf("--wait");
|
|
65
|
+
if (i === -1)
|
|
66
|
+
return false;
|
|
67
|
+
rest.splice(i, 1);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
62
70
|
export async function runPush(args) {
|
|
63
71
|
try {
|
|
64
72
|
const { slug, rest } = await resolveSlug(args);
|
|
65
|
-
const
|
|
73
|
+
const work = [...rest];
|
|
74
|
+
const wait = takeWaitFlag(work);
|
|
75
|
+
const messageIdx = work.indexOf("--message");
|
|
66
76
|
const message = messageIdx >= 0
|
|
67
|
-
?
|
|
68
|
-
:
|
|
77
|
+
? work[messageIdx + 1]
|
|
78
|
+
: work.find((a) => a.startsWith("--message="))?.slice("--message=".length);
|
|
69
79
|
const cwd = process.cwd();
|
|
70
80
|
const state = await readState(cwd);
|
|
71
81
|
if (!state.baseCommitSha) {
|
|
@@ -99,25 +109,51 @@ export async function runPush(args) {
|
|
|
99
109
|
if (data.commitSha) {
|
|
100
110
|
await writeState({ ...state, slug, baseCommitSha: data.commitSha }, cwd);
|
|
101
111
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
112
|
+
const pushPayload = {
|
|
113
|
+
slug,
|
|
114
|
+
changed: data.changed,
|
|
115
|
+
commitSha: data.commitSha,
|
|
116
|
+
baseCommitSha: data.baseCommitSha,
|
|
117
|
+
filesChanged: data.filesChanged,
|
|
118
|
+
};
|
|
119
|
+
if (!wait) {
|
|
120
|
+
if (isJson()) {
|
|
121
|
+
emitOk("push", pushPayload);
|
|
122
|
+
return EXIT_OK;
|
|
123
|
+
}
|
|
124
|
+
if (!data.changed) {
|
|
125
|
+
console.log("Nothing to commit (remote already matches).");
|
|
126
|
+
return EXIT_OK;
|
|
127
|
+
}
|
|
128
|
+
console.log(`Pushed ${data.filesChanged.length} file(s) → ${data.commitSha}`);
|
|
129
|
+
console.log("Updated .frontpage/state.json baseCommitSha");
|
|
130
|
+
console.log("Note: module markup may auto-activate modules (same as the editor agent).");
|
|
131
|
+
console.log("tokens.ts may be auto-repaired at commit if it would break the build.");
|
|
110
132
|
return EXIT_OK;
|
|
111
133
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
134
|
+
// --wait: after push, wait for agent-run busy to clear (same statuses as deploy wait).
|
|
135
|
+
// Does not invent a Vercel deploy oracle; file-only pushes with no active run return promptly.
|
|
136
|
+
if (!isJson()) {
|
|
137
|
+
if (!data.changed) {
|
|
138
|
+
console.log("Nothing to commit (remote already matches).");
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
console.log(`Pushed ${data.filesChanged.length} file(s) → ${data.commitSha}`);
|
|
142
|
+
console.log("Updated .frontpage/state.json baseCommitSha");
|
|
143
|
+
}
|
|
144
|
+
console.error("Waiting for agent run / deploy idle…");
|
|
115
145
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
const result = await waitForAgentIdle({
|
|
147
|
+
slug,
|
|
148
|
+
// Do not fail on an ancient canceled run when this push never saw busy work.
|
|
149
|
+
failClosedOnIdleTerminalFailure: false,
|
|
150
|
+
});
|
|
151
|
+
return emitWaitOutcome({
|
|
152
|
+
command: "push",
|
|
153
|
+
slug,
|
|
154
|
+
result,
|
|
155
|
+
extra: { ...pushPayload, waited: true },
|
|
156
|
+
});
|
|
121
157
|
}
|
|
122
158
|
catch (err) {
|
|
123
159
|
return emitErr("push", err);
|
package/dist/commands/runs.js
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { resolveSlug } from "../site.js";
|
|
2
2
|
import { EXIT_OK, EXIT_USAGE, v1Get, v1Post } from "../http/client.js";
|
|
3
|
-
import { emitErr, emitOk, emitUsage, isJson, progress } from "../runtime.js";
|
|
3
|
+
import { emitErr, emitFail, emitOk, emitUsage, isJson, progress } from "../runtime.js";
|
|
4
4
|
const TERMINAL = new Set(["succeeded", "failed", "canceled"]);
|
|
5
|
+
const FAIL_TERMINAL = new Set(["failed", "canceled"]);
|
|
5
6
|
function printRun(run) {
|
|
6
|
-
const msg = run.statusMessage || run.summary ||
|
|
7
|
+
const msg = run.statusMessage || run.summary || "";
|
|
7
8
|
console.log(`${run.id} ${run.status}${msg ? ` ${msg}` : ""}`);
|
|
9
|
+
if (run.error)
|
|
10
|
+
console.log(` error: ${run.error}`);
|
|
8
11
|
if (run.commitSha)
|
|
9
12
|
console.log(` commit: ${run.commitSha}`);
|
|
10
13
|
}
|
|
14
|
+
/** Finish runs wait — honest JSON (ok:false on failure) + always surface run.error. */
|
|
15
|
+
function finishWait(slug, run) {
|
|
16
|
+
const failed = FAIL_TERMINAL.has(run.status);
|
|
17
|
+
if (isJson()) {
|
|
18
|
+
if (failed) {
|
|
19
|
+
return emitFail("runs", {
|
|
20
|
+
message: run.error || run.statusMessage || `Run ${run.status}`,
|
|
21
|
+
exitCode: EXIT_USAGE,
|
|
22
|
+
data: { slug, action: "wait", run },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
emitOk("runs", { slug, action: "wait", run });
|
|
26
|
+
return EXIT_OK;
|
|
27
|
+
}
|
|
28
|
+
printRun(run);
|
|
29
|
+
return failed ? EXIT_USAGE : EXIT_OK;
|
|
30
|
+
}
|
|
11
31
|
export async function runRuns(args) {
|
|
12
32
|
try {
|
|
13
33
|
const sub = args[0];
|
|
@@ -84,17 +104,7 @@ async function waitRun(args) {
|
|
|
84
104
|
progress(`\r${data.run.status} ${data.run.statusMessage ?? ""} `);
|
|
85
105
|
if (TERMINAL.has(data.run.status)) {
|
|
86
106
|
progress("\n");
|
|
87
|
-
|
|
88
|
-
emitOk("runs", { slug, action: "wait", run: data.run });
|
|
89
|
-
}
|
|
90
|
-
else {
|
|
91
|
-
printRun(data.run);
|
|
92
|
-
}
|
|
93
|
-
if (data.run.status === "failed")
|
|
94
|
-
return EXIT_USAGE;
|
|
95
|
-
if (data.run.status === "canceled")
|
|
96
|
-
return EXIT_USAGE;
|
|
97
|
-
return EXIT_OK;
|
|
107
|
+
return finishWait(slug, data.run);
|
|
98
108
|
}
|
|
99
109
|
}
|
|
100
110
|
else {
|
|
@@ -107,13 +117,17 @@ async function waitRun(args) {
|
|
|
107
117
|
progress(`\r${current.status} ${current.statusMessage ?? ""} `);
|
|
108
118
|
if (TERMINAL.has(current.status) && !data.poll?.active) {
|
|
109
119
|
progress("\n");
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
120
|
+
// Refresh full run for error field when list view is thin.
|
|
121
|
+
if (!current.error) {
|
|
122
|
+
try {
|
|
123
|
+
const full = await v1Get(`/api/v1/sites/${encodeURIComponent(slug)}/agent-runs/${encodeURIComponent(current.id)}`);
|
|
124
|
+
return finishWait(slug, full.run);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return finishWait(slug, current);
|
|
128
|
+
}
|
|
115
129
|
}
|
|
116
|
-
return current
|
|
130
|
+
return finishWait(slug, current);
|
|
117
131
|
}
|
|
118
132
|
}
|
|
119
133
|
else {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type WaitRunSnapshot = {
|
|
2
|
+
id: string;
|
|
3
|
+
status: string;
|
|
4
|
+
statusMessage?: string | null;
|
|
5
|
+
summary?: string | null;
|
|
6
|
+
error?: string | null;
|
|
7
|
+
commitSha?: string | null;
|
|
8
|
+
};
|
|
9
|
+
export type WaitIdleResult = {
|
|
10
|
+
exitCode: number;
|
|
11
|
+
/** Run that determined the outcome (watched busy run, or latest when idle). */
|
|
12
|
+
run: WaitRunSnapshot | null;
|
|
13
|
+
/** True when we observed queued/running/deploying during this wait. */
|
|
14
|
+
observedBusy: boolean;
|
|
15
|
+
status: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function isBusyStatus(status: string): boolean;
|
|
18
|
+
export declare function isFailTerminal(status: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Decide exit after idle. Pure — unit-tested.
|
|
21
|
+
* - If we watched a busy run end: succeeded → 0; failed/canceled → 2.
|
|
22
|
+
* - If never busy: optional fail-closed on latest terminal failure (deploy wait);
|
|
23
|
+
* otherwise idle/succeeded → 0 (push --wait after a file-only push).
|
|
24
|
+
*/
|
|
25
|
+
export declare function decideIdleExit(args: {
|
|
26
|
+
watched: WaitRunSnapshot | null;
|
|
27
|
+
latest: WaitRunSnapshot | null;
|
|
28
|
+
observedBusy: boolean;
|
|
29
|
+
failClosedOnIdleTerminalFailure: boolean;
|
|
30
|
+
}): {
|
|
31
|
+
exitCode: number;
|
|
32
|
+
run: WaitRunSnapshot | null;
|
|
33
|
+
status: string;
|
|
34
|
+
};
|
|
35
|
+
export declare function waitForAgentIdle(args: {
|
|
36
|
+
slug: string;
|
|
37
|
+
/** When true, idle + latest failed/canceled → non-zero (deploy wait). */
|
|
38
|
+
failClosedOnIdleTerminalFailure: boolean;
|
|
39
|
+
maxMs?: number;
|
|
40
|
+
pollMs?: number;
|
|
41
|
+
}): Promise<WaitIdleResult>;
|
|
42
|
+
/** Emit deploy/push-wait JSON or text from a WaitIdleResult. */
|
|
43
|
+
export declare function emitWaitOutcome(args: {
|
|
44
|
+
command: "deploy" | "push";
|
|
45
|
+
slug: string;
|
|
46
|
+
result: WaitIdleResult;
|
|
47
|
+
/** Extra fields merged into JSON data (e.g. push commitSha). */
|
|
48
|
+
extra?: Record<string, unknown>;
|
|
49
|
+
}): number;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §CLI Phase D — shared wait for agent-run busy states (queued/running/deploying).
|
|
3
|
+
* Used by `deploy wait` and `push --wait`. Not a Vercel deploy oracle.
|
|
4
|
+
*/
|
|
5
|
+
import { EXIT_OK, EXIT_USAGE, v1Get } from "../http/client.js";
|
|
6
|
+
import { emitFail, emitOk, isJson, progress } from "../runtime.js";
|
|
7
|
+
const BUSY = new Set(["queued", "running", "deploying"]);
|
|
8
|
+
const FAIL_TERMINAL = new Set(["failed", "canceled"]);
|
|
9
|
+
export function isBusyStatus(status) {
|
|
10
|
+
return BUSY.has(status);
|
|
11
|
+
}
|
|
12
|
+
export function isFailTerminal(status) {
|
|
13
|
+
return FAIL_TERMINAL.has(status);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Decide exit after idle. Pure — unit-tested.
|
|
17
|
+
* - If we watched a busy run end: succeeded → 0; failed/canceled → 2.
|
|
18
|
+
* - If never busy: optional fail-closed on latest terminal failure (deploy wait);
|
|
19
|
+
* otherwise idle/succeeded → 0 (push --wait after a file-only push).
|
|
20
|
+
*/
|
|
21
|
+
export function decideIdleExit(args) {
|
|
22
|
+
if (args.observedBusy && args.watched) {
|
|
23
|
+
const status = args.watched.status;
|
|
24
|
+
// Stale snapshot still busy → not success (caller should keep polling).
|
|
25
|
+
if (isBusyStatus(status)) {
|
|
26
|
+
return { exitCode: EXIT_USAGE, run: args.watched, status: "still_busy" };
|
|
27
|
+
}
|
|
28
|
+
if (isFailTerminal(status)) {
|
|
29
|
+
return { exitCode: EXIT_USAGE, run: args.watched, status };
|
|
30
|
+
}
|
|
31
|
+
return { exitCode: EXIT_OK, run: args.watched, status: status || "succeeded" };
|
|
32
|
+
}
|
|
33
|
+
const latest = args.latest;
|
|
34
|
+
if (args.failClosedOnIdleTerminalFailure &&
|
|
35
|
+
latest &&
|
|
36
|
+
isFailTerminal(latest.status)) {
|
|
37
|
+
return { exitCode: EXIT_USAGE, run: latest, status: latest.status };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
exitCode: EXIT_OK,
|
|
41
|
+
run: latest,
|
|
42
|
+
status: latest?.status ?? "idle",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export async function waitForAgentIdle(args) {
|
|
46
|
+
const maxMs = args.maxMs ?? 30 * 60 * 1000;
|
|
47
|
+
const pollMs = args.pollMs ?? 2000;
|
|
48
|
+
const started = Date.now();
|
|
49
|
+
let observedBusy = false;
|
|
50
|
+
let watched = null;
|
|
51
|
+
while (Date.now() - started < maxMs) {
|
|
52
|
+
const data = await v1Get(`/api/v1/sites/${encodeURIComponent(args.slug)}/agent-runs`);
|
|
53
|
+
const busy = (data.active ?? []).filter((r) => isBusyStatus(r.status));
|
|
54
|
+
if (busy.length > 0) {
|
|
55
|
+
observedBusy = true;
|
|
56
|
+
// If we were watching a run that left the active set, resolve it before
|
|
57
|
+
// switching — otherwise a failed run can be skipped when the next starts.
|
|
58
|
+
if (watched && !busy.some((r) => r.id === watched.id)) {
|
|
59
|
+
try {
|
|
60
|
+
const detail = await v1Get(`/api/v1/sites/${encodeURIComponent(args.slug)}/agent-runs/${encodeURIComponent(watched.id)}`);
|
|
61
|
+
if (detail.run && isFailTerminal(detail.run.status)) {
|
|
62
|
+
progress("\n");
|
|
63
|
+
return {
|
|
64
|
+
exitCode: EXIT_USAGE,
|
|
65
|
+
run: detail.run,
|
|
66
|
+
observedBusy: true,
|
|
67
|
+
status: detail.run.status,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Fall through and follow the new busy head.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const prevId = watched?.id;
|
|
76
|
+
const still = prevId
|
|
77
|
+
? busy.find((r) => r.id === prevId)
|
|
78
|
+
: undefined;
|
|
79
|
+
const next = still ?? busy.find((r) => r.status === "deploying") ?? busy[0];
|
|
80
|
+
watched = next;
|
|
81
|
+
progress(`\r${next.status} ${next.statusMessage ?? ""} `);
|
|
82
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
// Idle — if we were watching a busy run, refresh it for terminal fields (error).
|
|
86
|
+
if (observedBusy && watched) {
|
|
87
|
+
const watching = watched;
|
|
88
|
+
let refreshed = watching;
|
|
89
|
+
try {
|
|
90
|
+
const detail = await v1Get(`/api/v1/sites/${encodeURIComponent(args.slug)}/agent-runs/${encodeURIComponent(watching.id)}`);
|
|
91
|
+
if (detail.run)
|
|
92
|
+
refreshed = detail.run;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Fall back to list `run` only when it is the same id.
|
|
96
|
+
if (data.run?.id === watching.id)
|
|
97
|
+
refreshed = data.run;
|
|
98
|
+
}
|
|
99
|
+
watched = refreshed;
|
|
100
|
+
// Active list cleared but our snapshot is still busy → keep polling.
|
|
101
|
+
if (isBusyStatus(refreshed.status)) {
|
|
102
|
+
progress(`\r${refreshed.status} ${refreshed.statusMessage ?? ""} `);
|
|
103
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
progress("\n");
|
|
108
|
+
const decided = decideIdleExit({
|
|
109
|
+
watched,
|
|
110
|
+
latest: data.run,
|
|
111
|
+
observedBusy,
|
|
112
|
+
failClosedOnIdleTerminalFailure: args.failClosedOnIdleTerminalFailure,
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
exitCode: decided.exitCode,
|
|
116
|
+
run: decided.run,
|
|
117
|
+
observedBusy,
|
|
118
|
+
status: decided.status,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
progress("\n");
|
|
122
|
+
return {
|
|
123
|
+
exitCode: EXIT_USAGE,
|
|
124
|
+
run: watched,
|
|
125
|
+
observedBusy,
|
|
126
|
+
status: "timed_out",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Emit deploy/push-wait JSON or text from a WaitIdleResult. */
|
|
130
|
+
export function emitWaitOutcome(args) {
|
|
131
|
+
const { result } = args;
|
|
132
|
+
const message = result.status === "timed_out"
|
|
133
|
+
? "Timed out waiting for deploy."
|
|
134
|
+
: result.status === "still_busy"
|
|
135
|
+
? "Timed out waiting for deploy (run still busy)."
|
|
136
|
+
: result.run?.error ||
|
|
137
|
+
result.run?.statusMessage ||
|
|
138
|
+
(isFailTerminal(result.status)
|
|
139
|
+
? `Latest run ${result.status}`
|
|
140
|
+
: result.status);
|
|
141
|
+
if (result.exitCode !== EXIT_OK) {
|
|
142
|
+
if (isJson()) {
|
|
143
|
+
return emitFail(args.command, {
|
|
144
|
+
message,
|
|
145
|
+
exitCode: result.exitCode,
|
|
146
|
+
data: {
|
|
147
|
+
slug: args.slug,
|
|
148
|
+
waited: true,
|
|
149
|
+
observedBusy: result.observedBusy,
|
|
150
|
+
status: result.status,
|
|
151
|
+
run: result.run,
|
|
152
|
+
...args.extra,
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
if (result.run) {
|
|
157
|
+
console.error(`${result.run.id} ${result.run.status}${result.run.error ? ` ${result.run.error}` : result.run.statusMessage ? ` ${result.run.statusMessage}` : ""}`);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
console.error(message);
|
|
161
|
+
}
|
|
162
|
+
return result.exitCode;
|
|
163
|
+
}
|
|
164
|
+
if (isJson()) {
|
|
165
|
+
emitOk(args.command, {
|
|
166
|
+
slug: args.slug,
|
|
167
|
+
waited: true,
|
|
168
|
+
observedBusy: result.observedBusy,
|
|
169
|
+
status: result.status,
|
|
170
|
+
run: result.run,
|
|
171
|
+
...args.extra,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
else if (result.run) {
|
|
175
|
+
console.log(`${result.run.id} ${result.run.status}${result.run.statusMessage ? ` ${result.run.statusMessage}` : ""}`);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log("No active deploy.");
|
|
179
|
+
}
|
|
180
|
+
return EXIT_OK;
|
|
181
|
+
}
|
package/dist/http/client.d.ts
CHANGED
|
@@ -3,13 +3,6 @@ export declare const EXIT_USAGE = 2;
|
|
|
3
3
|
export declare const EXIT_CONFLICT = 3;
|
|
4
4
|
export declare const EXIT_AUTH = 4;
|
|
5
5
|
export declare const EXIT_UNEXPECTED = 5;
|
|
6
|
-
export declare class CliHttpError extends Error {
|
|
7
|
-
readonly exitCode: number;
|
|
8
|
-
readonly status: number;
|
|
9
|
-
readonly code?: string;
|
|
10
|
-
constructor(message: string, exitCode: number, status: number, code?: string);
|
|
11
|
-
}
|
|
12
|
-
export declare function apiBase(): string;
|
|
13
6
|
export type V1Envelope = {
|
|
14
7
|
error?: {
|
|
15
8
|
code?: string;
|
|
@@ -17,6 +10,17 @@ export type V1Envelope = {
|
|
|
17
10
|
requestId?: string;
|
|
18
11
|
};
|
|
19
12
|
};
|
|
13
|
+
export declare class CliHttpError extends Error {
|
|
14
|
+
readonly exitCode: number;
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly code?: string;
|
|
17
|
+
/** v1 envelope requestId when the API returned one (§CLI Phase D). */
|
|
18
|
+
readonly requestId?: string;
|
|
19
|
+
constructor(message: string, exitCode: number, status: number, code?: string, requestId?: string);
|
|
20
|
+
}
|
|
21
|
+
/** Map a non-OK v1 response into CliHttpError (shared by JSON + multipart clients). */
|
|
22
|
+
export declare function httpErrorFromV1(status: number, parsed: V1Envelope, fallbackMessage?: string): CliHttpError;
|
|
23
|
+
export declare function apiBase(): string;
|
|
20
24
|
export declare function v1Get<T>(path: string): Promise<T>;
|
|
21
25
|
export declare function v1Post<T>(path: string, body?: unknown): Promise<T>;
|
|
22
26
|
export declare function v1Put<T>(path: string, body?: unknown): Promise<T>;
|
package/dist/http/client.js
CHANGED
|
@@ -12,14 +12,49 @@ export class CliHttpError extends Error {
|
|
|
12
12
|
exitCode;
|
|
13
13
|
status;
|
|
14
14
|
code;
|
|
15
|
-
|
|
15
|
+
/** v1 envelope requestId when the API returned one (§CLI Phase D). */
|
|
16
|
+
requestId;
|
|
17
|
+
constructor(message, exitCode, status, code, requestId) {
|
|
16
18
|
super(message);
|
|
17
19
|
this.name = "CliHttpError";
|
|
18
20
|
this.exitCode = exitCode;
|
|
19
21
|
this.status = status;
|
|
20
22
|
this.code = code;
|
|
23
|
+
this.requestId = requestId;
|
|
21
24
|
}
|
|
22
25
|
}
|
|
26
|
+
/** Map a non-OK v1 response into CliHttpError (shared by JSON + multipart clients). */
|
|
27
|
+
export function httpErrorFromV1(status, parsed, fallbackMessage) {
|
|
28
|
+
const message = parsed.error?.message ?? fallbackMessage ?? `Request failed (${status})`;
|
|
29
|
+
const code = parsed.error?.code;
|
|
30
|
+
const requestId = parsed.error?.requestId;
|
|
31
|
+
if (status === 401 || status === 403) {
|
|
32
|
+
return new CliHttpError(message, EXIT_AUTH, status, code, requestId);
|
|
33
|
+
}
|
|
34
|
+
if (status === 400 || code === "bad_request") {
|
|
35
|
+
return new CliHttpError(message, EXIT_USAGE, status, code, requestId);
|
|
36
|
+
}
|
|
37
|
+
if (status === 402 || status === 429 || code === "rate_limited" || code === "payment_required") {
|
|
38
|
+
return new CliHttpError(message, EXIT_USAGE, status, code, requestId);
|
|
39
|
+
}
|
|
40
|
+
if (status === 409 || code === "conflict") {
|
|
41
|
+
return new CliHttpError(message, EXIT_CONFLICT, status, code, requestId);
|
|
42
|
+
}
|
|
43
|
+
if (status === 413 || code === "payload_too_large") {
|
|
44
|
+
return new CliHttpError(message, EXIT_USAGE, status, code, requestId);
|
|
45
|
+
}
|
|
46
|
+
if (status === 503 || code === "unavailable") {
|
|
47
|
+
return new CliHttpError(message, EXIT_USAGE, status, code, requestId);
|
|
48
|
+
}
|
|
49
|
+
if (status === 404) {
|
|
50
|
+
const lower = message.toLowerCase();
|
|
51
|
+
if (lower === "not found." || lower === "not found") {
|
|
52
|
+
return new CliHttpError("API not available (is PUBLIC_CLI_ENABLED on for this environment?).", EXIT_UNEXPECTED, status, code, requestId);
|
|
53
|
+
}
|
|
54
|
+
return new CliHttpError(message, EXIT_AUTH, status, code, requestId);
|
|
55
|
+
}
|
|
56
|
+
return new CliHttpError(message, EXIT_UNEXPECTED, status, code, requestId);
|
|
57
|
+
}
|
|
23
58
|
export function apiBase() {
|
|
24
59
|
const fromEnv = process.env.FRONTPAGE_API_BASE?.replace(/\/$/, "");
|
|
25
60
|
if (fromEnv)
|
|
@@ -58,31 +93,7 @@ async function v1Request(method, path, body) {
|
|
|
58
93
|
}
|
|
59
94
|
if (res.ok)
|
|
60
95
|
return parsed;
|
|
61
|
-
|
|
62
|
-
const code = parsed.error?.code;
|
|
63
|
-
if (res.status === 401 || res.status === 403) {
|
|
64
|
-
throw new CliHttpError(message, EXIT_AUTH, res.status, code);
|
|
65
|
-
}
|
|
66
|
-
if (res.status === 402 || res.status === 429 || code === "rate_limited" || code === "payment_required") {
|
|
67
|
-
throw new CliHttpError(message, EXIT_USAGE, res.status, code);
|
|
68
|
-
}
|
|
69
|
-
if (res.status === 409 || code === "conflict") {
|
|
70
|
-
throw new CliHttpError(message, EXIT_CONFLICT, res.status, code);
|
|
71
|
-
}
|
|
72
|
-
if (res.status === 413 || code === "payload_too_large") {
|
|
73
|
-
throw new CliHttpError(message, EXIT_USAGE, res.status, code);
|
|
74
|
-
}
|
|
75
|
-
if (res.status === 503 || code === "unavailable") {
|
|
76
|
-
throw new CliHttpError(message, EXIT_USAGE, res.status, code);
|
|
77
|
-
}
|
|
78
|
-
if (res.status === 404) {
|
|
79
|
-
const lower = message.toLowerCase();
|
|
80
|
-
if (lower === "not found." || lower === "not found") {
|
|
81
|
-
throw new CliHttpError("API not available (is PUBLIC_CLI_ENABLED on for this environment?).", EXIT_UNEXPECTED, res.status, code);
|
|
82
|
-
}
|
|
83
|
-
throw new CliHttpError(message, EXIT_AUTH, res.status, code);
|
|
84
|
-
}
|
|
85
|
-
throw new CliHttpError(message, EXIT_UNEXPECTED, res.status, code);
|
|
96
|
+
throw httpErrorFromV1(res.status, parsed);
|
|
86
97
|
}
|
|
87
98
|
export async function v1Get(path) {
|
|
88
99
|
return v1Request("GET", path);
|
|
@@ -130,31 +141,7 @@ export async function v1PostMultipart(path, form) {
|
|
|
130
141
|
}
|
|
131
142
|
if (res.ok)
|
|
132
143
|
return parsed;
|
|
133
|
-
|
|
134
|
-
const code = parsed.error?.code;
|
|
135
|
-
if (res.status === 401 || res.status === 403) {
|
|
136
|
-
throw new CliHttpError(message, EXIT_AUTH, res.status, code);
|
|
137
|
-
}
|
|
138
|
-
if (res.status === 402 || res.status === 429 || code === "rate_limited" || code === "payment_required") {
|
|
139
|
-
throw new CliHttpError(message, EXIT_USAGE, res.status, code);
|
|
140
|
-
}
|
|
141
|
-
if (res.status === 409 || code === "conflict") {
|
|
142
|
-
throw new CliHttpError(message, EXIT_CONFLICT, res.status, code);
|
|
143
|
-
}
|
|
144
|
-
if (res.status === 413 || code === "payload_too_large") {
|
|
145
|
-
throw new CliHttpError(message, EXIT_USAGE, res.status, code);
|
|
146
|
-
}
|
|
147
|
-
if (res.status === 503 || code === "unavailable") {
|
|
148
|
-
throw new CliHttpError(message, EXIT_USAGE, res.status, code);
|
|
149
|
-
}
|
|
150
|
-
if (res.status === 404) {
|
|
151
|
-
const lower = message.toLowerCase();
|
|
152
|
-
if (lower === "not found." || lower === "not found") {
|
|
153
|
-
throw new CliHttpError("API not available (is PUBLIC_CLI_ENABLED on for this environment?).", EXIT_UNEXPECTED, res.status, code);
|
|
154
|
-
}
|
|
155
|
-
throw new CliHttpError(message, EXIT_AUTH, res.status, code);
|
|
156
|
-
}
|
|
157
|
-
throw new CliHttpError(message, EXIT_UNEXPECTED, res.status, code);
|
|
144
|
+
throw httpErrorFromV1(res.status, parsed);
|
|
158
145
|
}
|
|
159
146
|
export function exitCodeForError(err) {
|
|
160
147
|
if (err instanceof CliHttpError)
|
package/dist/registry.js
CHANGED
|
@@ -88,10 +88,17 @@ export const COMMANDS = [
|
|
|
88
88
|
{
|
|
89
89
|
name: "push",
|
|
90
90
|
summary: "Upload local allowlisted files (CAS + active-run guard)",
|
|
91
|
-
usage: "frontpage push [--site <slug>] [--message …] [--json]",
|
|
91
|
+
usage: "frontpage push [--site <slug>] [--message …] [--wait] [--json]",
|
|
92
92
|
topics: ["sync"],
|
|
93
|
-
examples: [
|
|
94
|
-
|
|
93
|
+
examples: [
|
|
94
|
+
'frontpage push --message "Update hero copy"',
|
|
95
|
+
'frontpage push --message "…" --wait',
|
|
96
|
+
],
|
|
97
|
+
flags: [
|
|
98
|
+
"--site <slug>",
|
|
99
|
+
"--message <text>",
|
|
100
|
+
"--wait: after push, wait until no agent run is queued/running/deploying (same as deploy wait; not a Vercel oracle)",
|
|
101
|
+
],
|
|
95
102
|
dangers: [
|
|
96
103
|
"Requires prior pull (baseCommitSha)",
|
|
97
104
|
"Refused while an agent run is active (exit 3)",
|
|
@@ -215,7 +222,10 @@ export const COMMANDS = [
|
|
|
215
222
|
"frontpage runs retry <runId>",
|
|
216
223
|
"frontpage runs get <runId>",
|
|
217
224
|
],
|
|
218
|
-
flags: [
|
|
225
|
+
flags: [
|
|
226
|
+
"There is no runs continue — mid-task continuation is automatic",
|
|
227
|
+
"runs wait: failed/canceled → exit 2; --json uses ok:false and includes run.error",
|
|
228
|
+
],
|
|
219
229
|
seeAlso: ["prompt", "deploy"],
|
|
220
230
|
},
|
|
221
231
|
{
|
|
@@ -248,6 +258,7 @@ export const COMMANDS = [
|
|
|
248
258
|
flags: [
|
|
249
259
|
"Optional build wait only — staging is https://<subdomain>.frontpage.host",
|
|
250
260
|
"Does NOT promote to production; use publish only when the user asks",
|
|
261
|
+
"Fail-closed: exit 2 when the waited run (or latest, if already idle) is failed/canceled",
|
|
251
262
|
],
|
|
252
263
|
},
|
|
253
264
|
{
|
|
@@ -422,7 +433,7 @@ export const TOPICS = {
|
|
|
422
433
|
frontpage status # tipDrift when remote moved since last pull
|
|
423
434
|
frontpage pull # downloads src/, public/, content/ into cwd
|
|
424
435
|
frontpage diff # local vs remote via git blobSha (--json → dirty + tipDrift)
|
|
425
|
-
frontpage push [--message …] # uploads allowlisted files; CAS + active-run guard
|
|
436
|
+
frontpage push [--message …] [--wait] # uploads allowlisted files; CAS + active-run guard
|
|
426
437
|
frontpage ls [prefix]
|
|
427
438
|
frontpage cat <path>
|
|
428
439
|
frontpage search <pattern> [path] [--regex] [--case-sensitive] [--whole-word] [--files-only]
|
|
@@ -431,6 +442,7 @@ export const TOPICS = {
|
|
|
431
442
|
Local state: .frontpage/state.json { slug, baseCommitSha }
|
|
432
443
|
Pull BEFORE EDITING when tip may have moved (UI agent / another checkout) — check tipDrift.
|
|
433
444
|
Pull before push so CAS has baseCommitSha. Do not push while the editor agent is building.
|
|
445
|
+
--wait after push blocks until no agent run is queued/running/deploying (same as deploy wait).
|
|
434
446
|
The UI/canvas agent does not use this CLI; only CLI checkouts need pull.
|
|
435
447
|
Pushing module markup may auto-activate modules (same as the agent).
|
|
436
448
|
tokens.ts may be auto-repaired at commit if it would break the build.
|
|
@@ -482,10 +494,10 @@ export const TOPICS = {
|
|
|
482
494
|
frontpage prompt "Use this logo" --attach ./logo.png
|
|
483
495
|
frontpage prompt "…" --attach /__media/uploads/<slug>/… --asset-url https://…
|
|
484
496
|
frontpage runs # latest + active
|
|
485
|
-
frontpage runs wait [runId]
|
|
497
|
+
frontpage runs wait [runId] # failed/canceled → exit 2; JSON ok:false + run.error
|
|
486
498
|
frontpage runs cancel <runId>
|
|
487
499
|
frontpage runs retry <runId>
|
|
488
|
-
frontpage deploy wait # optional
|
|
500
|
+
frontpage deploy wait # optional build wait; fail-closed on failed/canceled; NOT publish
|
|
489
501
|
frontpage publish # ONLY if the user asked — production promote
|
|
490
502
|
frontpage unpublish --yes # admin only; never unless asked; --yes when non-TTY
|
|
491
503
|
frontpage usage
|
|
@@ -497,8 +509,9 @@ export const TOPICS = {
|
|
|
497
509
|
frontpage extensions
|
|
498
510
|
|
|
499
511
|
--attach uploads local files via media (images/PDF/text) or accepts /__media/… / https URLs.
|
|
512
|
+
API errors include requestId in --json error envelopes (and stderr when not --json).
|
|
500
513
|
Mid-task continuation is automatic — there is no runs continue.
|
|
501
|
-
Do not
|
|
514
|
+
Do not file-sync while an agent run is active (see help sync for push --wait).
|
|
502
515
|
Default after work: review https://<subdomain>.frontpage.host; do not publish.`,
|
|
503
516
|
content: `Content
|
|
504
517
|
frontpage media list|upload <file>
|
|
@@ -547,8 +560,9 @@ export const TOPICS = {
|
|
|
547
560
|
|
|
548
561
|
Global --json envelope:
|
|
549
562
|
{ "ok": true, "command": "…", "data": {…} }
|
|
550
|
-
{ "ok": false, "command": "…", "error": { "message", "code?", "httpStatus?", "exitCode" } }
|
|
551
|
-
Failures with --json still use the process exit code; envelope is on stdout
|
|
563
|
+
{ "ok": false, "command": "…", "error": { "message", "code?", "httpStatus?", "exitCode", "requestId?" }, "data"? }
|
|
564
|
+
Failures with --json still use the process exit code; envelope is on stdout.
|
|
565
|
+
Prefer requestId from error envelopes when filing support issues.`,
|
|
552
566
|
};
|
|
553
567
|
export function findCommand(name) {
|
|
554
568
|
return COMMANDS.find((c) => c.name === name);
|
package/dist/runtime.d.ts
CHANGED
|
@@ -13,8 +13,18 @@ export type JsonErrorBody = {
|
|
|
13
13
|
code?: string;
|
|
14
14
|
httpStatus?: number;
|
|
15
15
|
exitCode: number;
|
|
16
|
+
requestId?: string;
|
|
16
17
|
};
|
|
17
18
|
export declare function emitOk(command: string, data: unknown): void;
|
|
19
|
+
/** Structured failure with optional data payload (e.g. failed run wait). */
|
|
20
|
+
export declare function emitFail(command: string, args: {
|
|
21
|
+
message: string;
|
|
22
|
+
exitCode: number;
|
|
23
|
+
code?: string;
|
|
24
|
+
requestId?: string;
|
|
25
|
+
httpStatus?: number;
|
|
26
|
+
data?: unknown;
|
|
27
|
+
}): number;
|
|
18
28
|
export declare function emitErr(command: string, err: unknown): number;
|
|
19
29
|
export declare function emitUsage(command: string, message: string, exitCode?: number): number;
|
|
20
30
|
/** Progress / spinner lines — suppressed when --quiet or --json. */
|
package/dist/runtime.js
CHANGED
|
@@ -31,23 +31,43 @@ export function requireYesOrTty(action) {
|
|
|
31
31
|
export function emitOk(command, data) {
|
|
32
32
|
console.log(JSON.stringify({ ok: true, command, data }));
|
|
33
33
|
}
|
|
34
|
+
/** Structured failure with optional data payload (e.g. failed run wait). */
|
|
35
|
+
export function emitFail(command, args) {
|
|
36
|
+
const body = {
|
|
37
|
+
message: args.message,
|
|
38
|
+
exitCode: args.exitCode,
|
|
39
|
+
};
|
|
40
|
+
if (args.code)
|
|
41
|
+
body.code = args.code;
|
|
42
|
+
if (args.httpStatus)
|
|
43
|
+
body.httpStatus = args.httpStatus;
|
|
44
|
+
if (args.requestId)
|
|
45
|
+
body.requestId = args.requestId;
|
|
46
|
+
if (jsonMode) {
|
|
47
|
+
const out = { ok: false, command, error: body };
|
|
48
|
+
if (args.data !== undefined)
|
|
49
|
+
out.data = args.data;
|
|
50
|
+
console.log(JSON.stringify(out));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const suffix = args.requestId ? ` (requestId: ${args.requestId})` : "";
|
|
54
|
+
console.error(`${args.message}${suffix}`);
|
|
55
|
+
}
|
|
56
|
+
return args.exitCode;
|
|
57
|
+
}
|
|
34
58
|
export function emitErr(command, err) {
|
|
35
59
|
const exitCode = exitCodeForError(err);
|
|
36
60
|
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
-
const body = { message, exitCode };
|
|
38
61
|
if (err instanceof CliHttpError) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
return emitFail(command, {
|
|
63
|
+
message,
|
|
64
|
+
exitCode,
|
|
65
|
+
code: err.code,
|
|
66
|
+
httpStatus: err.status || undefined,
|
|
67
|
+
requestId: err.requestId,
|
|
68
|
+
});
|
|
43
69
|
}
|
|
44
|
-
|
|
45
|
-
console.log(JSON.stringify({ ok: false, command, error: body }));
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
console.error(message);
|
|
49
|
-
}
|
|
50
|
-
return exitCode;
|
|
70
|
+
return emitFail(command, { message, exitCode });
|
|
51
71
|
}
|
|
52
72
|
export function emitUsage(command, message, exitCode = 2) {
|
|
53
73
|
if (jsonMode) {
|
package/llms.txt
CHANGED
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
- Modules setup: frontpage modules request-setup <key> --reason "…" (owner status bar; CLI cannot show it)
|
|
12
12
|
- Analytics ops: frontpage analytics summary|query|funnel|timeseries|compare|sql (bare analytics = dashboard)
|
|
13
13
|
- Prompt attachments: frontpage prompt "…" --attach <file|/__media/…|https://…> [--asset-url https://…] (media upload for locals; images/PDF/text)
|
|
14
|
+
- Errors: --json error envelopes include requestId; runs wait failed/canceled → ok:false + run.error (exit 2); deploy wait fail-closed; push --wait
|
|
14
15
|
- Auth: mint fp_live_ key in Frontpage UI (Developers); frontpage login KEY or FRONTPAGE_API_KEY
|
|
15
16
|
- Site id: subdomain only (frontpage use <subdomain>). Not custom domains.
|
|
16
17
|
- Staging: https://<subdomain>.frontpage.host — push/prompt/edit update staging; no separate staging deploy step for agents
|
|
17
18
|
- Production: custom domain after frontpage publish — NEVER publish/unpublish unless the user explicitly asks
|
|
18
|
-
- Sync: allowlist src/ public/ content/; platform overlay + secret-like paths rejected; pull before edit if tip may have moved (status/diff tipDrift); pull → edit → diff (blobSha) → push (CAS); no push during active agent run; UI agent does not use CLI pull; review on subdomain
|
|
19
|
+
- Sync: allowlist src/ public/ content/; platform overlay + secret-like paths rejected; pull before edit if tip may have moved (status/diff tipDrift); pull → edit → diff (blobSha) → push [--wait] (CAS); no push during active agent run; UI agent does not use CLI pull; review on subdomain
|
|
19
20
|
- Machine: global --json / --quiet; exit 0/2/3/4/5 (see help exit-codes)
|
|
20
|
-
- deploy wait = optional build wait only; it is NOT go-live / publish
|
|
21
|
+
- deploy wait = optional build wait only (fail-closed on failed/canceled); it is NOT go-live / publish
|
|
21
22
|
- No runs continue, domains, billing, members, MCP, or git remotes in v1
|