frizz 0.13.13 → 0.13.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -13
- package/dist/frizz.js +470 -75
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ $ npx frizz
|
|
|
43
43
|
A browser tab opens on the dashboard at `http://127.0.0.1:9393/`. Add a **project** there — any folder: a repo, a jj checkout, or a folder of scripts. Frizz has no opinion about version control and does not require Git. Each project gets its own board at `/project/<name>`, and from then on running `npx frizz` inside that folder opens its board. Frizz always listens on port 9393 (19393 if something else holds it), and one server serves every project on the machine, so a second run joins the server already running rather than starting another. Runs on macOS, Linux, and Windows.
|
|
44
44
|
|
|
45
45
|
<p align="center">
|
|
46
|
-
<img src="assets/board.png" alt="The Frizz board: the project rail down the left, the composer and the queue of threads beside it, and on the right a card where an agent is asking an answerable question with
|
|
46
|
+
<img src="assets/board.png" alt="The Frizz board: the project rail down the left, the composer and the queue of threads beside it, and on the right a card where an agent is asking an answerable question with lettered options, with its snooze clock and done check in the card's header." width="100%">
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
49
|
<br/>
|
|
@@ -84,21 +84,23 @@ The queue is strict about what earns a card, which is what keeps it a real todo
|
|
|
84
84
|
**Threads are built to run without you.** A worker keeps going until it reaches something only you can settle — a product call, a fork where guessing wrong is expensive to undo, an irreversible action — and then it hands back an answerable *question* rather than a wall of text for you to re-read and interpret.
|
|
85
85
|
|
|
86
86
|
<p align="center">
|
|
87
|
-
<img src="assets/question.png" alt="A question card titled Question: 'Should the settings store use SQLite or a JSON file?' with two options
|
|
87
|
+
<img src="assets/question.png" alt="A question card titled Question: 'Should the settings store use SQLite or a JSON file?' with two options lettered A and B, the first tagged RECOMMENDED, and a third row lettered C for typing something else, above the prompt box and its Send button." width="100%">
|
|
88
88
|
</p>
|
|
89
89
|
|
|
90
|
-
Options are
|
|
90
|
+
Options are lettered: click one to pick it, then Enter to send it, along with anything typed in the prompt box below. A worker marks its own recommendation when it has one. There is always a row for writing something else instead.
|
|
91
91
|
|
|
92
92
|
When the answer isn't one thing, the same card takes several: check any combination and add a note.
|
|
93
93
|
|
|
94
94
|
<p align="center">
|
|
95
|
-
<img src="assets/question-multi.png" alt="A question card titled Select multiple: 'Which of these findings should I fix in this pass?' with three
|
|
95
|
+
<img src="assets/question-multi.png" alt="A question card titled Select multiple: 'Which of these findings should I fix in this pass?' with three lettered checkbox options, the first two ticked, and a fourth lettered row for adding a note, above the prompt box, whose Send button is lit." width="100%">
|
|
96
96
|
</p>
|
|
97
97
|
|
|
98
98
|
### GitHub
|
|
99
99
|
|
|
100
100
|
Browse the repo's issues and pull requests from the composer, select any number of them, and each becomes its own thread.
|
|
101
101
|
|
|
102
|
+
Sign Frizz in once from Settings → GitHub, with a device code. The sign-in is read-only with one exception GitHub builds in: it can read code, issues, pull requests and checks on the accounts where you install [the Frizz GitHub App](https://github.com/apps/frizz-agent-console), plus public repos, and the only thing it can do beyond reading is comment on a commit. It's what watches pull requests and issues for your workers, overnight included, and it's separate from any `gh` sign-in of your own. It's stored encrypted, with a key unique to your machine: in the keychain on a Mac, and in a separate private file elsewhere. That keeps it from token scanners and copied files, but not from a program running as you that goes after Frizz on purpose.
|
|
103
|
+
|
|
102
104
|
<p align="center">
|
|
103
105
|
<img src="assets/github.png" alt="The GitHub picker open over the composer, listing real open issues from colinhacks/zod with numbers, authors, and reaction counts; three are checked and a Start investigations button is enabled." width="100%">
|
|
104
106
|
</p>
|
|
@@ -121,6 +123,12 @@ Give a thread a standing goal. Frizz sends it as a prompt every time the agent c
|
|
|
121
123
|
<img src="assets/goal.png" alt="The goal panel open above a queue card's prompt box: a goal saying to keep going until the test suite is green, sent at every rest and every 30 minutes." width="100%">
|
|
122
124
|
</p>
|
|
123
125
|
|
|
126
|
+
### Launch prefix
|
|
127
|
+
|
|
128
|
+
Start every worker through a command of your own: a secret injector, a token proxy, a sandbox. Set it per project under the gear beside **Claude Code** or **Codex** in the model picker. With `op run --env-file .env --` there, a new Claude worker starts as `op run --env-file .env -- claude …`, so the variables 1Password injects are in the worker's environment and nowhere on disk.
|
|
129
|
+
|
|
130
|
+
The command gets the worker's environment, and must run the command after it with stdin and stdout passed through and stay running until it exits (`exec` does both). Frizz splits the prefix into words like a shell would but never runs it in one, so for variables or pipes, write the shell in: `sh -c 'export ANTHROPIC_BASE_URL="$(my-proxy url)"; exec "$@"' sh`. Claude picks up a change on the next thread you dispatch. Codex's prefix wraps the app-server a project's Codex threads share, and Frizz restarts it once no Codex turn is running. With a prefix set, Frizz no longer refuses to dispatch when it finds no local sign-in, since the prefix may be what supplies the credentials.
|
|
131
|
+
|
|
124
132
|
<br/>
|
|
125
133
|
|
|
126
134
|
<h2 align="center">CLI</h2>
|
|
@@ -142,18 +150,18 @@ Options:
|
|
|
142
150
|
--no-app print the URL without opening a browser
|
|
143
151
|
--port <port> request a fixed port for a new workspace server
|
|
144
152
|
--sandbox a disposable Frizz to try things in: throwaway home and project, its
|
|
145
|
-
own port, deleted when this terminal closes; credentials (
|
|
146
|
-
cloudflared, Claude, Codex, the machine's frizz.sh
|
|
153
|
+
own port, deleted when this terminal closes; credentials (Frizz's
|
|
154
|
+
GitHub sign-in, cloudflared, Claude, Codex, the machine's frizz.sh
|
|
155
|
+
key) are shared
|
|
147
156
|
--link print a fresh single-use access link for the running board
|
|
148
157
|
--debug stream the full event feed to the terminal instead of the compact readout
|
|
149
158
|
-h, --help show this help
|
|
150
159
|
|
|
151
160
|
|
|
152
161
|
To reach the board from a phone or another machine, press R in the terminal running it: a short
|
|
153
|
-
walkthrough sets up a private frizz.sh
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
shows a single-use sign-in link as a QR; press L for a fresh one, or run --link from another shell.
|
|
162
|
+
walkthrough sets up a private frizz.sh subdomain (no account needed), a Cloudflare Tunnel,
|
|
163
|
+
Tailscale, or a proxy of your own, and remembers the choice, so a plain launch serves it from then
|
|
164
|
+
on. The board stays on loopback and shows a single-use sign-in link as a QR; press L for a fresh one, or run --link from another shell.
|
|
157
165
|
```
|
|
158
166
|
|
|
159
167
|
<br/>
|
|
@@ -170,7 +178,7 @@ shows a single-use sign-in link as a QR; press L for a fresh one, or run --link
|
|
|
170
178
|
<details>
|
|
171
179
|
<summary><b>Does anything leave my machine?</b></summary>
|
|
172
180
|
|
|
173
|
-
> Nothing from Frizz. There's no account, no telemetry, and the server binds to `127.0.0.1`; reaching it from another device is something you switch on yourself (press R in its terminal). The agents themselves talk to their providers, and
|
|
181
|
+
> Nothing from Frizz. There's no account, no telemetry, and the server binds to `127.0.0.1`; reaching it from another device is something you switch on yourself (press R in its terminal). The agents themselves talk to their providers, and Frizz reads GitHub directly with the read-only sign-in you give it, but Frizz is a local process looking at local files.
|
|
174
182
|
|
|
175
183
|
</details>
|
|
176
184
|
|
|
@@ -205,7 +213,7 @@ shows a single-use sign-in link as a QR; press L for a fresh one, or run --link
|
|
|
205
213
|
<details>
|
|
206
214
|
<summary><b>Can I reach it from another machine?</b></summary>
|
|
207
215
|
|
|
208
|
-
> Yes. Press **R** in the terminal running Frizz. A short walkthrough sets up one of four ways to reach the board — a
|
|
216
|
+
> Yes. Press **R** in the terminal running Frizz. A short walkthrough sets up one of four ways to reach the board — a frizz.sh subdomain, a Cloudflare Tunnel you own, Tailscale, or any proxy you run — checks what each needs, prints the commands, and remembers your choice. From then on a plain `npx frizz` serves it; pick **Off** in the same place to go back to loopback only.
|
|
209
217
|
>
|
|
210
218
|
> To try any of this without touching the board you run, launch a second one with `npx frizz --sandbox` — a throwaway home and project on its own port, deleted on ctrl-c.
|
|
211
219
|
>
|
|
@@ -216,7 +224,7 @@ shows a single-use sign-in link as a QR; press L for a fresh one, or run --link
|
|
|
216
224
|
<details>
|
|
217
225
|
<summary><b>Can I reach it from anywhere, not just my LAN?</b></summary>
|
|
218
226
|
|
|
219
|
-
> Same answer: press **R** and pick a private frizz.sh
|
|
227
|
+
> Same answer: press **R** and pick a private frizz.sh subdomain (unguessable, no account), a Cloudflare Tunnel, or Tailscale. (Custom frizz.sh subdomains are paused for now; a board that already holds one keeps it.) Each is reachable from anywhere the transport is — a frizz.sh subdomain and a Cloudflare Tunnel from the open internet, Tailscale from your own devices.
|
|
220
228
|
>
|
|
221
229
|
> Frizz has no accounts, so the single-use sign-in link **is** the door: a phone that scans it gets a session; nobody else gets in. Sessions are per device and can be listed and revoked with `npx frizz --sessions` and `npx frizz --sign-out`. Every project added or removed, thread started, message sent and question answered is recorded in `logs/audit.jsonl` under Frizz's data folder, with the session id of the device that did it and whether it came through that public address.
|
|
222
230
|
|
package/dist/frizz.js
CHANGED
|
@@ -13145,6 +13145,15 @@ var init_interactions = __esm({
|
|
|
13145
13145
|
}
|
|
13146
13146
|
});
|
|
13147
13147
|
|
|
13148
|
+
// packages/shared/src/launch-prefix.ts
|
|
13149
|
+
var LAUNCH_PREFIX_MAX_LENGTH;
|
|
13150
|
+
var init_launch_prefix = __esm({
|
|
13151
|
+
"packages/shared/src/launch-prefix.ts"() {
|
|
13152
|
+
"use strict";
|
|
13153
|
+
LAUNCH_PREFIX_MAX_LENGTH = 2048;
|
|
13154
|
+
}
|
|
13155
|
+
});
|
|
13156
|
+
|
|
13148
13157
|
// packages/shared/src/claim.ts
|
|
13149
13158
|
function isAnonymousClaimName(name) {
|
|
13150
13159
|
return ANONYMOUS_CLAIM_PATTERN.test(name);
|
|
@@ -13298,6 +13307,17 @@ var init_code_fences = __esm({
|
|
|
13298
13307
|
}
|
|
13299
13308
|
});
|
|
13300
13309
|
|
|
13310
|
+
// packages/shared/src/question-fence.ts
|
|
13311
|
+
var import_yaml;
|
|
13312
|
+
var init_question_fence = __esm({
|
|
13313
|
+
"packages/shared/src/question-fence.ts"() {
|
|
13314
|
+
"use strict";
|
|
13315
|
+
import_yaml = __toESM(require_dist(), 1);
|
|
13316
|
+
init_code_fences();
|
|
13317
|
+
init_src();
|
|
13318
|
+
}
|
|
13319
|
+
});
|
|
13320
|
+
|
|
13301
13321
|
// packages/shared/src/delta.ts
|
|
13302
13322
|
var init_delta = __esm({
|
|
13303
13323
|
"packages/shared/src/delta.ts"() {
|
|
@@ -13502,16 +13522,18 @@ function signoffNudgeText(needsInput) {
|
|
|
13502
13522
|
"the write-up.",
|
|
13503
13523
|
"",
|
|
13504
13524
|
"**DECIDE RATHER THAN ASK.** Stop only for a decision that is genuinely the human's AND that blocks you",
|
|
13505
|
-
"right now:
|
|
13525
|
+
"right now: ask that one in a ```question fence. Every other open choice INSIDE the task \u2014 a name, a",
|
|
13506
13526
|
"default, a reversible design call \u2014 is yours to make: decide it, say in one line which way you went and",
|
|
13507
13527
|
"what would reverse it, and carry on. A choice that would ENLARGE the task is not one of those: an",
|
|
13508
13528
|
"unanswered question is not permission to go build the answer.",
|
|
13509
13529
|
"",
|
|
13510
|
-
"Otherwise, sign off \u2014 a
|
|
13530
|
+
"Otherwise, sign off \u2014 a question in your prose, or a fence at the END of your next message:",
|
|
13511
13531
|
"",
|
|
13512
|
-
"-
|
|
13513
|
-
"
|
|
13514
|
-
"
|
|
13532
|
+
"- `` ```question `` \u2014 you need the human's decision. Write each fence right under the paragraph it",
|
|
13533
|
+
" decides, never stacked at the bottom: `id:` (a slug of your choosing), `kind:` (`choice`, `multi`,",
|
|
13534
|
+
" `text` or `secret`), a one-line `question:`, and `options:` with one-line trade-offs, the recommended",
|
|
13535
|
+
" one first. Frizz registers it when the message lands, and a question asked at this rest is its",
|
|
13536
|
+
" sign-off: rest normally.",
|
|
13515
13537
|
"- `` ```done `` \u2014 genuinely FINISHED. A DISMISSAL: the card is filed away and nobody looks again, so",
|
|
13516
13538
|
" if anything is still owed, it is not done. Body: 1-3 sentences, then bullets, each opening with a",
|
|
13517
13539
|
" **bolded verb phrase**.",
|
|
@@ -13527,14 +13549,14 @@ function signoffNudgeText(needsInput) {
|
|
|
13527
13549
|
" every value the act needs written in. Frizz reads them verbatim. Steps name the HUMAN as the wait,",
|
|
13528
13550
|
" so the fence needs no other name and no `for:`, and the thread goes into their queue. Their Done",
|
|
13529
13551
|
" comes back to you as their reply; anything else they need to say comes as a message of their own.",
|
|
13530
|
-
"-
|
|
13531
|
-
"
|
|
13532
|
-
"
|
|
13533
|
-
"
|
|
13552
|
+
"- A question you asked at an EARLIER rest, whose answer you still need: write a ```question fence",
|
|
13553
|
+
" carrying its `id:` alone. That draws its card in this message and keeps the thread in the human's",
|
|
13554
|
+
" queue. One you no longer need, withdraw with `frizzctl unask <id>`; one you leave unmentioned stays",
|
|
13555
|
+
" answerable where you asked it.",
|
|
13534
13556
|
"",
|
|
13535
13557
|
"**STILL OWED counts things you are not going to do yourself.** A decision you are RECOMMENDING, a",
|
|
13536
13558
|
"draft you wrote but did not send, follow-up work you discovered \u2014 all of it dies with the card, even",
|
|
13537
|
-
"the part that is someone else's to do. Each ends on a question with your recommendation as option
|
|
13559
|
+
"the part that is someone else's to do. Each ends on a question with your recommendation as option A,",
|
|
13538
13560
|
"or you DO it first \u2014 a sub-agent's result comes BACK to you, so it lands on your card; a new card via",
|
|
13539
13561
|
"`frizzctl spawn-thread` is the LAST resort, since nothing it learns returns to you or its siblings.",
|
|
13540
13562
|
"None of them is a `done`. And what is not",
|
|
@@ -13556,23 +13578,37 @@ function requireEffortOutsideAcp(profile, ctx) {
|
|
|
13556
13578
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["effort"], message: `effort is required for a ${profile.backend} profile` });
|
|
13557
13579
|
}
|
|
13558
13580
|
}
|
|
13581
|
+
function formatElapsed(ms) {
|
|
13582
|
+
if (!Number.isFinite(ms) || ms < 0) return "an unknown time";
|
|
13583
|
+
const s = Math.floor(ms / 1e3);
|
|
13584
|
+
if (s < 60) return `${s}s`;
|
|
13585
|
+
const m = Math.floor(s / 60);
|
|
13586
|
+
if (m < 60) return `${m}m`;
|
|
13587
|
+
const h = Math.floor(m / 60);
|
|
13588
|
+
if (h < 24) return m % 60 === 0 ? `${h}h` : `${h}h ${m % 60}m`;
|
|
13589
|
+
const d = Math.floor(h / 24);
|
|
13590
|
+
return h % 24 === 0 ? `${d}d` : `${d}d ${h % 24}h`;
|
|
13591
|
+
}
|
|
13559
13592
|
function fallbackPort(base) {
|
|
13560
13593
|
return base + 1e4;
|
|
13561
13594
|
}
|
|
13562
|
-
var
|
|
13595
|
+
var import_yaml2, ATTACHMENT_IMAGE_EXTENSIONS, ATTACHMENT_TEXT_EXTENSIONS, ATTACHMENT_OFFICE_EXTENSIONS, ATTACHMENT_DATA_EXTENSIONS, ATTACHMENT_ARCHIVE_EXTENSIONS, ATTACHMENT_DOC_EXTENSIONS, ATTACHMENT_EXTENSIONS, ATTACHMENT_MAX_BASE64_CHARS, ATTACHMENT_MAX_BYTES, ATTACHMENT_EXT_SET, ATTACHMENT_ACCEPT, FrizzStatus, BlockMechanism, RuntimeState, Backend, CodexModel, ClaudeModel, AcpAgent, AcpAgentModel, AcpAgentModels, AcpAgentModelsInput, ThreadProfileOption, ThreadAgent, SubAgentView, BgShellView, CompletionHoldOp, CompletionHold, AskOption, AskQuestion, PendingAsk, AwaitingHint, RETIRED_AWAITING_KINDS, RETIRED_LINE_RE, AWAITING_FOR_MAX_MS, SnoozeUntil, SNOOZE_PROMPT_MAX, SnoozePrompt, RECURRING_PROMPT_MAX, RECURRING_MIN_INTERVAL_SECONDS, RECURRING_MAX_INTERVAL_SECONDS, RecurringPromptText, RecurringIntervalSeconds, ThreadRecurringPrompt, TIMER_PROMPT_MAX, TIMER_MAX_DELAY_SECONDS, TimerPromptText, SIGNOFF_NUDGE_MARKER, SIGNOFF_NUDGE_MESSAGE, SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT, ThreadWatchKind, GithubChecksState, GithubMergeState, GithubWatchStatus, GithubIssueStatus, CommandWatchRun, MonitorStatus, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, COMMAND_WATCH_EVERY_MAX_MS, COMMAND_WATCH_FOR_MAX_MS, COMMAND_WATCH_COMMAND_MAX, COMMAND_WATCH_RUN_TIMEOUT_MS, CommandWatchState, CommandWatchView, AddOwnCommandWatchInput, AddOwnCommandWatchResult, DropOwnCommandWatchInput, DropOwnCommandWatchResult, ListOwnCommandWatchesInput, OwnCommandWatchesResult, MONITOR_FLOOD_MAX_EVENTS, MONITOR_FLOOD_WINDOW_MS, MONITOR_DESCRIPTION_MAX, AddOwnMonitorInput, AddOwnMonitorResult, MONITOR_FLOOD_REASON, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, QuestionFenceFaultView, ClosedQuestionView, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, ANSWER_ROW_ID_PATTERN, ANSWER_ROW_ID, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, BoardState, QuotaWindow, ProviderQuota, QuotaSnapshot, ProviderAuth, AccountEmails, AuthSnapshot, AccountBackend, AccountLogoutInput, AccountLogoutResult, PermissionMode, LocalFileOpener, Settings, DispatchProviderPreferences, DispatchPreferences, DispatchProfileSnapshotShape, DispatchProfileSnapshot, SetDispatchPreferenceInput, DispatchInput, ADOPT_THREAD_MESSAGE_MAX_CHARS, AdoptThreadInput, AdoptThreadResult, AdoptSessionInput, FollowUpInput, SteerThreadInput, SteerThreadResult, SpawnThreadInput, SpawnThreadResult, SpawnChildInput, SpawnChildResult, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, ReorderPinnedThreadsInput, SetThreadConsolePinnedInput, ReorderConsolePinnedThreadsInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, ThreadBand, ThreadListing, ListThreadsInput, ListThreadsResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, McpTarget, McpServerState, McpServerReading, McpStatusResult, McpSignInInput, McpSignInState, McpSignInResult, McpSignInAttemptInput, McpSignInPollResult, McpSignInFinishInput, SetThreadProfileInput, SetThreadProfileResult, UpgradeThreadModelInput, UpgradeThreadModelResult, DISPATCH_TASK_BANNER, DISPATCH_TASK_BANNER_MARKER, HUMAN_GAP_FLOOR_MS, GithubWakeItem, GithubWakeSteer, WAKE_SCOPE, WAKE_REF, WAKE_SINGLE, WAKE_MULTI_HEAD, PR_WATCH_FINISHED, PR_WATCH_CI_PASSED, PR_WATCH_CI_GATED, PR_WATCH_STATE, PR_WATCH_CI_FAILED, GithubStatus, GithubAccount, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, BoardDeltaEvent, ServerEvent, BoardDeltasInput, BoardDeltasResult, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketBoardClientMsg, SocketClientMsg, ProjectCard, ProjectRailCounts, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
|
|
13563
13596
|
var init_src = __esm({
|
|
13564
13597
|
"packages/shared/src/index.ts"() {
|
|
13565
13598
|
"use strict";
|
|
13566
|
-
|
|
13599
|
+
import_yaml2 = __toESM(require_dist(), 1);
|
|
13567
13600
|
init_zod();
|
|
13568
13601
|
init_interactions();
|
|
13602
|
+
init_launch_prefix();
|
|
13569
13603
|
init_thread_slug();
|
|
13570
13604
|
init_claim();
|
|
13571
13605
|
init_claude_editions();
|
|
13572
13606
|
init_code_fences();
|
|
13607
|
+
init_question_fence();
|
|
13573
13608
|
init_delta();
|
|
13574
13609
|
init_drainable_worker();
|
|
13575
13610
|
init_interactions();
|
|
13611
|
+
init_launch_prefix();
|
|
13576
13612
|
init_receipt_bus();
|
|
13577
13613
|
init_relay_protocol();
|
|
13578
13614
|
init_shell_writes();
|
|
@@ -13897,7 +13933,7 @@ var init_src = __esm({
|
|
|
13897
13933
|
questions: external_exports.array(AskQuestion)
|
|
13898
13934
|
});
|
|
13899
13935
|
AwaitingHint = external_exports.object({
|
|
13900
|
-
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "command", "for", "title", "status", "needs_input", "step", "question"]),
|
|
13936
|
+
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "command", "thread", "for", "title", "status", "needs_input", "step", "question"]),
|
|
13901
13937
|
value: external_exports.string()
|
|
13902
13938
|
});
|
|
13903
13939
|
RETIRED_AWAITING_KINDS = ["watch", "pr-watch", "human", "ci", "session", "shell", "agent", "timer", "pr", "reason"];
|
|
@@ -13938,7 +13974,7 @@ var init_src = __esm({
|
|
|
13938
13974
|
SIGNOFF_NUDGE_MARKER = "**This message is from frizz, not from the human.**";
|
|
13939
13975
|
SIGNOFF_NUDGE_MESSAGE = signoffNudgeText(false);
|
|
13940
13976
|
SIGNOFF_NUDGE_MESSAGE_NEEDS_INPUT = signoffNudgeText(true);
|
|
13941
|
-
ThreadWatchKind = external_exports.enum(["shell", "github", "timer"]);
|
|
13977
|
+
ThreadWatchKind = external_exports.enum(["shell", "github", "timer", "command"]);
|
|
13942
13978
|
GithubChecksState = external_exports.enum(["none", "running", "passing", "failing"]);
|
|
13943
13979
|
GithubMergeState = external_exports.enum(["mergeable", "blocked", "conflicting", "unknown"]);
|
|
13944
13980
|
GithubWatchStatus = external_exports.object({
|
|
@@ -13976,7 +14012,16 @@ var init_src = __esm({
|
|
|
13976
14012
|
/** A digest of WHICH jobs are failing right now. The head commit alone cannot express a re-run of the
|
|
13977
14013
|
* same job, or a slower job going red after the first — both of which are a second failure the worker
|
|
13978
14014
|
* must hear about. Empty when nothing is failing. */
|
|
13979
|
-
failureSig: external_exports.string().optional()
|
|
14015
|
+
failureSig: external_exports.string().optional(),
|
|
14016
|
+
/** WHO THE PR IS WAITING ON, the facts behind "will anything move here unless we act?"
|
|
14017
|
+
* (awaiting.prWaitsOnNoOne). Whether GitHub will merge it by itself (`queued` in a merge queue, `auto`
|
|
14018
|
+
* with auto-merge on, `none`), whether the signed-in `gh` account opened it and may merge it, and
|
|
14019
|
+
* whether a review is still requested of someone. Absent means unknown: the `gh` fallback and a
|
|
14020
|
+
* reading taken before 2026-10-27 carry none, and an unknown never counts as "nobody". */
|
|
14021
|
+
mergePath: external_exports.enum(["none", "queued", "auto"]).optional(),
|
|
14022
|
+
viewerAuthored: external_exports.boolean().optional(),
|
|
14023
|
+
viewerCanMerge: external_exports.boolean().optional(),
|
|
14024
|
+
reviewRequested: external_exports.boolean().optional()
|
|
13980
14025
|
}).strict();
|
|
13981
14026
|
GithubIssueStatus = external_exports.object({
|
|
13982
14027
|
/** OPEN | CLOSED, lowercased. A closed issue ends the wait outright. */
|
|
@@ -13992,6 +14037,20 @@ var init_src = __esm({
|
|
|
13992
14037
|
/** When frizz last heard from GitHub, for the same reason `GithubWatchStatus.polledAt` carries it. */
|
|
13993
14038
|
polledAt: external_exports.string()
|
|
13994
14039
|
}).strict();
|
|
14040
|
+
CommandWatchRun = external_exports.object({
|
|
14041
|
+
at: external_exports.string(),
|
|
14042
|
+
outcome: external_exports.enum(["exit", "timeout", "signal", "error"]),
|
|
14043
|
+
exitCode: external_exports.number().int().nullable(),
|
|
14044
|
+
output: external_exports.string()
|
|
14045
|
+
}).strict();
|
|
14046
|
+
MonitorStatus = external_exports.object({
|
|
14047
|
+
/** The worker's own words for what it watches ("errors in deploy.log"), when it gave any. */
|
|
14048
|
+
description: external_exports.string().optional(),
|
|
14049
|
+
/** Events printed so far: each batch of lines that woke (or will wake) the thread is one. */
|
|
14050
|
+
events: external_exports.number().int().nonnegative(),
|
|
14051
|
+
/** The newest event's text, trimmed, and when it was printed. */
|
|
14052
|
+
lastEvent: external_exports.object({ at: external_exports.string(), text: external_exports.string() }).strict().optional()
|
|
14053
|
+
}).strict();
|
|
13995
14054
|
ThreadWatchView = external_exports.object({
|
|
13996
14055
|
id: external_exports.string(),
|
|
13997
14056
|
kind: ThreadWatchKind,
|
|
@@ -14013,7 +14072,22 @@ var init_src = __esm({
|
|
|
14013
14072
|
* worker's prompt is the row's NAME (a `tmr_…` id names nothing to a human) and the fire instant is
|
|
14014
14073
|
* its status. Unlike `github` there is no polled half to be absent: a timer that exists is fully
|
|
14015
14074
|
* known, so a timer row always carries this. */
|
|
14016
|
-
timer: external_exports.object({ fireAt: external_exports.string(), prompt: external_exports.string() }).strict().optional()
|
|
14075
|
+
timer: external_exports.object({ fireAt: external_exports.string(), prompt: external_exports.string() }).strict().optional(),
|
|
14076
|
+
/** `command` rows only: the armed command watch's registration and its last check — what the worker's
|
|
14077
|
+
* own read-back carries (CommandWatchView), minus the id and state the row already has. The command is
|
|
14078
|
+
* the row's NAME and the cadence its status. Until 2026-10-13 a command watch had no row at all, so a
|
|
14079
|
+
* thread parked on nothing else drew a card that said only "Waiting for an external update." */
|
|
14080
|
+
command: external_exports.object({
|
|
14081
|
+
command: external_exports.string(),
|
|
14082
|
+
/** The cadence in the house duration grammar (`2m`, `1h 30m`). */
|
|
14083
|
+
every: external_exports.string(),
|
|
14084
|
+
expiresAt: external_exports.string(),
|
|
14085
|
+
runs: external_exports.number().int().nonnegative(),
|
|
14086
|
+
lastRun: CommandWatchRun.optional(),
|
|
14087
|
+
/** A MONITOR's own reading (`frizzctl monitor`), and present on nothing else: the row is a running
|
|
14088
|
+
* script, so its status is how many events it has printed, not a cadence. */
|
|
14089
|
+
monitor: MonitorStatus.optional()
|
|
14090
|
+
}).strict().optional()
|
|
14017
14091
|
}).strict();
|
|
14018
14092
|
PR_WATCH_FOR_MAX_MS = 365 * 24 * 60 * 60 * 1e3;
|
|
14019
14093
|
PR_WATCH_DEFAULT_FOR_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
@@ -14147,12 +14221,6 @@ var init_src = __esm({
|
|
|
14147
14221
|
COMMAND_WATCH_FOR_MAX_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
14148
14222
|
COMMAND_WATCH_COMMAND_MAX = 4e3;
|
|
14149
14223
|
COMMAND_WATCH_RUN_TIMEOUT_MS = 2 * 6e4;
|
|
14150
|
-
CommandWatchRun = external_exports.object({
|
|
14151
|
-
at: external_exports.string(),
|
|
14152
|
-
outcome: external_exports.enum(["exit", "timeout", "signal", "error"]),
|
|
14153
|
-
exitCode: external_exports.number().int().nullable(),
|
|
14154
|
-
output: external_exports.string()
|
|
14155
|
-
}).strict();
|
|
14156
14224
|
CommandWatchState = external_exports.enum(["armed", "dropped", "passed", "expired", "cancelled"]);
|
|
14157
14225
|
CommandWatchView = external_exports.object({
|
|
14158
14226
|
id: external_exports.string(),
|
|
@@ -14164,7 +14232,11 @@ var init_src = __esm({
|
|
|
14164
14232
|
expiresAt: external_exports.string(),
|
|
14165
14233
|
/** How many checks have run since it was armed, the registration's own first check included. */
|
|
14166
14234
|
runs: external_exports.number().int().nonnegative(),
|
|
14167
|
-
lastRun: CommandWatchRun.optional()
|
|
14235
|
+
lastRun: CommandWatchRun.optional(),
|
|
14236
|
+
/** Present on a MONITOR (`frizzctl monitor`) and nothing else: a script the server keeps RUNNING, whose
|
|
14237
|
+
* every printed line is a wake. A monitor has no cadence and no checks, so `every` reads `0s` and
|
|
14238
|
+
* `runs` 0 on one; read this instead. */
|
|
14239
|
+
monitor: MonitorStatus.optional()
|
|
14168
14240
|
}).strict();
|
|
14169
14241
|
AddOwnCommandWatchInput = external_exports.object({
|
|
14170
14242
|
slug: ThreadSlug,
|
|
@@ -14205,6 +14277,38 @@ var init_src = __esm({
|
|
|
14205
14277
|
}).strict();
|
|
14206
14278
|
ListOwnCommandWatchesInput = external_exports.object({ slug: ThreadSlug }).strict();
|
|
14207
14279
|
OwnCommandWatchesResult = external_exports.object({ watches: external_exports.array(CommandWatchView) }).strict();
|
|
14280
|
+
MONITOR_FLOOD_MAX_EVENTS = 30;
|
|
14281
|
+
MONITOR_FLOOD_WINDOW_MS = 10 * 6e4;
|
|
14282
|
+
MONITOR_DESCRIPTION_MAX = 120;
|
|
14283
|
+
AddOwnMonitorInput = external_exports.object({
|
|
14284
|
+
slug: ThreadSlug,
|
|
14285
|
+
/** Run with `/bin/sh -c` in the project root, with the server's environment. Each stdout line is an event. */
|
|
14286
|
+
command: external_exports.string().trim().min(1).max(COMMAND_WATCH_COMMAND_MAX),
|
|
14287
|
+
/** What it watches, in the worker's words — the name on its row and drawer, and the label on every wake.
|
|
14288
|
+
* Required (2026-11-03), as a sub-agent's description is: without one the row could only show the script. */
|
|
14289
|
+
description: external_exports.string().trim().min(1).max(MONITOR_DESCRIPTION_MAX),
|
|
14290
|
+
/** How long to keep it running, as a DURATION. Clamped to COMMAND_WATCH_FOR_MAX_MS and reported. */
|
|
14291
|
+
for: external_exports.string().trim().min(1).max(16)
|
|
14292
|
+
}).strict();
|
|
14293
|
+
AddOwnMonitorResult = external_exports.object({
|
|
14294
|
+
/** Present when the call was REFUSED: the gate, the caps, or a script that could not run here. Nothing
|
|
14295
|
+
* is armed, and the text says why and what to use instead. */
|
|
14296
|
+
refused: external_exports.string().optional(),
|
|
14297
|
+
id: external_exports.string().optional(),
|
|
14298
|
+
/** This exact command already runs as a monitor on this thread; nothing new was registered. */
|
|
14299
|
+
alreadyArmed: external_exports.boolean().default(false),
|
|
14300
|
+
/** Still running when registration stopped watching it (MONITOR_REGISTRATION_WINDOW_MS). */
|
|
14301
|
+
running: external_exports.boolean().default(false),
|
|
14302
|
+
/** The events it printed while registration watched — handed back here rather than woken. */
|
|
14303
|
+
events: external_exports.array(external_exports.string()).default([]),
|
|
14304
|
+
/** It EXITED while registration watched: how, and the end of what it printed. Nothing stays armed. */
|
|
14305
|
+
exited: CommandWatchRun.optional(),
|
|
14306
|
+
expiresAt: external_exports.string().optional(),
|
|
14307
|
+
/** The `for` the worker wrote, present ONLY when it was clamped. */
|
|
14308
|
+
clamped: external_exports.object({ for: external_exports.string() }).strict().optional(),
|
|
14309
|
+
watches: external_exports.array(CommandWatchView)
|
|
14310
|
+
}).strict();
|
|
14311
|
+
MONITOR_FLOOD_REASON = `it printed ${MONITOR_FLOOD_MAX_EVENTS} events inside ${formatElapsed(MONITOR_FLOOD_WINDOW_MS)}, and every event is a turn of yours. Register it again with a tighter filter: print only the lines you would act on (\`grep --line-buffered\`), never raw logs.`;
|
|
14208
14312
|
AskQuestionKind = external_exports.enum(["question", "multi"]);
|
|
14209
14313
|
AskedOptionSchema = external_exports.lazy(() => external_exports.object({
|
|
14210
14314
|
// 400, not the 120 it launched with (2026-09-03): a label is a chip line, but "Post the drafted
|
|
@@ -14223,15 +14327,17 @@ var init_src = __esm({
|
|
|
14223
14327
|
// 4000, not the 600 it launched with (2026-09-03): this field is the whole card context — everything
|
|
14224
14328
|
// the human needs to answer cold — and a question that carries its own evidence needs the room. The
|
|
14225
14329
|
// answer restates it, so QuestionAnswerSchema's cap matches.
|
|
14330
|
+
handle: external_exports.string().trim().min(1).max(64).optional(),
|
|
14226
14331
|
question: external_exports.string().trim().min(1).max(4e3),
|
|
14332
|
+
context: external_exports.string().trim().max(2e4).optional(),
|
|
14227
14333
|
header: external_exports.string().trim().max(24).optional(),
|
|
14228
14334
|
kind: AskQuestionKind,
|
|
14229
14335
|
danger: external_exports.boolean().optional(),
|
|
14230
14336
|
secret: external_exports.boolean().optional(),
|
|
14231
14337
|
// UNBOUNDED, deliberately. This carried `.max(8)` from launch until 2026-09-03, when the maintainer
|
|
14232
14338
|
// asked for the cap to go ("allow arbitrary numbers of options"): a `multi` over a long list — which
|
|
14233
|
-
// gates to run, which of twenty findings to act on — is a real shape, and the card
|
|
14234
|
-
// (
|
|
14339
|
+
// gates to run, which of twenty findings to act on — is a real shape, and the card letters past 26
|
|
14340
|
+
// (`AA.`) already. The count is the worker's to choose; the answer's `chosen` is unbounded to match.
|
|
14235
14341
|
options: external_exports.array(AskedOptionSchema).optional()
|
|
14236
14342
|
}).strict());
|
|
14237
14343
|
AskInput = external_exports.object({
|
|
@@ -14244,11 +14350,32 @@ var init_src = __esm({
|
|
|
14244
14350
|
/** Minted by frizz. The worker never chose it, which is why an answer RESTATES the question text —
|
|
14245
14351
|
* an id alone cannot be correlated back to what was asked. */
|
|
14246
14352
|
id: external_exports.string(),
|
|
14353
|
+
/** The worker's own id for it, from its ```question fence — what `unask` and every correction name it
|
|
14354
|
+
* by. Absent on a question `ask` registered, which only ever had the minted id. */
|
|
14355
|
+
handle: external_exports.string().optional(),
|
|
14247
14356
|
spec: AskedQuestionSchema,
|
|
14248
14357
|
askedAt: external_exports.string(),
|
|
14249
14358
|
/** A `secret` question only: the path its value will be served at. Known at registration, so the
|
|
14250
14359
|
* worker can prepare the command that reads it before the human has answered. */
|
|
14251
|
-
secretPath: external_exports.string().optional()
|
|
14360
|
+
secretPath: external_exports.string().optional(),
|
|
14361
|
+
/** ON THE BOARD ONLY: does this question STAND at the thread's current rest — asked at it, or
|
|
14362
|
+
* referenced by it (server question-fences.ts standingQuestions)? `false` is a question that has
|
|
14363
|
+
* FADED: still open and answerable where it was asked, but not what this rest is waiting on, so it
|
|
14364
|
+
* neither queues the thread nor marks its row. Absent — the worker's own read-back, a snapshot from
|
|
14365
|
+
* before 2026-10-13 — reads as standing (hasStandingQuestion). */
|
|
14366
|
+
standing: external_exports.boolean().optional()
|
|
14367
|
+
}).strict();
|
|
14368
|
+
QuestionFenceFaultView = external_exports.object({
|
|
14369
|
+
at: external_exports.string(),
|
|
14370
|
+
id: external_exports.string().optional(),
|
|
14371
|
+
faults: external_exports.array(external_exports.string())
|
|
14372
|
+
}).strict();
|
|
14373
|
+
ClosedQuestionView = external_exports.object({
|
|
14374
|
+
id: external_exports.string(),
|
|
14375
|
+
/** The worker's own id for it — what its fence in the transcript carries. */
|
|
14376
|
+
handle: external_exports.string(),
|
|
14377
|
+
state: external_exports.enum(["dismissed", "withdrawn"]),
|
|
14378
|
+
askedAt: external_exports.string()
|
|
14252
14379
|
}).strict();
|
|
14253
14380
|
SECRET_TTL_MS = 15 * 6e4;
|
|
14254
14381
|
AskResult = external_exports.object({
|
|
@@ -14262,6 +14389,14 @@ var init_src = __esm({
|
|
|
14262
14389
|
}).strict();
|
|
14263
14390
|
UnaskResult = external_exports.object({
|
|
14264
14391
|
withdrawn: external_exports.boolean(),
|
|
14392
|
+
/** The question the id resolved to, by the name the WORKER knows it by (its fence's `id:`, or the
|
|
14393
|
+
* `qst_…` an `ask` minted) — absent when the id names nothing on this thread. It is the ROOT's: a
|
|
14394
|
+
* follow-up rides inside its parent's row and cannot be withdrawn alone, so `unask sqlite-path`
|
|
14395
|
+
* withdraws nothing and answers `settings-store` here — the id that takes the pair back. */
|
|
14396
|
+
id: external_exports.string().optional(),
|
|
14397
|
+
/** What that question was when the call arrived. `open` beside `withdrawn: true` is the ordinary
|
|
14398
|
+
* withdrawal; anything else is why nothing was withdrawn — and `answered` means a wake is on its way. */
|
|
14399
|
+
state: external_exports.enum(["open", "answered", "withdrawn", "dismissed"]).optional(),
|
|
14265
14400
|
open: external_exports.array(RegisteredQuestionView)
|
|
14266
14401
|
}).strict();
|
|
14267
14402
|
QuestionAnswerSchema = external_exports.lazy(() => external_exports.object({
|
|
@@ -14305,6 +14440,8 @@ var init_src = __esm({
|
|
|
14305
14440
|
ThreadSettledQuestionsResult = external_exports.object({
|
|
14306
14441
|
questions: external_exports.array(SettledQuestionView)
|
|
14307
14442
|
}).strict();
|
|
14443
|
+
ANSWER_ROW_ID_PATTERN = "[a-z0-9][a-z0-9._-]{0,63}";
|
|
14444
|
+
ANSWER_ROW_ID = new RegExp(`^${ANSWER_ROW_ID_PATTERN}$`);
|
|
14308
14445
|
MarkOwnDoneInput = external_exports.object({
|
|
14309
14446
|
slug: ThreadSlug,
|
|
14310
14447
|
body: external_exports.string().trim().min(1).max(2e4)
|
|
@@ -14475,6 +14612,20 @@ var init_src = __esm({
|
|
|
14475
14612
|
// outlive the message that carried it. These rows can, and they carry the question itself rather than
|
|
14476
14613
|
// merely asserting one exists — the card renders from this instead of re-parsing prose.
|
|
14477
14614
|
questions: external_exports.array(RegisteredQuestionView).default([]),
|
|
14615
|
+
/** The ```question fences of this thread that could NOT register, from its latest sync — every one in
|
|
14616
|
+
* the server's ring of fenced messages, not only the current rest's. Present only when there is one.
|
|
14617
|
+
* A refused fence has no row, and so does a fence the registrar has not reached yet; this is what
|
|
14618
|
+
* tells them apart, so the transcript draws the first as the code block it is and only the second as
|
|
14619
|
+
* the question it is about to become (web lib/questionFaults). Its other job is the reused id: a fence
|
|
14620
|
+
* refused for naming a spent id must not move that id's card, which a name match alone would do. */
|
|
14621
|
+
questionFaults: external_exports.array(QuestionFenceFaultView).optional(),
|
|
14622
|
+
/** The questions this thread asked with a ```question fence that CLOSED WITHOUT AN ANSWER — dismissed
|
|
14623
|
+
* (the human's ×, or Frizz cancelling a question an autonomous thread wrote) or withdrawn by the
|
|
14624
|
+
* worker. Present only when there is one. Their fences are still in the transcript, and without this a
|
|
14625
|
+
* fence with no open and no answered row reads as a question about to become answerable; with it the
|
|
14626
|
+
* fence draws its question closed, saying which way (web ClosedQuestionCard). Compact on purpose — the
|
|
14627
|
+
* fence itself carries the question — because the board ships every thread on every keyframe. */
|
|
14628
|
+
closedQuestions: external_exports.array(ClosedQuestionView).optional(),
|
|
14478
14629
|
/** The answer the human has already SENT that the worker has not received yet, as the exact message
|
|
14479
14630
|
* the delivery will carry (board.answersInFlight → questionAnswerMessage). The chat parses it with
|
|
14480
14631
|
* the same reader it uses on the delivered turn, so the in-flight card and the landed one are the
|
|
@@ -14514,6 +14665,16 @@ var init_src = __esm({
|
|
|
14514
14665
|
foreign: external_exports.boolean().optional(),
|
|
14515
14666
|
// ui.db lifecycle for session threads (open|archived) — written ONLY by explicit Archive/Reopen.
|
|
14516
14667
|
state: external_exports.enum(["open", "archived"]).optional(),
|
|
14668
|
+
// The instant the thread was marked done (session.archived_at), present while it is archived. The
|
|
14669
|
+
// Done band's order: the one marked done most recently is at its top (maintainer 2026-10-23). Absent
|
|
14670
|
+
// on a thread archived before the column existed, which the band places by its rest time instead.
|
|
14671
|
+
archivedAt: external_exports.string().optional(),
|
|
14672
|
+
// The instant this thread last ENTERED the Running band (session.running_since), present while it is
|
|
14673
|
+
// in it: the board stamps it the first build that finds the thread there and clears it the first
|
|
14674
|
+
// build that does not. The Running band's order: the thread that most recently switched back to
|
|
14675
|
+
// running is at its top (maintainer 2026-10-23). A thread that stays in the band keeps its instant, so
|
|
14676
|
+
// a working rest, a sub-agent returning or a mid-turn message never moves it.
|
|
14677
|
+
runningSince: external_exports.string().optional(),
|
|
14517
14678
|
// Exact durable user snooze. While this instant is in the future, an otherwise-resting thread is
|
|
14518
14679
|
// suppressed from Queue and shown dimmed in Held. Hard interactive gates (question, permission,
|
|
14519
14680
|
// native approval, crash) deliberately break through it. Expired values are cleared server-side.
|
|
@@ -14527,6 +14688,23 @@ var init_src = __esm({
|
|
|
14527
14688
|
// this instant's order. Lifecycle metadata like the snooze: written only by the pin/unpin verb, and
|
|
14528
14689
|
// it outranks every derived state (a pinned thread that finishes stays pinned).
|
|
14529
14690
|
pinnedAt: external_exports.string().optional(),
|
|
14691
|
+
// The instant the human pinned this thread on the CONSOLE's "By status" view (absent = not pinned
|
|
14692
|
+
// there). A second shelf, independent of `pinnedAt`: pinning on the Console does not pin on the
|
|
14693
|
+
// project's board, nor the other way round. The instant is the Console's pinned band's order across
|
|
14694
|
+
// every project, and like the project pin it outranks every derived state.
|
|
14695
|
+
consolePinnedAt: external_exports.string().optional(),
|
|
14696
|
+
/** CHILD THREADS (Settings.childThreads): the thread that spawned this one with `frizzctl spawn-child`.
|
|
14697
|
+
* Present only while that thread still exists. A child is never a row of its own on the board — not in
|
|
14698
|
+
* any band, the queue, a count or a badge — and is drawn under its parent instead, unless it stands at a
|
|
14699
|
+
* gate only the human can settle (drawnUnderParent). */
|
|
14700
|
+
parentSlug: external_exports.string().optional(),
|
|
14701
|
+
/** This child's rest is one that would have put it in the human's queue, so its handoff goes to its
|
|
14702
|
+
* PARENT instead (board.ts deriveChildHandoff; the scheduler delivers it once per rest). A permission
|
|
14703
|
+
* prompt, a secret request and a usage-limit stop are not handoffs: they still queue for the human. */
|
|
14704
|
+
handoffToParent: external_exports.literal(true).optional(),
|
|
14705
|
+
/** The instant of the rest `handoffToParent` describes (the tailer's lastAssistantAt), so the scheduler
|
|
14706
|
+
* can tell a verdict about the rest it sees from one a cached board still carries about an older rest. */
|
|
14707
|
+
handoffRestAt: external_exports.string().optional(),
|
|
14530
14708
|
/** The EVENT snooze on the resting card is armed for this exact rest — the human has said "hide this
|
|
14531
14709
|
* until something reports". Distinct from `snoozedUntil`, which is a wall-clock park on the whole
|
|
14532
14710
|
* thread: this one has no deadline and clears itself when the thread comes to a NEW rest.
|
|
@@ -14535,8 +14713,20 @@ var init_src = __esm({
|
|
|
14535
14713
|
* the reasoning that the card states a FACT the drawer must keep showing or it blanks at rest and
|
|
14536
14714
|
* reads as "the agent died". That reasoning holds for a thread nobody has parked; once the human has
|
|
14537
14715
|
* explicitly parked THIS rest, showing them the same card with the same button one surface over is
|
|
14538
|
-
* not information, and they said so.
|
|
14716
|
+
* not information, and they said so.
|
|
14717
|
+
*
|
|
14718
|
+
* The header's "Until activity" (SnoozeMenu, 2026-10-19) arms the same snooze on any rested thread. */
|
|
14539
14719
|
bgSnoozed: external_exports.boolean().optional(),
|
|
14720
|
+
/** Something of the thread's own can wake it without a person — a running sub-agent or shell, an
|
|
14721
|
+
* armed watch or timer, a Goal that fires on rest or on a clock (board.wakesOnItsOwn). The snooze
|
|
14722
|
+
* menu greys out "Until activity" without it, and the server stops honouring that snooze once it
|
|
14723
|
+
* goes false. Absent on a dead worker and on an archived thread. */
|
|
14724
|
+
wakesOnItsOwn: external_exports.boolean().optional(),
|
|
14725
|
+
/** A QUIET PARK THE BOARD REFUSED because an earlier `needs_input` handoff still stands: the human has
|
|
14726
|
+
* neither typed to the thread nor snoozed a rest since it (board handoffStands). The surfaces keep that
|
|
14727
|
+
* handoff's fence drawn (web lib/fenceBlocks heldHandoffIndex), since it is what the thread is in the
|
|
14728
|
+
* queue for. Set only while it is what queued the thread. */
|
|
14729
|
+
heldHandoff: external_exports.boolean().optional(),
|
|
14540
14730
|
// Which Claude transport serves this thread. "broker" — a session-broker-owned Agent SDK session with
|
|
14541
14731
|
// a typed control channel — is the only one there is; ABSENT means a row dispatched before the broker
|
|
14542
14732
|
// became the sole transport, which frizz can no longer reach that way. Only the broker can be asked to
|
|
@@ -14690,6 +14880,11 @@ var init_src = __esm({
|
|
|
14690
14880
|
// falls back to rendering the plain `errors` strings.
|
|
14691
14881
|
errorItems: external_exports.array(BoardErrorItem).optional()
|
|
14692
14882
|
});
|
|
14883
|
+
BoardState = external_exports.object({
|
|
14884
|
+
board: BoardSnapshot,
|
|
14885
|
+
seq: external_exports.number().int().min(0),
|
|
14886
|
+
bootId: external_exports.string()
|
|
14887
|
+
});
|
|
14693
14888
|
QuotaWindow = external_exports.object({
|
|
14694
14889
|
key: external_exports.string(),
|
|
14695
14890
|
// stable id: "5h" | "weekly" (provider-neutral)
|
|
@@ -14705,8 +14900,20 @@ var init_src = __esm({
|
|
|
14705
14900
|
planType: external_exports.string().optional(),
|
|
14706
14901
|
// "pro" / "max" / etc. when the source reports it
|
|
14707
14902
|
windows: external_exports.array(QuotaWindow),
|
|
14708
|
-
detail: external_exports.string().optional()
|
|
14903
|
+
detail: external_exports.string().optional(),
|
|
14709
14904
|
// why unavailable, or an extra note
|
|
14905
|
+
// Recovery evidence from a LIVE Codex read only. Rollout quota has no account identity or freshness
|
|
14906
|
+
// guarantee and must never wake a failed thread. accountKey is a hash, not a credential/account id.
|
|
14907
|
+
codexRecovery: external_exports.object({
|
|
14908
|
+
observedAt: external_exports.number(),
|
|
14909
|
+
// request start, unix milliseconds (not the later cache/read time)
|
|
14910
|
+
accountKey: external_exports.string().optional(),
|
|
14911
|
+
creditsAvailable: external_exports.boolean().optional(),
|
|
14912
|
+
ordinaryUsageAllowed: external_exports.boolean().nullable().optional(),
|
|
14913
|
+
// null is explicitly unavailable
|
|
14914
|
+
blocked: external_exports.boolean().optional()
|
|
14915
|
+
// explicit workspace/spend-control denial beats apparent headroom
|
|
14916
|
+
}).optional()
|
|
14710
14917
|
});
|
|
14711
14918
|
QuotaSnapshot = external_exports.object({
|
|
14712
14919
|
claude: ProviderQuota,
|
|
@@ -14768,6 +14975,16 @@ var init_src = __esm({
|
|
|
14768
14975
|
* Machine-level: which chrome you want is a property of the person, not the repo.
|
|
14769
14976
|
*/
|
|
14770
14977
|
projectRail: external_exports.boolean(),
|
|
14978
|
+
/**
|
|
14979
|
+
* CHILD THREADS, an experiment (2026-11-04). On, a worker may run `frizzctl spawn-child`: a new thread
|
|
14980
|
+
* in this project, on either harness, linked to the thread that spawned it. Its rests that would
|
|
14981
|
+
* have queued for the human wake its parent instead, it can own its own PR watchers, and it sits
|
|
14982
|
+
* under its parent on the board rather than in the queue. Off (the default), the command refuses and
|
|
14983
|
+
* the worker contract never mentions it. A child spawned while it was on keeps its link either way.
|
|
14984
|
+
*
|
|
14985
|
+
* Machine-level, like the rail: whether to try an experiment is the person's call, not the repo's.
|
|
14986
|
+
*/
|
|
14987
|
+
childThreads: external_exports.boolean(),
|
|
14771
14988
|
// There is no `font` key any more. The interface rendered in one of two type families as a machine
|
|
14772
14989
|
// setting until 2026-09-19 (maintainer: "let's drop monospace as an option"); every surface is sans
|
|
14773
14990
|
// now, and index.html pins `data-font="sans"` on <html> directly. Settings is a non-strict object,
|
|
@@ -14807,6 +15024,19 @@ var init_src = __esm({
|
|
|
14807
15024
|
command: external_exports.string().trim().min(1).max(1024),
|
|
14808
15025
|
args: external_exports.array(external_exports.string().max(1024)).max(32).optional()
|
|
14809
15026
|
})).max(32).optional(),
|
|
15027
|
+
// A command each runtime's workers are started THROUGH, per runtime: `op run --env-file .env --`
|
|
15028
|
+
// makes a new Claude worker start as `op run --env-file .env -- /path/to/claude …`. Kept as the text
|
|
15029
|
+
// the operator typed and split into words by parseLaunchPrefix (launch-prefix.ts), the one parser the
|
|
15030
|
+
// settings field and the spawn share; it is never run in a shell. Claude applies it at every fork of a
|
|
15031
|
+
// thread's worker; Codex to the app-server the project's Codex threads share, which is replaced at
|
|
15032
|
+
// the next moment no Codex turn is running once the prefix changes. ACP agents need no key here:
|
|
15033
|
+
// their `command` above is already the operator's own. A string rather than a refined argv so a
|
|
15034
|
+
// stored value the parser later stops accepting fails ITS launch, not the whole settings parse
|
|
15035
|
+
// (getSettings falls back to every default on a parse miss). Optional so an old blob parses.
|
|
15036
|
+
launchPrefix: external_exports.object({
|
|
15037
|
+
claude: external_exports.string().max(LAUNCH_PREFIX_MAX_LENGTH).optional(),
|
|
15038
|
+
codex: external_exports.string().max(LAUNCH_PREFIX_MAX_LENGTH).optional()
|
|
15039
|
+
}).optional(),
|
|
14810
15040
|
// The GitHub batch-dispatch prompt template (the picker's per-item worker prompt). Optional: when
|
|
14811
15041
|
// unset OR blank the server falls back to its exported DEFAULT_GITHUB_PROMPT. Substitution tokens
|
|
14812
15042
|
// the server fills: {repo} {n} {title} {url} {labels} {body}. The leading `THREAD: <slug>` tag is
|
|
@@ -14911,8 +15141,10 @@ var init_src = __esm({
|
|
|
14911
15141
|
// human wrote it, and as fast as a steer does.
|
|
14912
15142
|
answers: external_exports.array(QuestionAnswerSchema).optional(),
|
|
14913
15143
|
// SEND TO THE QUEUE: while the agent is mid-turn, Frizz keeps this message and delivers it when the
|
|
14914
|
-
// thread comes to rest, so it opens the next turn instead of steering this one.
|
|
14915
|
-
//
|
|
15144
|
+
// thread comes to rest, so it opens the next turn instead of steering this one. The queue goes out ONE
|
|
15145
|
+
// message per turn (2026-10-20), so a message also waits, even at rest, behind one already held or one
|
|
15146
|
+
// whose turn has not ended. With nothing ahead of it, it is an ordinary send. See `held` and
|
|
15147
|
+
// nextHeldRelease in the server's delivery-ledger.ts.
|
|
14916
15148
|
hold: external_exports.boolean().optional(),
|
|
14917
15149
|
// Generated once before the optimistic clear so a transport replay can be idempotent.
|
|
14918
15150
|
deliveryId: external_exports.string().min(1).max(200).optional(),
|
|
@@ -14922,17 +15154,19 @@ var init_src = __esm({
|
|
|
14922
15154
|
// newer frizz build in place (hooks are read once, at startup). Everything else that needs a fresh
|
|
14923
15155
|
// process derives it server-side; see needsFreshProcessForLimit.
|
|
14924
15156
|
freshProcess: external_exports.boolean().optional(),
|
|
14925
|
-
//
|
|
14926
|
-
//
|
|
14927
|
-
//
|
|
14928
|
-
//
|
|
14929
|
-
//
|
|
15157
|
+
// SEND NOW: this message is read at once instead of when the operation the worker is running
|
|
15158
|
+
// finishes. The operator's forced send (⌘⏎), and opt-in because it can cost work in flight. Since
|
|
15159
|
+
// 2026-10-13 it usually does not: the runtime moves a running command or foreground sub-agent to the
|
|
15160
|
+
// background and takes the message into the same turn. Only when it has nothing to move — the model
|
|
15161
|
+
// is mid-answer, a wake Frizz wrote is waiting too — is the turn aborted, as it always was before,
|
|
15162
|
+
// and that costs the in-flight call its result. (An abort has not cost the worker's BACKGROUND
|
|
15163
|
+
// sub-agents since 2026-09-24: the broker's SDK query declares `perTaskStopAffordance`.)
|
|
14930
15164
|
//
|
|
14931
15165
|
// It exists because delivery is ALREADY as fast as queueing can be. Measured over 14 days of this
|
|
14932
15166
|
// project's own transcripts, Claude Code drains its queue at the first sampling boundary that
|
|
14933
15167
|
// exists; the wait an operator feels is the remaining time of whatever was in flight (a long `Bash`,
|
|
14934
15168
|
// or one 73–133s reasoning+answer generation), which put mid-turn operator prose at p50 13.8s,
|
|
14935
|
-
// p90 49s, p99 2.5m.
|
|
15169
|
+
// p90 49s, p99 2.5m. Getting that operation out of the way is the only lever left.
|
|
14936
15170
|
//
|
|
14937
15171
|
// Broker-backed Claude only — that is every Claude thread dispatched since the broker cutover. On
|
|
14938
15172
|
// any other runtime the message is delivered normally and this is ignored, never refused: a send
|
|
@@ -14947,16 +15181,38 @@ var init_src = __esm({
|
|
|
14947
15181
|
SteerThreadInput = external_exports.object({
|
|
14948
15182
|
from: ThreadSlug,
|
|
14949
15183
|
to: ThreadSlug,
|
|
15184
|
+
project: external_exports.string().min(1).max(200).optional(),
|
|
14950
15185
|
message: external_exports.string().min(1).max(1e5)
|
|
14951
15186
|
}).strict();
|
|
14952
15187
|
SteerThreadResult = external_exports.object({
|
|
14953
15188
|
slug: ThreadSlug,
|
|
14954
15189
|
title: external_exports.string(),
|
|
15190
|
+
// The target's project URL slug — present only when it is NOT the caller's project, so the receipt's
|
|
15191
|
+
// link can name it (`/project/<project>/thread/<slug>`) and a reply can be addressed back.
|
|
15192
|
+
project: external_exports.string().optional(),
|
|
14955
15193
|
// What the target was doing when the message arrived: `running` ⇒ it reads it at its next tool
|
|
14956
15194
|
// boundary; `resting` ⇒ the message started its next turn; `reopened` ⇒ it was marked done and this
|
|
14957
15195
|
// message put it back on the board.
|
|
14958
15196
|
delivery: external_exports.enum(["running", "resting", "reopened"])
|
|
14959
15197
|
}).strict();
|
|
15198
|
+
SpawnThreadInput = DispatchInput.pick({ title: true, prompt: true, model: true, backend: true, effort: true }).extend({
|
|
15199
|
+
from: ThreadSlug,
|
|
15200
|
+
project: external_exports.string().min(1).max(200)
|
|
15201
|
+
}).strict();
|
|
15202
|
+
SpawnThreadResult = external_exports.object({
|
|
15203
|
+
slug: ThreadSlug,
|
|
15204
|
+
sessionId: external_exports.string(),
|
|
15205
|
+
// The new thread's project URL slug — present only when it is NOT the caller's project, as on a steer.
|
|
15206
|
+
project: external_exports.string().optional()
|
|
15207
|
+
}).strict();
|
|
15208
|
+
SpawnChildInput = DispatchInput.pick({ title: true, prompt: true, model: true, backend: true, effort: true }).extend({
|
|
15209
|
+
from: ThreadSlug
|
|
15210
|
+
}).strict();
|
|
15211
|
+
SpawnChildResult = external_exports.union([
|
|
15212
|
+
external_exports.object({ slug: ThreadSlug, sessionId: external_exports.string() }).strict(),
|
|
15213
|
+
// The experiment is off, or the caller is itself a child. Exit 2 at the command, like every refusal.
|
|
15214
|
+
external_exports.object({ refused: external_exports.string() }).strict()
|
|
15215
|
+
]);
|
|
14960
15216
|
DismissFailedFollowUpInput = external_exports.object({
|
|
14961
15217
|
slug: ThreadSlug,
|
|
14962
15218
|
deliveryId: external_exports.string().min(1).max(200)
|
|
@@ -15009,6 +15265,17 @@ var init_src = __esm({
|
|
|
15009
15265
|
sessionId: external_exports.string().min(1),
|
|
15010
15266
|
pinned: external_exports.boolean()
|
|
15011
15267
|
}).strict();
|
|
15268
|
+
ReorderPinnedThreadsInput = external_exports.object({
|
|
15269
|
+
slugs: external_exports.array(ThreadSlug).max(500)
|
|
15270
|
+
}).strict();
|
|
15271
|
+
SetThreadConsolePinnedInput = external_exports.object({
|
|
15272
|
+
slug: ThreadSlug,
|
|
15273
|
+
sessionId: external_exports.string().min(1),
|
|
15274
|
+
pinned: external_exports.boolean()
|
|
15275
|
+
}).strict();
|
|
15276
|
+
ReorderConsolePinnedThreadsInput = external_exports.object({
|
|
15277
|
+
pins: external_exports.array(external_exports.object({ slug: ThreadSlug, at: SnoozeUntil }).strict()).max(500)
|
|
15278
|
+
}).strict();
|
|
15012
15279
|
SetThreadRecurringPromptInput = external_exports.object({
|
|
15013
15280
|
slug: ThreadSlug,
|
|
15014
15281
|
sessionId: external_exports.string().min(1),
|
|
@@ -15065,7 +15332,7 @@ var init_src = __esm({
|
|
|
15065
15332
|
timers: external_exports.array(ThreadTimerView)
|
|
15066
15333
|
}).strict();
|
|
15067
15334
|
ThreadActivityItem = external_exports.object({
|
|
15068
|
-
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "command"]),
|
|
15335
|
+
kind: external_exports.enum(["shell", "agent", "timer", "pr", "issue", "command", "thread"]),
|
|
15069
15336
|
/** The string a `<kind>:` fence line must carry. For a shell that is the runtime task id the worker
|
|
15070
15337
|
* was shown; for a PR, `owner/repo#N`; for a timer, its `tmr_…` row id. */
|
|
15071
15338
|
id: external_exports.string(),
|
|
@@ -15094,6 +15361,62 @@ var init_src = __esm({
|
|
|
15094
15361
|
* agent to read out the current set of watchers and questions?"). */
|
|
15095
15362
|
questions: external_exports.array(RegisteredQuestionView).default([])
|
|
15096
15363
|
}).strict();
|
|
15364
|
+
ThreadBand = external_exports.enum(["rested", "active", "snoozed", "done"]);
|
|
15365
|
+
ThreadListing = external_exports.object({
|
|
15366
|
+
slug: ThreadSlug,
|
|
15367
|
+
/** The title the sidebar shows (displayTitle). */
|
|
15368
|
+
title: external_exports.string(),
|
|
15369
|
+
band: ThreadBand,
|
|
15370
|
+
runtime: RuntimeState,
|
|
15371
|
+
backend: Backend.optional(),
|
|
15372
|
+
model: external_exports.string().optional(),
|
|
15373
|
+
/** The human pinned it to the top of this board's sidebar. */
|
|
15374
|
+
pinned: external_exports.literal(true).optional(),
|
|
15375
|
+
/** The caption of the tool call in flight, while a turn is running ("Running the focused tests"). */
|
|
15376
|
+
doing: external_exports.string().optional(),
|
|
15377
|
+
/** The first line of its newest message — the verdict line of a handoff. Capped. */
|
|
15378
|
+
lastLine: external_exports.string().optional(),
|
|
15379
|
+
/** The signal fence its last rest ended on, while the board still reads it. */
|
|
15380
|
+
fence: external_exports.enum(["done", "awaiting"]).optional(),
|
|
15381
|
+
/** Questions standing at its current rest (hasStandingQuestion's rule), plus a native ask or a
|
|
15382
|
+
* permission prompt the human has not answered. */
|
|
15383
|
+
questions: external_exports.number().int(),
|
|
15384
|
+
/** Its own sub-agents still running (direct children only, as the board counts them). */
|
|
15385
|
+
subAgents: external_exports.number().int(),
|
|
15386
|
+
/** Background shells still running. */
|
|
15387
|
+
shells: external_exports.number().int(),
|
|
15388
|
+
/** CHILD THREADS it started (`frizzctl spawn-child`) that are still open. A child is never a row of its
|
|
15389
|
+
* own here, as on the board (drawnUnderParent), so this is how another thread finds one to steer. */
|
|
15390
|
+
children: external_exports.array(external_exports.object({ slug: ThreadSlug, title: external_exports.string(), runtime: RuntimeState }).strict()).optional(),
|
|
15391
|
+
/** What its armed watches are on: `owner/repo#N` for a PR or issue, the prompt for a timer, the
|
|
15392
|
+
* command for a command watch. */
|
|
15393
|
+
watches: external_exports.array(external_exports.string()),
|
|
15394
|
+
/** Killed by a usage limit, waiting for the window to reset. */
|
|
15395
|
+
limited: external_exports.literal(true).optional(),
|
|
15396
|
+
/** Its process is gone with the turn unfinished. */
|
|
15397
|
+
crashed: external_exports.literal(true).optional(),
|
|
15398
|
+
spawnedAt: external_exports.string().optional(),
|
|
15399
|
+
/** When its own turn last came to rest (ThreadView.lastAssistantAt). */
|
|
15400
|
+
restedAt: external_exports.string().optional(),
|
|
15401
|
+
lastActivityAt: external_exports.string().optional(),
|
|
15402
|
+
/** A wall-clock snooze still in the future. */
|
|
15403
|
+
snoozedUntil: external_exports.string().optional(),
|
|
15404
|
+
archivedAt: external_exports.string().optional()
|
|
15405
|
+
}).strict();
|
|
15406
|
+
ListThreadsInput = external_exports.object({
|
|
15407
|
+
/** Another project's board — its slug, id or registered name — instead of the one the call lands on. */
|
|
15408
|
+
project: external_exports.string().trim().min(1).optional(),
|
|
15409
|
+
/** Include the Done band (archived threads), which on a long-lived board is nearly every thread. */
|
|
15410
|
+
all: external_exports.boolean().optional()
|
|
15411
|
+
}).strict();
|
|
15412
|
+
ListThreadsResult = external_exports.object({
|
|
15413
|
+
/** The board's project: its URL slug, its registry id, and the name the board shows. */
|
|
15414
|
+
project: external_exports.object({ slug: external_exports.string(), id: external_exports.string(), name: external_exports.string() }).strict(),
|
|
15415
|
+
/** Rested, then active, then snoozed, then done; newest activity first within each. */
|
|
15416
|
+
threads: external_exports.array(ThreadListing),
|
|
15417
|
+
/** Every band's size, the Done band's included when its threads are not listed. */
|
|
15418
|
+
counts: external_exports.object({ rested: external_exports.number().int(), active: external_exports.number().int(), snoozed: external_exports.number().int(), done: external_exports.number().int() }).strict()
|
|
15419
|
+
}).strict();
|
|
15097
15420
|
SetOwnThreadTimerResult = external_exports.object({
|
|
15098
15421
|
id: external_exports.string(),
|
|
15099
15422
|
fireAt: external_exports.string(),
|
|
@@ -15159,6 +15482,42 @@ var init_src = __esm({
|
|
|
15159
15482
|
}).strict();
|
|
15160
15483
|
ThreadSkillsInput = external_exports.object({ slug: ThreadSlug }).strict();
|
|
15161
15484
|
ThreadSkillsResult = external_exports.object({ skills: external_exports.array(ThreadSkill).max(1024) }).strict();
|
|
15485
|
+
McpTarget = external_exports.union([
|
|
15486
|
+
external_exports.object({ slug: ThreadSlug }).strict(),
|
|
15487
|
+
external_exports.object({ backend: Backend, model: external_exports.string().trim().max(200).optional() }).strict()
|
|
15488
|
+
]);
|
|
15489
|
+
McpServerState = external_exports.enum(["connected", "needs-auth", "failed", "pending", "disabled"]);
|
|
15490
|
+
McpServerReading = external_exports.object({
|
|
15491
|
+
name: external_exports.string().min(1).max(256),
|
|
15492
|
+
state: McpServerState,
|
|
15493
|
+
tools: external_exports.number().int().nonnegative().optional(),
|
|
15494
|
+
error: external_exports.string().max(2e3).optional(),
|
|
15495
|
+
remote: external_exports.boolean().optional()
|
|
15496
|
+
}).strict();
|
|
15497
|
+
McpStatusResult = external_exports.object({
|
|
15498
|
+
backend: Backend,
|
|
15499
|
+
/** The agent's display name, for the empty state ("Gemini CLI doesn't support …"). */
|
|
15500
|
+
agent: external_exports.string().max(200),
|
|
15501
|
+
supported: external_exports.boolean(),
|
|
15502
|
+
servers: external_exports.array(McpServerReading).max(256),
|
|
15503
|
+
/**
|
|
15504
|
+
* Why the servers can't be read right now (Claude Code isn't signed in on this machine), drawn as the
|
|
15505
|
+
* menu's note. An answer, not an error: every prompt box asks on page load, and a machine without the
|
|
15506
|
+
* agent's login is an ordinary machine, not a failing server.
|
|
15507
|
+
*/
|
|
15508
|
+
note: external_exports.string().max(500).optional()
|
|
15509
|
+
}).strict();
|
|
15510
|
+
McpSignInInput = external_exports.object({ target: McpTarget, server: external_exports.string().min(1).max(256) }).strict();
|
|
15511
|
+
McpSignInState = external_exports.enum(["waiting", "done", "failed", "expired", "cancelled"]);
|
|
15512
|
+
McpSignInResult = external_exports.object({
|
|
15513
|
+
attemptId: external_exports.string().min(1).max(64),
|
|
15514
|
+
/** Absent when the agent finished without the human (a refresh token was still good). */
|
|
15515
|
+
authUrl: external_exports.string().url().max(8192).optional(),
|
|
15516
|
+
state: McpSignInState
|
|
15517
|
+
}).strict();
|
|
15518
|
+
McpSignInAttemptInput = external_exports.object({ attemptId: external_exports.string().min(1).max(64) }).strict();
|
|
15519
|
+
McpSignInPollResult = external_exports.object({ state: McpSignInState, error: external_exports.string().max(2e3).optional() }).strict();
|
|
15520
|
+
McpSignInFinishInput = external_exports.object({ attemptId: external_exports.string().min(1).max(64), callbackUrl: external_exports.string().trim().min(1).max(8192) }).strict();
|
|
15162
15521
|
SetThreadProfileInput = external_exports.object({
|
|
15163
15522
|
slug: ThreadSlug,
|
|
15164
15523
|
model: external_exports.string().trim().min(1).max(200),
|
|
@@ -15219,6 +15578,21 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15219
15578
|
nameWithOwner: external_exports.string().nullable(),
|
|
15220
15579
|
authed: external_exports.boolean()
|
|
15221
15580
|
});
|
|
15581
|
+
GithubAccount = external_exports.object({
|
|
15582
|
+
signedIn: external_exports.boolean(),
|
|
15583
|
+
/** "app" — Frizz's GitHub App, signed in here; "token" — a stored token; "env" — FRIZZ_GITHUB_TOKEN. */
|
|
15584
|
+
source: external_exports.enum(["app", "token", "env"]).optional(),
|
|
15585
|
+
login: external_exports.string().nullable(),
|
|
15586
|
+
/** Why a stored sign-in stopped working. */
|
|
15587
|
+
problem: external_exports.string().optional(),
|
|
15588
|
+
/** Whether this build knows Frizz's GitHub App, i.e. whether "Sign in" can be offered at all. */
|
|
15589
|
+
appRegistered: external_exports.boolean(),
|
|
15590
|
+
signIn: external_exports.discriminatedUnion("phase", [
|
|
15591
|
+
external_exports.object({ phase: external_exports.literal("idle") }),
|
|
15592
|
+
external_exports.object({ phase: external_exports.literal("pending"), userCode: external_exports.string(), verificationUri: external_exports.string(), expiresAt: external_exports.number() }),
|
|
15593
|
+
external_exports.object({ phase: external_exports.literal("failed"), message: external_exports.string() })
|
|
15594
|
+
])
|
|
15595
|
+
});
|
|
15222
15596
|
GithubRefCard = external_exports.object({
|
|
15223
15597
|
ref: external_exports.string(),
|
|
15224
15598
|
kind: external_exports.enum(["issue", "pr", "commit"]),
|
|
@@ -15318,6 +15692,14 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15318
15692
|
// same pre-restart back-compat reason as on BoardSnapshot.
|
|
15319
15693
|
errorItems: external_exports.array(BoardErrorItem).optional()
|
|
15320
15694
|
});
|
|
15695
|
+
BoardDeltaEvent = external_exports.object({
|
|
15696
|
+
type: external_exports.literal("board-delta"),
|
|
15697
|
+
seq: external_exports.number(),
|
|
15698
|
+
bootId: external_exports.string(),
|
|
15699
|
+
upserts: external_exports.array(ThreadView),
|
|
15700
|
+
removed: external_exports.array(ThreadSlug),
|
|
15701
|
+
meta: BoardMeta.optional()
|
|
15702
|
+
});
|
|
15321
15703
|
ServerEvent = external_exports.discriminatedUnion("type", [
|
|
15322
15704
|
external_exports.object({
|
|
15323
15705
|
type: external_exports.literal("board"),
|
|
@@ -15329,17 +15711,7 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15329
15711
|
seq: external_exports.number().optional(),
|
|
15330
15712
|
bootId: external_exports.string().optional()
|
|
15331
15713
|
}),
|
|
15332
|
-
|
|
15333
|
-
// Keyed per-thread delta. `upserts` are COMPLETE ThreadViews for threads whose serialization
|
|
15334
|
-
// changed (or are new); `removed` are ids gone from the board; `meta` is present only when a
|
|
15335
|
-
// board-level field changed. Emitted only by a post-restart server → seq/bootId are required here.
|
|
15336
|
-
type: external_exports.literal("board-delta"),
|
|
15337
|
-
seq: external_exports.number(),
|
|
15338
|
-
bootId: external_exports.string(),
|
|
15339
|
-
upserts: external_exports.array(ThreadView),
|
|
15340
|
-
removed: external_exports.array(ThreadSlug),
|
|
15341
|
-
meta: BoardMeta.optional()
|
|
15342
|
-
}),
|
|
15714
|
+
BoardDeltaEvent,
|
|
15343
15715
|
external_exports.object({
|
|
15344
15716
|
type: external_exports.literal("notify"),
|
|
15345
15717
|
slug: ThreadSlug,
|
|
@@ -15366,6 +15738,14 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15366
15738
|
type: external_exports.literal("projects-changed")
|
|
15367
15739
|
}).strict()
|
|
15368
15740
|
]);
|
|
15741
|
+
BoardDeltasInput = external_exports.object({
|
|
15742
|
+
bootId: external_exports.string().min(1).max(200),
|
|
15743
|
+
seq: external_exports.number().int().min(0)
|
|
15744
|
+
});
|
|
15745
|
+
BoardDeltasResult = external_exports.union([
|
|
15746
|
+
external_exports.object({ bootId: external_exports.string(), deltas: external_exports.array(BoardDeltaEvent) }),
|
|
15747
|
+
external_exports.object({ bootId: external_exports.string(), resync: external_exports.enum(["boot", "expired", "ahead"]) })
|
|
15748
|
+
]);
|
|
15369
15749
|
TranscriptEdit = external_exports.object({
|
|
15370
15750
|
file: external_exports.string(),
|
|
15371
15751
|
old: external_exports.string(),
|
|
@@ -15733,7 +16113,12 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15733
16113
|
external_exports.object({ t: external_exports.literal("sub"), topic: external_exports.literal("file"), path: SocketFilePath }).strict(),
|
|
15734
16114
|
external_exports.object({ t: external_exports.literal("unsub"), topic: external_exports.literal("file"), path: SocketFilePath }).strict()
|
|
15735
16115
|
]);
|
|
15736
|
-
|
|
16116
|
+
SocketBoardClientMsg = external_exports.object({
|
|
16117
|
+
t: external_exports.literal("board"),
|
|
16118
|
+
bootId: external_exports.string().min(1).max(200),
|
|
16119
|
+
seq: external_exports.number().int().min(0)
|
|
16120
|
+
}).strict();
|
|
16121
|
+
SocketClientMsg = external_exports.union([SocketTranscriptClientMsg, SocketFileClientMsg, SocketBoardClientMsg]);
|
|
15737
16122
|
ProjectCard = external_exports.object({
|
|
15738
16123
|
id: external_exports.string(),
|
|
15739
16124
|
slug: external_exports.string(),
|
|
@@ -15743,12 +16128,15 @@ ${DISPATCH_TASK_BANNER}
|
|
|
15743
16128
|
/** The directory is gone — moved or deleted. The card stays so it can be reopened or forgotten. */
|
|
15744
16129
|
stale: external_exports.boolean(),
|
|
15745
16130
|
/**
|
|
15746
|
-
*
|
|
16131
|
+
* A hash of the icon's bytes; for a project with no icon, when it was last scanned. Absent if it
|
|
16132
|
+
* never has been.
|
|
15747
16133
|
*
|
|
15748
16134
|
* Carried purely so the client can hang it off the `/_frizz/project-icon` URL: the icon bytes are
|
|
15749
16135
|
* cached hard (a rail of forty squares is forty requests, and none of them should recur), which
|
|
15750
16136
|
* means a newly uploaded icon would otherwise stay invisible behind the cached old one. A changed
|
|
15751
16137
|
* version is a changed URL, so the swap is immediate without weakening the caching for everyone.
|
|
16138
|
+
* It is the HASH, not the scan time, because a rescan restamps the time every ten minutes whether
|
|
16139
|
+
* or not the picture changed, and every restamp was a fresh download of every icon.
|
|
15752
16140
|
*
|
|
15753
16141
|
* Deliberately NOT "does this project have an icon". Answering that for a project nobody has
|
|
15754
16142
|
* scanned yet would mean scanning it, and this list is one file read on purpose.
|
|
@@ -21281,7 +21669,7 @@ var init_project_root = __esm({
|
|
|
21281
21669
|
});
|
|
21282
21670
|
|
|
21283
21671
|
// packages/server/src/project-registry.ts
|
|
21284
|
-
import { closeSync as closeSync4, existsSync as existsSync5, fsyncSync as fsyncSync4, mkdirSync as mkdirSync9, openSync as openSync5, readFileSync as readFileSync9, readdirSync as readdirSync2, realpathSync as realpathSync4, renameSync as renameSync5, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
21672
|
+
import { closeSync as closeSync4, existsSync as existsSync5, fsyncSync as fsyncSync4, lstatSync, mkdirSync as mkdirSync9, openSync as openSync5, readFileSync as readFileSync9, readdirSync as readdirSync2, realpathSync as realpathSync4, renameSync as renameSync5, rmSync as rmSync7, unlinkSync, writeFileSync as writeFileSync9 } from "node:fs";
|
|
21285
21673
|
import { homedir as homedir9 } from "node:os";
|
|
21286
21674
|
import { basename as basename4, dirname as dirname9, join as join12 } from "node:path";
|
|
21287
21675
|
function registryPath(home = homedir9()) {
|
|
@@ -21448,6 +21836,7 @@ var init_project_registry = __esm({
|
|
|
21448
21836
|
"term",
|
|
21449
21837
|
"attach",
|
|
21450
21838
|
"local-image",
|
|
21839
|
+
"local-file",
|
|
21451
21840
|
"local-visualization",
|
|
21452
21841
|
// `control` answers `/control/stop` and `/control/status` — the supervisor channel the board polls
|
|
21453
21842
|
// every few seconds. It was missing from this list, so a directory called `control` could have taken
|
|
@@ -24025,7 +24414,7 @@ async function runDeviceFlow(options2) {
|
|
|
24025
24414
|
const clientId = options2.clientId === void 0 ? githubClientId() : options2.clientId;
|
|
24026
24415
|
if (!clientId) {
|
|
24027
24416
|
throw new GithubSignInError(
|
|
24028
|
-
"custom frizz.sh
|
|
24417
|
+
"custom frizz.sh subdomains are not available in this version of Frizz yet: its GitHub sign-in is not set up. A private frizz.sh subdomain needs no account and works today.",
|
|
24029
24418
|
"not-configured"
|
|
24030
24419
|
);
|
|
24031
24420
|
}
|
|
@@ -24563,7 +24952,7 @@ var CUSTOM_NAMES_OFFERED = false;
|
|
|
24563
24952
|
function customNamesOffered(env = process.env) {
|
|
24564
24953
|
return CUSTOM_NAMES_OFFERED || env.FRIZZ_CUSTOM_NAMES === "1";
|
|
24565
24954
|
}
|
|
24566
|
-
var CUSTOM_NAMES_PAUSED = "custom frizz.sh
|
|
24955
|
+
var CUSTOM_NAMES_PAUSED = "custom frizz.sh subdomains are paused for now \u2014 press enter for a private subdomain (no account needed), or answer with the hostname of a tunnel you run";
|
|
24567
24956
|
function isClaimedConfig(config) {
|
|
24568
24957
|
return typeof config.claim === "string" && config.claim.length > 0;
|
|
24569
24958
|
}
|
|
@@ -24583,7 +24972,7 @@ async function reconcileCloudConfig(config, port, home = homedir5(), onNotice, o
|
|
|
24583
24972
|
if (!label) return config;
|
|
24584
24973
|
if (!isAnonymousClaimName(label) && !customNamesOffered()) {
|
|
24585
24974
|
throw new Error(
|
|
24586
|
-
`${config.hostname} needs moving onto the Frizz relay, which means claiming it again, and custom frizz.sh
|
|
24975
|
+
`${config.hostname} needs moving onto the Frizz relay, which means claiming it again, and custom frizz.sh subdomains are paused for now \u2014 press R to pick a private subdomain or another setup`
|
|
24587
24976
|
);
|
|
24588
24977
|
}
|
|
24589
24978
|
onNotice?.(
|
|
@@ -24665,7 +25054,7 @@ async function establishCloudConfig(answer, port, home = homedir5(), origin, git
|
|
|
24665
25054
|
}
|
|
24666
25055
|
if (!REGISTRAR_IS_LIVE && !origin && !process.env.FRIZZ_REGISTRAR) {
|
|
24667
25056
|
throw new Error(
|
|
24668
|
-
"claiming a
|
|
25057
|
+
"claiming a frizz.sh subdomain is not available yet.\n Reach this board through a tunnel of your own instead \u2014 see docs/remote-access.md \u2014 and\n answer with its hostname rather than a bare name."
|
|
24669
25058
|
);
|
|
24670
25059
|
}
|
|
24671
25060
|
const anonymous = answer === "" || isAnonymousClaimName(answer);
|
|
@@ -24681,7 +25070,7 @@ async function establishCloudConfig(answer, port, home = homedir5(), origin, git
|
|
|
24681
25070
|
token = authorization.token;
|
|
24682
25071
|
if (authorization.login) console.log(` claiming ${name}.frizz.sh for GitHub user ${authorization.login}`);
|
|
24683
25072
|
} else {
|
|
24684
|
-
console.log(" claiming a private
|
|
25073
|
+
console.log(" claiming a private frizz.sh subdomain \u2014 no account needed");
|
|
24685
25074
|
}
|
|
24686
25075
|
let result;
|
|
24687
25076
|
try {
|
|
@@ -24881,8 +25270,8 @@ async function probeTailscale() {
|
|
|
24881
25270
|
// src/remote-pane.ts
|
|
24882
25271
|
init_src();
|
|
24883
25272
|
var CHOICES = [
|
|
24884
|
-
{ kind: "private", title: "
|
|
24885
|
-
{ kind: "frizz", title: "Custom
|
|
25273
|
+
{ kind: "private", title: "frizz.sh subdomain", blurb: "a private, unguessable address; no account, nothing to install" },
|
|
25274
|
+
{ kind: "frizz", title: "Custom subdomain", blurb: "<name>.frizz.sh of your choosing; needs a GitHub account" },
|
|
24886
25275
|
{ kind: "cloudflare", title: "Cloudflare Tunnel", blurb: "a domain you own on Cloudflare; cloudflared on this machine" },
|
|
24887
25276
|
{ kind: "tailscale", title: "Tailscale", blurb: "your tailnet; tailscale serve does the TLS" },
|
|
24888
25277
|
{ kind: "other", title: "Something else", blurb: "any proxy or tunnel you run \u2014 tell Frizz its address" },
|
|
@@ -24892,7 +25281,7 @@ function choicesFor(current) {
|
|
|
24892
25281
|
if (customNamesOffered()) return CHOICES;
|
|
24893
25282
|
if (kindOf(current) === "frizz") {
|
|
24894
25283
|
return CHOICES.map(
|
|
24895
|
-
(choice) => choice.kind === "frizz" ? { ...choice, blurb: `${current.hostname} \u2014 kept and renewed; new custom
|
|
25284
|
+
(choice) => choice.kind === "frizz" ? { ...choice, blurb: `${current.hostname} \u2014 kept and renewed; new custom subdomains are paused` } : choice
|
|
24896
25285
|
);
|
|
24897
25286
|
}
|
|
24898
25287
|
return CHOICES.filter((choice) => choice.kind !== "frizz");
|
|
@@ -24953,7 +25342,7 @@ function createRemotePane(options2) {
|
|
|
24953
25342
|
"",
|
|
24954
25343
|
...choices().map((choice, index) => {
|
|
24955
25344
|
const marker = index === s.index ? "\u276F" : " ";
|
|
24956
|
-
const title = choice.title.padEnd(
|
|
25345
|
+
const title = choice.title.padEnd(20);
|
|
24957
25346
|
const now = choice.kind === current ? ` ${DIM}(current)${RESET2}` : "";
|
|
24958
25347
|
return `${marker} ${title}${DIM}${choice.blurb}${RESET2}${now}`;
|
|
24959
25348
|
}),
|
|
@@ -25008,10 +25397,10 @@ function createRemotePane(options2) {
|
|
|
25008
25397
|
const formHead = (kind) => {
|
|
25009
25398
|
if (kind === "frizz") {
|
|
25010
25399
|
return [
|
|
25011
|
-
"Custom frizz.sh
|
|
25400
|
+
"Custom frizz.sh subdomain",
|
|
25012
25401
|
"",
|
|
25013
25402
|
...wrap(
|
|
25014
|
-
"Claims <name>.frizz.sh of your choosing, tied to your GitHub account so names cannot be squatted. The board dials out to frizz.sh \u2014 no port, no DNS record, no tunnel binary. One name per account; a name nobody runs for 30 days is released. (A private
|
|
25403
|
+
"Claims <name>.frizz.sh of your choosing, tied to your GitHub account so names cannot be squatted. The board dials out to frizz.sh \u2014 no port, no DNS record, no tunnel binary. One name per account; a name nobody runs for 30 days is released. (A private subdomain needs none of this.)"
|
|
25015
25404
|
),
|
|
25016
25405
|
...options2.sandbox ? ["", ...wrap("This is a sandbox, but a claim is real: it binds this machine's one name to your account, and your real board keeps it. Only the setup saved here is thrown away.")] : [],
|
|
25017
25406
|
"",
|
|
@@ -25035,7 +25424,7 @@ function createRemotePane(options2) {
|
|
|
25035
25424
|
" cloudflared tunnel route dns my-board board.example.com",
|
|
25036
25425
|
"",
|
|
25037
25426
|
...wrap(
|
|
25038
|
-
`
|
|
25427
|
+
`Point it at this board in ~/.cloudflared/frizz.yml (ingress to http://127.0.0.1:${options2.port}; https://frizz.sh/docs/cloudflare-tunnel has the file), then name them here. Frizz runs the tunnel beside the board, so the two share a lifetime.`
|
|
25039
25428
|
)
|
|
25040
25429
|
];
|
|
25041
25430
|
}
|
|
@@ -25145,7 +25534,7 @@ function createRemotePane(options2) {
|
|
|
25145
25534
|
};
|
|
25146
25535
|
const claimPrivate = async () => {
|
|
25147
25536
|
const back = { name: "menu", index: 0 };
|
|
25148
|
-
screen = { name: "busy", message: "claiming a private
|
|
25537
|
+
screen = { name: "busy", message: "claiming a private frizz.sh subdomain\u2026" };
|
|
25149
25538
|
paint();
|
|
25150
25539
|
try {
|
|
25151
25540
|
const next = await options2.claim("", { signal: new AbortController().signal, onDeviceCode: () => {
|
|
@@ -25618,7 +26007,7 @@ var LAUNCH_TIMEOUT_MS = BOOT_STALL_TIMEOUT_MS;
|
|
|
25618
26007
|
var LAUNCH_HARD_TIMEOUT_MS = BOOT_HARD_TIMEOUT_MS;
|
|
25619
26008
|
var RETIRED_NETWORK_FLAGS = ["--host", "--allowed-host", "--public-origin", "--cloud"];
|
|
25620
26009
|
function retiredFlagMessage(flag) {
|
|
25621
|
-
return `${flag} was retired \u2014 how a board is reached is set up from the running board now: press R in its terminal to pick a private frizz.sh
|
|
26010
|
+
return `${flag} was retired \u2014 how a board is reached is set up from the running board now: press R in its terminal to pick a private frizz.sh subdomain, a Cloudflare Tunnel, Tailscale or a proxy of your own. The choice is remembered, so a plain launch serves it.`;
|
|
25622
26011
|
}
|
|
25623
26012
|
function parseCliArgs(argv) {
|
|
25624
26013
|
const args2 = new Set(argv);
|
|
@@ -25760,9 +26149,13 @@ function shareCredentials(realHome, home, realEnv, sandboxEnv) {
|
|
|
25760
26149
|
mkdirSync10(dirname10(realKey), { recursive: true });
|
|
25761
26150
|
mkdirSync10(dirname10(sandboxKey), { recursive: true });
|
|
25762
26151
|
link(realKey, sandboxKey);
|
|
25763
|
-
|
|
25764
|
-
|
|
25765
|
-
|
|
26152
|
+
const realGithub = join13(frizzPaths({ home: realHome, env: realEnv }).data, "github");
|
|
26153
|
+
if (existsSync6(realGithub)) {
|
|
26154
|
+
const sandboxGithub = join13(frizzPaths({ home, env: sandboxEnv }).data, "github");
|
|
26155
|
+
mkdirSync10(dirname10(sandboxGithub), { recursive: true });
|
|
26156
|
+
link(realGithub, sandboxGithub);
|
|
26157
|
+
const keychain = join13(realHome, "Library", "Keychains", "login.keychain-db");
|
|
26158
|
+
if (process.platform === "darwin" && existsSync6(keychain)) sandboxEnv.FRIZZ_GITHUB_KEYCHAIN = keychain;
|
|
25766
26159
|
}
|
|
25767
26160
|
const cloudflared = join13(realHome, ".cloudflared");
|
|
25768
26161
|
if (existsSync6(cloudflared)) {
|
|
@@ -27347,7 +27740,7 @@ function createNpmRegistryReleaseAdapter(options2 = {}) {
|
|
|
27347
27740
|
return settle({ code: null, signal: null, stdout: "", stderr: error instanceof Error ? error.message : String(error) });
|
|
27348
27741
|
}
|
|
27349
27742
|
const started = Date.now();
|
|
27350
|
-
const child = spawn3(npm.command, [...npm.prefixArgs, "exec", "--yes", `--package=${packageSpec}`, "--", bin, ...args2], {
|
|
27743
|
+
const child = spawn3(npm.command, [...npm.prefixArgs, "exec", "--yes", "--min-release-age=0", `--package=${packageSpec}`, "--", bin, ...args2], {
|
|
27351
27744
|
cwd,
|
|
27352
27745
|
env,
|
|
27353
27746
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -27543,6 +27936,7 @@ function npmServerPackageInstaller(env = process.env, options2 = {}) {
|
|
|
27543
27936
|
prefix,
|
|
27544
27937
|
"--global=false",
|
|
27545
27938
|
"--ignore-scripts",
|
|
27939
|
+
"--min-release-age=0",
|
|
27546
27940
|
"--no-audit",
|
|
27547
27941
|
"--no-fund",
|
|
27548
27942
|
"--engine-strict",
|
|
@@ -27898,8 +28292,9 @@ Options:
|
|
|
27898
28292
|
--no-app print the URL without opening a browser
|
|
27899
28293
|
--port <port> request a fixed port for a new workspace server
|
|
27900
28294
|
--sandbox a disposable Frizz to try things in: throwaway home and project, its
|
|
27901
|
-
own port, deleted when this terminal closes; credentials (
|
|
27902
|
-
cloudflared, Claude, Codex, the machine's frizz.sh
|
|
28295
|
+
own port, deleted when this terminal closes; credentials (Frizz's
|
|
28296
|
+
GitHub sign-in, cloudflared, Claude, Codex, the machine's frizz.sh
|
|
28297
|
+
key) are shared
|
|
27903
28298
|
--link print a fresh single-use access link for the running board
|
|
27904
28299
|
--sessions list the devices signed in to the running board
|
|
27905
28300
|
--sign-out <id|all> sign one device out, or every one of them
|
|
@@ -27910,10 +28305,10 @@ Options:
|
|
|
27910
28305
|
|
|
27911
28306
|
|
|
27912
28307
|
To reach the board from a phone or another machine, press R in the terminal running it: a short
|
|
27913
|
-
walkthrough sets up a private frizz.sh
|
|
27914
|
-
|
|
27915
|
-
|
|
27916
|
-
|
|
28308
|
+
walkthrough sets up a private frizz.sh subdomain (no account needed), a Cloudflare Tunnel,
|
|
28309
|
+
Tailscale, or a proxy of your own, and remembers the choice, so a plain launch serves it from then
|
|
28310
|
+
on. The board stays on loopback and shows a single-use sign-in link as a QR; press L for a fresh
|
|
28311
|
+
one, or run --link from another shell.`
|
|
27917
28312
|
);
|
|
27918
28313
|
process.exit(0);
|
|
27919
28314
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frizz",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.14",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A local web UI for running many coding agents at once",
|
|
6
6
|
"keywords": [
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
"frizzServer": {
|
|
68
68
|
"package": "frizz-server",
|
|
69
|
-
"version": "0.15.
|
|
69
|
+
"version": "0.15.45",
|
|
70
70
|
"minimum": "0.15.21",
|
|
71
71
|
"protocol": 1,
|
|
72
72
|
"dataEpoch": 1
|