frizz 0.13.10 → 0.13.12
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 +9 -10
- package/dist/frizz.js +39 -45
- 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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
package/dist/frizz.js
CHANGED
|
@@ -862,11 +862,6 @@ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join3, relative
|
|
|
862
862
|
function legacyFrizzRoot(home = homedir2()) {
|
|
863
863
|
return join3(home, LEGACY_DIR_NAME);
|
|
864
864
|
}
|
|
865
|
-
function isLegacyStateRoot(root, exists) {
|
|
866
|
-
if (!exists(root)) return false;
|
|
867
|
-
if (!exists(join3(root, ".id")) && !exists(join3(root, ".gitignore"))) return true;
|
|
868
|
-
return exists(join3(root, "projects")) || exists(join3(root, "registry.json"));
|
|
869
|
-
}
|
|
870
865
|
function xdg(env, name) {
|
|
871
866
|
const value = env[name];
|
|
872
867
|
return value && (value.startsWith("/") || /^[A-Za-z]:[\\/]/u.test(value)) ? value : void 0;
|
|
@@ -909,7 +904,7 @@ function frizzPaths(options2 = {}) {
|
|
|
909
904
|
const home = options2.home ?? homedir2();
|
|
910
905
|
const exists = options2.exists ?? existsSync2;
|
|
911
906
|
const legacyRoot = legacyFrizzRoot(home);
|
|
912
|
-
if (
|
|
907
|
+
if (exists(legacyRoot)) {
|
|
913
908
|
return { data: legacyRoot, state: legacyRoot, cache: legacyRoot, legacy: true };
|
|
914
909
|
}
|
|
915
910
|
const explicit = {
|
|
@@ -931,6 +926,9 @@ function frizzRoots() {
|
|
|
931
926
|
function projectStateDir(projectId, home) {
|
|
932
927
|
return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "projects", projectId);
|
|
933
928
|
}
|
|
929
|
+
function launchHostDir(home) {
|
|
930
|
+
return join3(home ? frizzPaths({ home }).data : frizzRoots().data, "host");
|
|
931
|
+
}
|
|
934
932
|
var LEGACY_DIR_NAME, cached;
|
|
935
933
|
var init_frizz_paths = __esm({
|
|
936
934
|
"packages/server/src/frizz-paths.ts"() {
|
|
@@ -13641,7 +13639,7 @@ var init_src = __esm({
|
|
|
13641
13639
|
"running",
|
|
13642
13640
|
// process alive, turn in flight
|
|
13643
13641
|
"perm-prompt",
|
|
13644
|
-
// process alive,
|
|
13642
|
+
// process alive, parked on a permission request a person has not answered yet
|
|
13645
13643
|
"turn-idle",
|
|
13646
13644
|
// process alive, waiting at the prompt
|
|
13647
13645
|
"exited"
|
|
@@ -21105,13 +21103,6 @@ function isHomeDirectory(dir, home = homedir8()) {
|
|
|
21105
21103
|
return false;
|
|
21106
21104
|
}
|
|
21107
21105
|
}
|
|
21108
|
-
function isStateRootHome(dir, home = homedir8()) {
|
|
21109
|
-
return isHomeDirectory(dir, home) && frizzPaths({ home }).legacy;
|
|
21110
|
-
}
|
|
21111
|
-
function refuseStateRootHome(dir, home = homedir8()) {
|
|
21112
|
-
if (isStateRootHome(dir, home))
|
|
21113
|
-
throw new Error("Frizz keeps its own files in ~/.frizz on this machine, so the home folder cannot be a project here \u2014 choose a folder inside it.");
|
|
21114
|
-
}
|
|
21115
21106
|
function isWorldWritable(dir, platform = process.platform) {
|
|
21116
21107
|
if (platform === "win32") return false;
|
|
21117
21108
|
try {
|
|
@@ -21148,7 +21139,7 @@ function discoverProjectRoot(cwd = process.cwd(), home = homedir8()) {
|
|
|
21148
21139
|
const stop = resolve3(home);
|
|
21149
21140
|
const filesystemRoot = parse(dir).root;
|
|
21150
21141
|
for (let at = dir; ; at = dirname8(at)) {
|
|
21151
|
-
if (at === stop || at === filesystemRoot
|
|
21142
|
+
if (at === stop || at === filesystemRoot) break;
|
|
21152
21143
|
if (existsSync4(projectIdPath(at))) return at;
|
|
21153
21144
|
if (hasAny(at, REPO_MARKERS)) return at;
|
|
21154
21145
|
if (hasAny(at, PROJECT_MARKERS)) return at;
|
|
@@ -21160,7 +21151,6 @@ var FRIZZ_DIR, ID_FILE, SELF_IGNORE, REPO_MARKERS, PROJECT_MARKERS;
|
|
|
21160
21151
|
var init_project_root = __esm({
|
|
21161
21152
|
"packages/server/src/project-root.ts"() {
|
|
21162
21153
|
"use strict";
|
|
21163
|
-
init_frizz_paths();
|
|
21164
21154
|
init_project_identity();
|
|
21165
21155
|
FRIZZ_DIR = ".frizz";
|
|
21166
21156
|
ID_FILE = ".id";
|
|
@@ -21269,6 +21259,7 @@ function canonicalPath(dir) {
|
|
|
21269
21259
|
}
|
|
21270
21260
|
function registerProject(input, home = homedir9()) {
|
|
21271
21261
|
input = { ...input, dir: canonicalPath(input.dir) };
|
|
21262
|
+
if (input.dir === canonicalPath(launchHostDir(home))) return { action: "unlisted" };
|
|
21272
21263
|
const registry = readRegistry(home);
|
|
21273
21264
|
if (registry.forgotten) {
|
|
21274
21265
|
registry.forgotten = registry.forgotten.filter((id) => id !== input.id);
|
|
@@ -25467,6 +25458,7 @@ init_boot_progress();
|
|
|
25467
25458
|
init_frizz_paths();
|
|
25468
25459
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25469
25460
|
import { createServer as createServer2 } from "node:net";
|
|
25461
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
25470
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";
|
|
25471
25463
|
import { homedir as homedir10, tmpdir as tmpdir2 } from "node:os";
|
|
25472
25464
|
import { basename as basename5, dirname as dirname10, join as join13, resolve as resolve4 } from "node:path";
|
|
@@ -25606,6 +25598,7 @@ function prepareSandbox(env = process.env, realHome = homedir10()) {
|
|
|
25606
25598
|
} catch {
|
|
25607
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");
|
|
25608
25600
|
}
|
|
25601
|
+
writeProjectIdFile(project, randomUUID5());
|
|
25609
25602
|
process.chdir(project);
|
|
25610
25603
|
return { home, project };
|
|
25611
25604
|
}
|
|
@@ -25646,26 +25639,26 @@ function cleanupSandbox(home) {
|
|
|
25646
25639
|
} catch {
|
|
25647
25640
|
}
|
|
25648
25641
|
}
|
|
25649
|
-
function emptyLaunchMessage(intent) {
|
|
25650
|
-
return intent.reason === "home" ? "Frizz keeps its own files in ~/.frizz on this machine, so it cannot open your home directory as a project, and there is no other project to show yet. cd into another folder and run frizz there." : `A folder every account can write to cannot be a project: ${intent.directory}. Run frizz in a folder only you can write to.`;
|
|
25651
|
-
}
|
|
25652
25642
|
function resolveLaunchIntent(cwd = process.cwd(), home = homedir10(), env = process.env) {
|
|
25653
25643
|
const candidate = realpathSync5(discoverProjectRoot(cwd, home));
|
|
25654
25644
|
const known = isExistingProjectRoot(candidate) || findByPath(candidate, home) !== void 0;
|
|
25655
|
-
const
|
|
25656
|
-
|
|
25657
|
-
|
|
25658
|
-
|
|
25659
|
-
|
|
25660
|
-
|
|
25661
|
-
|
|
25662
|
-
|
|
25663
|
-
|
|
25664
|
-
|
|
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" };
|
|
25665
25658
|
}
|
|
25666
25659
|
function mostRecentProject(home, env) {
|
|
25667
25660
|
for (const entry of listProjects(home)) {
|
|
25668
|
-
if (entry.stale ||
|
|
25661
|
+
if (entry.stale || isHomeDirectory(entry.path, home) || isWorldWritable(entry.path)) continue;
|
|
25669
25662
|
try {
|
|
25670
25663
|
return resolveWorkspace(entry.path, home, env);
|
|
25671
25664
|
} catch {
|
|
@@ -25689,7 +25682,6 @@ function resolveWorkspace(cwd = process.cwd(), home = homedir10(), env = process
|
|
|
25689
25682
|
}
|
|
25690
25683
|
const root0 = realpathSync5(gitRoot ?? discoverProjectRoot(cwd, home));
|
|
25691
25684
|
refuseWorldWritableProject(root0);
|
|
25692
|
-
refuseStateRootHome(root0, home);
|
|
25693
25685
|
const identity = gitRoot ? resolveGitProjectIdentity(root0, home) : void 0;
|
|
25694
25686
|
const root = identity?.root ?? root0;
|
|
25695
25687
|
const id = ensureProjectIdFile(root, home, identity?.id);
|
|
@@ -26019,7 +26011,7 @@ init_restart_supervisor();
|
|
|
26019
26011
|
init_logging();
|
|
26020
26012
|
init_boot_progress();
|
|
26021
26013
|
import { fork } from "node:child_process";
|
|
26022
|
-
import { randomUUID as
|
|
26014
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
26023
26015
|
import { existsSync as existsSync7, mkdirSync as mkdirSync11, readFileSync as readFileSync11 } from "node:fs";
|
|
26024
26016
|
import { createServer as createNetServer } from "node:net";
|
|
26025
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";
|
|
@@ -26264,7 +26256,7 @@ var Supervisor = class {
|
|
|
26264
26256
|
watchSubscribe;
|
|
26265
26257
|
reexec;
|
|
26266
26258
|
supervisorLock;
|
|
26267
|
-
statusPublisherToken =
|
|
26259
|
+
statusPublisherToken = randomUUID6();
|
|
26268
26260
|
processGeneration = currentProcessGeneration();
|
|
26269
26261
|
ownerToken;
|
|
26270
26262
|
launchTarget;
|
|
@@ -27220,7 +27212,7 @@ var npmRegistryReleaseAdapter = createNpmRegistryReleaseAdapter();
|
|
|
27220
27212
|
// src/server-release.ts
|
|
27221
27213
|
init_frizz_paths();
|
|
27222
27214
|
import { execFile as execFile4 } from "node:child_process";
|
|
27223
|
-
import { createHash as createHash4, randomUUID as
|
|
27215
|
+
import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
|
|
27224
27216
|
import {
|
|
27225
27217
|
closeSync as closeSync5,
|
|
27226
27218
|
existsSync as existsSync9,
|
|
@@ -27376,7 +27368,7 @@ function npmServerPackageInstaller(env = process.env) {
|
|
|
27376
27368
|
}
|
|
27377
27369
|
function atomicJson2(path, value) {
|
|
27378
27370
|
mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
|
|
27379
|
-
const temp = `${path}.${
|
|
27371
|
+
const temp = `${path}.${randomUUID7()}.tmp`;
|
|
27380
27372
|
try {
|
|
27381
27373
|
const fd = openSync6(temp, "wx", 384);
|
|
27382
27374
|
try {
|
|
@@ -27477,7 +27469,7 @@ var ServerReleaseStore = class {
|
|
|
27477
27469
|
const spec = { ...this.spec, version };
|
|
27478
27470
|
assertSpec(spec, this.compatibility);
|
|
27479
27471
|
mkdirSync12(this.generations, { recursive: true, mode: 448 });
|
|
27480
|
-
const id =
|
|
27472
|
+
const id = randomUUID7();
|
|
27481
27473
|
const staging = join15(this.generations, `${id}.staging`);
|
|
27482
27474
|
const destination = join15(this.generations, id);
|
|
27483
27475
|
mkdirSync12(staging, { mode: 448 });
|
|
@@ -27503,7 +27495,7 @@ var ServerReleaseStore = class {
|
|
|
27503
27495
|
// src/server-owner.ts
|
|
27504
27496
|
init_frizz_paths();
|
|
27505
27497
|
init_project_launch();
|
|
27506
|
-
import { randomUUID as
|
|
27498
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
27507
27499
|
import { mkdirSync as mkdirSync13, readFileSync as readFileSync13, realpathSync as realpathSync7 } from "node:fs";
|
|
27508
27500
|
import { join as join16 } from "node:path";
|
|
27509
27501
|
var STABLE_SERVER_OWNER_NAME = "frizz-server";
|
|
@@ -27548,7 +27540,7 @@ function wrapLease(lease) {
|
|
|
27548
27540
|
token: lease.token,
|
|
27549
27541
|
pid: lease.pid,
|
|
27550
27542
|
processStart: lease.processStart,
|
|
27551
|
-
publisherToken:
|
|
27543
|
+
publisherToken: randomUUID8()
|
|
27552
27544
|
};
|
|
27553
27545
|
}
|
|
27554
27546
|
function acquireStableServerOwner(roots) {
|
|
@@ -27679,10 +27671,11 @@ if (options.help) {
|
|
|
27679
27671
|
|
|
27680
27672
|
Usage: npx ${PACKAGE_NAME} [options]
|
|
27681
27673
|
|
|
27682
|
-
Run it
|
|
27683
|
-
|
|
27684
|
-
|
|
27685
|
-
|
|
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.
|
|
27686
27679
|
|
|
27687
27680
|
Options:
|
|
27688
27681
|
--no-app print the URL without opening a browser
|
|
@@ -27733,7 +27726,6 @@ var workspace = (() => {
|
|
|
27733
27726
|
return workspaceFromLaunchTarget(pinned);
|
|
27734
27727
|
}
|
|
27735
27728
|
const intent = resolveLaunchIntent();
|
|
27736
|
-
if (intent.kind === "empty") throw new Error(emptyLaunchMessage(intent));
|
|
27737
27729
|
launchIntent = intent;
|
|
27738
27730
|
return intent.workspace;
|
|
27739
27731
|
} catch (error) {
|
|
@@ -27879,7 +27871,7 @@ async function openOrPrint(port, reused, path = "") {
|
|
|
27879
27871
|
if (sandbox) warnings.push(`Sandbox: everything here is throwaway (${sandbox.home}) and is deleted when this terminal closes.`);
|
|
27880
27872
|
if (sessionNotice && !reused) warnings.push(sessionNotice);
|
|
27881
27873
|
if (!readout) {
|
|
27882
|
-
console.log(`${reused ? "reusing" : "started"} Frizz for ${workspace.root}`);
|
|
27874
|
+
console.log(`${reused ? "reusing" : "started"} Frizz${launchIntent && launchIntent.kind !== "open" ? "" : ` for ${workspace.root}`}`);
|
|
27883
27875
|
console.log(url);
|
|
27884
27876
|
if (publicOrigin) console.log(activeAccessLink?.url ?? `${publicOrigin}/`);
|
|
27885
27877
|
for (const warning of warnings) console.log(warning);
|
|
@@ -27890,7 +27882,9 @@ async function openOrPrint(port, reused, path = "") {
|
|
|
27890
27882
|
[
|
|
27891
27883
|
{ label: "Local", value: boardAddress(url), accent: true },
|
|
27892
27884
|
...publicOrigin ? [{ label: "Public", value: activeAccessLink?.url ?? `${publicOrigin}/`, accent: true }] : [],
|
|
27893
|
-
|
|
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)}` }],
|
|
27894
27888
|
...logger.file ? [{ label: "Logs", value: tildePath(logger.file, home) }] : []
|
|
27895
27889
|
],
|
|
27896
27890
|
reused ? (
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frizz",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.12",
|
|
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.22",
|
|
70
70
|
"protocol": 1,
|
|
71
71
|
"dataEpoch": 1
|
|
72
72
|
}
|