frizz 0.13.9 → 0.13.11

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.
Files changed (3) hide show
  1. package/README.md +11 -12
  2. package/dist/frizz.js +57 -53
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -27,22 +27,20 @@ Frizz is for you if you have any of these opinions:
27
27
 
28
28
  **Requirements.** Node 22.13+, and a [Claude Code](https://claude.com/claude-code) or [Codex](https://developers.openai.com/codex) subscription you are signed in to — Frizz drives the subscription you already pay for. Frizz brings its own pinned copy of each CLI (downloaded once, on first start), so the version on your PATH is yours to manage and never changes what a thread runs.
29
29
 
30
- Then run it in any directory — a repo, a jj checkout, or a folder of scripts. Frizz has no opinion about version control and does not require Git.
30
+ Then run it, from anywhere.
31
31
 
32
32
  ```sh
33
- $ cd path/to/acme
34
33
  $ npx frizz
35
34
 
36
35
  FRIZZ v0.4.0 ready in 4.0s
37
36
 
38
- ➜ Local: http://127.0.0.1:9393/project/acme/
39
- ➜ Project: acme — path/to/acme
37
+ ➜ Local: http://127.0.0.1:9393/
40
38
  ➜ Logs: ~/Library/Application Support/Frizz/projects/979dae3c-fe15-4038-817e-11d0e7491959/logs/frizz-2026-08-01T13-44-43-16931.log
41
39
 
42
40
  press ctrl-c to stop · run with --debug for the full event feed
43
41
  ```
44
42
 
45
- A browser tab opens at `http://127.0.0.1:9393/project/acme/`. Frizz always listens on port 9393 (19393 if something else holds it), and one server serves every project on the machine. Each directory you run it in becomes a **project** with its own board at `/project/<name>`, so running `npx frizz` in a second repo registers that project and opens its board in the server already running rather than starting another. Runs on macOS, Linux, and Windows.
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.
46
44
 
47
45
  <p align="center">
48
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 numbered options drawn as keycaps, with its snooze clock and done check in the card's header." width="100%">
@@ -67,7 +65,7 @@ Frizz is a browser tab, a queue, and the agent CLIs you already pay for. It brin
67
65
 
68
66
  ### Projects
69
67
 
70
- Every directory you run `npx frizz` in becomes a project with its own board, all served by the one Frizz on your machine. The home page at `http://127.0.0.1:9393/` lists them.
68
+ Every folder you add becomes a project with its own board, all served by the one Frizz on your machine. The dashboard at `http://127.0.0.1:9393/` lists them, and it is where `npx frizz` lands when it is run outside one.
71
69
 
72
70
  <p align="center">
73
71
  <img src="assets/projects.png" alt="The Frizz home page: a rail of project icons down the left, and four project cards — design-system, acme-web, acme-api, frizz — each with its home-relative path and when it was last opened, plus an Add a project card." width="100%">
@@ -134,10 +132,11 @@ Frizz production launcher
134
132
 
135
133
  Usage: npx frizz [options]
136
134
 
137
- Run it in the directory you want to work in. One server serves EVERY project on this machine,
138
- each at its own /project/<name> URL, so a second run joins the one already going. Runs the
139
- npm-resolved immutable Frizz package, then opens it in your default browser. Use frizz-dev only
140
- for a source checkout.
135
+ Run it anywhere. One server serves EVERY project on this machine, each at its own
136
+ /project/<name> URL, so a second run joins the one already going. In a folder that is already a
137
+ project it opens that project's board; anywhere else it opens the dashboard, where projects are
138
+ added. Runs the npm-resolved immutable Frizz package, then opens it in your default browser. Use
139
+ frizz-dev only for a source checkout.
141
140
 
142
141
  Options:
143
142
  --no-app print the URL without opening a browser
@@ -254,7 +253,7 @@ Frizz has its own small vocabulary. Most of it names a feature, so this doubles
254
253
  | **Snooze** | Hide a card until later — an hour, tomorrow morning, or a date you pick — optionally with a follow-up prompt attached. |
255
254
  | **Goal** | A standing prompt a thread receives on its own — every time it rests, on a clock, or both — until you switch it off or the agent says it's done. |
256
255
  | **Scratchpad** | A thread's durable working memory, readable under its **Doc** tab. Where a worker keeps what a summary would otherwise lose: the approach, the alternatives it rejected, the decisions you made and reversed. |
257
- | **`FRIZZ.md`** | An optional file at your repo root whose contents are injected into every thread, for when you want agents to follow your repo's own norms. Only agents Frizz starts read it. A project with no threads and no `FRIZZ.md` opens on a short questionnaire — how agents land work, how independently they act, anything else they should know — that writes one for you. |
256
+ | **`FRIZZ.md`** | An optional file at your repo root whose contents are injected into every thread, for when you want agents to follow your repo's own norms. Only agents Frizz starts read it. |
258
257
 
259
258
  <br/>
260
259
 
@@ -267,7 +266,7 @@ Frizz has its own small vocabulary. Most of it names a feature, so this doubles
267
266
 
268
267
  <h2 align="center">Contributing</h2>
269
268
 
270
- Issues and pull requests are welcome. Fork the repo, branch off `main`, and open the PR against `main` — CI runs on every pull request.
269
+ Issues and pull requests are welcome. Fork the repo, branch off `main`, and open the PR against `main` — CI runs on every pull request. Keep **Allow edits by maintainers** checked (GitHub offers it only on forks in a personal account, not an organization), so a follow-up commit from the maintainer can land in your PR rather than separately on `main`.
271
270
 
272
271
  Three checks run in CI, and they need no install:
273
272
 
package/dist/frizz.js CHANGED
@@ -926,6 +926,9 @@ function frizzRoots() {
926
926
  function projectStateDir(projectId, home) {
927
927
  return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "projects", projectId);
928
928
  }
929
+ function launchHostDir(home) {
930
+ return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "host");
931
+ }
929
932
  var LEGACY_DIR_NAME, cached;
930
933
  var init_frizz_paths = __esm({
931
934
  "packages/server/src/frizz-paths.ts"() {
@@ -13275,31 +13278,6 @@ var init_drainable_worker = __esm({
13275
13278
  }
13276
13279
  });
13277
13280
 
13278
- // packages/shared/src/frizz-md.ts
13279
- var FrizzMdLanding, FrizzMdAutonomy, FRIZZ_MD_NOTES_MAX, FrizzMdAnswers, FrizzMdStatus;
13280
- var init_frizz_md = __esm({
13281
- "packages/shared/src/frizz-md.ts"() {
13282
- "use strict";
13283
- init_zod();
13284
- FrizzMdLanding = external_exports.enum(["pull-request", "main", "main-push", "branch"]);
13285
- FrizzMdAutonomy = external_exports.enum(["decide", "balanced", "check-in"]);
13286
- FRIZZ_MD_NOTES_MAX = 8e3;
13287
- FrizzMdAnswers = external_exports.object({
13288
- landing: FrizzMdLanding,
13289
- autonomy: FrizzMdAutonomy,
13290
- notes: external_exports.string().max(FRIZZ_MD_NOTES_MAX)
13291
- }).strict();
13292
- FrizzMdStatus = external_exports.object({
13293
- /** A FRIZZ.md is already at the project root: the project has its rules, so nothing is asked. */
13294
- exists: external_exports.boolean(),
13295
- /** The operator skipped the questionnaire for this project: it is not asked again. */
13296
- skipped: external_exports.boolean(),
13297
- /** The branch the landing rules name — the remote's default, else the checked-out one, else `main`. */
13298
- defaultBranch: external_exports.string()
13299
- });
13300
- }
13301
- });
13302
-
13303
13281
  // packages/shared/src/receipt-bus.ts
13304
13282
  var init_receipt_bus = __esm({
13305
13283
  "packages/shared/src/receipt-bus.ts"() {
@@ -13547,7 +13525,7 @@ function requireEffortOutsideAcp(profile, ctx) {
13547
13525
  function fallbackPort(base) {
13548
13526
  return base + 1e4;
13549
13527
  }
13550
- var import_yaml, 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, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, 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, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, 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, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, ProjectRailCounts, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
13528
+ var import_yaml, 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, ThreadWatchView, PR_WATCH_FOR_MAX_MS, PR_WATCH_DEFAULT_FOR_MS, PrWatchView, AddOwnPrWatchInput, AddOwnPrWatchResult, DropOwnPrWatchInput, DropOwnPrWatchResult, OwnWatchKind, AddOwnWatchInput, OwnWatchView, AddOwnWatchResult, DropOwnWatchInput, DropOwnWatchResult, AskQuestionKind, AskedOptionSchema, AskedQuestionSchema, AskInput, RegisteredQuestionView, SECRET_TTL_MS, AskResult, UnaskInput, UnaskResult, QuestionAnswerSchema, AnswerQuestionsInput, AnswerQuestionsResult, DismissQuestionsInput, DismissQuestionsResult, SettledQuestionView, ThreadSettledQuestionsResult, MarkOwnDoneInput, MarkOwnDoneResult, ListOwnPrWatchesInput, OwnPrWatchesResult, ThreadTimerView, ThreadFence, LimitWindow, LimitPause, ProviderError, ThreadLinkView, UpsertOwnLinkInput, UpsertOwnLinkResult, DropOwnLinkInput, DropOwnLinkResult, LiveTool, ThreadView, BoardErrorItem, BoardSnapshot, 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, DismissFailedFollowUpInput, DismissFailedFollowUpResult, UnqueueFollowUpInput, UnqueueFollowUpResult, DeliverQueuedNowInput, DeliverQueuedNowResult, CompactThreadInput, SetThreadSnoozeInput, SetThreadPinnedInput, SetThreadRecurringPromptInput, SetOwnThreadRecurringPromptInput, SetOwnThreadRecurringPromptResult, GetOwnThreadRecurringPromptInput, OwnThreadRecurringPromptResult, SetOwnThreadTimerInput, CancelOwnThreadTimerInput, ListOwnThreadTimersInput, OwnThreadTimersResult, ThreadActivityItem, ListOwnThreadActivityInput, OwnThreadActivityResult, SetOwnThreadTimerResult, CancelOwnThreadTimerResult, SetOwnThreadStopHookInput, SetOwnThreadHeartbeatInput, ThreadPluginReloadResult, RenameThreadInput, AiRenameThreadInput, AiRenameThreadResult, SetOwnThreadTitleInput, SetOwnThreadTitleResult, SetThreadPermissionInput, SetThreadPermissionResult, ThreadProfileOptionsInput, ThreadProfileOptionsResult, ThreadSkillSource, ThreadSkill, ThreadSkillsInput, ThreadSkillsResult, 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, GithubRefCard, GithubRefPreviewInput, GithubRefPreviewResult, GithubItem, GithubListInput, GithubListResult, GithubBatchInput, GithubBatchResult, BoardMeta, ServerEvent, TranscriptEdit, TranscriptTodo, TranscriptToolCall, TranscriptPart, TranscriptMessage, TranscriptPageCursor, EditedFile, TranscriptPage, TranscriptEarlierInput, SocketTranscriptSlug, SocketFilePath, SocketTranscriptClientMsg, SocketFileClientMsg, SocketClientMsg, ProjectCard, ProjectRailCounts, PROJECT_ICON_MAX_BASE64_CHARS, DirectoryPickResult, ThreadLocation, FRIZZ_ROUTE_PREFIX, DEFAULT_PORT;
13551
13529
  var init_src = __esm({
13552
13530
  "packages/shared/src/index.ts"() {
13553
13531
  "use strict";
@@ -13560,7 +13538,6 @@ var init_src = __esm({
13560
13538
  init_code_fences();
13561
13539
  init_delta();
13562
13540
  init_drainable_worker();
13563
- init_frizz_md();
13564
13541
  init_interactions();
13565
13542
  init_receipt_bus();
13566
13543
  init_relay_protocol();
@@ -13662,7 +13639,7 @@ var init_src = __esm({
13662
13639
  "running",
13663
13640
  // process alive, turn in flight
13664
13641
  "perm-prompt",
13665
- // process alive, paused on an interactive permission prompt (answer in the terminal)
13642
+ // process alive, parked on a permission request a person has not answered yet
13666
13643
  "turn-idle",
13667
13644
  // process alive, waiting at the prompt
13668
13645
  "exited"
@@ -14859,6 +14836,19 @@ var init_src = __esm({
14859
14836
  // ambiguous, and only the operator decides to risk a second copy.
14860
14837
  supersedes: external_exports.string().min(1).max(200).optional()
14861
14838
  });
14839
+ SteerThreadInput = external_exports.object({
14840
+ from: ThreadSlug,
14841
+ to: ThreadSlug,
14842
+ message: external_exports.string().min(1).max(1e5)
14843
+ }).strict();
14844
+ SteerThreadResult = external_exports.object({
14845
+ slug: ThreadSlug,
14846
+ title: external_exports.string(),
14847
+ // What the target was doing when the message arrived: `running` ⇒ it reads it at its next tool
14848
+ // boundary; `resting` ⇒ the message started its next turn; `reopened` ⇒ it was marked done and this
14849
+ // message put it back on the board.
14850
+ delivery: external_exports.enum(["running", "resting", "reopened"])
14851
+ }).strict();
14862
14852
  DismissFailedFollowUpInput = external_exports.object({
14863
14853
  slug: ThreadSlug,
14864
14854
  deliveryId: external_exports.string().min(1).max(200)
@@ -21199,11 +21189,13 @@ function readRegistry(home = homedir9()) {
21199
21189
  try {
21200
21190
  const parsed = JSON.parse(raw);
21201
21191
  if (parsed?.version !== 1 || !Array.isArray(parsed.projects)) return { ...EMPTY, projects: [] };
21192
+ const forgotten = Array.isArray(parsed.forgotten) ? parsed.forgotten.filter((id) => typeof id === "string") : [];
21202
21193
  return {
21203
21194
  version: 1,
21204
21195
  projects: parsed.projects.filter(
21205
21196
  (p) => p && typeof p.id === "string" && typeof p.path === "string" && typeof p.slug === "string"
21206
- )
21197
+ ),
21198
+ ...forgotten.length > 0 ? { forgotten } : {}
21207
21199
  };
21208
21200
  } catch {
21209
21201
  return { ...EMPTY, projects: [] };
@@ -21267,7 +21259,12 @@ function canonicalPath(dir) {
21267
21259
  }
21268
21260
  function registerProject(input, home = homedir9()) {
21269
21261
  input = { ...input, dir: canonicalPath(input.dir) };
21262
+ if (input.dir === canonicalPath(launchHostDir(home))) return { action: "unlisted" };
21270
21263
  const registry = readRegistry(home);
21264
+ if (registry.forgotten) {
21265
+ registry.forgotten = registry.forgotten.filter((id) => id !== input.id);
21266
+ if (registry.forgotten.length === 0) delete registry.forgotten;
21267
+ }
21271
21268
  const at = (input.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
21272
21269
  const byId = registry.projects.find((p) => p.id === input.id);
21273
21270
  if (byId) {
@@ -25461,6 +25458,7 @@ init_boot_progress();
25461
25458
  init_frizz_paths();
25462
25459
  import { execFileSync as execFileSync3 } from "node:child_process";
25463
25460
  import { createServer as createServer2 } from "node:net";
25461
+ import { randomUUID as randomUUID5 } from "node:crypto";
25464
25462
  import { existsSync as existsSync6, mkdirSync as mkdirSync10, mkdtempSync, readdirSync as readdirSync3, readFileSync as readFileSync10, realpathSync as realpathSync5, rmSync as rmSync8, symlinkSync as symlinkSync2, writeFileSync as writeFileSync10 } from "node:fs";
25465
25463
  import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
25466
25464
  import { basename as basename5, dirname as dirname10, join as join13, resolve as resolve4 } from "node:path";
@@ -25600,6 +25598,7 @@ function prepareSandbox(env = process.env, realHome = homedir10()) {
25600
25598
  } catch {
25601
25599
  throw new Error("--sandbox mints a throwaway git repository and git is not available \u2014 install git, or play in a repo of your own with a temp HOME");
25602
25600
  }
25601
+ writeProjectIdFile(project, randomUUID5());
25603
25602
  process.chdir(project);
25604
25603
  return { home, project };
25605
25604
  }
@@ -25643,13 +25642,19 @@ function cleanupSandbox(home) {
25643
25642
  function resolveLaunchIntent(cwd = process.cwd(), home = homedir10(), env = process.env) {
25644
25643
  const candidate = realpathSync5(discoverProjectRoot(cwd, home));
25645
25644
  const known = isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0;
25646
- const refused = isHomeDirectory(candidate, home) ? "home" : isWorldWritable(candidate) ? "world-writable" : void 0;
25647
- if ((known || hasProjectMarker(candidate)) && !refused)
25648
- return { kind: "open", workspace: resolveWorkspace(cwd, home, env) };
25649
- const host = mostRecentProject(home, env);
25650
- const reason = refused ?? "unadopted";
25651
- if (!host) return { kind: "empty", directory: candidate, reason };
25652
- return reason === "unadopted" ? { kind: "offer", workspace: host, directory: candidate } : { kind: "grid", workspace: host };
25645
+ const refused = isHomeDirectory(candidate, home) || isWorldWritable(candidate);
25646
+ if (known && !refused) return { kind: "open", workspace: resolveWorkspace(cwd, home, env) };
25647
+ const host = mostRecentProject(home, env) ?? hostWorkspace(home);
25648
+ return !refused && hasProjectMarker(candidate) ? { kind: "offer", workspace: host, directory: candidate } : { kind: "grid", workspace: host };
25649
+ }
25650
+ function hostWorkspace(home = homedir10()) {
25651
+ const dir = launchHostDir(home);
25652
+ mkdirSync10(dir, { recursive: true });
25653
+ const root = realpathSync5(dir);
25654
+ const id = ensureProjectIdFile(root, home);
25655
+ const stateDir = projectStateDir(id, home);
25656
+ mkdirSync10(stateDir, { recursive: true });
25657
+ return { root, id, stateDir, name: basename5(root), identityScope: "repository" };
25653
25658
  }
25654
25659
  function mostRecentProject(home, env) {
25655
25660
  for (const entry of listProjects(home)) {
@@ -26006,7 +26011,7 @@ init_restart_supervisor();
26006
26011
  init_logging();
26007
26012
  init_boot_progress();
26008
26013
  import { fork } from "node:child_process";
26009
- import { randomUUID as randomUUID5 } from "node:crypto";
26014
+ import { randomUUID as randomUUID6 } from "node:crypto";
26010
26015
  import { existsSync as existsSync7, mkdirSync as mkdirSync11, readFileSync as readFileSync11 } from "node:fs";
26011
26016
  import { createServer as createNetServer } from "node:net";
26012
26017
  import { basename as basename6, dirname as dirname11, extname as extname2, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5, sep as sep3 } from "node:path";
@@ -26251,7 +26256,7 @@ var Supervisor = class {
26251
26256
  watchSubscribe;
26252
26257
  reexec;
26253
26258
  supervisorLock;
26254
- statusPublisherToken = randomUUID5();
26259
+ statusPublisherToken = randomUUID6();
26255
26260
  processGeneration = currentProcessGeneration();
26256
26261
  ownerToken;
26257
26262
  launchTarget;
@@ -27207,7 +27212,7 @@ var npmRegistryReleaseAdapter = createNpmRegistryReleaseAdapter();
27207
27212
  // src/server-release.ts
27208
27213
  init_frizz_paths();
27209
27214
  import { execFile as execFile4 } from "node:child_process";
27210
- import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
27215
+ import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
27211
27216
  import {
27212
27217
  closeSync as closeSync5,
27213
27218
  existsSync as existsSync9,
@@ -27363,7 +27368,7 @@ function npmServerPackageInstaller(env = process.env) {
27363
27368
  }
27364
27369
  function atomicJson2(path, value) {
27365
27370
  mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
27366
- const temp = `${path}.${randomUUID6()}.tmp`;
27371
+ const temp = `${path}.${randomUUID7()}.tmp`;
27367
27372
  try {
27368
27373
  const fd = openSync6(temp, "wx", 384);
27369
27374
  try {
@@ -27464,7 +27469,7 @@ var ServerReleaseStore = class {
27464
27469
  const spec = { ...this.spec, version };
27465
27470
  assertSpec(spec, this.compatibility);
27466
27471
  mkdirSync12(this.generations, { recursive: true, mode: 448 });
27467
- const id = randomUUID6();
27472
+ const id = randomUUID7();
27468
27473
  const staging = join15(this.generations, `${id}.staging`);
27469
27474
  const destination = join15(this.generations, id);
27470
27475
  mkdirSync12(staging, { mode: 448 });
@@ -27490,7 +27495,7 @@ var ServerReleaseStore = class {
27490
27495
  // src/server-owner.ts
27491
27496
  init_frizz_paths();
27492
27497
  init_project_launch();
27493
- import { randomUUID as randomUUID7 } from "node:crypto";
27498
+ import { randomUUID as randomUUID8 } from "node:crypto";
27494
27499
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, realpathSync as realpathSync7 } from "node:fs";
27495
27500
  import { join as join16 } from "node:path";
27496
27501
  var STABLE_SERVER_OWNER_NAME = "frizz-server";
@@ -27535,7 +27540,7 @@ function wrapLease(lease) {
27535
27540
  token: lease.token,
27536
27541
  pid: lease.pid,
27537
27542
  processStart: lease.processStart,
27538
- publisherToken: randomUUID7()
27543
+ publisherToken: randomUUID8()
27539
27544
  };
27540
27545
  }
27541
27546
  function acquireStableServerOwner(roots) {
@@ -27666,10 +27671,11 @@ if (options.help) {
27666
27671
 
27667
27672
  Usage: npx ${PACKAGE_NAME} [options]
27668
27673
 
27669
- Run it in the directory you want to work in. One server serves EVERY project on this machine,
27670
- each at its own /project/<name> URL, so a second run joins the one already going. Runs the
27671
- npm-resolved immutable Frizz package, then opens it in your default browser. Use frizz-dev only
27672
- for a source checkout.
27674
+ Run it anywhere. One server serves EVERY project on this machine, each at its own
27675
+ /project/<name> URL, so a second run joins the one already going. In a folder that is already a
27676
+ project it opens that project's board; anywhere else it opens the dashboard, where projects are
27677
+ added. Runs the npm-resolved immutable Frizz package, then opens it in your default browser. Use
27678
+ frizz-dev only for a source checkout.
27673
27679
 
27674
27680
  Options:
27675
27681
  --no-app print the URL without opening a browser
@@ -27720,10 +27726,6 @@ var workspace = (() => {
27720
27726
  return workspaceFromLaunchTarget(pinned);
27721
27727
  }
27722
27728
  const intent = resolveLaunchIntent();
27723
- if (intent.kind === "empty")
27724
- throw new Error(
27725
- intent.reason === "home" ? "frizz cannot open your home directory as a project, and there is no other project to show yet. cd into a repository and run frizz there." : intent.reason === "world-writable" ? `A folder every account can write to cannot be a project: ${intent.directory}. Run frizz in a folder only you can write to.` : `${intent.directory} is not a Frizz project yet, and there is no other project to show. Run frizz inside a repository, or add this one from the projects page once a board is open.`
27726
- );
27727
27729
  launchIntent = intent;
27728
27730
  return intent.workspace;
27729
27731
  } catch (error) {
@@ -27869,7 +27871,7 @@ async function openOrPrint(port, reused, path = "") {
27869
27871
  if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
27870
27872
  if (sessionNotice && !reused) warnings.push(sessionNotice);
27871
27873
  if (!readout) {
27872
- console.log(`${reused ? "reusing" : "started"} Frizz for ${workspace.root}`);
27874
+ console.log(`${reused ? "reusing" : "started"} Frizz${launchIntent && launchIntent.kind !== "open" ? "" : ` for ${workspace.root}`}`);
27873
27875
  console.log(url);
27874
27876
  if (publicOrigin) console.log(activeAccessLink?.url ?? `${publicOrigin}/`);
27875
27877
  for (const warning of warnings) console.log(warning);
@@ -27880,7 +27882,9 @@ async function openOrPrint(port, reused, path = "") {
27880
27882
  [
27881
27883
  { label: "Local", value: boardAddress(url), accent: true },
27882
27884
  ...publicOrigin ? [{ label: "Public", value: activeAccessLink?.url ?? `${publicOrigin}/`, accent: true }] : [],
27883
- { label: "Project", value: `${workspace.name} \u2014 ${tildePath(workspace.root, home)}` },
27885
+ // Only an `open` launch is ABOUT its workspace; any other is hosted on a project the operator
27886
+ // did not pick (launcher.ts resolveLaunchIntent), and naming it here reads as "you opened this".
27887
+ ...launchIntent && launchIntent.kind !== "open" ? [] : [{ label: "Project", value: `${workspace.name} \u2014 ${tildePath(workspace.root, home)}` }],
27884
27888
  ...logger.file ? [{ label: "Logs", value: tildePath(logger.file, home) }] : []
27885
27889
  ],
27886
27890
  reused ? (
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frizz",
3
- "version": "0.13.9",
3
+ "version": "0.13.11",
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.18",
69
+ "version": "0.15.22",
70
70
  "protocol": 1,
71
71
  "dataEpoch": 1
72
72
  }